[OPENGL] 2D Camera following player

Started by ShadowDragon, December 07, 2018, 17:09:17

Previous topic - Next topic

ShadowDragon

Hi there,

I'm currently struggling to get my camera follow my player. Currently I can move my player around, but he can move outside of
the screen which is not what I want (thought I also don't want the world to be limited by screen size etc.).
I've already searched through the internet, but it seems like everyone is either using deprecated OpenGL stuff or 3D perspective cameras. :/

Note: I'm using an 2D orthogonal camera!

I'm also wondering how to move the camera code out into a seperate camera class.

Below you can find a minimal compileable example:
moveable player:
public class TestPlayer {

    private int x = 0, y = 0;
    private float size = 1.f;
    
    private int matLocation = 0;

    private int shaderProgram = 0;
    private IntBuffer vertexArray = BufferUtils.createIntBuffer(1);
    private IntBuffer vertexBuffer = BufferUtils.createIntBuffer(1);
    private IntBuffer indexBuffer = BufferUtils.createIntBuffer(1);

    private String[] vertexShaderSource = {
        "#version 450 core\n",
        "\n",
        "layout (location = 0) in vec3 position;\n",
        "\n",
        "uniform mat4 u_MVP;\n",
        "\n",
        "void main() {\n",
        "   gl_Position = u_MVP * vec4(position, 1);\n",
        "}\n"
    };

    private String[] fragmentShaderSource = {
        "#version 450 core\n",
        "\n",
        "layout (location = 0) out vec4 colour;\n",
        "layout (location = 1) uniform vec4 u_Colour;\n",
        "\n",
        "void main() {\n",
        "   colour = u_Colour;\n",
        "}"
    };

    public TestPlayer(Vector3f position, Vector3f lookat) {
        this.updatePositions();

        this.shaderProgram = this.createShader();
        glUseProgram(this.shaderProgram);
        matLocation = glGetUniformLocation(this.shaderProgram, "u_MVP");
        glUseProgram(0);
        glBindVertexArray(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    private void updatePositions() {
        if(vertexArray.hasRemaining()) {
            glDeleteVertexArrays(this.vertexArray);
            vertexArray.clear();
        }
        if(indexBuffer.hasRemaining()) {
            glDeleteBuffers(this.indexBuffer);
            indexBuffer.clear();
        }
        if(vertexBuffer.hasRemaining()) {
            glDeleteBuffers(this.vertexBuffer);
            vertexBuffer.clear();
        }

        float[] positions = {
            x-size, y-size,
            x     , y+size,
            x+size, y-size
        };

        int[] indices = {
            0, 1, 2
        };

        glGenVertexArrays(this.vertexArray);
        FloatBuffer vertexData = BufferUtils.createFloatBuffer(3 * 2);
        vertexData.put(positions);
        vertexData.flip();
        glGenBuffers(this.vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
        glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW);

        glBindVertexArray(this.vertexArray.get(0));
        glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, false, Float.BYTES * 2, 0);

        IntBuffer indexData = BufferUtils.createIntBuffer(3);
        indexData.put(indices);
        indexData.flip();

        glGenBuffers(this.indexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData, GL_STATIC_DRAW);
    }
    
    private Vector3f position = new Vector3f(x, y, 0);
    private Vector3f direction = new Vector3f(0, 0, -1);
    private Vector3f up = new Vector3f(0, 1, 0);
    
    private Vector3f tmp = new Vector3f();
    
    private Matrix4f projection = new Matrix4f();
    public static Matrix4f view = new Matrix4f();
    private Matrix4f model = new Matrix4f().translate(position);
    private Matrix4f modelView = new Matrix4f();
    
    private FloatBuffer buffer = BufferUtils.createFloatBuffer(4 * 4);
    
    public void update() {
        Matrix4f tmpM = new Matrix4f().identity();
        projection.setOrtho2D(-16.f, 16.f, -9.f, 9.f);
        view.setLookAt(position, tmp.set(position).add(direction), up);
        view.mul(model, tmpM);
        projection.mul(tmpM, modelView);
    }
    
    public void keyUpdate(int key) {
        if(key == GLFW.GLFW_KEY_W)
            position.sub(0, 1, 0);
        if(key == GLFW.GLFW_KEY_S)
            position.add(0, 1, 0);
        if(key == GLFW.GLFW_KEY_A)
            position.add(1, 0, 0);
        if(key == GLFW.GLFW_KEY_D)
            position.sub(1, 0, 0);
    }

    public void render(double currentTime) {
        this.updatePositions();
        glUseProgram(this.shaderProgram);
        glBindVertexArray(this.vertexArray.get(0));
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));

        // colour
        glUniform4f(1, 0.f, .8f, 1.f, 1.f);
        
        glUniformMatrix4fv(matLocation, false, modelView.get(buffer));
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
    }

    public void dispose() {
        glBindVertexArray(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glDeleteVertexArrays(this.vertexArray);
        glDeleteBuffers(this.indexBuffer);
        glDeleteBuffers(this.vertexBuffer);

        glUseProgram(0);
        glDeleteProgram(this.shaderProgram);
    }

    private int createShader() {
        int program = glCreateProgram();
        int vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, vertexShaderSource);
        glCompileShader(vertexShader);

        if(glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE) {
            System.err.println("ERROR: Compiling vertex shader");
            System.err.println(glGetShaderInfoLog(vertexShader));
            glDeleteShader(vertexShader);
            return -1;
        }

        int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, fragmentShaderSource);
        glCompileShader(fragmentShader);

        if(glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE) {
            System.err.println("ERROR: Compiling fragment shader");
            System.err.println(glGetShaderInfoLog(fragmentShader));
            glDeleteShader(fragmentShader);
            return -1;
        }

        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);

        glValidateProgram(program);

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        return program;
    }
}


Static object:
public class TestPlayer2 {

    private int x = 3, y = 3;
    private float size = 1.f;

    private int shaderProgram = 0;
    private IntBuffer vertexArray = BufferUtils.createIntBuffer(1);
    private IntBuffer vertexBuffer = BufferUtils.createIntBuffer(1);
    private IntBuffer indexBuffer = BufferUtils.createIntBuffer(1);

    private int matLocation = 0;
    private Vector3f position = new Vector3f(x, y, 0);
    private Vector3f direction = new Vector3f(0, 0, -1);
    private Vector3f up = new Vector3f(0, 1, 0);
    
    private Vector3f tmp = new Vector3f();
    
    private Matrix4f projection = new Matrix4f();
    public static Matrix4f view = new Matrix4f();
    private Matrix4f model = new Matrix4f().translate(position);
    private Matrix4f modelView = new Matrix4f();
    
    private FloatBuffer buffer = BufferUtils.createFloatBuffer(4 * 4);
    
    private String[] vertexShaderSource = {
        "#version 450 core\n",
        "\n",
        "layout (location = 0) in vec4 position;\n",
        "\n",
        "uniform mat4 u_MVP;\n",
        "\n",
        "void main() {\n",
        "   gl_Position = u_MVP * position;\n",
        "}\n"
    };

    private String[] fragmentShaderSource = {
        "#version 450 core\n",
        "\n",
        "layout (location = 0) out vec4 colour;\n",
        "layout (location = 1) uniform vec4 u_Colour;\n",
        "\n",
        "void main() {\n",
        "   colour = u_Colour;\n",
        "}"
    };
    
    public TestPlayer2() {
        float[] positions = {
            x-size, y-size,
            x     , y+size,
            x+size, y-size
        };

        int[] indices = {
            0, 1, 2
        };

        glGenVertexArrays(this.vertexArray);
        FloatBuffer vertexData = BufferUtils.createFloatBuffer(3 * 2);
        vertexData.put(positions);
        vertexData.flip();
        glGenBuffers(this.vertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
        glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW);

        glBindVertexArray(this.vertexArray.get(0));
        glBindBuffer(GL_ARRAY_BUFFER, this.vertexBuffer.get(0));
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, false, Float.BYTES * 2, 0);

        IntBuffer indexData = BufferUtils.createIntBuffer(3);
        indexData.put(indices);
        indexData.flip();

        glGenBuffers(this.indexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData, GL_STATIC_DRAW);
        
        this.shaderProgram = this.createShader();
        glUseProgram(this.shaderProgram);
        matLocation = glGetUniformLocation(this.shaderProgram, "u_MVP");
        glUseProgram(0);
        
        glBindVertexArray(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    private int createShader() {
        int program = glCreateProgram();
        int vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, vertexShaderSource);
        glCompileShader(vertexShader);

        if(glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE) {
            System.err.println("ERROR: Compiling vertex shader");
            System.err.println(glGetShaderInfoLog(vertexShader));
            glDeleteShader(vertexShader);
            return -1;
        }

        int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, fragmentShaderSource);
        glCompileShader(fragmentShader);

        if(glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE) {
            System.err.println("ERROR: Compiling fragment shader");
            System.err.println(glGetShaderInfoLog(fragmentShader));
            glDeleteShader(fragmentShader);
            return -1;
        }

        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);

        glValidateProgram(program);

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        return program;
    }
    
    public void update() {
        // Nothing in here, static entity
        
        Matrix4f tmpM = new Matrix4f().identity();
        projection.setOrtho2D(-16.f, 16.f, -9.f, 9.f);
        view.setLookAt(position, tmp.set(position).add(direction), up);
        view.mul(model, tmpM);
        projection.mul(tmpM, modelView);
    }

    public void render(double currentTime) {
        glUseProgram(this.shaderProgram);
        glBindVertexArray(this.vertexArray.get(0));
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer.get(0));

        glUniform4f(1, 8.f, .8f, 1.f, 1.f);
        glUniformMatrix4fv(matLocation, false, modelView.get(buffer));
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
    }

    public void dispose() {
        glBindVertexArray(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glDeleteVertexArrays(this.vertexArray);
        glDeleteBuffers(this.indexBuffer);
        glDeleteBuffers(this.vertexBuffer);

        glUseProgram(0);
        glDeleteProgram(this.shaderProgram);
    }
}


Main class:
public class HelloWorld {

    // The window handle
    private long window;

    private TestPlayer player;
    private TestPlayer2 player2;

    private Callback glErrorCallback;

    private static final IntBuffer SCREEN_WIDTH = BufferUtils.createIntBuffer(1);
    private static final IntBuffer SCREEN_HEIGHT = BufferUtils.createIntBuffer(1);

    public void run() {
        // Output in my case: Hello LWJGL 3.2.0 build 12!
        System.out.println("Hello LWJGL " + Version.getVersion() + "!");

        init();
        loop();

        if(glErrorCallback != null)
            this.glErrorCallback.free();

        // Free the window callbacks and destroy the window
        glfwFreeCallbacks(window);
        glfwDestroyWindow(window);

        GL.setCapabilities(null);

        // Terminate GLFW and free the error callback
        glfwTerminate();
        glfwSetErrorCallback(null).free();
    }

    private void init() {
        GLFWErrorCallback.createPrint(System.err).set();
        if ( !glfwInit() )
            throw new IllegalStateException("Unable to initialize GLFW");

        // Configure GLFW
        glfwDefaultWindowHints();
        glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

        glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);

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

        glfwSetKeyCallback(window, (window, key, scancode, action, mods) -> {
            if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
                glfwSetWindowShouldClose(window, true);

            if ( action == GLFW.GLFW_PRESS)
                if(player != null)
                    player.keyUpdate(key);
        });

        // Get the thread stack and push a new frame
        try ( MemoryStack stack = stackPush() ) {
            IntBuffer pWidth = stack.mallocInt(1); // int*
            IntBuffer pHeight = stack.mallocInt(1); // int*

            // Get the window size passed to glfwCreateWindow
            glfwGetWindowSize(window, pWidth, pHeight);

            // Get the resolution of the primary monitor
            GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());

            // Center the window
            glfwSetWindowPos(
                window,
                (vidmode.width() - pWidth.get(0)) / 2,
                (vidmode.height() - pHeight.get(0)) / 2
            );
        } // the stack frame is popped automatically

        glfwMakeContextCurrent(window);
        // Enable v-sync
        glfwSwapInterval(1);

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

    private void loop() {
        GL.createCapabilities();

        glfwGetFramebufferSize(this.window, SCREEN_WIDTH, SCREEN_HEIGHT);
        glViewport(0, 0, SCREEN_WIDTH.get(), SCREEN_HEIGHT.get());

        this.glErrorCallback = GLUtil.setupDebugMessageCallback();
        // Set the clear color
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);


        player = new TestPlayer(new Vector3f(0, 0, 0), new Vector3f(0, 0, 0));
        player2 = new TestPlayer2();

        // Run the rendering loop until the user has attempted to close
        // the window or has pressed the ESCAPE key.
        while ( !glfwWindowShouldClose(window) ) {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer

            // normally, update would not be called as often as render
            player.update();
            player2.update();
            player2.render(glfwGetTime());

            // the player with the camera
            player.render(glfwGetTime());

            glfwSwapBuffers(window); // swap the color buffers

            // Poll for window events. The key callback above will only be
            // invoked during this call.
            glfwPollEvents();
        }
        player.dispose();
        player2.dispose();
    }

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


Thx in advance.
ShadowDragon