Search code examples
openglgraphicscameratranslationlwjgl

Assistance translating a rotated camera


I have implemented camera rotation around a centre entity and now want to add camera translation. I cannot just do centre.xy += mouse.delta.xy as if the camera is rotated facing the z axis and I drag to the right, this will obviously move the camera towards me (because x axis being incremented). In this instance, the centre.z attribute would need to be increased. I suppose I need to incorporate the camera's pitch, yaw and roll properties into this calculation but not sure how to go about it... any suggestions/links?

I also tried playing around with ray casting (which I have implemented), in place of the mouse delta, but to no avail.

EDIT - simple method:

val right = Vector3f(viewMatrix.m00(), viewMatrix.m01(), viewMatrix.m02()).mul(lmb.delta.x)
val up = Vector3f(viewMatrix.m10(), viewMatrix.m11(), viewMatrix.m12()).mul(lmb.delta.y)
val delta = right.add(up)
center.add(delta)

Solution

  • You did not write a lot about how you represent your camera, but I assume the following:

    The camera is represented by a focus point centre and three Euler angles that describe the rotation about that focus point. Probably also a distance to the focus point.

    I'll explain two ways - one rather simple and one more sophisticated.

    Simple Way

    Let's recap what you were trying to do:

    centre.xy += mouse.delta.xy
    

    This fails when the camera is not aligned with the coordinate system. A more general formulation of this approach would be:

    centre += mouse.delta.x * right + mouse.delta.y * up
    

    Here, right is a world-space vector pointing to the right side of the screen and up is a world-space vector pointing upwards. Depending on your mouse delta, you may instead want a down vector.

    So, where do we get those vectors from? Easy. The view matrix has all we need. The first row (the first three entries of the row) are the right vector. The second row is the up vector. So, simply get the view matrix, read those vectors, and update the focus center. You might also want to add some scale.

    More Sophisticated

    In many applications, the panning functionality is designed in a way such that a certain 3D point under the mouse stays under the mouse during panning. This can be achieved in the following way:

    First, we need the depth of the 3D point that we want to keep under the mouse. Two common options are the depth of the focus point or the actual depth of the 3D scene under the mouse (which you get from the depth map). I will explain the former.

    We first need this depth in Normalized Device Coordinates. To do this, we first calculate the view-projection matrix:

    VP = ProjectionMatrix * ViewMatrix
    

    Then, we transform the focus point into clip space:

    focusClip = VP * (focus, 1)
    

    (focus, 1) is a 4D vector with a 1 as its last component. Finally, we derive NDC depth as

    focusDepthNDC = focusClip.z / focusClip.w
    

    Ok, now we have the depth. So we can calculate the 3D point that we want to keep under the mouse. First, lets invert the view-projection matrix because this allows us to go from clip space to world space:

    VPInv = inverse(VP)
    

    Then, the point under the mouse is (I'll call it x):

    x = VPInv * (mouseStartNDC.x, mouseStartNDC.y, focusDepthNDC, 1)
    

    mouseStartNDC is the mouse position before the shift. Keep in mind that this needs to be in normalized device coordinates. If you only have screen space coordinates, then:

    ndcX = 2 * screenX / windowWidth - 1
    ndcY = -2 * screenY / windowHeight + 1
    

    x is again a 4D vector. Do the perspective divide:

    x *= 1.0 / x.w
    

    Now we have our 3D point. We just need to find a shift of the camera that keeps this position under the mouse at the mouse location after the shift:

    newX = VPInv * (mouseEndNDC.x, mouseEndNDC.y, focusDepthNDC, 1)
    

    Do the perspective divide again:

    newX *= 1.0 / newX.w
    

    And finally update your camera center:

    centre += (x - newX).xyz
    

    This approach works with any camera model that you can express in matrix form.