圆柱体球体上两点之间的方向,Scenekit,Quaternions IOS

Pre*_*dea 7 3d opengl-es objective-c ios scenekit

我一直在尝试使用SceneKit在球体外边缘的两个点之间绘制一个圆柱体.我已经使用原始几何和带有SCNRendering委托的openGL在这两个点之间产生了一条线,但现在我需要在这两个点之间产生一个圆柱体(好吧,不仅仅是两个,而是任何两个位于球体表面的三维矢量) ).我已经连续工作了大约3天了,我已经完成了实现四元数以实现这一切的所有内容,但就目前而言,我无法让它发挥作用.学术文章,科学研究,什么都没有,没有什么工作在两个固定点之间重新调整圆柱体.我需要一个算法来做到这一点.

无论如何,这是我最近的代码不起作用,但这只是我迄今为止所做的近2k行代码的一小段代码,没有预期的结果.我知道我可以转向更先进的东西,比如构建我自己的SCNProgram和/或SCNRenderer然后访问GLSL,OpenGL和Metal复杂性,但这看起来像是应该可以使用Scenekit并在GLKit向量结构与SCNVector之间进行转换结构,但到目前为止,这是不可能的:

码:

以下代码摄取经度和纬度坐标并将它们投影到3D球体的表面上.这些坐标是通过我构建的专有函数返回的,其中我收到了{x,y,z}坐标的SCNVector3,它在我的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;
Run Code Online (Sandbox Code Playgroud)

我尝试过的其他代码包括同样的方法,事实上,我甚至在Objective-C中构建了自己的SCNVector3和SCNVector4数学库,看看我的数学方法是否产生了与使用GLKit数学不同的值,但是我得到了相同的结果用这两种方法.任何帮助都会很棒,但是现在,我不打算跳到比SceneKit更复杂的东西.我将不再潜入Metal和/或OpenGL一两个月.谢谢!

编辑:

变量"cooridnateSetOne"和"cooridnateSetTwo"是由另一个函数生成的SCNNode,该函数强制原始线几何进入此节点,然后将其返回到SCNScene的子类实现.

ric*_*ter 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) ]
Run Code Online (Sandbox Code Playgroud)

很抱歉,如果您正在寻找一个特定于ObjC的解决方案,但我可以更快地在OS X Swift游乐场中对其进行原型设计.(此外,CGFloatiOS中需要较少的转换,因为元素类型SCNVector3就在Float那里.)


Chr*_*bek 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
}
Run Code Online (Sandbox Code Playgroud)

需要以下扩展:

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)
}
Run Code Online (Sandbox Code Playgroud)


Pre*_*dea 2

这是使用 Objective-C 的完整方法

首先,介绍一下如何使用它:

SCNNode * testNode = [self lat1:-35 lon1:108 height1:tall lat2:-35 lon2:30 height2:0];
Run Code Online (Sandbox Code Playgroud)

输入:

第一个位置 lat1 = 第一个位置的纬度 lon1 = 第一个位置的经度 height1 = 第一个位置距地球的距离 lat2 = 第二个位置的纬度 lon2 = 第二个位置的纬度 height2 = 第二个位置距地球的距离

第二种方法为上述每个位置创建 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;
}
Run Code Online (Sandbox Code Playgroud)

第二种方法使用地球半径和曲率的硬编码数字,我展示这一点只是为了显示 100% 总精度所需的数字,这就是它的工作原理。显然,您需要将其更改为适合场景的正确尺寸,但方法如下。这是Link使用的方法的改编。可以在这里找到解释:链接。我很快就把它组合在一起,但它有效且准确,请随意根据您的喜好更改数字格式。

-(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);
}
Run Code Online (Sandbox Code Playgroud)