如何在SceneKit中绘制两个点之间的线?

3

如果在SceneKit中有两个点(例如(1,2,3)(-1,-1,-1)),如何在它们之间绘制一条线?

我看到了一个SCNBox对象,可能可以使用,但这只允许我指定中心点(例如通过simdPosition)。其他修改方式是使用变换(我不知道如何使用)或欧拉角(我不确定如何计算需要使用哪些欧拉角)。

3个回答

5
您可以使用以下方法在两个点之间绘制一条直线:
import SceneKit

extension SCNGeometry {

    class func line(vector1: SCNVector3,
                    vector2: SCNVector3) -> SCNGeometry {

        let sources = SCNGeometrySource(vertices: [vector1,
                                                   vector2])
        let index: [Int32] = [0,1]

        let elements = SCNGeometryElement(indices: index,
                                    primitiveType: .line)

        return SCNGeometry(sources: [sources],
                          elements: [elements])
    }
}

然后将其传递给 ViewController 中的 addLine 函数:

class ViewController: UIViewController { 

    // Some code...

    func addLine(start: SCNVector3, end: SCNVector3) {
        let lineGeo = SCNGeometry.line(vector1: start,
                                       vector2: end)
        let lineNode = SCNNode(geometry: lineGeo)
        sceneView.scene.rootNode.addChildNode(lineNode)
    }
}

众所周知线条的宽度无法更改(因为没有相应的属性),所以你可以使用cylinder原始几何体代替line:
extension SCNGeometry {

    class func cylinderLine(from: SCNVector3, 
                              to: SCNVector3,
                        segments: Int) -> SCNNode {

        let x1 = from.x
        let x2 = to.x

        let y1 = from.y
        let y2 = to.y

        let z1 = from.z
        let z2 = to.z

        let distance =  sqrtf( (x2-x1) * (x2-x1) +
                               (y2-y1) * (y2-y1) +
                               (z2-z1) * (z2-z1) )

        let cylinder = SCNCylinder(radius: 0.005,
                                   height: CGFloat(distance))

        cylinder.radialSegmentCount = segments

        cylinder.firstMaterial?.diffuse.contents = UIColor.green

        let lineNode = SCNNode(geometry: cylinder)

        lineNode.position = SCNVector3(x: (from.x + to.x) / 2,
                                       y: (from.y + to.y) / 2,
                                       z: (from.z + to.z) / 2)

        lineNode.eulerAngles = SCNVector3(Float.pi / 2,
                                          acos((to.z-from.z)/distance),
                                          atan2((to.y-from.y),(to.x-from.x)))

        return lineNode
    }
}

然后以同样的方式将其提供给ViewController:

class ViewController: UIViewController { 

    // Some code...

    func addLine(start: SCNVector3, end: SCNVector3) {

        let cylinderLineNode = SCNGeometry.cylinderLine(from: start, 
                                                          to: end, 
                                                    segments: 3)

        sceneView.scene.rootNode.addChildNode(cylinderLineNode)
    }
}

2
首先,您需要计算两点之间的航向和俯仰角。这里提供了完整帖子,而这个答案则解释了如何在任意两点之间进行计算。
一旦您有了两个角度,如果尝试在SCNBox上使用欧拉角,您会发现当您只修改俯仰角(eulerAngles.x)或只修改航向角(eulerAngles.y)时,一切都很顺利。然而,当您尝试同时修改两者时,就会遇到问题。其中一个解决方案是将一个节点包装在另一个节点中
这似乎是一个非常有用的建议,因此我创建了一个方便的包装节点,可以处理所有三个轴上的旋转:
import Foundation
import SceneKit

struct HeadingPitchBank {
    let heading: Float
    let pitch: Float
    let bank: Float

    /// returns the heading and pitch (bank is 0) represented by the vector
    static func from(vector: simd_float3) -> HeadingPitchBank {
        let heading = atan2f(vector.x, vector.z)
        let pitch = atan2f(sqrt(vector.x*vector.x + vector.z*vector.z), vector.y) - Float.pi / 2.0

        return HeadingPitchBank(heading: heading, pitch: pitch, bank: 0)
    }
}

class HeadingPitchBankWrapper: SCNNode {

    init(wrappedNode: SCNNode) {
        headingNode = SCNNode()
        pitchNode = SCNNode()
        bankNode = SCNNode()
        _wrappedNode = wrappedNode

        super.init()

        addChildNode(headingNode)
        headingNode.addChildNode(pitchNode)
        pitchNode.addChildNode(bankNode)
        bankNode.addChildNode(wrappedNode)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    var heading: Float {
        get {
            return headingNode.eulerAngles.y
        }
        set {
            headingNode.eulerAngles.y = newValue
        }
    }

    var pitch: Float {
        get {
            return pitchNode.eulerAngles.x
        }
        set {
            pitchNode.eulerAngles.x = newValue
        }
    }

    var bank: Float {
        get {
            return bankNode.eulerAngles.z
        }
        set {
            bankNode.eulerAngles.z = newValue
        }
    }

    var headingPitchBank: HeadingPitchBank {
        get {
            return HeadingPitchBank(heading: heading, pitch: pitch, bank: bank)
        }
        set {
            heading = newValue.heading
            pitch = newValue.pitch
            bank = newValue.bank
        }
    }

    var wrappedNode: SCNNode {
        return _wrappedNode
    }

    private var headingNode: SCNNode
    private var pitchNode: SCNNode
    private var bankNode: SCNNode
    private var _wrappedNode: SCNNode
}

你可以使用这个方法轻松地在两点之间画一条线:
func createLine(start: simd_float3 = simd_float3(), end: simd_float3, color: UIColor, opacity: CGFloat? = nil, radius: CGFloat = 0.005) -> SCNNode {
    let length = CGFloat(simd_length(end-start))
    let box = SCNNode(geometry: SCNBox(width: radius, height: radius, length: length, chamferRadius: 0))
    box.geometry!.firstMaterial!.diffuse.contents = color

    let wrapper = HeadingPitchBankWrapper(wrappedNode: box)
    wrapper.headingPitchBank = HeadingPitchBank.from(vector: end - start)
    wrapper.simdPosition = midpoint(start, end)
    if let opacity = opacity {
        wrapper.opacity = opacity
    }
    return wrapper
}

2

只需使用SCNGeometryPrimitiveType.line构建自定义几何体:

let vertices: [SCNVector3] = [
    SCNVector3(1, 2, 3),
    SCNVector3(-1, -1, -1)
]

let linesGeometry = SCNGeometry(
    sources: [
        SCNGeometrySource(vertices: vertices)
    ],
    elements: [
        SCNGeometryElement(
            indices: [Int32]([0, 1]),
            primitiveType: .line
        )
    ]
)

let line = SCNNode(geometry: linesGeometry)
scene.rootNode.addChildNode(line)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接