swiftscenekitscnnode

How to create a SCNNode with a custom geometry?


I am trying to create a SCNNode whose geometry is created using an array of x, y, and z positions. I am using the following code; however, this is not showing up. Does anyone know what's wrong?

class CustomShapeNode: SCNNode {
    init(positions: [(Double, Double, Double)]) {
        super.init()
        
        // Create an array of SCNVector3 from the positions array
        var scnPositions = [SCNVector3]()
        for position in positions {
            scnPositions.append(SCNVector3(Float(position.0), Float(position.1), Float(position.2)))
        }
        
        // Create a geometry from the positions array
        let geometry = SCNGeometry(sources: [SCNGeometrySource(vertices: scnPositions)], elements: [SCNGeometryElement(indices: Array(0..<scnPositions.count), primitiveType: .triangles)])
        
        // Set the geometry to the node
        self.geometry = geometry
        
        // Set the color of the node geometry
        self.geometry?.firstMaterial?.diffuse.contents = UIColor.red
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


Solution

  • Use the following code:

    import SceneKit
    
    class ViewController: UIViewController {
    
        var sceneView: SCNView? = nil
        
        let node = CustomShapeNode([SCNVector3( 0,0,0),       // CCW
                                    SCNVector3( 1,1,0),
                                    SCNVector3(-1,1,0)])
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            sceneView = self.view as? SCNView
            sceneView?.scene = SCNScene()
            sceneView?.backgroundColor = .darkGray
            sceneView?.allowsCameraControl = true
            sceneView?.scene?.rootNode.addChildNode(node)
        }
    }
    
    class CustomShapeNode: SCNNode {
            
        init(_ positions: [SCNVector3]) {
            super.init()
            
            let normalsPerFace = 1
            let indices: [Int32] = [0, 1, 2]
    
            let source = SCNGeometrySource(vertices: [positions[0],
                                                      positions[1],
                                                      positions[2]])
    
            let normals = [positions[0],positions[1],positions[2]].map {
                [SCNVector3](repeating: $0, count: normalsPerFace)
            }.flatMap { $0 }
    
            let normalSource = SCNGeometrySource(normals: normals)
            let data = Data(bytes: indices,
                            count: indices.count * MemoryLayout<Int32>.size)
    
            let element = SCNGeometryElement(data: data,
                                    primitiveType: .triangles,
                                   primitiveCount: 1,
                                    bytesPerIndex: MemoryLayout<Int32>.size)
            
            let p1 = CGPoint(x: CGFloat(positions[0].x),
                             y: CGFloat(positions[0].y))
            let p2 = CGPoint(x: CGFloat(positions[1].x),
                             y: CGFloat(positions[1].y))
            let p3 = CGPoint(x: CGFloat(positions[2].x),
                             y: CGFloat(positions[2].y))
                
            let texCoord = SCNGeometrySource(textureCoordinates: [p1, p2, p3])
                        
            self.geometry = SCNGeometry(sources: [source, normalSource, texCoord],
                                       elements: [element])
                    
            self.geometry?.firstMaterial?.diffuse.contents = UIColor.systemOrange
            self.geometry?.firstMaterial?.lightingModel = .constant
            self.geometry?.firstMaterial?.isDoubleSided = true
        }
    
        required init?(coder: NSCoder) {
            fatalError("Hasn't been implemented yet")
        }
    }
    

    enter image description here