Scenekit - Custom Geometry Does Not Show Up

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:

Sample Image

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 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