mbaranowski
1/3/2018 - 6:11 PM

Make a Cylinder using SceneKit in swift

Make a Cylinder using SceneKit in swift

//
//  GameViewController.swift
//  SceneKitTest
//
//  Created by Matt Baranowski on 12/19/17.
//  Copyright © 2017 Matt Baranowski. All rights reserved.
//

import SceneKit
import QuartzCore

class GameViewController: NSViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // create a new scene
        let scene = SCNScene(named: "art.scnassets/ship.scn")!
        
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        
        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
        
        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = NSColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        
        // retrieve the ship node
        let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
        ship.isHidden = true
        
        let cyclinderNode = SCNNode(geometry: buildCylinder(numSlices: 12, numStacks: 1))
        cyclinderNode.name = "cyclinder"
        scene.rootNode.addChildNode(cyclinderNode)
        
        let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
        let cubeNode = SCNNode(geometry: cubeGeometry)
        cubeNode.position = SCNVector3(x: 2.0, y: 0, z: 0)
        scene.rootNode.addChildNode(cubeNode)
        
        // animate the 3d object
        //ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
        
        // retrieve the SCNView
        let scnView = self.view as! SCNView
        
        // set the scene to the view
        scnView.scene = scene
        
        // allows the user to manipulate the camera
        scnView.allowsCameraControl = true
        
        // show statistics such as fps and timing information
        scnView.showsStatistics = true
        scnView.debugOptions.insert(SCNDebugOptions.showWireframe)
        
        // configure the view
        scnView.backgroundColor = NSColor.lightGray
        
        // Add a click gesture recognizer
        let clickGesture = NSClickGestureRecognizer(target: self, action: #selector(handleClick(_:)))
        var gestureRecognizers = scnView.gestureRecognizers
        gestureRecognizers.insert(clickGesture, at: 0)
        scnView.gestureRecognizers = gestureRecognizers
    }
    
    func buildCylinder(numSlices: Int, numStacks: Int) -> SCNGeometry {
        
        var verts: [SCNVector3] = []
        var normals: [SCNVector3] = []
        var texCoords: [CGPoint] = []
        var indices: [Int32] = []
        
        let sliceStep: CGFloat = (CGFloat.pi * 2) / CGFloat(numSlices)
        var sliceAngle: CGFloat = 0
        
        var capCenterVert: Int32 = 0
        verts.append(SCNVector3(x: 0, y: 0, z: 0))
        normals.append(SCNVector3(x: 0, y: 0, z: -1))
        texCoords.append(CGPoint(x: 0, y: 0))
        for index in 1...Int32(numSlices) {
            verts.append(SCNVector3(x: cos(sliceAngle), y: sin(sliceAngle), z: 0))
            normals.append(SCNVector3(x: 0, y: 0, z: -1))
            texCoords.append(CGPoint(x: cos(sliceAngle), y: sin(sliceAngle)))
            
            let triangle: [Int32] = [capCenterVert, index, index == numSlices ? (capCenterVert+1) : index+1 ]
            indices.append(contentsOf: triangle)
            sliceAngle += sliceStep
        }
        
        
        // top cap
        capCenterVert = Int32(verts.count)
        verts.append(SCNVector3(x: 0, y: 0, z: 1))
        normals.append(SCNVector3(x: 0, y: 0, z: 1))
        texCoords.append(CGPoint(x: 0, y: 0))
        
        sliceAngle = 0
        for index in 1...Int32(numSlices) {
            verts.append(SCNVector3(x: cos(sliceAngle), y: sin(sliceAngle), z: 1))
            normals.append(SCNVector3(x: 0, y: 0, z: 1))
            texCoords.append(CGPoint(x: cos(sliceAngle), y: sin(sliceAngle)))
            let triangle: [Int32] = [capCenterVert, capCenterVert + index, index == numSlices ? (capCenterVert+1) : capCenterVert+index+1 ]
            indices.append(contentsOf: triangle)
            sliceAngle += sliceStep
        }
        
        // TODO: sides
        
        
        let vertSrc = SCNGeometrySource(vertices: verts)
        let normalSrc = SCNGeometrySource(normals: normals)
        let texCoordSrc = SCNGeometrySource(textureCoordinates: texCoords)
        let elementSrc = SCNGeometryElement(indices: indices, primitiveType: .triangles)
        
        let geometry = SCNGeometry(sources: [vertSrc, normalSrc, texCoordSrc], elements: [elementSrc])

        let material = SCNMaterial()
        material.diffuse.contents = NSColor.red
        material.isDoubleSided = true
        material.emission.contents = NSColor.red
        
        geometry.firstMaterial = material
        return geometry
    }
    
    @objc
    func handleClick(_ gestureRecognizer: NSGestureRecognizer) {
        // retrieve the SCNView
        let scnView = self.view as! SCNView
        
        // check what nodes are clicked
        let p = gestureRecognizer.location(in: scnView)
        let hitResults = scnView.hitTest(p, options: [:])
        // check that we clicked on at least one object
        if hitResults.count > 0 {
            // retrieved the first clicked object
            let result = hitResults[0]
            
            // get its material
            let material = result.node.geometry!.firstMaterial!
            
            // highlight it
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 0.5
            
            // on completion - unhighlight
            SCNTransaction.completionBlock = {
                SCNTransaction.begin()
                SCNTransaction.animationDuration = 0.5
                
                material.emission.contents = NSColor.black
                
                SCNTransaction.commit()
            }
            
            material.emission.contents = NSColor.red
            
            SCNTransaction.commit()
        }
    }
}