0
\$\begingroup\$

I'm working on a camera that moves around the player at a fixed distance. It's the standard right-stick-to-move-cam-around kind. The camera follows the player but stays at a fixed angle to (0,0,1) until moved by input, and the player is free to move in any direction without changing the camera rotation with respect to the world. Imagine the cam as being a point on an invisible sphere with the player at the centre.

However, there's a problem with it in that when I rotate the camera around the player too fast, the lerping I added to try and smooth the movement tries to "shortcut" across instead of moving around the sphere gracefully when the desired position gets too far away. The image below is a top-down view, the camera takes the red route, but I want it to take the green route while remaining smooth. Bad Cam

Currently I'm:

  • Checking whether the look controls are inverted, and setting a modifier value.
  • Updating the current angle to (0,0,1) using the normalized X component of a Vector2 set by keyboard input (cam left, cam right).
  • Updating the current angle to the Y axis using the Y component, set by input (cam up, cam down).
  • Finding the desired cam position from the two angles and position of the focus point (player).
  • Lerping between the current cam position and the desired one and updating the lookAt vector.

Here is the update code (it's in Go which isn't especially common for game dev, but hopefully the syntax should be easy enough to understand what's going on):

// Update the gameplay camera with the focus at focusPos.
func StepGameplayCam(focusPos math32.Vector3, delta float64) {
    invertLookMod := float32(1)
    if settings.Control().InvertLook {
        invertLookMod = -1
    }

    //Get up/down and left/right components and set angles.
    currentAngleToZ = util.WrapAngleRad(currentAngleToZ + float32(movementVec.X*float32(delta)*settings.Control().CamSpeed*invertLookMod))

    if !((movementVec.Y < 0 && currentAngleToY <= math32.DegToRad(15)) || (movementVec.Y > 0 && currentAngleToY >= math32.DegToRad(165))) {
        currentAngleToY = util.WrapAngleRad(currentAngleToY + float32(movementVec.Y*float32(delta)*settings.Control().CamSpeed*invertLookMod))
    }

    distanceToFocus = maxDistanceToFocus //TODO cam collisions/not clipping through things

    //Set desired cam position.
    targetPos.X = focusPos.X + distanceToFocus*math32.Cos(currentAngleToZ)*math32.Sin(currentAngleToY)
    targetPos.Z = focusPos.Z + distanceToFocus*math32.Sin(currentAngleToZ)*math32.Sin(currentAngleToY)
    targetPos.Y = focusPos.Y + distanceToFocus*math32.Cos(currentAngleToY)
    camPos.Lerp(targetPos, float32(delta)) //TODO problem is here, we're LERPing between vectors but we need to do it spherically
    gameCam.SetPositionVec(camPos)

    gameCam.LookAt(&focusPos)

    if settings.Dev().DebugEnabled {
        updateCamDebug(focusPos.X, focusPos.Y, focusPos.Z)
    }
}

I have a strong feeling slerp is what I'm looking for here, but my understanding of 3D mathematics is starting to brickwall at this point, and I have no idea how to use it in this context.

How can I stop my camera from "shortcutting" through the imaginary sphere around the player it is bound to?

\$\endgroup\$
1
\$\begingroup\$

First, take the center and radius out of the equation, and just think of your two camera locations as directions - unit vectors pointing out from 0:

(For brevity, I'll pretend we're using a vector library so we don't have to repeat everything 3 times for x y & z)

// Unit vector in direction from focus position to the old camera position.
camDirection = Normalize(camPos - focusPos);

// Unit vector in direction from focus position to the new camera position.
targetDirection = (math32.Cos(currentAngleToZ)*math32.Sin(currentAngleToY),
                   math32.Sin(currentAngleToZ)*math32.Sin(currentAngleToY).
                   distanceToFocus*math32.Cos(currentAngleToY));

// Time-correct your Lerp if you want a framerate-independent exponential ease.
blend = 1.0f - math32.Pow(1.0f - 1.0f/30.0f, 30.0f * float32(delta));

// Take the shortcut.
intermediate = Lerp(camDirection, targetDirection, blend);

// Bend out the shortcut into a long-cut:
camPos = focusPosition + distanceToFocus * Normalize(intermediate);
\$\endgroup\$
  • \$\begingroup\$ It took a bit of tweaking to get the speed right, and the vector library I'm using has a little clunkier syntax so it doesn't look as graceful, but it works! Thank you. \$\endgroup\$ – MetallicHydrogen Apr 2 at 23:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.