Hello, I know this may not be an OpenGL question, though I assume several users on this forum did mouse picking in their graphics applications before and I was hoping I could get some help regarding the issue I am having with my 3D mouse picking.
I have a basic scene and I am trying to pick a mesh (a triangle in my case) and move it around with my mouse. So far I have been able to implement that correctly but I can’t seem to be able to pick the object from far away. I’d have to move the camera closer to the triangle in order for it to intersect with my ray. I will include code that is relevant to the mechanic below in case I’m doing something wrong with my calculation, thank you for reading my thread.
EDIT: Essentially what I’m trying to do is simply make it so that you can pick the object up and move it around from a reasonable far distance, instead of having to move really close to it first in order to pick it up.
Get the ray direction
// Function that takes mouse position on screen and return ray in world coords
glm::vec3 PhysicsEngine::GetRayFromMouse()
{
glm::vec2 ray_nds = glm::vec2(mouseX, mouseY);
glm::vec4 ray_clip = glm::vec4(ray_nds.x, ray_nds.y, -1.0f, 1.0f);
glm::mat4 invProjMat = glm::inverse(m_Camera.GetProjectionMatrix());
glm::vec4 eyeCoords = invProjMat * ray_clip;
eyeCoords = glm::vec4(eyeCoords.x, eyeCoords.y, -1.0f, 0.0f);
glm::mat4 invViewMat = glm::inverse(m_Camera.ViewMatrix());
glm::vec4 rayWorld = invViewMat * eyeCoords;
glm::vec3 rayDirection = glm::normalize(glm::vec3(rayWorld));
return rayDirection;
}
Check for ray-sphere collision
// Function that checks for ray-sphere intersection and returns true or false
bool PhysicsEngine::ray_sphere(vec3 ray_origin_wor, vec3 ray_direction_wor, float sphere_radius)
{
vec3 v = glm::vec3(m_Transformation.GetPos().x, m_Transformation.GetPos().y, 0.5f) - m_Camera.GetCameraPosition();
float a = glm::dot(ray_direction_wor, ray_direction_wor);
float b = 2 * glm::dot(v, ray_direction_wor);
float c = glm::dot(v, v) - sphere_radius * sphere_radius;
float b_squared_minus_4ac = b * b - 4 * a * c;
if (b_squared_minus_4ac > 0)
{
float x1 = (-b - sqrt(b_squared_minus_4ac)) / 2.0f;
float x2 = (-b + sqrt(b_squared_minus_4ac)) / 2.0f;
if (x1 >= 0.0f && x2 >= 0.0f)
return true;
if (x1 < 0.0f && x2 >= 0.0f)
return true;
}
return false;
}
Utilizing the mechanic
case SDL_MOUSEMOTION:
{
// Check if user is not picking the triangle
if (!bReplacingTriangle)
{
// If so update camera view vector upon mouse movement (to make it fps-like)
m_Camera.MouseUpdate(glm::vec2(_event.motion.x, _event.motion.y));
// Check if mouse cursor is out of bounds, if so snap it back to the center of the screen
if (_event.motion.x > WIDTH - 10)
SDL_WarpMouseInWindow(m_MainWindow, WIDTH / 2, HEIGHT / 2);
if (_event.motion.x < 10)
SDL_WarpMouseInWindow(m_MainWindow, WIDTH / 2, HEIGHT / 2);
if (_event.motion.y < 5)
SDL_WarpMouseInWindow(m_MainWindow, WIDTH / 2, HEIGHT / 2);
if (_event.motion.y > HEIGHT - 10)
SDL_WarpMouseInWindow(m_MainWindow, WIDTH / 2, HEIGHT / 2);
}
// Normalised Device Coordinates
mouseX = (2.0f * _event.motion.x) / WIDTH - 1.0f;
mouseY = 1.0f - (2.0f * _event.motion.y) / HEIGHT;
// Check if user is picking triangle, if so set its position to mouseX and mouseY
if (bReplacingTriangle)
m_Transformation.SetPos(vec3(mouseX, mouseY, 0.0f)); // Note that m_Transformation is the transformation matrix for the triangle
break;
}
case SDL_MOUSEBUTTONDOWN:
{
// Get ray direction
glm::vec3 rayDirection = GetRayFromMouse();
// If the user has the triangle picked up already, then register its new location and turn off picking
if (bReplacingTriangle)
{
std::cout << "Placed Triangle at new X Coord: " << mouseX << ", Y Coord: " << mouseY << "
";
m_Transformation.SetPos(vec3(mouseX, mouseY, 0.0f));
bReplacingTriangle = false;
}
// Check for intersection with sphere and enable picking if collision occurs
if (ray_sphere(m_Camera.GetCameraPosition(), rayDirection, 0.5f))
{
bReplacingTriangle = true;
std::cout << "Picked Triangle at location X Coord: " << mouseX << ", Y Coord: " << mouseY << "
";
}
}