Search code examples
iosswiftscenekit

How to render spheres in SceneKit at user's tap position?


I am working on an iOS app with Swift and SceneKit. When user taps on screen, I want to render a dot in SceneKit at the location that user just tapped. How can I do that?

I am using unprojectPoint() method but my object is not rendered at expected location.

I am having a SceneView, with the camera like this:

cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

And then I render the dot at tap position like this:

func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    // retrieve the SCNView
    let scnView = self.view as! SCNView
    
    // create dot
    let geo = SCNSphere(radius: CGFloat(0.1))
    geo.firstMaterial?.diffuse.contents = UIColor.red
    let dotNode = SCNNode(geometry: geo)
    
    let p = gestureRecognize.location(in: scnView)
    let uP = scnView.unprojectPoint(SCNVector3(p.x, p.y, 5))
    // HERE - how to set position for the dot ?
    dotNode.position = SCNVector3(uP.x, uP.y, 5)
    
    scnView.scene?.rootNode.addChildNode(dotNode)
}

Here is my Github repo: https://github.com/rudyhuynh/SceneKitExample


Solution

  • Unprojecting a Point

    To render dot nodes at expected position, use the following approach. Don't forget that you are looking at the scene in perspective projection. Unprojecting a point whose Z-coordinate is 0.0 returns a point on the near clipping plane. Unprojecting a point whose Z-coordinate is 1.0 returns a point on the far clipping plane.

    import SceneKit
    
    class GameViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()            
            let sceneView = self.view as! SCNView
            sceneView.scene = SCNScene()
            sceneView.backgroundColor = UIColor.black
                    
            let cameraNode = SCNNode()
            cameraNode.camera = SCNCamera()
            cameraNode.simdPosition = simd_float3(0, 0, 1)
            sceneView.scene?.rootNode.addChildNode(cameraNode)
            
            let tapGesture = UITapGestureRecognizer(target: self,
                                                    action: #selector(tap))
            sceneView.addGestureRecognizer(tapGesture)
        }
    
        @objc func tap(_ gestureRecognizer: UITapGestureRecognizer) {
            let scnView = self.view as! SCNView
            
            let geo = SCNSphere(radius: 0.01)
            geo.firstMaterial?.diffuse.contents = UIColor.white
            let dotNode = SCNNode(geometry: geo)
    
            let point = gestureRecognizer.location(in: scnView)
            let unprojected = scnView.unprojectPoint(.init(point.x, point.y, 0))
     
            dotNode.position = SCNVector3(unprojected.x, unprojected.y, -0.01)
            scnView.scene?.rootNode.addChildNode(dotNode)
        }
    }
    

    enter image description here

    To run a camera in orthographic projection, use:

    cameraNode.camera?.usesOrthographicProjection = true