Scngeometryelement Setup in Swift 3

SCNGeometryElement setup in Swift 3

You fixed one sizeof but not the other, and you're creating a new pointer where such is unnecessary — any array (given the right element type) can be passed to APIs that take C-style pointers. The direct fix for your code is then:

let indexes: [CInt] = [0,1,2,3]
let dat = Data(bytes: indexes, count: MemoryLayout<CInt>.size * indexes.count)
let ele = SCNGeometryElement(data:dat, primitiveType: .triangles, primitiveCount: 2, bytesPerIndex: MemoryLayout<CInt>.size)

(Note also the fixes to make your MemoryLayouts consistent with the data they describe.)

However, unless you have some need for the extra Data object, for fun with pointers, or for the extra specificity in describing your element, you can use the simpler form:

let indices: [UInt8] = [0,1,2,3] 
let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)

This generic initializer automatically manages memory on the way in, infers the count of the array, and infers the primitiveCount based on the count of the array and the primitiveType you specify.

(Note that an array of four indices is an unusual number for .triangles; either you have one triangle and one unused index, or you actually mean a .triangleStrip containing two primitives.)

SceneKit SCNGeometryElement TriangleStrip vs triangles

For a triangle strip the primitiveCount should be equal to indexCount - 2.

Alternatively you can use the Swift-friendly SCNGeometryElement(indices:primitiveType:) (see also this post).

Swift & SceneKit 3D multi-colour tetrahedron

Your code is perfect fine. The only problem is you don't have enough geometry elements when you build geometry. A quick fix like the following , you can get four colors.

  let element0 = SCNGeometryElement(indices: [UInt16](indices[0...2]), primitiveType: .triangles)
let element1 = SCNGeometryElement(indices: [UInt16](indices[3...5]), primitiveType: .triangles)
let element2 = SCNGeometryElement(indices: [UInt16](indices[6...8]), primitiveType: .triangles)
let element3 = SCNGeometryElement(indices: [UInt16](indices[9...11]), primitiveType: .triangles)

let geometry = SCNGeometry(sources: [vertexSource], elements: [element0, element1,element2,element3])

SceneKit – Custom geometry does not show up

Note: see Ash's answer, which is a much better approach for modern Swift than this one.

Your index array has the wrong size element. It's being inferred as [Int]. You need [CInt].

I broke out your elements setup into:

    let indices = [0, 2, 3, 0, 1, 2] // [Int]
print(sizeof(Int)) // 8
print(sizeof(CInt)) // 4
let elements = [
SCNGeometryElement(indices: indices, primitiveType: .Triangles)
]

To get the indices to be packed like the expected C array, declare the type explicitly:

    let indices: [CInt] = [0, 2, 3, 0, 1, 2]

Custom SceneKit Geometry in Swift on iOS not working but equivalent Objective C code does goes into more detail, but it's written against Swift 1, so you'll have to do some translation.

SCNGeometryElement(indices:, primitiveType:) doesn't appear to be documented anywhere, although it does appear in the headers.

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

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

Sample Image

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.

Sample Image

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)

Extracting vertices from scenekit


The geometry source

When you call geometrySourcesForSemantic: you are given back an array of SCNGeometrySource objects with the given semantic in your case the sources for the vertex data).

This data could have been encoded in many different ways and a multiple sources can use the same data with a different stride and offset. The source itself has a bunch of properties for you to be able to decode the data like for example

  • dataStride
  • dataOffset
  • vectorCount
  • componentsPerVector
  • bytesPerComponent

You can use combinations of these to figure out which parts of the data to read and make vertices out of them.

Decoding

The stride tells you how many bytes you should step to get to the next vector and the offset tells you how many bytes offset from the start of that vector you should offset before getting to the relevant pars of the data for that vector. The number of bytes you should read for each vector is componentsPerVector * bytesPerComponent

Code to read out all the vertices for a single geometry source would look something like this

// Get the vertex sources
NSArray *vertexSources = [geometry geometrySourcesForSemantic:SCNGeometrySourceSemanticVertex];

// Get the first source
SCNGeometrySource *vertexSource = vertexSources[0]; // TODO: Parse all the sources

NSInteger stride = vertexSource.dataStride; // in bytes
NSInteger offset = vertexSource.dataOffset; // in bytes

NSInteger componentsPerVector = vertexSource.componentsPerVector;
NSInteger bytesPerVector = componentsPerVector * vertexSource.bytesPerComponent;
NSInteger vectorCount = vertexSource.vectorCount;

SCNVector3 vertices[vectorCount]; // A new array for vertices

// for each vector, read the bytes
for (NSInteger 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)
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];
float y = vectorData[1];
float z = vectorData[2];

// ... Maybe even save it as an SCNVector3 for later use ...
vertices[i] = SCNVector3Make(x, y, z);

// ... or just log it
NSLog(@"x:%f, y:%f, z:%f", x, y, z);
}

The geometry element

This will give you all the vertices but won't tell you how they are used to construct the geometry. For that you need the geometry element that manages the indices for the vertices.

You can get the number of geometry elements for a piece of geometry from the geometryElementCount property. Then you can get the different elements using geometryElementAtIndex:.

The element can tell you if the vertices are used a individual triangles or a triangle strip. It also tells you the bytes per index (the indices may have been ints or shorts which will be necessary to decode its data.

Swift 3 UnsafePointer($0) no longer compile in Xcode 8 beta 6

From the Release Notes of Xcode 8 beta 6:

  • An Unsafe[Mutable]RawPointer type has been introduced, replacing Unsafe[Mutable]Pointer<Void>. Conversion from UnsafePointer<T> to
    UnsafePointer<U> has been disallowed. Unsafe[Mutable]RawPointer
    provides an API for untyped memory access, and an API for binding
    memory to a type. Binding memory allows for safe conversion between
    pointer types. See bindMemory(to:capacity:), assumingMemoryBound(to:),
    and withMemoryRebound(to:capacity:). (SE-0107)

In your case, you may need to write something like this:

let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
}
}

Extract faces information from SCNGeometry in SceneKit

If you have a SCNGeometry object (which you can get from a SCNNode with node.geometry) then you can look at the elements property which will contain the face information as an array of SCNGeometryElement objects.

e.g. assuming you just want the first element

let element = geometry.elements[0]
let faces = element.data.withUnsafeBytes {(ptr: UnsafeRawBufferPointer) -> [Int32] in
guard let boundPtr = ptr.baseAddress?.assumingMemoryBound(to: Int32.self) else {return []}
let buffer = UnsafeBufferPointer(start: boundPtr, count: element.data.count / 4)
return Array<Int32>(buffer)
}
print(faces)

Depending on element.primitiveType you will need to interpret the indices differently. See the documentation for SCNGeometryPrimitiveType.

How to debug custom geometry in SceneKit with Swift

When creating custom SCNGeometryElements the type of the indices needs to be Int161. I don't think this documented anywhere. But when you change the declaration of the indices too

let indices: [Int16] = [
0, 2, 1
]

the triangle should appear.


Edit

1: As @mnuages has pointed out, SceneKit supports only 32bit Integers as indices. So you can use Int8, Int16 and Int32.



Related Topics



Leave a reply



Submit