Orientation Cylindrique entre deux points sur une sphère, Scenekit, Quaternions IOS

J'ai essayé de dessiner un cylindre entre deux points sur le bord extérieur d'une sphère en utilisant SceneKit. J'ai déjà produit une ligne entre ces deux points en utilisant la geometry primitive et openGL avec SCNRendering Delegate, mais maintenant je dois produire un cylindre entre ces deux (bien, pas seulement deux, mais deux vectors 3D qui siègent à la surface de la sphère ). Cela fait environ 3 jours que je travaille là-dessus maintenant, et j'ai parcouru tout ce que j'ai pu find sur l'implémentation de Quaternions pour y parvenir, mais tel qu'il est, je ne peux pas le faire fonctionner. Des articles académiques, des études scientifiques, et rien, rien ne fonctionne pour réaligner un cylindre entre deux points fixes. J'ai besoin d'un algorithm pour le faire.

Quoi qu'il en soit, voici mon code le plus récent qui ne fonctionne pas, mais c'est juste un petit extrait de près de 2k lignes de code que j'ai travaillé jusqu'à présent sans le résultat escompté. Je sais que je peux passer à quelque chose de plus avancé comme build mon propre SCNProgram et / ou SCNRenderer pour ensuite accéder à GLSL, OpenGL, et la complexité Metal, mais cela devrait être possible avec Scenekit et la conversion entre les structures vectorielles GLKit vers et depuis SCNVector structs, mais jusqu'à présent, c'est impossible:

Code:

Le code suivant ingère les coordonnées Longitude et Latitude et les projette sur la surface d'une sphère 3D. Ces coordonnées sont renvoyées à travers une fonction propriétaire que je construis où j'ai reçu un SCNVector3 des coordonnées {x, y, z} qui s'affichent avec précision sur ma sphère 3D. Je trace une ligne entre deux sets de coordonnées Longitude et Latitude où les lignes tracées à l'aide de primitives traversent le centre de la sphère. Donc, comme je l'ai mentionné plus haut, je veux cette même fonctionnalité mais avec des cylindres, pas des lignes (d'ailleurs, les coordonnées de longitude et de latitude indiquées ici sont fausses, elles sont générées randomment mais tombent à la surface de la Terre).

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)); qw += GLKQuaternionLength(q); q = GLKQuaternionNormalize(q); SCNVector4 final = SCNVector4FromGLKVector4(GLKVector4Make(qx, qy, qz, qw)); test.orientation = final; 

L'autre code que j'ai essayé inclut ce même type de méthode, en fait, j'ai même construit mes propres bibliothèques Math SCNVector3 et SCNVector4 en Objective-C pour voir si mes methods mathématiques produisaient des valeurs différentes de celles de GLKit, mais j'obtiens les mêmes résultats avec les deux methods. Toute aide serait géniale, mais pour l'instant, je ne cherche pas à sauter dans quelque chose de plus compliqué que SceneKit. Je ne vais pas plonger dans Metal et / ou OpenGL pour un mois ou deux. Merci!

MODIFIER:

Les variables "cooridnateSetOne" et "cooridnateSetTwo" sont des SCNNodes qui sont produits par une autre fonction qui force une geometry de ligne primitive dans ce nœud, puis la renvoie à une implémentation de sous-class de SCNScene.

Voici une méthode complète utilisant Objective-C

D'abord, voici comment vous l'utilisez:

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

Consortingbutions:

1er location lat1 = latitude de la première position lon1 = longitude de la première position hauteur1 = distance de la terre pour la première position lat2 = latitude de la deuxième position lon2 = latitude de la deuxième position hauteur2 = distance de la terre pour la deuxième position

La deuxième méthode crée les points SCNVector3 pour chaque location en question ci-dessus:

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

Cette deuxième méthode utilise des nombres codés en dur pour le rayon et la courbure de la Terre, je montre ceci juste pour montrer les nombres requirejs pour une précision totale de 100%, c'est ainsi que cela fonctionne. Vous voudrez changer cela aux bonnes dimensions pour votre scène, évidemment, mais voici la méthode. Ceci est une adaptation des methods utilisées par http://www.gdal.org/index.html . Une explication peut être trouvée ici: http://www.gdal.org/osr_tutorial.html . Je mets cela set très rapidement mais ça marche et c'est précis, n'hésitez pas à changer les formats de nombre à votre goût.

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

Voici une démo rapide utilisant la hiérarchie des nœuds (pour get le cylindre situé de telle sorte que sa fin est en un point et sa longueur le long de l'axe z local) et une contrainte (pour que cet axe z regarde un autre 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) ] 

Désolé si vous étiez à la search d'une solution spécifique à ObjC, mais il m'a fallu plus de time pour le prototyper dans un terrain de jeu OS X Swift. (En outre, less de conversion CGFloat est nécessaire dans iOS, car le type d'élément de SCNVector3 est simplement Float ici.)

Merci, Rickster! Je l'ai pris un peu plus loin et en ai fait une class:

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

J'ai testé cette class avec succès dans une application iOS, en utilisant cette fonction, qui dessine 100 lignes (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 lignes aléatoires Cordialement. (Je ne vois que des objects en 3D .. Je n'ai jamais vu de "ligne" dans ma vie: o)

Juste pour reference une implémentation SCNCyclinder plus élégante pour connecter une position de début et de fin avec un rayon donné:

 func makeCylinder(from: SCNVector3, to: SCNVector3, radius: CGFloat, color: NSColor) -> 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 = SCNMasortingx4(x: x, y: y, z: z, w: from) let geometry = SCNCylinder(radius: CGFloat(self.radius), height: CGFloat(height)) let childNode = SCNNode(geometry: geometry) childNode.transform = SCNMasortingx4MakeTranslation(0.0, height / 2.0, 0.0) * transform return result } 

A besoin de l'extension suivante:

 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 SCNMasortingx4 { public init(x: SCNVector3, y: SCNVector3, z: SCNVector3, w: SCNVector3) { self.init( m11: xx, m12: xy, m13: xz, m14: 0.0, m21: yx, m22: yy, m23: yz, m24: 0.0, m31: zx, m32: zy, m33: zz, m34: 0.0, m41: wx, m42: wy, m43: wz, 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: SCNMasortingx4, right: SCNMasortingx4) -> SCNMasortingx4 { return SCNMasortingx4Mult(left, right) } 

Je cherchais une solution pour faire du cylindre entre deux points et grâce à rickster , j'ai utilisé sa réponse pour faire l'extension SCNNode . Là, j'ai ajouté des conditions manquantes pour une éventuelle orientation du cylindre afin d'éviter sa mauvaise direction opposée.

 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 }