Switching between "arcball" and "fps-style" camera rotation

I’m setting up a program where I would like to be able to use two different styles of camera rotation. I want to be able to rotate the camera in the style of a first-person shooter, where you are in a position and you swing the camera around to look everywhere. But then I would like to be able to switch to “arcball” style, where moving the mouse actually swings you around the focal point of the camera. Is it possible to switch between these two seamlessly?

From a naive perspective, it seems like the difference between these two modes is basically just the order in which you multiply the translate and rotate matrices. If I use translaterotate, I get an arcball effect. If I use rotatetranslate, it behaves like an FPS.


var translate = float4x4()
translate.translate(camPosition.x, y: camPosition.y, z: camPosition.z)


var rotate = float4x4()
rotate.rotateAroundX(camRotation.x, y: camRotation.y, z: camRotation.z)


if arcball {
     viewMatrix = translate * rotate
}
else {
     viewMatrix = rotate * translate
}

But I can’t just seamlessly switch back and forth between these two. The ‘arcball’ style essentially always points at the origin, whereas the ‘FPS’ style aims out from the camera position in which ever direction it’s pointing. How could I calculate things so that I could flip back and forth between these? It seems like it would basically involve moving the arcball ‘origin’ point to where the FPS camera is pointing. And IS there a specific point at which a ViewMatrix aims? I’m a little confused about that. I know you can make a ViewMatrix by specifying an Eye position and a LookAt position, but is that basically just used to establish a direction? Or is there an actual focal position encoded into a View Matrix? If not, it seems like I could still maybe do what I want just by specifying a constant focal distance from the eye, with which to calculate the arcball origin.

Hopefully this question makes sense and someone can provide some guidance!! I flunked linear algebra in college :frowning:
Thanks

A “FPS” camera requires incremental updates. Each “tick”, you get an incremental change in position and rotation. Each of these changes is specified in the coordinate system established by all of the preceding transformations. You can’t coalesce the translations and rotations independently; you can coalesce the rotations to obtain the final rotation, but each translation must be transformed by the rotation in effect at the time of that translation.

No, only a direction. If you wanted to implement an orbital camera given a transformation, you’d have to choose the initial distance of the origin from the viewer.

Yes. If you look at the definition of gluLookAt(), the first thing it does is subtract the eye position from the centre (target) position to obtain a “forward” vector (which is normalised), and it never uses the centre position thereafter. Thus, the distance between the eye and centre positions is discarded; there’s no way to recover it from the matrix.

Thanks. I think it is still possible – with an extra transformation I think when in arcball mode, to move the camera to where it “really” is after the rotation.

Why not just keep track of the camera position and rotation as separate components so that you always have those? I am already doing that and not having trouble – it’s just switching between the two modes that I am wondering about.

Okay, I’ve been tearing my hair out over this – have given up on my original desire for the combo camera. Now I just would like to get a simple FPS style camera to work. I’ve gone through several online examples and none of them work for me – quaternions, Euler angles. I can’t figure out what I am doing wrong.

Right now I’ve just dropped all the Euler angles and Quaternions and am just trying to get a camera to work using a simple rotation matrix which works incrementally, as @GClements stated. I’ve boiled it down to this for this post example:


func calculateViewMatrix() {
    let lookVector = rotationMatrix*float4(0.0,0.0,1.0,1.0)    
    camUp = rotationMatrix*float4(0.0,1.0,0.0,1.0)
    camTarget = camPosition + lookVector
 
    viewMatrix = float4x4.makeLookAt(camPosition.x, camPosition.y, camPosition.z, camTarget.x, camTarget.y, camTarget.z, camUp.x, camUp.y, camUp.z)
}

func rotateCamera(x:Float, y:Float) {
    rotationMatrix.rotate(x, y: y, z: 0.0)
}

It sort of works, but without even changing position, just rotating, the camera quickly tilts left or right. That’s if I multiply the UP vector by the camera rotation, which I need to do if I want to rotate the camera vertically past 90 degrees. See this video:
http://www.bobito.com/CamSwing1.mov

On the other hand, if I keep my UP vector at 0.0,1.0,0.0, then it is somewhat better – but the camera still doesn’t swing around Y on a perfect horizontal – it dips and rises as it swings around. And then of course if I turn it straight up or down then the axis does a flip as it approaches 90 degrees, because the look and up vectors are becoming the same I guess?
http://www.bobito.com/CamSwing2.mov

What am I doing wrong? I thought doing a simple incrementally-changing rotation matrix camera would be simple.
Thanks for any replies!

If you can look up and down, the elevation needs to be kept separate from the incrementally-updated player transformation. So rotations about the vertical and movement are accumulated into the player transformation, rotations about the horizontal are accumulated separately, then the two transformations are combined each frame.

Basically, for walking you want left-right rotations to rotate about the world-space vertical axis, not the view-space vertical axis. For ground vehicles, you want rotation to rotate about a vector perpendicular to the ground (regardless of any view elevation). Rotating about view-space axes makes sense for a spacecraft (where there isn’t any global “up” direction).

I’m setting up a program where I would like to be able to use two different styles of camera rotation. I want to be able to rotate the camera in the style of a first-person shooter, where you are in a position and you swing the camera around to look everywhere. But then I would like to be able to switch to “arcball” style, where moving the mouse actually swings you around the focal point of the camera. Is it possible to switch between these two seamlessly?

yes, why not ?!

camera 1 needs:
vec3 position
quaternion rotation

the view matrix = inverse(translate(position) * toMat4(rotation))


camera 2 needs:
bool hasfocalpoint
vec3 focalpoint
float radius, inclination, azimuth;

the position depends on (= is a function of) these 4 values

the view matrix = lookAt(positionfunction(), focalpoint, upfunction())

upfunction() can simply return vec3(0, 1, 0) as the “default” up direction, meaning that the “roll” angle is always leveled-out (= 0)


how you do the logic part in switching between those 2 modes is our problem, but changes for 1 camera should also be done on the other camera if that’s what you mean by “seamlessly” switching …

[QUOTE=GClements;1289134]If you can look up and down, the elevation needs to be kept separate from the incrementally-updated player transformation. So rotations about the vertical and movement are accumulated into the player transformation, rotations about the horizontal are accumulated separately, then the two transformations are combined each frame.
[/QUOTE]

Now I’m really confused. I thought the whole point of “incremental” transformations to the matrix is that the new Y or X rotations were applied to the combination of what had come before. If rather I am keeping a separate vertical and horizontal transformation and then combining them at the end, how is that any different from just using Euler angles – keeping track of total X and Y rotations and just computing the view matrix each frame from those?

You’d still need to transform the lateral motion by the accumulated rotation.

The main thing to bear in mind is that you have two different objects: the player’s body and their head. Motion is affected by the orientation of the body, not the head. Lateral motion and rotation about the vertical are accumulated to obtain the body transformation at each update. Rotation about the horizontal is accumulated to obtain the transformation of the head relative to the body. The view transformation is the absolute head transformation.

It’s entirely feasible to implement all this using a 3D position vector and two angles, and only convert to a matrix at each frame. But you need to transform the relative motion at each update by the current rotation around the vertical before adding it to the current position. Given that you’re probably going to be computing the appropriate matrix (translation + vertical rotation) in the process of computing the view matrix (translation + vertical rotation + horizontal rotation), you may as well maintain that as a matrix rather than a position vector and heading angle. You can’t avoid keeping the elevation separate, whether as an angle or a matrix, because that doesn’t affect the movement direction.

One advantage of using a vector and angles is that matrices can decay (deviate from orthonormal) over time due to rounding error. With double precision, it would take days for this to become significant, but it can be an issue with single precision (and it was very much an issue with fixed-point arithmetic and approximations commonly used on 8-bit and 16-bit systems).