2
\$\begingroup\$

I'm trying to create a simple OpenGL app where I can drag objects with the mouse.

The code below is based on a raycasting algorithm covered here.

I'm using the diff between the current mouse coord and an anchor point. The anchor point is initially where the mouse was originally clicked, and then later the last current coord after the frame is rendered.

I'm missing the proper formula to use against the distance. I believe it has something to do with the viewport to world space frustum. As a result, the object being dragged either moves too fast or too slow.

// Hold mouse coords
struct mstate {
    double x = 0;
    double y = 0;
    double lastX = 0;
    double lastY = 0;
    double deltaX = 0;
    double deltaY = 0;
    bool pressed = FALSE;
    glm::vec3 worldAnchor;
    glm::vec3 worldAnchorTranslated;
} MouseState;

// Initialize OpenGL window/scene
Application::Application() {
    if (!glfwInit())
    {
        fprintf(stderr, "Failed to initialize GLFW\n");
    }

    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Map Editor", NULL, NULL);
    if (window == NULL)
    {
        fprintf(stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible.");
        glfwTerminate();
        return;
    }
    glfwMakeContextCurrent(window);

    glewExperimental = true;
    if (glewInit() != GLEW_OK)
    {
        fprintf(stderr, "Failed to initialize GLEW\n");
        return;
    }

    glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

    glClearColor(0.0f, 0.0f, 0.4f, 0.0f);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    //
    // Initialize GL program
    //
    //GLuint VertexArrayID;
    glGenVertexArrays(1, &GlobalVertexArrayID);
    glBindVertexArray(GlobalVertexArrayID);

    ProgramID = LoadShaders("SimpleVertexShader.vertexshader", "TextureFragmentShader.fragmentshader");
    GlobalProgramID = ProgramID;


    // Model Matrix stuff
    MatrixID = glGetUniformLocation(ProgramID, "MVP");

    Projection = glm::perspective(1.0f, 4.0f / 3.0f, 0.1f, 1500.0f);
    GlobalProjection = Projection;

    View = glm::lookAt(
        glm::vec3(0, 0, 500), // Camera location
        glm::vec3(0, 0, 0), // Where camera is looking
        glm::vec3(0, 1, 0) // Camera head is facing up
    );
    GlobalView = View;

    Model = glm::mat4(1.0f);
    GlobalModel = Model;

    MVP = Projection * View * Model; // Matrix multiplication

    TextureSampler = glGetUniformLocation(ProgramID, "myTextureSampler"); // Corresponds with fragment shader

    glfwSetMouseButtonCallback(window, buttonDownCallback);
    glfwSetCursorPosCallback(window, cursorPositionCallback);

    init_font();
}

// Handle mouse events, keep track of coords
void Application::buttonDownCallback(GLFWwindow* window, int button, int action, int mods)
{
    int state = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT);
    if (state == GLFW_PRESS)
    {
        MouseState.pressed = TRUE;

        MouseState.lastX = MouseState.x;
        MouseState.lastY = MouseState.y;

        OutputDebugString("Pressed");
    } else {
        MouseState.pressed = FALSE;
        MouseState.lastX = 0.0;
        MouseState.lastY = 0.0;

        OutputDebugString("Not-Pressed");
    }

    glm::vec3 pos = glm::vec3(MouseState.x, MouseState.y, 0.0f);

    vec3 un = Application::viewToWorldCoordTransform(MouseState.x, MouseState.y);

    MouseState.worldAnchor = glm::vec3(MouseState.x, MouseState.y, 1.0);
    MouseState.worldAnchorTranslated = glm::vec3(un[0], un[1], un[2]);  
}

// Convert cords from viewport to world view
vec3 Application::viewToWorldCoordTransform(int mouse_x, int mouse_y) {
    // NORMALISED DEVICE COORDINATES
    float x = (2.0f * mouse_x) / WINDOW_WIDTH - 1.0f;
    //Debugger::LogLn("Normalised X: " + std::to_string(x));

    float y = 1.0f - (2.0f * mouse_y / WINDOW_HEIGHT);
    float z = 1.0f;
    vec3 ray_nds = vec3(x, y, z);

    // HOMOGENEOUS CLIP COORDINATES
    vec4 ray_clip = vec4(ray_nds.x, ray_nds.y, -1.0, 1.0);
    //Debugger::LogLn("Homogeneous X: " + std::to_string(ray_nds.x));

    // EYE (CAMERA) COORDINATES
    vec4 ray_eye = inverse(GlobalProjection) * ray_clip;
    ray_eye = vec4(ray_eye.x, ray_eye.y, -1.0, 0.0);
    //Debugger::LogLn("Eye Coordinates: " + std::to_string(ray_eye.x));

    // WORLD COORDINATES
    vec4 ray_wor_temp = (inverse(GlobalView) * ray_eye);
    //Debugger::LogLn("ray temp x: " + std::to_string(ray_wor_temp.x));
    vec3 un = vec3(ray_wor_temp.x, ray_wor_temp.y, ray_wor_temp.z);
    un = normalize(un);
    //Debugger::LogLn("World Coordinates: " + std::to_string(un[0]));
    return un;
}

// Main loop, moves object according to mouse distance
void Application::loop() {
    glfwPollEvents();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(ProgramID);


    if (MouseState.pressed)
    {
        Debugger::LogLn("====================");

        vec3 delta_transformed1 = Application::viewToWorldCoordTransform(MouseState.lastX, MouseState.lastY);
        vec3 delta_transformed2 = Application::viewToWorldCoordTransform(MouseState.x, MouseState.y);
        glm::vec3 transformed_diff_vec = glm::vec3(delta_transformed1.x - delta_transformed2.x, 0.0, 0.0);

        Debugger::LogLn("Last X: " + std::to_string(MouseState.lastX));
        Debugger::LogLn("X: " + std::to_string(MouseState.x));
        Debugger::LogLn("Transformed Delta X: " + std::to_string(transformed_diff_vec.x));

        Tiles[0]._x -= transformed_diff_vec.x * 500; // <-- Uses camera distance. Close, too little distance
        //Tiles[0]._x -= MouseState.lastX - MouseState.x; // <-- Too much distance
        Debugger::LogLn("Tiles[0]._x: " + std::to_string(Tiles[0]._x));
    }

    Model = glm::translate(glm::mat4(1.0f), glm::vec3(Tiles[0]._x, Tiles[0]._y, 0.0));

    MVP = Projection * View * Model;
    glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

    Tiles[0].draw();
    Debugger::LogOpenGLError("Tile draw");
    glfwSwapBuffers(window);

    MouseState.worldAnchor = glm::vec3(MouseState.x, MouseState.y, 1.0);
    MouseState.worldAnchorTranslated = glm::vec3(0.0, 0.0, 1.0);
    MouseState.deltaX = 0;
    MouseState.deltaY = 0;
    MouseState.lastX = MouseState.x;
    MouseState.lastY = MouseState.y;
    loopNum++;
}

My questions are:

  1. What is the correct algorithm to calculate the correct distance in world space?
  2. Are there any suggestions on articles/documentations to give a better basic and in depth understanding of 3D space calculations?
\$\endgroup\$
2
  • \$\begingroup\$ Found a possible solution that was referred to me. Will try it out later this evening. antongerdelan.net/opengl/raycasting.html \$\endgroup\$
    – Shroder
    Oct 23, 2015 at 17:47
  • \$\begingroup\$ Just noticing my viewToWorldCoordTransform is wrong. Will update this post once I get it fixed. \$\endgroup\$
    – Shroder
    Feb 21, 2016 at 14:36

1 Answer 1

0
\$\begingroup\$

The problem I was running into was essentially of the view to matrix algorithm being incorrect.

Key Problems

1.) I was normalizing the return value:

un = normalize(un); // Removed from new code

2.) I was transforming view coords to screen coords as a point and not a vector:

// Old
vec4 ray_clip = vec4(ray_nds.x, ray_nds.y, -1.0, 1.0);

// New
glm::vec4 screenPos = glm::vec4(x, -y, -0.0f, 0.0f);

Code

Here's the key areas of code that were modified:

viewToWorldCoordTransform

glm::vec3 Application::viewToWorldCoordTransform(int mouse_x, int mouse_y) {
    double x = 2.0 * mouse_x / WINDOW_WIDTH - 1;
    double y = 2.0 * mouse_y / WINDOW_HEIGHT - 1;

    glm::mat4 ProjectView = GlobalProjection * GlobalView;
    glm::mat4 viewProjectionInverse = inverse(ProjectView);
    glm::vec4 screenPos = glm::vec4(x, -y, -0.0f, 0.0f);
    glm::vec4 worldPos = viewProjectionInverse * screenPos;

    return glm::vec3(worldPos);
}

Main Loop

void Application::loop() {
    glfwPollEvents();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(ProgramID);


    if (MouseState.pressed)
    {
        totalXMovement += MouseState.lastX - MouseState.x;

        vec3 delta_transformed1 = Application::viewToWorldCoordTransform(MouseState.lastX, MouseState.lastY);
        vec3 delta_transformed2 = Application::viewToWorldCoordTransform(MouseState.x, MouseState.y);

        glm::vec3 transformed_diff_vec = glm::vec3(delta_transformed1.x - delta_transformed2.x, 0.0, 0.0);

        Tiles[0]._x = static_cast<float>(delta_transformed2.x) *CameraDistance;
        Tiles[0]._y = static_cast<float>(delta_transformed2.y) *CameraDistance;
        Tiles[0]._z = static_cast<float>(delta_transformed2.z);// *CameraDistance;

        Model = glm::translate(glm::mat4(1.0f), glm::vec3(Tiles[0]._x, Tiles[0]._y, 0));
    }

    MVP = Projection * View * Model;
    glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

    Tiles[0].draw();
    glfwSwapBuffers(window);

    MouseState.worldAnchor = glm::vec3(MouseState.x, MouseState.y, 1.0);
    MouseState.worldAnchorTranslated = glm::vec3(0.0, 0.0, 1.0);
    MouseState.deltaX = 0;
    MouseState.deltaY = 0;
    MouseState.lastX = MouseState.x;
    MouseState.lastY = MouseState.y;
    loopNum++;
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .