Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is an example of drawing custom nodes with vertices in swift SceneKit?

This area is hardly documented online and it would be great to see a working Swift 3 example, of say, a custom drawn cube with manual SCNvector3s. There is this in objective-C but not Swift. This might not be a usual form of question but I know it would help many. If there is somewhere I missed, please mention.

The documentation is not very helpful

scngeometrysource, etc.

Thanks

like image 224
harry lakins Avatar asked May 12 '17 07:05

harry lakins


1 Answers

A custom geometry is constructed from a set of vertices and normals.

Vertices

In this context, a vertex is a point where two or more lines intersect. For a cube, the vertices are the corners shown in the following figure

enter image description here

We construct the geometry by building the cube's faces with a set of triangles, two triangles per face. Our first triangle is defined by vertices 0, 2, and 3 as shown in the below figure, and the second triangle is defined by vertices 0, 1, and 2. It is important to note that each triangle has a front and back side. The side of the triangle is determined by the order of the vertices, where the front side is specified in counter-clockwise order. For our cube, the front side will always be the outside of the cube.

If the cube's center is the origin, the six vertices that define one of the cube's face can be defined by

let vertices:[SCNVector3] = [
        SCNVector3(x:-1, y:-1, z:1),    // 0
        SCNVector3(x:1, y:1, z:1),      // 2
        SCNVector3(x:-1, y:1, z:1)      // 3

        SCNVector3(x:-1, y:-1, z:1),    // 0
        SCNVector3(x:1, y:-1, z:1),     // 1
        SCNVector3(x:1, y:1, z:1)       // 2
]

and we create the vertex source by

let vertexSource = SCNGeometrySource(vertices: vertices)

At this point, we have a vertex source that can be use to construct a face of the cube; however, SceneKit doesn't know how the triangle should react to light sources in the scene. To properly reflect light, we need to provide our geometry with a least one normal vector for each vertex.

Normals

A normal is a vector that specifies the orientation of a vertex that affects how light reflects off the corresponding triangle. In this case, the normal vectors for the six vertices of the triangle are the same; they all point in the positive z direction (i.e., x = 0, y = 0, and z = 1); see the red arrows in the below figure.

enter image description here

The normals are defined by

let normals:[SCNVector3] = [
        SCNVector3(x:0, y:0, z:1),      // 0
        SCNVector3(x:0, y:0, z:1),      // 2
        SCNVector3(x:0, y:0, z:1),      // 3

        SCNVector3(x:0, y:0, z:1),      // 0
        SCNVector3(x:0, y:0, z:1),      // 1
        SCNVector3(x:0, y:0, z:1)       // 2
]

and the source is defined by

let normalSource = SCNGeometrySource(normals: normals)

We now have the sources (vertices and normals) needed to construct a limited geometry, i.e., one cube face (two triangles). The final piece is to create an array of indices into the vertex and normal arrays. In this case, the indices are sequential because the vertices are in the order they are used.

var indices:[Int32] = [0, 1, 2, 3, 4, 5]

From the indices, we create an geometry element. The setup is a bit more involved because SCNGeometryElement requires an NSData as a parameter.

let indexData = NSData(bytes: &indices, length: MemoryLayout<Int32>.size * indices.count)

let element = SCNGeometryElement(data: indexData as Data, primitiveType: .triangles, primitiveCount: indices.count, bytesPerIndex: MemoryLayout<Int32>.size)

We can now create the custom geometry with

let geometry = SCNGeometry(sources: [vertexSource, normalSource], elements: [element])

    

and lastly create a node and assign the custom geometry to its geometry property

let node = SCNNode()
node.geometry = geometry

scene.rootNode.addChildNode(node)

We now extend the vertices and normals to including all of the cube faces:

    // The vertices
    let v0 = SCNVector3(x:-1, y:-1, z:1)
    let v1 = SCNVector3(x:1, y:-1, z:1)
    let v2 = SCNVector3(x:1, y:1, z:1)
    let v3 = SCNVector3(x:-1, y:1, z:1)
    
    let v4 = SCNVector3(x:-1, y:-1, z:-1)
    let v5 = SCNVector3(x:1, y:-1, z:-1)
    let v6 = SCNVector3(x:-1, y:1, z:-1)
    let v7 = SCNVector3(x:1, y:1, z:-1)
    
    // All the cube faces
    let vertices:[SCNVector3] = [
        // Front face
        v0, v2, v3,
        v0, v1, v2,
        
        // Right face
        v1, v7, v2,
        v1, v5, v7,
        
        // Back
        v5, v6, v7,
        v5, v4, v6,
        
        // Left
        v4, v3, v6,
        v4, v0, v3,
        
        // Top
        v3, v7, v6,
        v3, v2, v7,
        
        // Bottom
        v1, v4, v5,
        v1, v0, v4
    ]
    
    let normalsPerFace = 6
    let plusX = SCNVector3(x:1, y:0, z:0)
    let minusX = SCNVector3(x:-1, y:0, z:0)
    let plusZ = SCNVector3(x:0, y:0, z:1)
    let minusZ = SCNVector3(x:0, y:0, z:-1)
    let plusY = SCNVector3(x:0, y:1, z:0)
    let minusY = SCNVector3(x:0, y:-1, z:0)
    
    // Create an array with the direction of each vertex. Each array element is
    // repeated 6 times with the map function. The resulting array or arrays
    // is then flatten to an array
    let normals:[SCNVector3] = [
        plusZ,
        plusX,
        minusZ,
        minusX,
        plusY,
        minusY
        ].map{[SCNVector3](repeating:$0,count:normalsPerFace)}.flatMap{$0}
    
    // Create an array of indices [0, 1, 2, ..., N-1]
    let indices = vertices.enumerated().map{Int32($0.0)}
    
    let vertexSource = SCNGeometrySource(vertices: vertices)
    
    let normalSource = SCNGeometrySource(normals: normals)
    
    let pointer = UnsafeRawPointer(indices)
    let indexData = NSData(bytes: pointer, length: MemoryLayout<Int32>.size * indices.count)
    
    let element = SCNGeometryElement(data: indexData as Data, primitiveType: .triangles, primitiveCount: indices.count/3, bytesPerIndex: MemoryLayout<Int32>.size)
    
    let geometry = SCNGeometry(sources: [vertexSource, normalSource], elements: [element])
    
    // Create a node and assign our custom geometry
    let node = SCNNode()
    node.geometry = geometry
    
    scene.rootNode.addChildNode(node)
like image 133
0x141E Avatar answered Nov 03 '22 02:11

0x141E