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的子类实现.
这是一个使用节点层次结构的快速演示(使圆柱体位于其一端,其长度位于局部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游乐场中对其进行原型设计.(此外,CGFloat
iOS中需要较少的转换,因为元素类型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
}
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)
这是使用 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)