Draw a SceneKit object between two points

Having made some progress in geometry, I proceed to create an entire scene. This scene has a couple dozen objects, each defined by a bounding cube whose angles are defined by two SCNVector3 (originally two sets of x, y, z).

Here is an example of what I have so far - this is an 11-element log-periodic antenna, similar to the old school television antennas of the 70s. Each of the gray lines represents an “element”, usually made of an aluminum rod. I used SCNCylinders from + ve to -ve Y, and all this is less than 100 lines (SK is pretty amazing).

olde schoole

The problem is what happens if the elements are not symmetrical with respect to X, and thus the cylinder SCN must rotate. I found this example , but I can’t understand the specifics ... it uses the fact that the sphere is symmetrical, so the corners seem to “go away”.

Does anyone have a common function that will take two three-dimensional points and return SCNVector3 suitable for installing the eulerAngle node, or a similar solution?

+7
source share
6 answers

Both of the solutions mentioned above work very well, and I can introduce a third solution to this issue.

//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

you can link to http://danceswithcode.net/engineeringnotes/quaternions/quaternions.html

, , , . , . , , , , .

PLS, .

+19

: IOS 11

! 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)
        }
    }
}
+6

@maury-markowitz , (Swift4). , SCNVector3 Swift, \t21 > - (, ).

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

. . :

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

, , , .

+2

Sprout ( , !) 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))

, node:

    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
source

Here's a solution using simd and quaternions for rotation. I based the extension from @Bersaelor's answer.

I used this output ( fooobar.com/questions/76827 / ... ) to create a quaternion of two vectors. Hope this helps.

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
    }
}
0
source

Source: https://habr.com/ru/post/1625903/


All Articles