在两点之间绘制SceneKit对象

时间:2016-01-25 20:57:59

标签: swift scenekit

在我geometry side的事情上取得了一些进展,我正在整理整个场景。那个场景有几十个对象,每个对象都由一个边界立方体定义,其边角由两个SCNVector3指定(最初是两组x,y,z)。

这是我迄今为止所拥有的一个例子 - 它是一个11元素的对数周期天线,就像70年代的旧式学校电视天线一样。每条灰线都是一个"元素",通常由铝棒制成。我使用了从+ ve到-ve Y的SCNCylinders,整个东西不到100行(SK非常棒)。

olde schoole

问题是如果元素在X上不对称并且因此必须旋转SCNCylinder会发生什么。我找到了this example,但我无法理解这些细节...它似乎利用了一个球体是对称的这一事实,因此角度类型的“消失”#34;。

是否有人拥有一个通用功能,它将获得两个3D点并返回适合设置节点的eulerAngle或类似解决方案的SCNVector3?

7 个答案:

答案 0 :(得分:18)

上面提到的两种解决方案都运行良好,我可以为这个问题提供第三种解决方案。

//extension code starts

func normalizeVector(_ iv: SCNVector3) -> SCNVector3 {
    let length = sqrt(iv.x * iv.x + iv.y * iv.y + iv.z * iv.z)
    if length == 0 {
        return SCNVector3(0.0, 0.0, 0.0)
    }

    return SCNVector3( iv.x / length, iv.y / length, iv.z / length)

}

extension SCNNode {

    func buildLineInTwoPointsWithRotation(from startPoint: SCNVector3,
                              to endPoint: SCNVector3,
                              radius: CGFloat,
                              color: UIColor) -> SCNNode {
        let w = SCNVector3(x: endPoint.x-startPoint.x,
                           y: endPoint.y-startPoint.y,
                           z: endPoint.z-startPoint.z)
        let l = CGFloat(sqrt(w.x * w.x + w.y * w.y + w.z * w.z))

        if l == 0.0 {
            // two points together.
            let sphere = SCNSphere(radius: radius)
            sphere.firstMaterial?.diffuse.contents = color
            self.geometry = sphere
            self.position = startPoint
            return self

        }

        let cyl = SCNCylinder(radius: radius, height: l)
        cyl.firstMaterial?.diffuse.contents = color

        self.geometry = cyl

        //original vector of cylinder above 0,0,0
        let ov = SCNVector3(0, l/2.0,0)
        //target vector, in new coordination
        let nv = SCNVector3((endPoint.x - startPoint.x)/2.0, (endPoint.y - startPoint.y)/2.0,
                            (endPoint.z-startPoint.z)/2.0)

        // axis between two vector
        let av = SCNVector3( (ov.x + nv.x)/2.0, (ov.y+nv.y)/2.0, (ov.z+nv.z)/2.0)

        //normalized axis vector
        let av_normalized = normalizeVector(av)
        let q0 = Float(0.0) //cos(angel/2), angle is always 180 or M_PI
        let q1 = Float(av_normalized.x) // x' * sin(angle/2)
        let q2 = Float(av_normalized.y) // y' * sin(angle/2)
        let q3 = Float(av_normalized.z) // z' * sin(angle/2)

        let r_m11 = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3
        let r_m12 = 2 * q1 * q2 + 2 * q0 * q3
        let r_m13 = 2 * q1 * q3 - 2 * q0 * q2
        let r_m21 = 2 * q1 * q2 - 2 * q0 * q3
        let r_m22 = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3
        let r_m23 = 2 * q2 * q3 + 2 * q0 * q1
        let r_m31 = 2 * q1 * q3 + 2 * q0 * q2
        let r_m32 = 2 * q2 * q3 - 2 * q0 * q1
        let r_m33 = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3

        self.transform.m11 = r_m11
        self.transform.m12 = r_m12
        self.transform.m13 = r_m13
        self.transform.m14 = 0.0

        self.transform.m21 = r_m21
        self.transform.m22 = r_m22
        self.transform.m23 = r_m23
        self.transform.m24 = 0.0

        self.transform.m31 = r_m31
        self.transform.m32 = r_m32
        self.transform.m33 = r_m33
        self.transform.m34 = 0.0

        self.transform.m41 = (startPoint.x + endPoint.x) / 2.0
        self.transform.m42 = (startPoint.y + endPoint.y) / 2.0
        self.transform.m43 = (startPoint.z + endPoint.z) / 2.0
        self.transform.m44 = 1.0
        return self
    }
}

//extension ended.

//in your code, you can like this.
let twoPointsNode1 = SCNNode()
        scene.rootNode.addChildNode(twoPointsNode1.buildLineInTwoPointsWithRotation(
            from: SCNVector3(1,-1,3), to: SCNVector3( 7,11,7), radius: 0.2, color: .cyan))
//end

您可以参考http://danceswithcode.net/engineeringnotes/quaternions/quaternions.html

顺便说一下,当你使用圆柱体从3种方法中的两点之间划线时,你会得到相同的结果。但实际上,它们会有不同的正常线条。换句话说,如果你在两点之间使用盒子,盒子的两边(顶部和底部除外)将面向与上述3种方法不同的方向。

如果您需要进一步解释,请告诉我。

答案 1 :(得分:6)

编辑:低于或等于IOS 11

我给你带来了好消息!你可以链接两个点并在这个Vector上放一个SCNNode!

拿这个,享受两点之间的画线!

class   CylinderLine: SCNNode
{
    init( parent: SCNNode,//Needed to add destination point of your line
        v1: SCNVector3,//source
        v2: SCNVector3,//destination
        radius: CGFloat,//somes option for the cylinder
        radSegmentCount: Int, //other option
        color: UIColor )// color of your node object
    {
        super.init()

        //Calcul the height of our line
        let  height = v1.distance(v2)

        //set position to v1 coordonate
        position = v1

        //Create the second node to draw direction vector
        let nodeV2 = SCNNode()

        //define his position
        nodeV2.position = v2
        //add it to parent
        parent.addChildNode(nodeV2)

        //Align Z axis
        let zAlign = SCNNode()
        zAlign.eulerAngles.x = Float(M_PI_2)

        //create our cylinder
        let cyl = SCNCylinder(radius: radius, height: CGFloat(height))
        cyl.radialSegmentCount = radSegmentCount
        cyl.firstMaterial?.diffuse.contents = color

        //Create node with cylinder
        let nodeCyl = SCNNode(geometry: cyl )
        nodeCyl.position.y = -height/2
        zAlign.addChildNode(nodeCyl)

        //Add it to child
        addChildNode(zAlign)

        //set contrainte direction to our vector
        constraints = [SCNLookAtConstraint(target: nodeV2)]
    }

    override init() {
        super.init()
    }
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

private extension SCNVector3{
    func distance(receiver:SCNVector3) -> Float{
        let xd = receiver.x - self.x
        let yd = receiver.y - self.y
        let zd = receiver.z - self.z
        let distance = Float(sqrt(xd * xd + yd * yd + zd * zd))

        if (distance < 0){
            return (distance * -1)
        } else {
            return (distance)
        }
    }
}

答案 2 :(得分:3)

@ maury-markowitz的答案对我有用,这里是最新的(Swift4)版本。 对于在Swift中使用dashboard.factory('User', ['$resource', 'urlPrefix', function ($resource, urlPrefix) { return $resource(urlPrefix + '/user/json/getUsersByName', {}, {}); } ]); 的任何人,我只建议在代码中的某处添加SCNVector3运算符重载(e.g. from here)。

+-*/

答案 3 :(得分:2)

为了另一种方法,我通过三角函数实现了这一点。这使得代码非常少。这是最终结果:

enter image description here

在我的情况下,节点始终放置在固定Y轴的固定平面上。

// Create Cylinder Geometry                    
let line = SCNCylinder(radius: 0.002, height: node1.distance(to: node2))

// Create Material 
let material = SCNMaterial()
material.diffuse.contents = UIColor.red
material.lightingModel = .phong
line.materials = [material]

// Create Cylinder(line) Node                   
let newLine = SCNNode()
newLine.geometry = line
newLine.position = posBetween(first: node1, second: node2)

// This is the change in x,y and z between node1 and node2
let dirVector = SCNVector3Make(node2.x - node1.x, node2.y - node1.y, node2.z - node1.z)

// Get Y rotation in radians
let yAngle = atan(dirVector.x / dirVector.z)

// Rotate cylinder node about X axis so cylinder is laying down
currentLine.eulerAngles.x = .pi / 2

// Rotate cylinder node about Y axis so cylinder is pointing to each node
currentLine.eulerAngles.y = yAngle

此函数用于获取两个节点之间的位置,并将其放置在您的类中:

func posBetween(first: SCNVector3, second: SCNVector3) -> SCNVector3 {
        return SCNVector3Make((first.x + second.x) / 2, (first.y + second.y) / 2, (first.z + second.z) / 2)
}

这是获取圆柱高度的节点之间距离的扩展,将其放置在班级之外的某个地方:

extension SCNVector3 {
    func distance(to destination: SCNVector3) -> CGFloat {
        let dx = destination.x - x
        let dy = destination.y - y
        let dz = destination.z - z
        return CGFloat(sqrt(dx*dx + dy*dy + dz*dz))
    }
}

如果您没有像我这样的固定轴,则可以做一些额外的尝试来使用此方法。

答案 4 :(得分:0)

Sprout's(哇,自动更正不允许我实际输入他的名字!)post确实是一个解决方案,但我在我的代码中实现了一个非常不同的解决方案。

我所做的是根据两端的X,Y和Z位置计算线的长度和两个端点:

let w = SCNVector3(x: CGFloat(x2m-x1m), y: CGFloat(y2m-y1m), z: CGFloat(z2m-z1m))
let l = w.length()

长度只是pythag。现在我创建一个SCNNode来保存SCNCylinder,并将其放在行的中间位置:

    let node = SCNNode(geometry: cyl)
    node.position = SCNVector3(x: CGFloat((x1m+x2m)/2.0), y: CGFloat((y1m+y2m)/2.0), z: CGFloat((z1m+z2m)/2.0))

现在是讨厌的部分,我们计算欧拉角并旋转节点:

    let lxz = (Double(w.x)**2 + Double(w.z)**2)**0.5
    var pitch, pitchB: Double
    if w.y < 0 {
        pitchB = M_PI - asin(Double(lxz)/Double(l))
    } else {
        pitchB = asin(Double(lxz)/Double(l))
    }
    if w.z == 0 {
        pitch = pitchB
    } else {
        pitch = sign(Double(w.z)) * pitchB
    }
    var yaw: Double
    if w.x == 0 && w.z == 0 {
        yaw = 0
    } else {
        let inner = Double(w.x) / (Double(l) * sin (pitch))
        if inner > 1 {
            yaw = M_PI_2
        } else if inner < -1 {
            yaw = M_PI_2
        } else {
            yaw = asin(inner)
        }
    }
    node.eulerAngles = SCNVector3(CGFloat(pitch), CGFloat(yaw), 0)

我怀疑有一种更简单的方法可以使用其他旋转输入之一来完成此操作,但这是有效的,并且正常工作!

答案 5 :(得分:0)

这是使用simd和四元数进行旋转的解决方案。我以@Bersaelor的答案作为扩展名。

我使用此导数(https://stackoverflow.com/a/1171995/6693924)从两个向量创建四元数。希望这会有所帮助。

extension SCNNode {
    static func lineNode(from: simd_float3, to: simd_float3, radius : CGFloat = 0.25) -> SCNNode
    {
        let vector = to - from
        let height = simd_length(vector)

        //cylinder
        let cylinder = SCNCylinder(radius: radius, height: CGFloat(height))
        cylinder.firstMaterial?.diffuse.contents = UIColor.white

        //line node
        let lineNode = SCNNode(geometry: cylinder)

        //adjust line position
        let line_axis = simd_float3(0, height/2, 0)
        lineNode.simdPosition = from + line_axis

        let vector_cross = simd_cross(line_axis, vector)
        let qw = simd_length(line_axis) * simd_length(vector) + simd_dot(line_axis, vector)
        let q = simd_quatf(ix: vector_cross.x, iy: vector_cross.y, iz: vector_cross.z, r: qw).normalized

        lineNode.simdRotate(by: q, aroundTarget: from)
        return lineNode
    }
}

答案 6 :(得分:0)

绘制两个节点之间的线:

func generateLine( startPoint: SCNVector3, endPoint: SCNVector3) -> SCNGeometry {

        let vertices: [SCNVector3] = [startPoint, endPoint]
        let data = NSData(bytes: vertices, length: MemoryLayout<SCNVector3>.size * vertices.count) as Data

        let vertexSource = SCNGeometrySource(data: data,
                                             semantic: .vertex,
                                             vectorCount: vertices.count,
                                             usesFloatComponents: true,
                                             componentsPerVector: 3,
                                             bytesPerComponent: MemoryLayout<Float>.size,
                                             dataOffset: 0,
                                             dataStride: MemoryLayout<SCNVector3>.stride)

        let indices: [Int32] = [ 0, 1]

        let indexData = NSData(bytes: indices, length: MemoryLayout<Int32>.size * indices.count) as Data

        let element = SCNGeometryElement(data: indexData,
                                         primitiveType: .line,
                                         primitiveCount: indices.count/2,
                                         bytesPerIndex: MemoryLayout<Int32>.size)

        return SCNGeometry(sources: [vertexSource], elements: [element])

    }

使用方法

let line = generateLine(startPoint: SCNVector3Make(1, 1, 1), endPoint: SCNVector3Make(8, 8, 8))
        let lineNode = SCNNode(geometry: line)
        lineNode.position = SCNVector3Make(15, 15, 10)
        scene.rootNode.addChildNode(lineNode)

线的粗细需要实现SCNSceneRendererDelegate,尤其是:

func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval){
        glLineWidth(10)
}
相关问题