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 MemoryLayout
s 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
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.
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 int
s or short
s 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, replacingUnsafe[Mutable]Pointer<Void>
. Conversion fromUnsafePointer<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. SeebindMemory(to:capacity:)
,assumingMemoryBound(to:)
,
andwithMemoryRebound(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 SCNGeometryElement
s the type of the indices needs to be Int16
1. 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
Using Uiapplicationdelegateadaptor to Get Callbacks from Userdidacceptcloudkitsharewith Not Working
Alamofire Type 'Parameterencoding' Has No Member 'Url' Swift 3
How to Prevent Timer Slowing Down in Background
What Is Trailing Closure Syntax in Swift
Detect Left and Right Click Events on Nsstatusitem (Swift)
How to Use Trailing Closure in If Condition
Physicsbody: Could Not Create Physics Body
Swift: Double Conversion Inconsistency. How to Correctly Compare Doubles
Swiftui: How to Make Entire Shape Recognize Gestures When Stroked
Animating a Navigation Bar Color
Gcd with Static Functions of a Struct
How to Stream Remote Audio in iOS 13? (Swiftui)
Multi-Face Detection in Realitykit
How to Pass Int.Init to a Function in Swift
How to Distinguish Between Multiple Uipickerviews on One Page