Search code examples
c++openglcamera2dglm-math

OpenGL: Move 2D Orthographic Camera with Mouse


I'm making a level editor for my game with OpenGL in C++. I'm trying to make Editor Camera just like in Unity Engine 2D Scene Camera, but I have an issue when I try to implement mouse movement for the camera (Camera Panning). I'm converting mouse position from screen to world space.

ScreenToWorldSpace Method:

Vector3 Application::ScreenToWorldSpace(int mousex, int mousey)
{
    double x = 2.0 * mousex / viewportWidth - 1;
    double y = 2.0 * mousey / viewportHeight - 1;

    Vector4 screenPos = Vector4(x, -y, -1.0f, 1.0f);

    Matrix4 ProjectionViewMatrix = camera1->GetProjectionMatrix() * camera1->GetViewMatrix();

    Matrix4 InverseProjectionViewMatrix = glm::inverse(ProjectionViewMatrix);

    Vector4 worldPos = InverseProjectionViewMatrix * screenPos;

    return Vector3(worldPos);
}

The above method works correctly.

But I'm using ScreenToWorldSpace coordinates to update camera position.

Render Method:

void Application::Render(float deltaTime)
{
    Vector3 pos = ScreenToWorldSpace(mousePosition.x, mousePosition.y);

    // This is the position of a tile not the camera
    position = Vector3(0, 0, 0);
    Vector3 rotation = Vector3(0, 0, 0);
    Vector3 scale = Vector3(1);

    Matrix4 translationMatrix = glm::translate(Matrix4(1.0f), position);
    Matrix4 rotationMatrix = glm::eulerAngleYXZ(rotation.y, rotation.x, rotation.z);
    Matrix4 scaleMatrix = glm::scale(Matrix4(1.0f), scale);

    modelMatrix = translationMatrix * rotationMatrix * scaleMatrix;

    if (mouseButtonDown)
    {
        Console << pos.x << ", " << pos.y << Endl;
        camera1->position = Vector3(pos.x, pos.y, -10);
    }

    {
        glScissor(0, 0, 900, 600);
        glEnable(GL_SCISSOR_TEST);
        glClearColor(236 / 255.0f, 64 / 255.0f, 122 / 255.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glViewport(0, 0, 900, 600);

        basicShader->Use();
        dirt_grass_tex->Use();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        camera1->SetZoom(zoomFactor);
        camera1->Update();

        Matrix4 mvp = camera1->GetProjectionMatrix() * camera1->GetViewMatrix() * modelMatrix;

        basicShader->SetUniformMat4("MVP", mvp);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glDisable(GL_SCISSOR_TEST);
    }

}

Camera Class:

#include "camera.h"

Camera::Camera(int width, int height)
{
    swidth = width;
    sheight = height;
    position = Vector3(0, 0, -10);
    rotation = Vector3(0, 0, 0);

    m_direction = Vector3(0, 0, -5);
    m_up = Vector3(0, 1, 0);
    m_right = Vector3(1, 0, 0);

    m_offset = Vector3(-swidth / 2 * m_zoom, -sheight / 2 * m_zoom, 0);

    m_projection = glm::ortho(0.0f * m_zoom, (float)swidth * m_zoom, 0.0f * m_zoom, (float)sheight * m_zoom, -1000.0f, 0.0f);
}

Camera::~Camera()
{

}

void Camera::Update()
{

    Vector3 finalPos = position + m_offset;

    m_up = glm::cross(m_right, m_direction);

    m_viewMatrix = glm::lookAt(finalPos, finalPos + m_direction, m_up);

    m_viewMatrix = glm::scale(m_viewMatrix, Vector3(100));

}

void Camera::SetZoom(float zoom)
{
    m_zoom = zoom;
    m_offset = Vector3(-swidth / 2 * m_zoom, -sheight / 2 * m_zoom, 0);
    m_projection = glm::ortho(0.0f * m_zoom, (float)swidth * m_zoom, 0.0f * m_zoom, (float)sheight * m_zoom, -1000.0f, 0.0f);
}

The following is the output I get when I try to move camera with mouse position converted from Screen to World Space:

enter image description here

if (mouseButtonDown)
{
    Console << pos.x << ", " << pos.y << Endl;
    position = Vector3(pos.x, pos.y, 0);
}

But if I use mouse position converted from Screen to World space using ScreenToWorldSpace Method the object moves perfectly. Have a look at the following gif:

enter image description here

Following is what I'm trying to achieve: enter image description here

So I'm Trying to make Game Engine Editor, in that I want to implement Editor Scene Camera like unity / unreal engine scene camera. Following is the editor I'm currently working on:

enter image description here

I tried looking into different resources, but i'm clueless. Help me understand how to move the camera with mouse.

What I think is happening: Since I'm converting mouse position from screen to world space using camera's projectionView matrix and using those world coordinates to move camera position is causing the problem, because when ever camera moves, projectionView is updated which in turn changes mouse position relative to viewMatrix recursively.

I would Appreciate some help.


Solution

  • Ordinarily, you wouldn't want to write the mouse position directly into the camera location (because that will be of limited use in practice - whenever you click on the screen, the camera would jump).

    What you probably want to do something along these lines:

    Vector3 g_lastPosition;
    
    void onMousePressed(int x, int y) {
    
      // record starting position!
      g_lastPosition  = ScreenToWorldSpace(x, y);
    }
    
    void onMouseMove(int x, int y) {
    
      // find the difference between new position, and last, in world space
      Vector3 new_pos = ScreenToWorldSpace(x, y);
      Vector3 offset = new_pos - g_lastPosition;
      g_lastPosition = new_pos;
    
      // now move camera by offset
      camera->position += offset
    }
    

    If you are in an orthographic view, then really you don't need to worry about the projection matrix at all.

    int g_lastX;
    int g_lastY;
    
    void onMousePressed(int x, int y) {
    
       // store mouse pos
       g_lastX = x;
       g_lastY = y;
    }
    
    void onMouseMove(int x, int y) {
    
       // find the difference between new position, and last, in pixels
       int offsetX = x - g_lastX;
       int offsetY = y - g_lastY;
    
       // update mouse pos
       g_lastX = x;
       g_lastY = y;
    
       // get as ratio +/- 1
       float dx = ((float) offsetX) / swidth;
       float dy = ((float) offsetY) / sheight;
    
       // now move camera by offset (might need to multiply by 2 here?)
       camera->position.x += camera->m_offset.x * dx;
       camera->position.y += camera->m_offset.y * dy;
    }
    

    But in general, for any mouse based movement, you always want to be thinking in terms of adding an offset, rather than setting an exact position.