Как нарисовать линию между двумя точками в SceneKit? - PullRequest
1 голос
/ 20 октября 2019

Если у меня есть две точки в SceneKit (например, (1,2,3) и (-1,-1,-1)). Как нарисовать линию между ними?

Я вижу, что есть объект SCNBox, который я могу использовать, но это позволяет мне только указать центр (например, через simdPosition). Другие способы изменить это преобразование (которое я не знаю, как использовать), или углы Эйлера (которые я не уверен, как вычислить, какие из них мне нужно использовать).

Ответы [ 3 ]

1 голос
/ 20 октября 2019

Просто создайте пользовательскую геометрию, используя 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)
0 голосов
/ 25 октября 2019

Вы можете нарисовать линию между двумя точками, используя следующий подход:

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])
    }
}

... и затем передать его в функцию addLine в ViewController:

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)
    }
}
0 голосов
/ 20 октября 2019

Сначала вам нужно рассчитать курс и шаг между двумя точками. Полный пост здесь и этот ответ объясняет, как сделать это между любыми двумя точками.

Если у вас есть два угла, если вы попытаетесь использовать ЭйлераУглы на SCNBox, вы заметите, что, когда вы только изменяете высоту (eulerAngles.x) или изменяете только заголовок (eulerAngles.y), все работает нормально. Однако, как только вы попытаетесь изменить оба, вы столкнетесь с проблемами . Одно из решений состоит в том, чтобы обернуть один узел внутри другого .

Это казалось таким полезным предложением, что я создал удобный узел-обертку, который должен обрабатывать повороты по всем 3 осям:

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
}
...