Search code examples
c++openglglfwglm-mathisometric

how to detect mouse event in opengl isometric scene (from learnopengl.com)


I'm going through learnopengl.com and trying to make a 3d isometric scene.

I've got it working quite nicely so far.

But something not in the learnopengl course is how to detect mouse clicks in 3d space.

I'm using instanced drawing using a vector of mat4 for drawing each cube of the world.

What I would like to do next is click on each cube and modify it (for now just change the z position).

I've found some articles on using glReadPixels but because of my isometric projection, the x &/or y co-ordinates don't seem correct.

Also, since i'm using instanced drawing, i'm not sure how i will figure out which instance in the vector was clicked on.

My code is on git hub here.

And here is the main class I'm using for drawing the cubes:

#include "worldTile.h"

worldTile::worldTile() : drawable("instance.shader.vs", "instance.shader.fs")
{
    drawType = GL_STATIC_DRAW;

    baseModel = glm::mat4(1.0f);
    baseModel = glm::rotate(baseModel, glm::radians(-45.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    baseModel = glm::rotate(baseModel, glm::radians(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
    baseModel = glm::rotate(baseModel, glm::radians(-45.0f), glm::vec3(0.0f, 0.0f, 1.0f));
}

worldTile::~worldTile()
{
    cleanup();
}

void worldTile::cleanup()
{

}

void worldTile::setup()
{
    float blockSize = 0.81f;
    int worldSize = 2; 
    // 50 gives 15-16fps using drawElements
    // 50 gives 480fps using drawElementsInstanced
    // 50 gives 380fps using matrix drawElementsInstanced 

    for (int i = 0; i < worldSize; i++)
    {
        for (int j = 0; j < worldSize; j++)
        {
            float xPos = i * blockSize;
            float yPos = j * blockSize;

            glm::mat4 model = baseModel;
            model = glm::translate(baseModel, glm::vec3(xPos, yPos, 0.0f));
            modelMatrices.push_back(model);

            if (i != 0)
            {
                model = glm::translate(baseModel, glm::vec3(-xPos, yPos, 0.0f));
                modelMatrices.push_back(model);
            }
            if (j != 0)
            {
                model = glm::translate(baseModel, glm::vec3(xPos, -yPos, 0.0f));
                modelMatrices.push_back(model);
            }
            if (i != 0 && j != 0)
            {
                model = glm::translate(baseModel, glm::vec3(-xPos, -yPos, 0.0f));
                modelMatrices.push_back(model);
            }
        }
    }

    //modelMatrices[0] = glm::translate(modelMatrices[0], glm::vec3(0, 0, 0.2f));

    colors = {
        glm::vec3(0.55f, 0.71f, 0.29f), // 0 top green
        glm::vec3(0.49f, 0.64f, 0.26f), // 1 front green
        glm::vec3(0.45f, 0.59f, 0.24f), // 2 side green
        glm::vec3(0.68f, 0.51f, 0.34f), // 3 brown
        glm::vec3(0.73f, 0.55f, 0.36f), // 4 front brown
        glm::vec3(0.62f, 0.47f, 0.35f), // 5 side brown
    };

    vertices = {
    // top face
    -0.4f, -0.4f,  0.1f,     //  0
     0.4f, -0.4f,  0.1f,
     0.4f,  0.4f,  0.1f,
    -0.4f,  0.4f,  0.1f,

    // bottom face
    -0.4f, -0.4f, -0.1f,     //  4
     0.4f, -0.4f, -0.1f,
     0.4f,  0.4f, -0.1f,
    -0.4f,  0.4f, -0.1f,

    // left face - bottom
    -0.4f,  0.4f,  0.0f,     //  8
    -0.4f,  0.4f, -0.1f,
    -0.4f, -0.4f, -0.1f,
    -0.4f, -0.4f,  0.0f,

    // left face - top
    -0.4f,  0.4f,  0.1f,     //  12
    -0.4f,  0.4f, -0.0f,
    -0.4f, -0.4f, -0.0f,
    -0.4f, -0.4f,  0.1f,

    // right face - bottom
     0.4f,  0.4f,  0.0f,     //  16
     0.4f,  0.4f, -0.1f,
     0.4f, -0.4f, -0.1f,
     0.4f, -0.4f,  0.0f,

     // right face - top
     0.4f,  0.4f,  0.1f,     //  20
     0.4f,  0.4f, -0.0f,
     0.4f, -0.4f, -0.0f,
     0.4f, -0.4f,  0.1f,

     // left side face - bottom
    -0.4f, -0.4f, -0.1f,     //  24
     0.4f, -0.4f, -0.1f,
     0.4f, -0.4f,  0.0f,
    -0.4f, -0.4f,  0.0f,

    // left side face - top
    -0.4f, -0.4f, -0.0f,     //  28
     0.4f, -0.4f, -0.0f,
     0.4f, -0.4f,  0.1f,
    -0.4f, -0.4f,  0.1f,

    // back face - bottom
    -0.4f,  0.4f, -0.1f,     // 32
     0.4f,  0.4f, -0.1f,
     0.4f,  0.4f,  0.0f,
    -0.4f,  0.4f,  0.0f,

    // back face - top
    -0.4f,  0.4f, -0.0f,     //  36
     0.4f,  0.4f, -0.0f,
     0.4f,  0.4f,  0.1f,
    -0.4f,  0.4f,  0.1f,
    };
    indices = {  // note that we start from 0!
        0, 1, 3,  // first Triangle
        1, 2, 3,   // second Triangle

        4, 5, 7,
        5, 6, 7,

        8,9,11,
        9,10,11,

        12,13,15,
        13,14,15,

        16,17,19,
        17,18,19,

        20,21,23,
        21,22,23,

        24,25,27,
        25,26,27,

        28,29,31,
        29,30,31,

        32,33,35,
        33,34,35,

        36,37,39,
        37,38,39,
    };

    std::cout << "world title draw type" << std::endl;

    glGenVertexArrays(1, VAO);

    glGenBuffers(1, VBO);
    glGenBuffers(1, EBO); // for index drawing

    glGenBuffers(1, &matrixBuffer);

    reloadBuffers();

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glEnableVertexAttribArray(2);
    glEnableVertexAttribArray(3);
    glEnableVertexAttribArray(4);
    glEnableVertexAttribArray(5);


    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

    // color attribute
    //glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(3 * sizeof(float)));

    // matrix attribute
    // set attribute pointers for matrix (4 times vec4)
    glBindBuffer(GL_ARRAY_BUFFER, matrixBuffer);
    glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(1 * sizeof(glm::vec4)));
    glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
    glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));

    glVertexAttribDivisor(2, 1);
    glVertexAttribDivisor(3, 1);
    glVertexAttribDivisor(4, 1);
    glVertexAttribDivisor(5, 1);

    glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind

    ourShader.use();
    ourShader.setVec3("colors", 6, colors.data());
}

bool mouseDown = false;
glm::mat4 aProjection = glm::mat4(1.0f);
glm::mat4 aView = glm::mat4(1.0f);
void worldTile::processInput(GLFWwindow* window)
{
    if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_RELEASE && mouseDown == true)
    {
        std::cout << "Mouse button released" << std::endl;
        mouseDown = false;
    }

    if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS && mouseDown == false)
    {
        double xpos, ypos;
        glfwGetCursorPos(window, &xpos, &ypos);

        //ypos = 600 - ypos;

        mouseDown = true;

        unsigned char pixeldata[4];
        GLfloat depth;
        // reading pixel data at current cursor position ...
        glReadBuffer(GL_COLOR_ATTACHMENT1); // read from second framebuffer layer
        glReadPixels(xpos, 600 - ypos, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
        glReadPixels(xpos, 600 - ypos, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);

        glm::vec4 viewport = glm::vec4(0, 0, 800, 600);
        glm::vec3 wincoord = glm::vec3(xpos, 600 - ypos, depth);

        glm::mat4 baseModel = glm::mat4(1.0f);
        baseModel = glm::rotate(baseModel, glm::radians(-45.0f), glm::vec3(1.0f, 0.0f, 0.0f));

        glm::vec3 objcoord = glm::unProject(wincoord, baseModel, aProjection, viewport);

        printf("Coordinates in object space: %f, %f, %f\n", objcoord.x, objcoord.y, objcoord.z);

        int modelIndex = 0;
        // TODO: find out which model from modelMatrices was clicked on
        modelMatrices[0] = glm::translate(modelMatrices[0], glm::vec3(0, 0, 0.2f));

        glBindBuffer(GL_ARRAY_BUFFER, matrixBuffer);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(glm::mat4), &modelMatrices.data()[0]);
        //glBufferData(GL_ARRAY_BUFFER, modelMatrices.size() * sizeof(glm::mat4), &modelMatrices.data()[0], drawType);

        // convert pixel color back to (int)ID ...
        //unsigned int m_trackedID = (pixeldata[0] << 0) | (pixeldata[1] << 8) | (pixeldata[2] << 16) | (pixeldata[3] << 24);
        // ------------
        //std::cout << "Mouse button pressed: " << xpos << "-" << ypos << " - " << depth << " - " << m_trackedID << std::endl;
    }

    if (isPressed(window, GLFW_KEY_Z))
    {
        for (int i = 0; i < 40; i++)
        {
            int index = i * 4;
            vertices[index] -= 0.01f;

            glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
            glBufferSubData(GL_ARRAY_BUFFER, index * sizeof(float), sizeof(float), &vertices[index]);
        }
    }
    else if (isPressed(window, GLFW_KEY_X))
    {
        for (int i = 0; i < 40; i++)
        {
            int index = i * 4;
            vertices[index] += 0.01f;

            glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
            glBufferSubData(GL_ARRAY_BUFFER, index * sizeof(float), sizeof(float), &vertices[index]);
        }
    }
}

void worldTile::draw(glm::mat4& currentModel, glm::mat4& currentProjection, glm::mat4& currentView)
{
    ourShader.use();
    ourShader.setMat4("projection", currentProjection);
    ourShader.setMat4("view", currentView);

    aProjection = currentProjection;
    aView = currentView;

    glBindVertexArray(VAO[0]);
    glDrawElementsInstanced(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0, modelMatrices.size());
}

void worldTile::reloadBuffers()
{
    glBindVertexArray(VAO[0]);

    // load vertex buffers
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), drawType);

    // load index buffers
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), drawType);

    // load matrix buffer
    glBindBuffer(GL_ARRAY_BUFFER, matrixBuffer);
    glBufferData(GL_ARRAY_BUFFER, modelMatrices.size() * sizeof(glm::mat4), &modelMatrices.data()[0], drawType);
}

Any help is much appreciated.


Solution

  • I've figured it out.

    I have to do to opposite of any translations and rotations i've done on my scene and models when calling unproject.

    So i have the following as the model being passed into unproject:

    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(0, 0, zoffset));
    model = glm::rotate(model, glm::radians(-45.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    model = glm::rotate(model, glm::radians(-45.0f), glm::vec3(0.0f, 0.0f, 1.0f));
    
    glm::vec3 objcoord = glm::unProject(wincoord, model, aProjection, viewport);
    

    I've also had to pass the scene projection, and zoffset (for zooming) to my model.

    I also have an extra vector which keeps track of all my object's world positions so after i call unproject, I can use the calculated work coordinates to figure out which cube i clicked on. It's a bit of extra work, but it works. I can clean it up to use a clever algorithm or something to figure out which cube was selected.

    But this is the basics of what i've done.