【问题标题】:How to draw a line between two points in SceneKit?如何在 SceneKit 中的两点之间画一条线?
【发布时间】:2020-02-16 14:16:16
【问题描述】:

如果我在 SceneKit 中有两个点(例如 (1,2,3)(-1,-1,-1))。如何在两者之间划清界限?

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

【问题讨论】:

    标签: swift math 3d scenekit arkit


    【解决方案1】:

    您可以使用以下方法在两点之间画一条线:

    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】:

      首先,您需要计算两点之间的航向和俯仰角。 Full post 在这里,this answer 解释了如何在任意两点之间进行操作。

      一旦你有了两个角度,如果你尝试在 SCNBox 上使用欧拉角,你会注意到当你只修改音高 (eulerAngles.x) 或只修改航向 (eulerAngles.y) 时,一切正常。但是,当您尝试修改两者时,您将run into issues。一种解决方案是wrap one node inside another

      这似乎是一个非常有用的建议,我创建了一个方便的包装节点来处理所有 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
      }
      

      【讨论】:

        【解决方案3】:

        只需使用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)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-03-20
          • 2022-01-25
          • 1970-01-01
          • 1970-01-01
          • 2014-03-01
          • 2013-06-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多