簡體   English   中英

圓柱體球體上兩點之間的方向,Scenekit,Quaternions IOS

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

我一直在嘗試使用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;

我嘗試過的其他代碼包括同樣的方法,事實上,我甚至在Objective-C中構建了自己的SCNVector3和SCNVector4數學庫,看看我的數學方法是否產生了與使用GLKit數學不同的值,但是我得到了相同的結果用這兩種方法。 任何幫助都會很棒,但是現在,我不打算跳到比SceneKit更復雜的東西。 我將不再潛入Metal和/或OpenGL一兩個月。 謝謝!

編輯:

變量“cooridnateSetOne”和“cooridnateSetTwo”是由另一個函數生成的SCNNode,該函數強制原始線幾何進入此節點,然后將其返回到SCNScene的子類實現。

這是一個使用節點層次結構的快速演示(使圓柱體位於其一端,其長度位於局部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 Swift游樂場中對其進行原型設計。 (此外,iOS中需要較少的CGFloat轉換,因為SCNVector3的元素類型只是Float那里。)

僅供參考更優雅的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)
}

謝謝你,里克斯特! 我已經采取了一些進一步的方法,並從中提出了一個類:

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行(oops柱面: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條隨機線 問候。 (順便說一句。我只能看到3D物體..我一生中從未見過一條“線”:o)

這是使用Objective-C的整個方法

首先,這是你如何使用它:

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

輸入:

第一個位置lat1 =第一個位置的緯度lon1 =第一個位置的經度1 =第一個位置距離地面的距離lat2 =第二個位置的緯度lon2 =第二個位置的緯度高度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);
}

我一直在尋找一個在兩點之間制作圓柱的解決方案,並且由於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
}

我使用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
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM