2016-01-25 89 views
4

在我正在着手整理一個場景的geometry side方面取得了一些進展。那個場景有幾十個對象,每個對象由一個邊界立方體定義,邊界立方體的角由兩個SCNVector3s(最初是兩組x,y,z)指定。繪製兩點之間的SceneKit對象

這是我迄今爲止的一個例子 - 它是一個11元對數週期天線,就像70年代的老派電視天線一樣。每條灰線都是一個「元素」,通常由鋁棒製成。我使用+ ve的SCNCylinders到-ve Y,整個事情少於100行(SK非常驚人)。

olde schoole

的問題是,如果元件沒有跨越X對稱並且因此SCNCylinder必須旋轉會發生什麼。我發現this example,但我無法理解具體情況......它似乎利用了一個事實,即一個球體是對稱的,所以角度有點「消失」。

有沒有人有一個通用的功能,將採取兩個三維點,並返回適合設置節點的eulerAngle或類似的解決方案的SCNVector3?

+0

您是否找到解決方案?謝謝 ! –

+0

沒有運氣,我很害怕。我轉移到應用程序的其他部分。 –

回答

14

上述兩種解決方案都工作得很好,我可以爲這個問題提供第三種解決方案。

//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種方法使兩點之間的直線,你會得到同樣的結果。但的確,他們會有不同的法線。換句話說,如果你在兩點之間使用方框,除了頂部和底部之外,方框的兩側將面向與上述三種方法不同的方向。

讓我知道如果你需要進一步的解釋。

5

我對你有好消息!你可以鏈接兩個點,並將SCNNode放在這個Vector上!

把這個,並享受兩點之間的畫線!

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

如何在代碼中使用它? @E。 Spiroux –

+0

您可以使用scene.rootNode.addChildNode()添加此節點到您的scenekit視圖!我用這個類來模擬兩個原子之間的蛋白質鏈接。 –

0

萌芽的(哇,自動更正不會讓我真正鍵入他的名字!)後確實是一個解決方案,但我已經實現了我的代碼非常不同的解決方案。

我要做的就是計算線的長度和兩個端點,基於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) 

我懷疑有一種更簡單的方式來使用其他旋轉輸入之一來做到這一點,但這個工作和工作是一個功能!

0

@ 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) 
    } 
} 
+1

請注意,如果您使用Swift 4(因此可能針對iOS 11/macOS 10.13/etc),則可以更輕鬆地使用SIMD向量類型而不是'SCNVector',從而獲得像操作符重載(更快因爲他們使用CPU內部函數)。另外,像SCNNode.look(at:)這樣的新的便利功能可能是相關的。 – rickster