Search code examples
c++openglcursorglfwglm-math

Get cursor position on Z = 0 plane using glm::unproject()?


I'm trying to get the coordinates (x,y) of the grid (z = 0) using only the cursor coordinates. After a long search I found this way to do that using the glm::unproject.

First I'm getting the cursor coordinates using the callback:

void cursorCallback(GLFWwindow *window, double x, double y)
{
    this->cursorCoordinate = glm::vec3(x, (this->windowHeight - y - 1.0f), 0.0f);
}

an then converting these coordinates:

glm::vec3 cursorCoordinatesToWorldCoordinates()
{
            glm::vec3 pointInitial = glm::unProject(
                                              glm::vec3(this->cursorCoordinate.x, this->cursorCoordinate.y, 0.0),
                                              this->modelMatrix * this->viewMatrix,
                                              this->projectionMatrix,
                                              this->viewPort
                                              );

            glm::vec3 pointFinal = glm::unProject(
                                              glm::vec3(this->cursorCoordinate.x, this->cursorCoordinate.y, 1.0),
                                              this->modelMatrix * this->viewMatrix,
                                              this->projectionMatrix,
                                              this->viewPort
                                              );

            glm::vec3 vectorDirector = pointFinal - pointInitial;

            double lambda = (-pointInitial.y) / vectorDirector.y;

            double x = pointInitial.x + lambda * vectorDirector.x;
            double y = pointInitial.z + lambda * vectorDirector.z;

            return glm::vec3(x, y, 0.0f);
}

I use an ArcBall camera to rotate the world around specified axis, so that is how I generate the MVP matrixes:

this->position = glm::vec3(0.0f, 10.0f, 5.0f);
this->up = glm::vec3(0.0f, 1.0f, 0.0f);
this->lookAt = glm::vec3(0.0f, 0.0f, 0.0f);

this->fieldView = 99.0f;
this->farDistance = 100.0f;
this->nearDistance = 0.1f;

this->modelMatrix      = glm::mat4(1.0f);
this->viewMatrix       = glm::lookAt(this->position, this->lookAt, this->up) * glm::rotate(glm::degrees(this->rotationAngle) * this->dragSpeed, this->rotationAxis);
this->projectionMatrix = glm::perspective(glm::radians(this->fieldView), 1.0f, this->nearDistance, this->farDistance);

But something is going wrong because I'm not getting the right results. Look this print of the application:

enter image description here

each square is 1 unit, the cube is rendered at position (0, 0, 0). With rotationAngle = 0 when a put the cursor at (0,0), (1,1), (2,2), (3,3), (4,4), (5,5) I get (0, 5.7), (0.8, 6.4), (1.6, 6.9), (2.4, 7.6), (3.2, 8.2), (4.2, 8.8) respectivally. That's not expected.

  • Why y is delayed by 6 units?
  • It's necessary rotate the result cursorCoordinatesToWorldCoordinates based on rotationAngle isn't?

--

That I already did:

  • Checked if the viewport match with glViewport - OK
  • Checked the opengl coordinates (Y is up, not Z) - OK

Solution

  • You want to intersect the ray from glm::vec3(this->cursorCoordinate.x, this->cursorCoordinate.y, 0.0) to glm::vec3(this->cursorCoordinate.x, this->cursorCoordinate.y, 1.0) with the grid in world space, rather than model space (of the cuboid). You've to skip this.modelMatrix:

    glm::vec3 pointInitial = glm::unProject(
        glm::vec3(this->cursorCoordinate.x, this->cursorCoordinate.y, 0.0),
        this->viewMatrix,
        this->projectionMatrix,
        this->viewPort);
    
    glm::vec3 pointFinal = glm::unProject(
        glm::vec3(this->cursorCoordinate.x, this->cursorCoordinate.y, 1.0),
        this->viewMatrix,
        this->projectionMatrix,
        this->viewPort);
    

    In any case this->modelMatrix * this->viewMatrix is incorrect. If you eant to intersect the ray with an object in model space, then it has to be this->viewMatrix * this->modelMatrix. Matrix multiplication is not Commutative.