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:
- What is the correct algorithm to calculate the correct distance in world space?
- Are there any suggestions on articles/documentations to give a better basic and in depth understanding of 3D space calculations?