FPS Camera

Started by ic5y, July 23, 2013, 01:44:36

Previous topic - Next topic

ic5y

Hello!

I'm working on a prototype, and I need an fps camera. For now, I just used glTranslate and glRotate, but I want to use matrices now. I tried to search for opengl fps camera, with no success. The problem I got, or I really have, that when for example I rotate the camera a bit up (on the X axis), then I want to look e.g. left, it rotates around the up vector, not (0,1,0).

I post my code here. I cut out some things (imports, property methods for example):

public class Camera {

    Matrix4f projection;
    Matrix4f view = new Matrix4f();

    Vector3f rotation = new Vector3f();
    Vector3f position = new Vector3f();

    Vector3f viewDir = new Vector3f();
    Vector3f upVector = new Vector3f();
    Vector3f rightVector = new Vector3f();

    public Camera()
    {
        projection = createPerpective(60f, RenderContext.RATIO, 0.01f, 1000f);
        viewDir = new Vector3f(0,0,-1);
        upVector = new Vector3f(0,1,0);
        rightVector = new Vector3f(1,0,0);
    }

    public void rotateX(float angle)
    {
        rotation.x += angle;

        Vector2f haha = MathExtra.rotatePoint(new Vector2f(viewDir.getX(), viewDir.getY()), new Vector2f(), angle);
        viewDir.x = haha.getX();
        viewDir.y = haha.getY();

        Vector3f temp = new Vector3f();

        Vector3f.cross(viewDir, rightVector, temp);

        upVector = temp;
    }

    public void rotateY(float angle)
    {
        rotation.y += angle;

        Vector2f haha = MathExtra.rotatePoint(new Vector2f(viewDir.getX(), viewDir.getZ()), new Vector2f(), angle);
        viewDir.x = haha.getX();
        viewDir.z = haha.getY();

        Vector3f temp = new Vector3f();

        Vector3f.cross(viewDir, upVector, temp);

        rightVector = temp;
    }

    public void update(int delta)
    {
        view = new Matrix4f();

        Vector3f movement = new Vector3f();

        if(Keyboard.isKeyDown(Keyboard.KEY_W))
        {
            movement.z -= 1f;
        }
        else if(Keyboard.isKeyDown(Keyboard.KEY_S))
        {
            movement.z += 1f;
        }
        if(Keyboard.isKeyDown(Keyboard.KEY_A))
        {
        }
        else if(Keyboard.isKeyDown(Keyboard.KEY_D))
        {
        }
        if(Keyboard.isKeyDown(Keyboard.KEY_Q))
        {
            movement.y -= 1f;
        }
        else if(Keyboard.isKeyDown(Keyboard.KEY_E))
        {
            movement.y += 1f;
        }

        if(Keyboard.isKeyDown(Keyboard.KEY_UP))
        {
            rotateX(0.1f * delta);
        }
        else if(Keyboard.isKeyDown(Keyboard.KEY_DOWN))
        {
            rotateX(-0.1f * delta);
        }
        if(Keyboard.isKeyDown(Keyboard.KEY_LEFT))
        {
            rotateY(-0.1f * delta);
        }
        else if(Keyboard.isKeyDown(Keyboard.KEY_RIGHT))
        {
            rotateY(+0.1f * delta);
        }

        if(movement.lengthSquared() != 0)
            movement.normalise();

        movement = MathExtra.multiply(movement, 0.005f);
        movement = MathExtra.multiply(movement, delta);

        Vector2f rot = MathExtra.rotatePoint(new Vector2f(movement.x, movement.z), new Vector2f(), rotation.getY());

        movement.x = rot.x;
        movement.z = rot.y;

        Vector3f.add(movement, position, position);

        Vector3f viewPoint = new Vector3f();
        Vector3f.add(position, viewDir, viewPoint);

        Matrix4f.translate(MathExtra.inverse(position), view, view);
        Matrix4f.mul(lookAt(position, viewPoint, upVector), view, view);
    }

    private Matrix4f createPerpective(float fov, float ratio, float near, float far)
    {
        Matrix4f projectionMatrix = new Matrix4f();
        float fieldOfView = fov;
        float aspectRatio = ratio;
        float near_plane = near;
        float far_plane = far;

        float y_scale = (float) (1f / Math.tan(Math.toRadians(fieldOfView / 2f)));
        float x_scale = y_scale / aspectRatio;
        float frustum_length = far_plane - near_plane;

        projectionMatrix.m00 = x_scale;
        projectionMatrix.m11 = y_scale;
        projectionMatrix.m22 = -((far_plane + near_plane) / frustum_length);
        projectionMatrix.m23 = -1;
        projectionMatrix.m32 = -((2 * near_plane * far_plane) / frustum_length);
        projectionMatrix.m33 = 0;

        return projectionMatrix;
    }

    public static Matrix4f lookAt(Vector3f eye, Vector3f center, Vector3f up)
    {
        Vector3f f = new Vector3f();
        Vector3f.sub(center, eye, f);
        f.normalise();

        Vector3f u = new Vector3f();
        up.normalise(u);
        Vector3f s = new Vector3f();
        Vector3f.cross(f, u, s);
        s.normalise();
        Vector3f.cross(s, f, u);

        Matrix4f mat = new Matrix4f();

        mat.m00 = s.x;
        mat.m10 = s.y;
        mat.m20 = s.z;
        mat.m01 = u.x;
        mat.m11 = u.y;
        mat.m21 = u.z;
        mat.m02 = -f.x;
        mat.m12 = -f.y;
        mat.m22 = -f.z;

        Matrix4f temp = new Matrix4f();
        Matrix4f.translate(new Vector3f(-eye.x, -eye.y, -eye.z), mat, temp);

        return mat;
    }

}

quew8

You need to change the order in which you multiply the various transform matrices together, and remember that for to matrices A and B,
A * B != B * A

ic5y

I think my multiplications are okay. So now I changed to this:

Matrix4f.mul(lookAt(position, viewPoint, upVector), view, view);
Matrix4f.translate(MathExtra.inverse(position), view, view);


still bad the X rot. I made a quick video to illustrate it: http://youtu.be/3NTeW16Jj5E

quew8

I'm so sorry - shame on me, I didn't even look at your code, I just assumed you were doing it a different way. The problem you are having is called gimbal lock. The simplest solution (by which I mean the one that requires the least rewriting of code from where you are currently) is this.

Essentially you have to ensure that you rotate about the y axis before you rotate about the x axis and then don't rotate any more about the y. Here is what I would advise. In your rotateX() and rotateY() methods, only add to the rotation variable, don't work anything out. Then in update(), work out the forward vector by rotating the original forward vector (0, 0, -1) by rotation.y and then again by rotation.x. I think that should do it.

Sorry again for stupidity in previous post.

ic5y

This is what I have now. I rotate the viewDirection on the Y axis, it's working, okay. But I have problems with rotating the Up vector as it needed for the lookAt method. I have no idea how to rotate the up vector.
The code and the way to calculate it looks okay for me, but when rotation.x == +-90 then I'm not looking exactly up.

private void updateRotation()
    {
        if(rotation.x >= 90)
            rotation.x = 90;
        else if(rotation.x <= -90)
            rotation.x = -90;

        rotation.y = rotation.y % 360;

        Vector2f rotateOnY = MathExtra.rotatePoint(new Vector2f(0, -1), new Vector2f(), rotation.getY());

        viewDir.x = rotateOnY.x;
        viewDir.z = rotateOnY.y;

        Vector3f.cross(viewDir, new Vector3f(0, 1, 0), rightVector);

        ///////// BAD PART ////////
        Vector3f temp = new Vector3f();

        Vector3f.cross(viewDir, rightVector, temp);

        upVector = MathExtra.inverse(temp);
    }

quew8

The method you use to compute the right and up vectors is this:

1) Assume (0, 1, 0) as the up vector.
2) Cross the up and forward vectors to get the right vector (cross product gives a perpendicular vector in 3D)
3) Recompute the up vector by crossing the right and forward vectors.

The problem you have is this, when rotation.x == +-90 the angle between the forward vector and (0, 1, 0) is 0 or 180. The cross product in this scenario gives a magnitude zero vector. As per
Quote| A X B | = | A |*| B |*sin(theta)
this then screws up your right and then your up vectors, making them both (0, 0, 0)


The solution I believe is to compute the right and up vectors as you did the forward vector, by rotating (1, 0, 0) and (0, 1, 0) respectively.