Cylinder Orientation between two points on a sphere, Scenekit, Quaternions IOS

后端 未结 6 870
南笙
南笙 2020-12-17 00:04

I\'ve been trying to draw a cylinder between two points on the outer edge of a sphere using SceneKit. I have already produced a line between these two points using primitive

相关标签:
6条回答
  • 2020-12-17 00:11

    Here's an entire method using Objective-C

    First, here's how you use it:

    SCNNode * testNode = [self lat1:-35 lon1:108 height1:tall lat2:-35 lon2:30 height2:0];
    

    Inputs:

    1rst location lat1 = latitude of 1rst location lon1 = longitude of 1rst location height1 = distance from earth for 1rst location lat2 = latitude of 2nd location lon2 = latitude of 2nd location height2 = distance from earth for 2nd location

    The second method creates the SCNVector3 points for each location in question above:

    -(SCNNode *)lat1:(double)lat1 lon1:(double)lon1 height1:(float)height1 lat2:(double)lat2 lon2:(double)lon2 height2:(float)height2 {
        SCNVector3 positions[] = {[self lat:lat1 lon:lon1 height:height1], [self lat:lat2 lon:lon2 height:height2]};
    
        float cylHeight = GLKVector3Distance(SCNVector3ToGLKVector3(positions[0]), SCNVector3ToGLKVector3(positions[1]))/4;
    
        SCNCylinder * masterCylinderNode = [SCNCylinder cylinderWithRadius:0.05 height:cylHeight];
    
        SCNMaterial *material = [SCNMaterial material];
        [[material diffuse] setContents:[SKColor whiteColor]];
        material.lightingModelName = SCNLightingModelConstant;
        material.emission.contents = [SKColor whiteColor];
        [masterCylinderNode setMaterials:@[material]];
    
        SCNNode *mainLocationPointNodeTestA = [mainLocationPointNode clone];
        SCNNode *mainLocationPointNodeTestB = [mainLocationPointNode clone];
    
        mainLocationPointNodeTestA.position = positions[0];
        mainLocationPointNodeTestB.position = positions[1];
    
        SCNNode * mainParentNode = [SCNNode node];
        SCNNode * tempNode2 =[SCNNode nodeWithGeometry:masterCylinderNode];
    
        [mainParentNode addChildNode:mainLocationPointNodeTestA];
        [mainParentNode addChildNode:mainLocationPointNodeTestB];
        [mainParentNode addChildNode:tempNode2];
    
        [mainParentNode setName:@"parentToLineNode"];
    
        tempNode2.position = SCNVector3Make((positions[0].x+positions[1].x)/2, (positions[0].y+positions[1].y)/2, (positions[0].z+positions[1].z)/2);
        tempNode2.pivot = SCNMatrix4MakeTranslation(0, cylHeight*1.5, 0);
    
        GLKVector3 normalizedVectorStartingPosition = GLKVector3Make(0.0, 1.0, 0.0);
        GLKVector3 magicAxis = GLKVector3Normalize(GLKVector3Subtract(GLKVector3Make(positions[0].x/2, positions[0].y/2, positions[0].z/2), GLKVector3Make(positions[1].x/2, positions[1].y/2, positions[1].z/2)));
    
        GLKVector3 rotationAxis = GLKVector3CrossProduct(normalizedVectorStartingPosition, magicAxis);
        CGFloat rotationAngle = GLKVector3DotProduct(normalizedVectorStartingPosition, magicAxis);
    
        GLKVector4 rotation = GLKVector4MakeWithVector3(rotationAxis, acos(rotationAngle));
        tempNode2.rotation = SCNVector4FromGLKVector4(rotation);
    
        return mainParentNode;
    }
    

    This second method uses hard coded numbers for earth's radius and curvature, I'm showing this just to show the numbers required for total 100% accuracy, this is how it works. You'll want to change this to the correct dimensions for your scene, obviously, but here's the method. This is an adaptation of methods used by http://www.gdal.org/index.html. An explanation an be found here: http://www.gdal.org/osr_tutorial.html. I put this together very quickly but it works and is accurate, feel free to change the number formats to your liking.

    -(SCNVector3)lat:(double)lat lon:(double)lon height:(float)height {
        double latd = 0.0174532925;
        double latitude = latd*lat;
        double longitude = latd*lon;
    
        Float64 rad = (Float64)(6378137.0);
        Float64 f = (Float64)(1.0/298.257223563);
    
        double cosLat = cos(latitude);
    
        double sinLat = sin(latitude);
    
        double FF = pow((1.0-f), 2);
        double C = 1/(sqrt(pow(cosLat,2) + FF * pow(sinLat,2)));
        double S = C * FF;
    
        double x = ((rad * C)*cosLat * cos(longitude))/(1000000/(1+height));
        double y = ((rad * C)*cosLat * sin(longitude))/(1000000/(1+height));
        double z = ((rad * S)*sinLat)/(1000000/(1+height));
    
        return SCNVector3Make(y+globeNode.position.x, z+globeNode.position.y, x+globeNode.position.z);
    }
    
    0 讨论(0)
  • 2020-12-17 00:20

    i use SCNVector3 extensions with:

     func cylVector(from : SCNVector3, to : SCNVector3) -> SCNNode {
        let vector = to - from,
            length = vector.length()
    
        let cylinder = SCNCylinder(radius: cylsRadius, height: CGFloat(length))
        cylinder.radialSegmentCount = 6
        cylinder.firstMaterial = material
    
        let node = SCNNode(geometry: cylinder)
    
        node.position = (to + from) / 2
        node.eulerAngles = SCNVector3Make(CGFloat(Double.pi/2), acos((to.z-from.z)/length), atan2((to.y-from.y), (to.x-from.x) ))
    
        return node
    }
    
    0 讨论(0)
  • 2020-12-17 00:21

    Just for reference a more elegant SCNCyclinder implementation to connect a start and end position with a given radius:

    func makeCylinder(from: SCNVector3, to: SCNVector3, radius: CGFloat) -> SCNNode
    {
        let lookAt = to - from
        let height = lookAt.length()
    
        let y = lookAt.normalized()
        let up = lookAt.cross(vector: to).normalized()
        let x = y.cross(vector: up).normalized()
        let z = x.cross(vector: y).normalized()
        let transform = SCNMatrix4(x: x, y: y, z: z, w: from)
    
        let geometry = SCNCylinder(radius: radius, 
                                   height: CGFloat(height))
        let childNode = SCNNode(geometry: geometry)
        childNode.transform = SCNMatrix4MakeTranslation(0.0, height / 2.0, 0.0) * 
          transform
    
        return childNode
    }
    

    Needs the following extension:

    extension SCNVector3 {
        /**
         * Calculates the cross product between two SCNVector3.
         */
        func cross(vector: SCNVector3) -> SCNVector3 {
            return SCNVector3Make(y * vector.z - z * vector.y, z * vector.x - x * vector.z, x * vector.y - y * vector.x)
        }
    
        func length() -> Float {
            return sqrtf(x*x + y*y + z*z)
        }
    
        /**
         * Normalizes the vector described by the SCNVector3 to length 1.0 and returns
         * the result as a new SCNVector3.
         */
        func normalized() -> SCNVector3 {
            return self / length()
        }
    }
    
    extension SCNMatrix4 {
        public init(x: SCNVector3, y: SCNVector3, z: SCNVector3, w: SCNVector3) {
            self.init(
                m11: x.x,
                m12: x.y,
                m13: x.z,
                m14: 0.0,
    
                m21: y.x,
                m22: y.y,
                m23: y.z,
                m24: 0.0,
    
                m31: z.x,
                m32: z.y,
                m33: z.z,
                m34: 0.0,
    
                m41: w.x,
                m42: w.y,
                m43: w.z,
                m44: 1.0)
        }
    }
    
    /**
     * Divides the x, y and z fields of a SCNVector3 by the same scalar value and
     * returns the result as a new SCNVector3.
     */
    func / (vector: SCNVector3, scalar: Float) -> SCNVector3 {
        return SCNVector3Make(vector.x / scalar, vector.y / scalar, vector.z / scalar)
    }
    
    func * (left: SCNMatrix4, right: SCNMatrix4) -> SCNMatrix4 {
        return SCNMatrix4Mult(left, right)
    }
    
    0 讨论(0)
  • 2020-12-17 00:22

    Thank you, Rickster! I have taken it a little further and made a class out of it:

    class LineNode: SCNNode
    {
        init( parent: SCNNode,     // because this node has not yet been assigned to a parent.
                  v1: SCNVector3,  // where line starts
                  v2: SCNVector3,  // where line ends
              radius: CGFloat,     // line thicknes
          radSegmentCount: Int,    // number of sides of the line
            material: [SCNMaterial] )  // any material.
        {
            super.init()
            let  height = v1.distance(v2)
    
            position = v1
    
            let ndV2 = SCNNode()
    
            ndV2.position = v2
            parent.addChildNode(ndV2)
    
            let ndZAlign = SCNNode()
            ndZAlign.eulerAngles.x = Float(M_PI_2)
    
            let cylgeo = SCNCylinder(radius: radius, height: CGFloat(height))
            cylgeo.radialSegmentCount = radSegmentCount
            cylgeo.materials = material
    
            let ndCylinder = SCNNode(geometry: cylgeo )
            ndCylinder.position.y = -height/2
            ndZAlign.addChildNode(ndCylinder)
    
            addChildNode(ndZAlign)
    
            constraints = [SCNLookAtConstraint(target: ndV2)]
        }
    
        override init() {
            super.init()
        }
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
     }
    

    I have tested this class successfully in an iOS app, using this function, which draws 100 lines (oops cylinders :o).

        func linesTest3()
        {
            let mat = SCNMaterial()
            mat.diffuse.contents  = UIColor.whiteColor()
            mat.specular.contents = UIColor.whiteColor()
    
            for _ in 1...100    // draw 100 lines (as cylinders) between random points.
            {
                let v1 =  SCNVector3( x: Float.random(min: -50, max: 50),
                                      y: Float.random(min: -50, max: 50),
                                      z: Float.random(min: -50, max: 50) )
    
                let v2 =  SCNVector3( x: Float.random(min: -50, max: 50),
                                      y: Float.random(min: -50, max: 50),
                                      z: Float.random(min: -50, max: 50) )
    
                // Just for testing, add two little spheres to check if lines are drawn correctly:
                // each line should run exactly from a green sphere to a red one:
    
                root.addChildNode(makeSphere(v1, radius: 0.5, color: UIColor.greenColor()))
                root.addChildNode(makeSphere(v2, radius: 0.5, color: UIColor.redColor()))
    
                // Have to pass the parentnode because 
                // it is not known during class instantiation of LineNode.
    
                let ndLine = LineNode(
                             parent: scene.rootNode, // ** needed
                                 v1: v1,    // line (cylinder) starts here
                                 v2: v2,    // line ends here
                             radius: 0.2,   // line thickness
                    radSegmentCount: 6,     // hexagon tube
                           material: [mat] )  // any material
    
                root.addChildNode(ndLine)
            }
        }
    

    100 random lines Regards. (btw. I can only see 3D objects.. I have never seen a "line" in my life :o)

    0 讨论(0)
  • 2020-12-17 00:25

    Here's a quick demo using node hierarchy (to get the cylinder situated such that its end is at one point and its length is along the local z-axis) and a constraint (to make that z-axis look at another point).

    let root = view.scene!.rootNode
    
    // visualize a sphere
    let sphere = SCNSphere(radius: 1)
    sphere.firstMaterial?.transparency = 0.5
    let sphereNode = SCNNode(geometry: sphere)
    root.addChildNode(sphereNode)
    
    // some dummy points opposite each other on the sphere
    let rootOneThird = CGFloat(sqrt(1/3.0))
    let p1 = SCNVector3(x: rootOneThird, y: rootOneThird, z: rootOneThird)
    let p2 = SCNVector3(x: -rootOneThird, y: -rootOneThird, z: -rootOneThird)
    
    // height of the cylinder should be the distance between points
    let height = CGFloat(GLKVector3Distance(SCNVector3ToGLKVector3(p1), SCNVector3ToGLKVector3(p2)))
    
    // add a container node for the cylinder to make its height run along the z axis
    let zAlignNode = SCNNode()
    zAlignNode.eulerAngles.x = CGFloat(M_PI_2)
    // and position the zylinder so that one end is at the local origin
    let cylinder = SCNNode(geometry: SCNCylinder(radius: 0.1, height: height))
    cylinder.position.y = -height/2
    zAlignNode.addChildNode(cylinder)
    
    // put the container node in a positioning node at one of the points
    p2Node.addChildNode(zAlignNode)
    // and constrain the positioning node to face toward the other point
    p2Node.constraints = [ SCNLookAtConstraint(target: p1Node) ]
    

    Sorry if you were looking for an ObjC-specific solution, but it was quicker for me to prototype this in an OS X Swift playground. (Also, less CGFloat conversion is needed in iOS, because the element type of SCNVector3 is just Float there.)

    0 讨论(0)
  • 2020-12-17 00:28

    I have been looking for a solution to make cylinder between two points and thanks to rickster, I have used his answer to make SCNNode extension. There, I have added missing conditions for a possible cylinder orientation to avoid its wrong opposite direction.

    func makeCylinder(positionStart: SCNVector3, positionEnd: SCNVector3, radius: CGFloat , color: NSColor, transparency: CGFloat) -> SCNNode
    {
        let height = CGFloat(GLKVector3Distance(SCNVector3ToGLKVector3(positionStart), SCNVector3ToGLKVector3(positionEnd)))
        let startNode = SCNNode()
        let endNode = SCNNode()
    
        startNode.position = positionStart
        endNode.position = positionEnd
    
        let zAxisNode = SCNNode()
        zAxisNode.eulerAngles.x = CGFloat(M_PI_2)
    
        let cylinderGeometry = SCNCylinder(radius: radius, height: height)
        cylinderGeometry.firstMaterial?.diffuse.contents = color
        let cylinder = SCNNode(geometry: cylinderGeometry)
    
        cylinder.position.y = -height/2
        zAxisNode.addChildNode(cylinder)
    
        let returnNode = SCNNode()
    
        if (positionStart.x > 0.0 && positionStart.y < 0.0 && positionStart.z < 0.0 && positionEnd.x > 0.0 && positionEnd.y < 0.0 && positionEnd.z > 0.0)
        {
            endNode.addChildNode(zAxisNode)
            endNode.constraints = [ SCNLookAtConstraint(target: startNode) ]
            returnNode.addChildNode(endNode)
    
        }
        else if (positionStart.x < 0.0 && positionStart.y < 0.0 && positionStart.z < 0.0 && positionEnd.x < 0.0 && positionEnd.y < 0.0 && positionEnd.z > 0.0)
        {
            endNode.addChildNode(zAxisNode)
            endNode.constraints = [ SCNLookAtConstraint(target: startNode) ]
            returnNode.addChildNode(endNode)
    
        }
        else if (positionStart.x < 0.0 && positionStart.y > 0.0 && positionStart.z < 0.0 && positionEnd.x < 0.0 && positionEnd.y > 0.0 && positionEnd.z > 0.0)
        {
            endNode.addChildNode(zAxisNode)
            endNode.constraints = [ SCNLookAtConstraint(target: startNode) ]
            returnNode.addChildNode(endNode)
    
        }
        else if (positionStart.x > 0.0 && positionStart.y > 0.0 && positionStart.z < 0.0 && positionEnd.x > 0.0 && positionEnd.y > 0.0 && positionEnd.z > 0.0)
        {
            endNode.addChildNode(zAxisNode)
            endNode.constraints = [ SCNLookAtConstraint(target: startNode) ]
            returnNode.addChildNode(endNode)
    
        }
        else
        {
            startNode.addChildNode(zAxisNode)
            startNode.constraints = [ SCNLookAtConstraint(target: endNode) ]
            returnNode.addChildNode(startNode)
        }
    
        return returnNode
    }
    
    0 讨论(0)
提交回复
热议问题