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.
SceneKit Custom Geometry Shape not showing up
Try my light-weighting code (macOS version for quick testing).
It's working:
import SceneKit
class GameViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 3)
let geometry: SCNGeometry?
let positions = [
SCNVector3(0, 1, 0),
SCNVector3(-0.5, 0, 0.5),
SCNVector3(0.5, 0, 0.5),
SCNVector3(0.5, 0, -0.5),
SCNVector3(-0.5, 0, -0.5),
SCNVector3(0, -1, 0),
]
let source = SCNGeometrySource(vertices: positions)
let indices: [UInt32] = [
0, 1, 2,
2, 3, 0,
3, 4, 0,
4, 1, 0,
1, 5, 2,
2, 5, 3,
3, 5, 4,
4, 5, 1
]
let element = SCNGeometryElement(indices: indices, primitiveType:.triangles)
geometry = SCNGeometry(sources: [source], elements: [element])
geometry!.firstMaterial?.diffuse.contents = NSColor.red
let geometryNode = SCNNode(geometry: geometry)
scene.rootNode.addChildNode(geometryNode)
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.autoenablesDefaultLighting = true
scnView.backgroundColor = NSColor.black
}
}
Custom SceneKit Geometry in Swift on iOS not working but equivalent Objective C code does
Well, the two pieces of code doesn't translate exactly to one another. The int
in C is not the same as Int
in Swift. It's actually called CInt
in Swift:
/// The C 'int' type.
typealias CInt = Int32
If you change both occurrences to use CInt
instead, the error message that you previously got goes away (at least for me in an OS X Playground. However, it still doesn't render anything for me.
I don't think sizeofValue
is used to return the size of an array. It looks to me like it's returning the size of the pointer:
let indexes: CInt[] = [0, 1, 2]
sizeofValue(indexes) // is 8
sizeof(CInt) // is 4
sizeof(CInt) * countElements(indexes) // is 12
// compare to other CInt[]
let empty: CInt[] = []
let large: CInt[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sizeofValue(indexes) // is 8 (your array of indices again)
sizeofValue(empty) // is 8
sizeofValue(large) // is 8
So, for me the following code works (I've put the arguments on different lines to make it easier to point out my changes):
let src = SCNGeometrySource(vertices: &verts, count: 3)
let indexes: CInt[] = [0, 1, 2] // Changed to CInt
let dat = NSData(
bytes: indexes,
length: sizeof(CInt) * countElements(indexes) // Changed to size of CInt * count
)
let ele = SCNGeometryElement(
data: dat,
primitiveType: .Triangles,
primitiveCount: 1,
bytesPerIndex: sizeof(CInt) // Changed to CInt
)
let geo = SCNGeometry(sources: [src], elements: [ele])
let nd = SCNNode(geometry: geo)
scene.rootNode.addChildNode(nd)
With this result:
SceneKit issues applying texture to custom geometry
A single vertex can't have more than one texture coordinate. Since each vertex in a cube appears in a corner and is part of three different faces, you'll need to repeat the vertex data three times and match that up with the texture coordinates and then reference those with your indices.
Another way to look at it is that the same index that you're using to reference the vertex is also used to reference a texture coordinate, so since you only have 8 vertices, you'll only be able to reference the first 8 texture coordinates.
In one of the chapters from my book on SceneKit I create a custom cube geometry. Its texture coordinates display the complete texture on each face — so you'll have to change that part — but its indices should work for your cube as well:
// Indices that turn the source data into triangles
// ------------------------------------------------
int indices[] = {
// bottom
0, 2, 1,
1, 2, 3,
// back
10, 14, 11, // 2, 6, 3, + 8
11, 14, 15, // 3, 6, 7, + 8
// left
16, 20, 18, // 0, 4, 2, + 16
18, 20, 22, // 2, 4, 6, + 16
// right
17, 19, 21, // 1, 3, 5, + 16
19, 23, 21, // 3, 7, 5, + 16
// front
8, 9, 12, // 0, 1, 4, + 8
9, 13, 12, // 1, 5, 4, + 8
// top
4, 5, 6,
5, 7, 6
};
// Custom geometry data for a cube
// -------------------------------
SCNVector3 vertices[] = {
SCNVector3Make(-halfSide, -halfSide, halfSide),
SCNVector3Make( halfSide, -halfSide, halfSide),
SCNVector3Make(-halfSide, -halfSide, -halfSide),
SCNVector3Make( halfSide, -halfSide, -halfSide),
SCNVector3Make(-halfSide, halfSide, halfSide),
SCNVector3Make( halfSide, halfSide, halfSide),
SCNVector3Make(-halfSide, halfSide, -halfSide),
SCNVector3Make( halfSide, halfSide, -halfSide),
// repeat exactly the same
SCNVector3Make(-halfSide, -halfSide, halfSide),
SCNVector3Make( halfSide, -halfSide, halfSide),
SCNVector3Make(-halfSide, -halfSide, -halfSide),
SCNVector3Make( halfSide, -halfSide, -halfSide),
SCNVector3Make(-halfSide, halfSide, halfSide),
SCNVector3Make( halfSide, halfSide, halfSide),
SCNVector3Make(-halfSide, halfSide, -halfSide),
SCNVector3Make( halfSide, halfSide, -halfSide),
// repeat exactly the same
SCNVector3Make(-halfSide, -halfSide, halfSide),
SCNVector3Make( halfSide, -halfSide, halfSide),
SCNVector3Make(-halfSide, -halfSide, -halfSide),
SCNVector3Make( halfSide, -halfSide, -halfSide),
SCNVector3Make(-halfSide, halfSide, halfSide),
SCNVector3Make( halfSide, halfSide, halfSide),
SCNVector3Make(-halfSide, halfSide, -halfSide),
SCNVector3Make( halfSide, halfSide, -halfSide)
};
SCNVector3 normals[] = {
// up and down
SCNVector3Make( 0, -1, 0),
SCNVector3Make( 0, -1, 0),
SCNVector3Make( 0, -1, 0),
SCNVector3Make( 0, -1, 0),
SCNVector3Make( 0, 1, 0),
SCNVector3Make( 0, 1, 0),
SCNVector3Make( 0, 1, 0),
SCNVector3Make( 0, 1, 0),
// back and front
SCNVector3Make( 0, 0, 1),
SCNVector3Make( 0, 0, 1),
SCNVector3Make( 0, 0, -1),
SCNVector3Make( 0, 0, -1),
SCNVector3Make( 0, 0, 1),
SCNVector3Make( 0, 0, 1),
SCNVector3Make( 0, 0, -1),
SCNVector3Make( 0, 0, -1),
// left and right
SCNVector3Make(-1, 0, 0),
SCNVector3Make( 1, 0, 0),
SCNVector3Make(-1, 0, 0),
SCNVector3Make( 1, 0, 0),
SCNVector3Make(-1, 0, 0),
SCNVector3Make( 1, 0, 0),
SCNVector3Make(-1, 0, 0),
SCNVector3Make( 1, 0, 0),
};
CGPoint UVs[] = {
CGPointMake(0, 0), // bottom
CGPointMake(1, 0), // bottom
CGPointMake(0, 1), // bottom
CGPointMake(1, 1), // bottom
CGPointMake(0, 1), // top
CGPointMake(1, 1), // top
CGPointMake(0, 0), // top
CGPointMake(1, 0), // top
CGPointMake(0, 1), // front
CGPointMake(1, 1), // front
CGPointMake(1, 1), // back
CGPointMake(0, 1), // back
CGPointMake(0, 0), // front
CGPointMake(1, 0), // front
CGPointMake(1, 0), // back
CGPointMake(0, 0), // back
CGPointMake(1, 1), // left
CGPointMake(0, 1), // right
CGPointMake(0, 1), // left
CGPointMake(1, 1), // right
CGPointMake(1, 0), // left
CGPointMake(0, 0), // right
CGPointMake(0, 0), // left
CGPointMake(1, 0), // right
};
SceneKit: Geometry not visible behind semi-transparent texture. Box inside Box
For those who will encounter the same problem as me: it has been solved but not completely.
The solution consists in the calculation at render order depending on the size in geometry. The result is not stable, but better than nothing.
cubeDict[@"render_order"] = @((sizeX + inflateDouble) + (sizeY + inflateDouble) + (sizeZ + inflateDouble));
SceneKit Custom Geometry Texture Wrong
A vertex position and all related attributes form a record. This means, if a vertex position has to be used more than once, with different texture coordinates, the the vertex position has to be add to the buffer once for each texture coordinate. It is not possible to associate the element array with an texture coordinate.
You have to create a vertex position buffer with 4 vertices for each side of the cube and a texture coordinate buffer with 4 texture coordinates for each side of the cube:
texureCoordsSide =[
vector_float2(Float(bottomLeft.x), Float(bottomLeft.y)),
vector_float2(Float(bottomRight.x), Float(bottomRight.y)),
vector_float2(Float(topRight.x), Float(topRight.y)),
vector_float2(Float(topLeft.x), Float(topLeft.y))]
// Front Face
vertices += [startFace.bottomLeft, startFace.bottomRight, startFace.topRight, startFace.topLeft];
textCords += texureCoordsSide
// Left Face
vertices += [endFace.bottomLeft, startFace.bottomLeft, startFace.topLeft, endFace.topLeft];
textCords += texureCoordsSide
// Top Face
vertices += [startFace.topLeft, startFace.topRight, endFace.topRight, endFace.topLeft];
textCords += texureCoordsSide
// Right Face
vertices += [startFace.bottomRight, endFace.bottomRight, endFace.topRight, startFace.topRight];
textCords += texureCoordsSide
// Bottom Face
vertices += [endFace.bottomRight, endFace.bottomLeft, startFace.bottomLeft, startFace.bottomRight];
textCords += texureCoordsSide
// Back Face
vertices += [endFace.bottomRight, endFace.bottomLeft, endFace.topLeft, endFace.topRight];
textCords += texureCoordsSide
The element array (indices
) has to contain the 24 indices (4 for each of the 6 sides of the cube) in conscutive order from 0 to 23.
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
How to Restrict a Protocol to Value Types Only
Swift & Firebase - How to Store More User Data Other Than Email and Password
Play Local Audio File with Avaudioplayer
Swift 3 - Search Result Also with Diacritics
Converting Swift 2.3 to Swift 3.0 - Error, Cannot Invoke 'Datatask' with an Argument List of Type'
How to Find the Operator Definition in Swift
Store Encodables in a Swift Dictionary
How to Distinguish Between Multiple Uipickerviews on One Page
How to Define Static Constant in a Class in Swift
Differencebetween Final Class and Class
How to Set Exit Code Value for a Command Line Utility in Swift
How to Deallocate Realitykit Arview()
Use of Undeclared Type 'Attributedstring'
Accessor Gives the Wrong Value in Swift 1.2/2.0 Release Build Only
A Switch Bug in Swift? - "Switch Must Be Exhaustive, Consider Adding a Default Clause."
Swift - Get File Path of Currently Opened Document in Another Application