Rotate and Move Camera around object does not work Swift SceneKit

122 Views Asked by At

I have a basic 3d Interactive globe built with swift and SceneKit. When the view launches, the camera is pointing towards Africa. I want to rotate and move the camera to point at Oregon, United States. I have the correct SCNVector3 for the position of Oregon, but my function incorrectly moves the camera. How can I compute the rotation to accurately move the camera to any SCNVector3 position?

My calculations below are wrong. I'm assuming you have to take into account the camera's current position and use that to compute the rotation and movement to the new position.

    public var earthNode: SCNNode!
    internal var sceneView : SCNView!
    private var cameraNode: SCNNode!

    func centerCameraOnDot(dotPosition: SCNVector3) {
        let targetPhi = atan2(dotPosition.x, dotPosition.z)
        let targetTheta = asin(dotPosition.y / dotPosition.length())

        // Convert spherical coordinates back to Cartesian
        let newX = 1 * sin(targetTheta) * sin(targetPhi)
        let newY = 1 * cos(targetTheta)
        let newZ = 1 * sin(targetTheta) * cos(targetPhi)

        let fixedDistance: Float = 6.0
        let newCameraPosition = SCNVector3(newX, newY, newZ).normalized().scaled(to: fixedDistance)

        let moveAction = SCNAction.move(to: newCameraPosition, duration: 0.8)

        // Create additional actions as needed
        let rotateAction = SCNAction.rotateBy(x: 0, y: 0, z: 0, duration: 0.5)

        // Create an array of actions
        let sequenceAction = SCNAction.sequence([rotateAction])

        // Run the sequence action on the cameraNode
        cameraNode.runAction(sequenceAction)
    }
1

There are 1 best solutions below

5
VonC On BEST ANSWER

You would need to adjust both the position and the orientation of the camera. Your current approach seems to correctly calculate the new position, but does not seem to properly handle the camera's rotation.

  • Use spherical coordinates to find the new camera position.
  • Point the camera at the new target position (Oregon).

A modified version of your centerCameraOnDot function would be:

func centerCameraOnDot(dotPosition: SCNVector3) {
    let targetPhi = atan2(dotPosition.x, dotPosition.z)
    let targetTheta = asin(dotPosition.y / dotPosition.length())

    // Convert spherical coordinates back to Cartesian
    let newX = sin(targetTheta) * cos(targetPhi)
    let newY = cos(targetTheta)
    let newZ = sin(targetTheta) * sin(targetPhi)

    let fixedDistance: Float = 6.0
    let newCameraPosition = SCNVector3(newX, newY, newZ).normalized().scaled(to: fixedDistance)

    let moveAction = SCNAction.move(to: newCameraPosition, duration: 0.8)

    // Update the camera's position
    cameraNode.position = newCameraPosition

    // Orient the camera to look at the target position (Oregon)
    let lookAtConstraint = SCNLookAtConstraint(target: earthNode)
    lookAtConstraint.isGimbalLockEnabled = true
    cameraNode.constraints = [lookAtConstraint]

    // Optionally, add animation for smooth transition
    cameraNode.runAction(moveAction)
}

That code first calculates the new position of the camera as you did, but then, instead of manually rotating the camera, it uses a SCNLookAtConstraint. That constraint automatically adjusts the camera's orientation to always look at the target node (in your case, the Earth node).
And the camera's position is updated before applying the constraint to make sure it points from the correct location.

That assumes that your earthNode is correctly positioned at the center of your globe model. If earthNode is not at the center, you might need to create a separate node at the globe's center and use it for the SCNLookAtConstraint.


when trying this out on Oregon it partly works, but Oregon is on the right edge of the globe after the transition. This also does not animate the transition. When I try the location for South Korea for example, this just rotates the globe a tiny bit upwards, but does not work."

The SCNLookAtConstraint should make the camera look directly at the target, but if the target is not at the center of the camera's view, this could be due to the initial orientation of the Earth node or the camera node.

You would need to animate both the camera's position and its orientation. The position animation is straightforward using SCNAction.
For the orientation, you should smoothly transition the SCNLookAtConstraint to focus on the target.

func centerCameraOnDot(dotPosition: SCNVector3) {
    let fixedDistance: Float = 6.0
    let newCameraPosition = dotPosition.normalized().scaled(to: fixedDistance)

    // Position animation
    let moveAction = SCNAction.move(to: newCameraPosition, duration: 1.5)

    // Set up lookAt constraint for orientation
    let constraint = SCNLookAtConstraint(target: earthNode)
    constraint.isGimbalLockEnabled = true

    // Animate the transition
    SCNTransaction.begin()
    SCNTransaction.animationDuration = 1.5

    cameraNode.constraints = [constraint]
    cameraNode.runAction(moveAction)

    SCNTransaction.commit()
}

The camera's new position is computed to be a fixed distance from the target, ensuring the target is centered.
The SCNLookAtConstraint is still used for orientation, but now within an animation transaction to smoothly transition the view.
The animation durations are set to 1.5 seconds, but you can adjust this based on your preference for how quick the transition should be.