【问题标题】:Draw SceneKit object between two points在两点之间绘制 SceneKit 对象
【发布时间】:2016-05-02 07:56:31
【问题描述】:

geometry side 方面取得了一些进展,我正着手将整个场景组合在一起。该场景有几十个对象,每个对象由一个边界立方体定义,其角由两个 SCNVector3(最初是两组 x、y、z)指定。

这是我目前所拥有的一个示例 - 它是一个 11 元素对数周期天线,就像 70 年代的老式电视天线一样。每条灰线都是一个“元素”,通常由铝棒制成。我使用了从 +ve 到 -ve Y 的 SCNCylinders,整个内容不到 100 行(SK 非常棒)。

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

有没有人有一个通用的函数,它将获取两个 3D 点并返回适合设置节点的 eulerAngle 的 SCNVector3,或类似的解决方案?

【问题讨论】:

  • 您找到解决方案了吗?谢谢!
  • 恐怕运气不好。我转到了应用程序的其他部分。

标签: swift scenekit


【解决方案1】:

上面提到的两种解决方案都非常有效,我可以为这个问题提供第三个解决方案。

//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种方法不同的方向。

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

【讨论】:

  • 当我使用 ARImageTrackingConfiguration 而不是 ARWorldTrackingConfiguration 时,是否将线路放在固定位置?
【解决方案2】:

编辑:对于低于或等于 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)
        }
    }
}

【讨论】:

  • 您可以使用scene.rootNode.addChildNode() 将此节点添加到您的scenekit 视图中!我已经使用这个类来模拟两个原子之间的蛋白质连接。
【解决方案3】:

@maury-markowitz 的回答对我有用,这是它的最新 (Swift4) 版本。 对于在 Swift 中使用 SCNVector3 的任何人,我只能建议在代码中的某处添加 +-*/ 运算符重载 (e.g. from here)。

extension SCNNode {
    static func lineNode(from: SCNVector3, to: SCNVector3, radius: CGFloat = 0.25) -> SCNNode {
        let vector = to - from
        let height = vector.length()
        let cylinder = SCNCylinder(radius: radius, height: CGFloat(height))
        cylinder.radialSegmentCount = 4
        let node = SCNNode(geometry: cylinder)
        node.position = (to + from) / 2
        node.eulerAngles = SCNVector3.lineEulerAngles(vector: vector)
        return node
    }
}

extension SCNVector3 {
    static func lineEulerAngles(vector: SCNVector3) -> SCNVector3 {
        let height = vector.length()
        let lxz = sqrtf(vector.x * vector.x + vector.z * vector.z)
        let pitchB = vector.y < 0 ? Float.pi - asinf(lxz/height) : asinf(lxz/height)
        let pitch = vector.z == 0 ? pitchB : sign(vector.z) * pitchB

        var yaw: Float = 0
        if vector.x != 0 || vector.z != 0 {
            let inner = vector.x / (height * sinf(pitch))
            if inner > 1 || inner < -1 {
                yaw = Float.pi / 2
            } else {
                yaw = asinf(inner)
            }
        }
        return SCNVector3(CGFloat(pitch), CGFloat(yaw), 0)
    }
}

【讨论】:

  • 请注意,如果您正在使用 Swift 4(因此可能针对 iOS 11 / macOS 10.13 / 等),您可以更轻松地使用 SIMD 向量类型而不是 SCNVector,从而得到东西像免费的运算符重载(因为它们使用 CPU 内在函数而更快)。此外,SCNNode.look(at:) 等新的便利功能也可能是相关的。
【解决方案4】:

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

在我的例子中,节点总是放置在一个固定的平面上,该平面切割 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))
    }
}

如果你没有像我这样的固定轴,那么你可以做额外的三角来使用这种方法。

【讨论】:

    【解决方案5】:

    这是一个使用 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
        }
    }
    

    【讨论】:

    • 我需要使用 lineNode.simdRotate(by: q, aroundTarget: (to+from)/2) 但除此之外,这对我来说非常有效,而且非常简洁!谢谢!
    【解决方案6】:

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

    我所做的是根据两端的 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)
    

    我怀疑有一种更简单的方法可以使用其他旋转输入之一来执行此操作,但这很有效,而且工作是一项功能!

    【讨论】:

      【解决方案7】:

      在两个节点之间画线:

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

      【讨论】:

      • 'glLineWidth' 已弃用:首先在 iOS 12.0 中弃用 - OpenGLES API 已弃用。
      【解决方案8】:

      Winchill 答案的 Objective-C 版本:

      -(void)lineNodeFrom:(SCNVector3)to to:(SCNVector3)from radius:(float)radius{
          
          
          SCNVector3 w = SCNVector3Make(to.x - from.x, to.y - from.y, from.z - to.z);
          float l = sqrtf(powf(w.x, 2) + powf(w.y, 2) + powf(w.z, 2.0f));
          
          SCNCylinder * cylinder = [SCNCylinder cylinderWithRadius:radius height:l];
          SCNMaterial * material = [SCNMaterial material];
          material.diffuse.contents = [[UIColor darkGrayColor] colorWithAlphaComponent:0.75f];
          cylinder.materials = @[material];
          
          [self setGeometry:cylinder];
          
          //original vector of cylinder above 0,0,0
          SCNVector3 ov = SCNVector3Make(0, l/2.0,0);
          //target vector, in new coordination
          SCNVector3 nv = SCNVector3Make((from.x - to.x)/2.0, (from.y - to.y)/2.0, (from.z-to.z)/2.0);
          // axis between two vector
          SCNVector3 av = SCNVector3Make((ov.x + nv.x)/2.0, (ov.y+nv.y)/2.0, (ov.z+nv.z)/2.0);
          
          //normalized axis vector
          SCNVector3 av_normalized = [self normaliseVector:av];
          float q0 = 0.0f; //cos(angel/2), angle is always 180 or M_PI
          float q1 = av_normalized.x; // x' * sin(angle/2)
          float q2 = av_normalized.y; // y' * sin(angle/2)
          float q3 = av_normalized.z; // z' * sin(angle/2)
          
          float r_m11 = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3;
          float r_m12 = 2 * q1 * q2 + 2 * q0 * q3;
          float r_m13 = 2 * q1 * q3 - 2 * q0 * q2;
          float r_m21 = 2 * q1 * q2 - 2 * q0 * q3;
          float r_m22 = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3;
          float r_m23 = 2 * q2 * q3 + 2 * q0 * q1;
          float r_m31 = 2 * q1 * q3 + 2 * q0 * q2;
          float r_m32 = 2 * q2 * q3 - 2 * q0 * q1;
          float r_m33 = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3;
          
          SCNMatrix4 transform;
          transform.m11 = r_m11;
          transform.m12 = r_m12;
          transform.m13 = r_m13;
          transform.m14 = 0.0;
          
          transform.m21 = r_m21;
          transform.m22 = r_m22;
          transform.m23 = r_m23;
          transform.m24 = 0.0;
          
          transform.m31 = r_m31;
          transform.m32 = r_m32;
          transform.m33 = r_m33;
          transform.m34 = 0.0;
          
          transform.m41 = (to.x + from.x) / 2.0;
          transform.m42 = (to.y + from.y) / 2.0;
          transform.m43 = (to.z + from.z) / 2.0;
          transform.m44 = 1.0;
       
          self.transform = transform;
          
      }
      
      -(SCNVector3)normaliseVector:(SCNVector3)iv{
          
          float length = sqrt(iv.x * iv.x + iv.y * iv.y + iv.z * iv.z);
          if (length == 0){
              return SCNVector3Make(0.0, 0.0, 0.0);
          }
          
          return SCNVector3Make(iv.x / length, iv.y / length, iv.z / length);
          
      }
      

      【讨论】:

        猜你喜欢
        • 2015-07-23
        • 2014-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-02-11
        • 2011-07-23
        • 1970-01-01
        相关资源
        最近更新 更多