在球体上,Scenekit,四元IOS两点之间缸方向

问题描述 投票:6回答:6

我一直在试图得出两个点之间的圆柱体上使用SceneKit球体的外缘。我已经产生这两个点之间的线使用原始形状和OpenGL与SCNRendering代表,但现在我需要产生这两者之间的气缸(当然,不只是两个,而是任意两个3D矢量坐球体的表面上)。我一直对这个约3天直现在,我已经通过了所有我能找到实现四元数要做到这一点了,但因为它的立场,我不能让它开始工作。学术文章,科学的研究,并没有什么,没有什么工作重新调整两个固定点之间的圆柱体。我需要一个算法来做到这一点。

无论如何,这是我最近不工作的代码,但是这是没有预期的结果时的代码中,我通过迄今几乎工作2k行只是一个小片段。我知道我可以移动的东西更先进的像建设自己的SCNProgram和/或SCNRenderer来然后访问GLSL,OpenGL和金属复杂,但是这似乎喜欢的事,应该使用Scenekit和GLKit矢量结构之间的转换和从SCNVector可能结构,但到目前为止,这是不可能的:

码:

下面的代码摄取经纬度坐标和它们投影到3D球体的表面上。这些坐标通过专有功能我建立其中I接收的SCNVector3 {X,Y,Z}准确坐标上我的3D球体的显示返回。我画了两套经纬度之间的坐标线,其中正在使用的图元绘制的线条射穿球的中心。所以,正如我上面提到的,我想这相同的功能,但用气瓶,不是线(顺便说一下,经度和纬度坐标这里列出的是假的,他们是随机生成的但都落在地球表面上)。

drawLine = [self lat1:37.76830 lon1:-30.40096 height1:tall lat2:3.97620 lon2:63.73095 height2:tall];

float cylHeight = GLKVector3Distance(SCNVector3ToGLKVector3(cooridnateSetOne.position), SCNVector3ToGLKVector3(coordinateSetTwo.position));

SCNCylinder * cylTest = [SCNCylinder cylinderWithRadius:0.2 height:cylHeight];
SCNNode * test = [SCNNode nodeWithGeometry:cylTest];

SCNMaterial *material = [SCNMaterial material];
[[material diffuse] setContents:[SKColor whiteColor]];
material.diffuse.intensity = 60;
material.emission.contents = [SKColor whiteColor];

material.lightingModelName = SCNLightingModelConstant;
[cylTest setMaterials:@[material]];

GLKVector3 u = SCNVector3ToGLKVector3(cooridnateSetOne.position);
GLKVector3 v = SCNVector3ToGLKVector3(cooridnateSetTwo.position);

GLKVector3 w = GLKVector3CrossProduct(u, v);

GLKQuaternion q = GLKQuaternionMakeWithAngleAndVector3Axis(GLKVector3DotProduct(u,v), GLKVector3Normalize(w));
q.w += GLKQuaternionLength(q);
q = GLKQuaternionNormalize(q);
SCNVector4 final = SCNVector4FromGLKVector4(GLKVector4Make(q.x, q.y, q.z, q.w));

test.orientation = final;

我试过其他的代码包括此相同种类的方法,其实,我甚至在建的Objective-C自己SCNVector3和SCNVector4数学库,看看我的数学方法比使用GLKit数学产生了不同的价值,但我得到了相同的结果用这两种方法。任何帮助将是真棒,但现在,我不希望跳进什么比SceneKit更加复杂。我不会深入到金属和/或OpenGL为一两个月。谢谢!

编辑:

变量“cooridnateSetOne”和“cooridnateSetTwo”是由迫使一个原始线几何到该节点的另一函数产生SCNNodes,然后将其返回到一个子类实施SCNScene的。

ios objective-c opengl-es 3d scenekit
6个回答
2
投票

下面是使用Objective-C的整个方法

首先,这里是你如何使用它:

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

输入:

1RST位置1RST位置lon1的LAT1 =纬度=的位置1RST经度height1从=地球为1RST位置第二位置lon2的LAT2 =纬度距离=第二位置身高2的纬度=从地球为第二位置的距离

第二种方法创建用于在上述问题的每个位置的SCNVector3点:

-(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;
}

第二种方法使用地球半径和曲率硬编码数字,我展示这只是为了显示总准确率100%所需要的数字,这是它如何工作的。你会想它更改为正确的尺寸为场景,很明显,但这里的方法。这是由http://www.gdal.org/index.html使用的方法的适应。一种解释的在这里找到:http://www.gdal.org/osr_tutorial.html。我很快把这个在一起,但它的工作原理和准确,随意更改数字格式以自己的喜好。

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

9
投票

下面是使用节点层次结构中的快速演示(获得气缸位于使得它到底是在一个点和它的长度是沿着局部Z轴)和约束(以使该z轴看另一点)。

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

很抱歉,如果你正在寻找一种特定ObjC的解决方案,但它的速度更快,我要在OS X斯威夫特游乐场原型此。 (另外,需要较少的CGFloat转换在IOS,因为SCNVector3的元素类型只是Float那里。)


5
投票

仅供参考更优雅SCNCyclinder实现连接具有给定半径的起点和终点的位置:

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
}

需要以下扩展:

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

4
投票

谢谢你,Rickster!我已采取它远一点,并提出了阶级出来的:

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

我已经在iOS应用成功测试这个类中,使用该功能,其中提请100线(糟糕缸:○)。

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

问候。 (顺便说一句,我只能看到3D对象。我从来没有见过我生命中的“行”:O)


0
投票

我一直在寻找一个解决方案,使两个点并感谢rickster之间缸,我用他的回答使SCNNode扩展。在那里,我已经添加缺少的条件的可能的气缸取向,以避免其错误的方向相反。

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
投票

我使用SCNVector3与扩展:

 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
}
© www.soinside.com 2019 - 2024. All rights reserved.