3
votes

I want to implement a FPS style mouse look in my lwjgl3 java application, but since there isn't any Mouse.getDX() or Mouse.getDY(), I'm looking for a different approach using the glfw bindings. I wrote this method that gets called in my update() method:

public double[] pollMouseDelta() {
    DoubleBuffer x = BufferUtils.createDoubleBuffer(1);
    DoubleBuffer y = BufferUtils.createDoubleBuffer(1);

    glfwGetCursorPos(WINDOW, x, y);
    x.rewind();
    y.rewind();

    double tempX = mouseX;
    double tempY = mouseY;

    mouseX = x.get();
    mouseY = y.get();

    return new double[] {
           mouseX - tempX,
           mouseY - tempY
    };
}

where mouseX and mouseY are global variables.

In my update method I do the following:

double[] mouseDelta = pollMouseDelta();

camera.rotate(Camera.Direction.LEFT_RIGHT, (float) (0.2f * -mouseDelta[0]));
camera.rotate(Camera.Direction.UP_DOWN, (float) (0.2f * mouseDelta[1]));

camera.update();

I also set the GLFW_CURSOR mode to GLFW_CURSOR_DISABLED.
But the camera is stuttering and making big jumps. Also, sometimes the mouse seems to be inverted.

Any suggestions?

3
You're confusing me. Mouse is an LWJGL class, which definitely has Mouse.getDX/DY().Joehot200
They removed all classes with static methods that had to do something with the window system, since they use glfw now to support multiple windowstobs

3 Answers

4
votes

Here is a code snippet of with the basis for my FPS style camera. As you can see I am calculating the delta of the mouse position with the center of the screen along the X and Y axis.

You can see in my System.out.println calls the values I am calculating for deltas and the fact that it should be rotating by this amount. I then multiply these values by my sensitivity and rotate the camera by this amount.

import org.lwjgl.BufferUtils;
import org.lwjgl.Sys;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;

import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFW.glfwGetCursorPos;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL30.*;
import static org.lwjgl.system.MemoryUtil.*;

public class Main {

    // We need to strongly reference callback instances.
    private GLFWErrorCallback errorCallback;
    private GLFWKeyCallback   keyCallback;

    // The window handle
    private long window;

    public void run() {
        System.out.println("Hello LWJGL " + Sys.getVersion() + "!");

        try {
            init();
            loop();

            // Release window and window callbacks
            glfwDestroyWindow(window);
            keyCallback.release();
        } finally {
            // Terminate GLFW and release the GLFWerrorfun
            glfwTerminate();
            errorCallback.release();
        }
    }

    private void init() {
        // Setup an error callback. The default implementation
        // will print the error message in System.err.
        glfwSetErrorCallback(errorCallback = errorCallbackPrint(System.err));

        // Initialize GLFW. Most GLFW functions will not work before doing this.
        if ( glfwInit() != GL11.GL_TRUE )
            throw new IllegalStateException("Unable to initialize GLFW");

        // Configure our window
        glfwDefaultWindowHints(); // optional, the current window hints are already the default
        glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // the window will stay hidden after creation
        glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // the window will be resizable
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);


        int WIDTH = 800;
        int HEIGHT = 600;

        // Create the window
        window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL);
        if ( window == NULL )
            throw new RuntimeException("Failed to create the GLFW window");

        // Setup a key callback. It will be called every time a key is pressed, repeated or released.
        glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() {
            @Override
            public void invoke(long window, int key, int scancode, int action, int mods) {
                if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
                    glfwSetWindowShouldClose(window, GL_TRUE); // We will detect this in our rendering loop
            }
        });

        // Get the resolution of the primary monitor
        ByteBuffer vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        // Center our window
        glfwSetWindowPos(
                window,
                (GLFWvidmode.width(vidmode) - WIDTH) / 2,
                (GLFWvidmode.height(vidmode) - HEIGHT) / 2
        );

        // Make the OpenGL context current
        glfwMakeContextCurrent(window);
        // Enable v-sync
        glfwSwapInterval(1);

        // Make the window visible
        glfwShowWindow(window);
    }

    private void loop() {
        // This line is critical for LWJGL's interoperation with GLFW's
        // This line is critical for LWJGL's interoperation with GLFW's
        // OpenGL context, or any context that is managed externally.
        // LWJGL detects the context that is current in the current thread,
        // creates the ContextCapabilities instance and makes the OpenGL
        // bindings available for use.
        GLContext.createFromCurrent();


        // Create a FloatBuffer to hold our vertex data
        FloatBuffer vertices = BufferUtils.createFloatBuffer(9);
        // Add vertices of the triangle
        vertices.put(new float[]
                {
                        0.0f,  0.5f,  0.0f,
                        0.5f, -0.5f,  0.0f,
                        -0.5f, -0.5f,  0.0f
                });
        // Rewind the vertices
        vertices.rewind();


        int vbo = glGenBuffers();
        int vao = glGenVertexArrays();

        glBindBuffer (GL_ARRAY_BUFFER, vbo);
        glBufferData (GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
        glBindVertexArray(vao);

        glEnableVertexAttribArray (0);
        glBindBuffer (GL_ARRAY_BUFFER, vbo);
        glVertexAttribPointer (0, 3, GL_FLOAT, false, 0, 0);

        final String vertex_shader =
                "#version 410\n" +
                        "in vec3 vp;\n" +
                        "void main () {\n" +
                        "  gl_Position = vec4 (vp, 1.0);\n" +
                        "}";

        final String frag_shader =
                "#version 400\n"    +
                        "out vec4 frag_colour;" +
                        "void main () {"         +
                        "  frag_colour = vec4 (0.5, 0.0, 0.5, 1.0);" +
                        "}";

        int shader_programme = glCreateProgram();


        int vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShaderID, vertex_shader);
        glCompileShader (vertexShaderID);

        if(glGetShaderi(vertexShaderID, GL_COMPILE_STATUS) == 0){
            System.err.println(glGetShaderInfoLog(vertexShaderID, 1024));
            System.exit(1);
        }

        glAttachShader (shader_programme, vertexShaderID);

        int fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShaderID, frag_shader);
        glCompileShader (fragmentShaderID);

        if(glGetShaderi(fragmentShaderID, GL_COMPILE_STATUS) == 0){
            System.err.println(glGetShaderInfoLog(fragmentShaderID, 1024));
            System.exit(1);
        }

        glAttachShader (shader_programme, fragmentShaderID);

        glLinkProgram (shader_programme);

        if(glGetProgrami(shader_programme, GL_LINK_STATUS) == 0){
            System.err.println(glGetProgramInfoLog(shader_programme, 1024));
            System.exit(1);
        }

        glValidateProgram(shader_programme);

        if(glGetProgrami(shader_programme, GL_VALIDATE_STATUS) == 0){
            System.err.println(glGetProgramInfoLog(shader_programme, 1024));
            System.exit(1);
        }

        boolean mouseLocked = false;
        double newX = 400;
        double newY = 300;

        double prevX = 0;
        double prevY = 0;

        boolean rotX = false;
        boolean rotY = false;

        glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);


        while ( glfwWindowShouldClose(window) == GL_FALSE ) {

            if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) {
                glfwSetCursorPos(window, 800/2, 600/2);

                mouseLocked = true;
            }

            if (mouseLocked){
                DoubleBuffer x = BufferUtils.createDoubleBuffer(1);
                DoubleBuffer y = BufferUtils.createDoubleBuffer(1);

                glfwGetCursorPos(window, x, y);
                x.rewind();
                y.rewind();

                newX = x.get();
                newY = y.get();

                double deltaX = newX - 400;
                double deltaY = newY - 300;

                rotX = newX != prevX;
                rotY = newY != prevY;

                if(rotY) {
                    System.out.println("ROTATE Y AXIS: " + deltaY);

                }
                if(rotX) {
                    System.out.println("ROTATE X AXIS: " + deltaX);
                }

                prevX = newX;
                prevY = newY;


                System.out.println("Delta X = " + deltaX + " Delta Y = " + deltaY);

                glfwSetCursorPos(window, 800/2, 600/2);
            }

            // wipe the drawing surface clear
            glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glUseProgram (shader_programme);
            glBindVertexArray (vao);
            // draw points 0-3 from the currently bound VAO with current in-use shader
            glDrawArrays (GL_TRIANGLES, 0, 3);
            // update other events like input handling
            glfwPollEvents ();
            // put the stuff we've been drawing onto the display
            glfwSwapBuffers (window);

        }
    }

    public static void main(String[] args) {
        new Main().run();
    }

}
1
votes

An alternative way to get your mouse position is to extend the GLFWCursorPosCallback class and then just use the x and y values that it provides in it's invoke method.

A good tutorial on doing this can be found here:

LWJGL 3 Mouse Position

1
votes

I am working on something exactly like this right now.

The idea is to reset the camera position to the center every time you loop, like this:

glfwSetCursorPos(YOUR WINDOW HANDLE, YOUR_WINDOW_WIDTH / 2, YOU_WINDOW_HEIGHT / 2);

This way your delta is always in comparison to the distance from the center of the screen. So that should solve your jumping around issues.

Edit (Leaving my answer above for posterity):

OK, so I was also having this same issue as you. Disregard what I have said above its not quite right.

I ended up implementing something similar to what you are doing but received the same results where the delta is too big so the camera is moving much too fast.

What I did to fix this was actually do the mouse polling every single frame and calculating the "delta" with the following operation:

deltaX = currentMousePos.x - lastFrameMousePos.x
deltaY = currentMousePos.y - lastFrameMousePos.y

Now the delta should be sufficiently small and also calculated per frame.

Then for calculating my angle of rotation I use this:

deltaPos.getY() * sensitivity * Time.getDelta()

Where Time.getDelta() is the time diff between each frame loop. What I end up with is a fairly smooth camera albeit not perfect yet.

Let me know if you want anymore info.

Edit 2:

Check out this link which has my similar issues Link on the LWJGL forums. I do actually have this working nicely now. I'll post a separate answer with code.