Create a Cube Sphere in SceneKit
前言
Cube Sphere 是一种奇特的球体,不同于 Geodesic Sphere 和一般 Sphere,更像是一个 Cube 通过某种变换得到的 Sphere。
Cube Sphere 有个好处,就是更适合来做过程生成的星球,因为每个面都可以用 QuadTree 无限细分(详见:https://acko.net/blog/making-worlds-4-the-devils-in-the-details/)
过程
1.新建一个 SCNBox
2.对其修改顶点(vertices)
3.重新计算 Normal
4.刷新 SCNode 的 Geometry
1.新建 SCNBox
SCNBox *SCNBoxToSphereMapping = [SCNBox boxWithWidth:60.0f height:60.0f length:60.0f chamferRadius:0.0f];
SCNBoxToSphereMapping.widthSegmentCount = 16;
SCNBoxToSphereMapping.heightSegmentCount = 16;
SCNBoxToSphereMapping.lengthSegmentCount = 16;
SCNNode *PlanetNode = [SCNNode nodeWithGeometry:SCNBoxToSphereMapping];
[PlanetSceneKitView.scene.rootNode addChildNode:PlanetNode];
[SCNTransaction flush];
- SegmentCount 是 2 的 n 次方 因为后面(当然不是这篇文章)要做 QuadTree
- [SCNTransaction flush]; 是很关键的一步,(详见http://stackoverflow.com/questions/17760275/geometry-from-scenekit-primitives?lq=1 简单说来,就是不 flush 的话,获取的 geometry data 就是 SegmentCount = 1 的默认 SCNBox 数据,而默认的 SCNBox 顶点只有八个,不足以变换成球体)
2.对其修改顶点
// Get the vertex sources
NSArray *vertexSources = [PlanetNode.geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];
// Get the first source
SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources
long stride = vertexSource.dataStride; // in bytes
long offset = vertexSource.dataOffset; // in bytes
long componentsPerVector = vertexSource.componentsPerVector;
long bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
long vectorCount = (long)vertexSource.vectorCount;
SCNVector3 vertices[vectorCount]; // A new array for vertices
// for each vector, read the bytes
for (long i=0; i<vectorCount; i++)
{
// Assuming that bytes per component is 4 (a float)
// If it was 8 then it would be a double (aka CGFloat)
//xyz 3 componet
float vectorData[componentsPerVector];
// The range of bytes for this vector
NSRange byteRange = NSMakeRange(i*stride + offset, // Start at current stride + offset
bytesPerVector); // and read the lenght of one vector
// Read into the vector data buffer
[vertexSource.data getBytes:&vectorData range:byteRange];
// At this point you can read the data from the float array
//
float x = vectorData[0] / SCNBoxToSphereMapping.width * 2.0f;
float y = vectorData[1] / SCNBoxToSphereMapping.width * 2.0f;
float z = vectorData[2] / SCNBoxToSphereMapping.width * 2.0f;
float SphereX = x*sqrt(1-pow(y,2)/2.0f-pow(z,2)/2.0f + pow(y*z,2)/3.0f) * SCNBoxToSphereMapping.width / 2.0f;
float SphereY = y*sqrt(1-pow(z,2)/2.0f-pow(x,2)/2.0f + pow(x*z,2)/3.0f) * SCNBoxToSphereMapping.width / 2.0f;
float SphereZ = z*sqrt(1-pow(x,2)/2.0f-pow(y,2)/2.0f + pow(y*x,2)/3.0f) * SCNBoxToSphereMapping.width / 2.0f;
// ... Maybe even save it as an SCNVector3 for later use ...
vertices[i] = SCNVector3Make(SphereX, SphereY, SphereZ);
// ... or just log it
NSLog(@"x:%f, y:%f, z:%f", x, y, z);
NSLog(@"SphereX:%f, SphereY:%f, SphereX:%f", SphereX, SphereY, SphereZ);
}
- vertices(顶点)通过 SCNGeometrySource 获取(详见:http://stackoverflow.com/questions/17250501/extracting-vertices-from-scenekit)
- 获取的 vertices 是正方体上的,需要做一个 Mapping 到球体的位置上,就是修改 vertices 的 xyz 值,方法引用自 http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html
3.重新计算 Normal
SCNGeometrySource *DeformedGeometrySource = [SCNGeometrySource geometrySourceWithVertices:vertices count:vectorCount];
NSArray *SCNGeometrySourceArray = [NSArray arrayWithObject:DeformedGeometrySource];
NSArray *DeformGeometryElement = [PlanetNode.geometry geometryElements];
SCNGeometry *DeformedGeometry = [SCNGeometry geometryWithSources:SCNGeometrySourceArray elements:DeformGeometryElement];
MDLMesh *DeformedGeometryUsingMDL = [MDLMesh meshWithSCNGeometry:DeformedGeometry];
[DeformedGeometryUsingMDL addNormalsWithAttributeNamed:MDLVertexAttributeNormal creaseThreshold:1.0f];
- 一个自定义 SCNGeometry 需要两样东西,SCNGeometrySource 和 SCNGeometryElements,SCNGeometryElements 在我的理解中就是用来描述顶点之间连接成三角形的顺序。当然实际肯定并非如此简单,只是便于理解我们只是修改了顶点的位置,但连接顺序并没有改变,因此 SCNBox 的 SCNGeometryElements 是可以继续用在新的 Sphere 上的
- MDLMesh 是 Model I/O 的东西,用的时候和 SceneKit 一起记得导入以下头文件:
#import <SceneKit/SceneKit.h>
#import <ModelIO/ModelIO.h>
#import <SceneKit/ModelIO.h>
- MDLMesh 可以计算 Normal 和 Tangent,详见https://developer.apple.com/library/prerelease/ios/documentation/ModelIO/Reference/MDLMesh_Class/index.html
4.刷新 Geometry
DeformedGeometry = [SCNGeometry geometryWithMDLMesh:DeformedGeometryUsingMDL];
PlanetNode.geometry = DeformedGeometry;
记得打开 Debug 的线框预览功能
PlanetSceneKitView.debugOptions = SCNDebugOptionShowWireframe;
效果如下: