For one of my first projects on Apple, I developed a significant amount of prototype 3D interface with the Apple SceneKit framework, and I was impressed with the balance between power and simplicity. SceneKit makes it very easy to set up a 3D Object Graph and provides surprisingly powerful control over lighting, cameras and object materials.
SceneKit has continued to grow more impressive with each update, and I recently went back to it over the weekend to play with a single concept I wanted to experiment for a while.
There are a number of possible approaches to this, this described, has been implemented in a custom SCNNode subcategory called TerrainMesh . The class creates a mask of any size that can then be deformed or adjusted at the level of individual angles . In discussing how this example works and how to use it, more concepts will be affected:
- Create Custom SceneKit Geometry
- Calculate Different Components of the geometry ( Normal Texture Coordinates etc.)
- Deform and manipulate that geometry
Creating a Mesh
To create our deformable terrain, we will build a mask of triangles that can then be manipulated. Creation of this SceneKit geometry is handled automatically by TerrainMesh, but under the hood, this geometry of consists of several components :
- Vertices to define each item on the mask
- Set indexes to define each triangle
- Normal to enable proper lighting / shading
- Texture Coordinates to specify how to map the image drive
The image to the right shows the relationship between the size number vertices and the number triangles . In the next sections, each aspect of the geometry creation will be discussed in more detail.
SceneKit Custom Geometry
In SceneKit, our geometry will be built from several closely related components. We want to create a SCNGeometry object from a set of SCNGeometrySources. These sources will enclose the data for vertices triangles normals and texture co-ordinates . Our SCNGeometry also comes with a SCNGeometryElement, which defines the total terrain element that is built from triangles that make up the network.
Finally, once all this has been set up, the simple part is plugged into a SCNNode geometry property. Within TerrainMesh, this happens as part of the -configureGeometry conversation.
The wheels in our terrain are the actual points in 3D Objects that define the network. In the picture above they correspond to the dots in each corner. By default, the TerrainMesh example creates a 2D array of the peak where 0 the peak begins at 0.0, 0.0 in the object space and where the X and Y values increase gradually with peak index . The final index is the "peaked" point in the network, with an X, Y value of which page the device length has been submitted. Example: for a 2×2 web with a page length of 5.0, 0 vertex would be 0.0, 0.0 and the final peak of ] index 3 would be 5.0, 5.0 .
By default, the TerrainMesh constructions are oriented so that the network extends along X and Y aircraft and Z the value of each vertex control its depth defines the "height" of the terrain for that point.
These corners form the basis of our custom SceneKit geometry . Creating SCNGeometrySource for them is simply using one of SceneKit's factory methods: + [SCNGeometrySource geometrySourceWithVertices : count :] .
The triangles of the network are defined using our former vertices . The TerrainMesh class does this automatically, but in short, the triangles are defined by creating sets of index values that map our angles. In the example image of the network, 1st triangle of the network is defined using an index set of 4, 3, 0 .
The advantage of this approach is that we can use the same peak for three triangles which prevents us from unnecessarily defining the same point. In the example mask image, vertex 4 is actually used as part of 6 different triangles.
Trianglene is used to create our SCNGeometryElement object. An example of how SCNGeometryElement is created from the data:
SCNGeometryElement * element = [SCNGeometryElement geometryElementWithData:[NSData dataWithBytes:indices length:length] primitiveType: SCNGeometryPrimitiveTypeTriangles primitiveCount: totalTriangles bytesPerIndex: sizeof (int)];
We make an NSData based on our selection of index set and submit it to the SCNGeometryElement factory method along with some basic parameters that describe how we want the item to be created.
So now we have created the nights, and the triangles that make up the network, we should be done, right? Almost!
SceneKit needs some additional information in addition to the crosses and our SCNGeometryElement built with our triangles. In order to shade and light the surface of our network, we must give the normals for each vertex.
The scenes allow SceneKit to understand the surface of our geometry and how it will be rendered under light. For a more detailed definition on normals, this is a great and concise explanation. As our previously created vertex source SCNGeometrySource for our norms was created simply using one of SceneKit's convenience methods: + [SCNGeometrySource geometrySourceWithNormals:count:].
(Image Source: Blender 3D.)
Geometry: Texture Coordinates
Finally, if we want to texture our terrain to make it nice and terrain-like, we must define texture coordinates . This information allows us to flexibly control how some SCN materials are mapped on our custom 3D geometry.
For our purpose, we only create one X, Y par values for each vertex. X and Y have values in the range 0.0 – 1.0 to define which part of material texture should be applied at this time.
Again, the sample code handles all this with a set of reasonable default values. If you want to create a custom source for this, you can do the same as how we created our other sources, except that you use one of the SCNGeometrySource methods that allows you to specify SCNGeometrySourceSemanticTexcoord as semantic for geometry source data.
Sets it together
Building custom geometry in SceneKit may seem daunting first, but consists of several individual components which in itself are relatively simple. The end result of the example code is a nice flat triangle. In itself, it does not look very interesting right now. However, because with our custom geometry, we have granular control above each peak, we can do some fun things with our network.
The TerrainMesh demo app is an example of this embedded in the TerrainMesh API and provides a brush style effect for raising or lowering the terrain:
void ) derformTerrainAt: ( CGPoint ) punktbrenselRadius: ( double ) brushRadius intensity: ( double ) intensity;
Under the hood, TerrainMesh, only vertices and other geometry (disclaimer: do not restore the most effective way to add] and then update TerrainMesh SCNNode. The result is a flexible and morphable terrain that we can paint slopes or valleys into:
If you want animate change your geometry to bring mountains and valleys alive, take a look at Apple's SCNMorpher class, which makes it very easy to do.
This example was thrown together for fun and for demonstration purposes, but hopefully it has shown how flexible SceneKit can be, especially when creating custom geometry .
Any questions, please contact.