[英]Using OpenGL ES 2.0 with iOS, how do I draw a cylinder between two points?
[英]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)
}
}
問候。 (順便說一句。我只能看到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.