Search code examples
3dcamerascenekitquaternionshittest

SceneKit, projecting a node along camera orientation


With SceneKit, I see that a node, to which a camera is attached, has a eulerAngles (SCNVector3 of radian angles) and orientation (SCNQuaternion)

My cameraNode is at 0,0,0 and can change its orientation at any moment.

I need to do hitTestWithSegmentFromPoint... toPoint...

fromPoint is always known.

But I need to project, based on camera orientation a far away point that lies in the line of the camera.

My mind hasn't grabbed quaternions properly yet, and I'm not getting anywhere based on eulerAngles.


Solution

  • Using some linear algebra

    func findNextPoint(p0: SCNVector3, direction: SCNVector3) -> SCNVector3{
    
        var x = Float()
        var y = Float()
        var z = Float()
        let t = 100 as Float
    
        x = p0.x + t * direction.x
        y = p0.y + t * direction.y
        z = p0.z + t * direction.z
    
        let result = SCNVector3Make(x, y, z)
        return result
    
    }
    

    This uses p0 as a starting point to, then, find the next point you want on a given direction. t is the length of the vector, or the distance you want to travel from p0.

    To find the direction the camera is pointing, you must get its rotation and multiply by a Rotation Matrix.

    func calculateCameraDirection(cameraNode: SCNNode) -> GLKVector3 {
    
        let x = -cameraNode.rotation.x
        let y = -cameraNode.rotation.y
        let z = -cameraNode.rotation.z
        let w = cameraNode.rotation.w
    
        let cameraRotationMatrix = GLKMatrix3Make(cos(w) + pow(x, 2) * (1 - cos(w)),
            x * y * (1 - cos(w)) - z * sin(w),
            x * z * (1 - cos(w)) + y*sin(w),
    
            y*x*(1-cos(w)) + z*sin(w),
            cos(w) + pow(y, 2) * (1 - cos(w)),
            y*z*(1-cos(w)) - x*sin(w),
    
            z*x*(1 - cos(w)) - y*sin(w),
            z*y*(1 - cos(w)) + x*sin(w),
            cos(w) + pow(z, 2) * ( 1 - cos(w)))
    
        let cameraDirection = GLKMatrix3MultiplyVector3(cameraRotationMatrix, GLKVector3Make(0.0, 0.0, -1.0))
    
        return cameraDirection
    
    }
    

    You then just need to perform the hitTest using both coordinates. In this case I used rayTestWithSegmentFromPoint:ToPoint , but you may use any other kind of test there, such as hitTestWithSegmentFromPoint:toPoint: .

    func raycastTest(cameraDirection: GLKVector3) -> SCNVector3{
    
        let nextPoint = findNextPoint(cameraNode.position, direction: SCNVector3Make(cameraDirection.x, cameraDirection.y, cameraDirection.z))
        let hitTest = sceneView.scene!.physicsWorld.rayTestWithSegmentFromPoint(cameraNode.position, toPoint: nextPoint, options: nil)
    
        if hitTest.count > 0 {
            return (hitTest.first?.worldCoordinates)!
        }else{
            return nextPoint
        }
    }