TheFearlessHobbit

08-10-2017, 08:52 PM

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 << "\n";

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 << "\n";

}

}

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 << "\n";

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 << "\n";

}

}