Search code examples
javaopengllwjglinterpolation

Camera should smoothly follow a specific point


In my game (3D game based on LWJGL) I walk in a voxel (block) world. The character goes blocks up and down quite fast so I want the camera too smoothly follow the character. I tried interpolation but the point, which I have to interpolate to is changing all the time, because the character is not taking the step at once (about 5-8 frames). This leads to some shaking which doesn't look nice. Is there a way to do this better?

Greetings

cameraYPosition = MathHelper.sinerp(cameraYCorrectionPoint, player.y, ((float)glfwGetTime() - cameraYCorrectionTime) * (1/cameraYCorrectionDuration));

This is the deciding line. The cameraYCorrectionPoint is the point, where the camera started to interpolate, while player.y is the position to interpolate towards (which can obviously change every frame). The other part is to calculate the time passed and scaling it up so it ranges from 0 to 1.

This isn't really working, since the position can change again before the initial interpolation is done, resulting into ugly interpolation. So what can I do for a better approach?

Here the rest of the code, but I don't think this will help much:

if(cameraYPosition == Float.MIN_VALUE) cameraYPosition = player.y;

    if(player.y != cameraYPosition) {
        if(cameraYCorrectionPoint == Float.MIN_VALUE) {
            cameraYCorrectionPoint = cameraYPosition;
            cameraYCorrectionTime = (float)glfwGetTime();
        }

        if(cameraYCorrectionTime <= glfwGetTime()) {
            float absoluteDistance = cameraYCorrectionPoint - player.y;

            float relativeDistance = cameraYPosition - player.y;

            if(glfwGetTime() - cameraYCorrectionTime <= cameraYCorrectionDuration) {
                cameraYPosition = MathHelper.sinerp(cameraYCorrectionPoint, player.y, ((float)glfwGetTime() - cameraYCorrectionTime) * (1/cameraYCorrectionDuration));
            }
            else {
                cameraYPosition = player.y;
            }

            lastPlayerY = player.y;

            if(absoluteDistance > 0 && cameraYPosition < player.y || absoluteDistance < 0 && cameraYPosition > player.y) {
                cameraYCorrectionDuration = cameraYBaseCorrectionDuration;

                if(relativeDistance < 0 && cameraYPosition - player.y > 0 || relativeDistance > 0 && cameraYPosition - player.y < 0) {
                    cameraYPosition = player.y;
                }
                else {
                    cameraYCorrectionPoint = cameraYPosition;
                }
            }
        }
    }
    else {
        cameraYCorrectionDuration = cameraYBaseCorrectionDuration;

        cameraYCorrectionPoint = Float.MIN_VALUE;
    }

And my interpolation code:

package main;

public class MathHelper {
    public static float hermite(float start, float end, float value) {
        return lerp(start, end, value * value * (3.0f - 2.0f * value));
    }

    public static float sinerp(float start, float end, float value) {
        return lerp(start, end, (float)Math.sin(value * Math.PI * 0.5f));
    }

    public static float coserp(float start, float end, float value) {
        return lerp(start, end, 1.0f - (float)Math.cos(value * Math.PI * 0.5f));
    }

    public static float lerp(float start, float end, float value) {
        return ((1.0f - value) * start) + (value * end);
    }
}

Solution

  • You can model the camera a bit differently. Split the position into a current position and a target position. When you want to move the camera, only move the target position. Then, each frame, update the current camera position to get closer to the target position. I have good experience with the following update:

    factor = f ^ t
    currentCamera = factor * currentCamera + (1 - factor) * targetCamera
    

    f is a factor, which lets you choose how immediate the reaction of the camera will be. Higher values will result in a very lose motion, a value of 0 will make the camera follow exactly its target. t is the time since the last update call. If t is measured in milliseconds, f should have values between 0.95 and 0.99.