I am trying to rotate a mesh about its origin using a standard arcball rotation. Whenever I click on the 3D object, I cast a ray from the mouse to find the intersection point. I measure the distance of that intersection point from the 3D object origin to create a sphere that will be used as the "arcball" in that rotation (until the mouse is released). The reason I do this at the start of every rotation is because I want the clicked point to remain under the mouse. The object rotates correctly, its just that the magnitude of the rotation is smaller and as a result the clicked point does not remain under the mouse. The following example is trying to rotate a cube. I paint a black rectangle on the texture to keep track of the initially clicked point. Here is a video of the problem:
This is the function that gets the arcball vector based on the mouse position and the radius of the sphere (arcballRadius) calculated on click (I am worried that this function does not consider the location of the cube object, although it so happens that the cube object is located at (0,0,z):
/**
* Get a normalized vector from the center of the virtual ball O to a
* point P on the virtual ball surface, such that P is aligned on
* screen's (X,Y) coordinates. If (X,Y) is too far away from the
* sphere, return the nearest point on the virtual ball surface.
*/
glm::vec3 get_arcball_vector(double x, double y) {
glm::vec3 P = glm::vec3(x,y,0);
float OP_squared = P.x * P.x + P.y * P.y;
if (OP_squared <= arcballRadius*arcballRadius)
P.z = sqrt(arcballRadius*arcballRadius - OP_squared); // Pythagore
else
{
static int i;
std::cout << i++ << "Nearest point" << std::endl;
P = glm::normalize(P); // nearest point
}
return P;
}
Whenever the mouse is moved
//get two vectors, one for the previous point and one for the current point
glm::vec3 va = glm::normalize(get_arcball_vector(prevMousePos.x, prevMousePos.y)); //previous point
glm::vec3 vb = glm::normalize(get_arcball_vector(mousePos.x, mousePos.y)); //current point
float angle = acos(glm::dot(va, vb)); //angle between those two vectors based on dot product
//since these axes are in camera coordinates they must be converted before applied to the object
glm::vec3 axis_in_camera_coord = glm::cross(va, vb);
glm::mat3 camera2object = glm::inverse(glm::mat3(viewMatrix) * glm::mat3(cube.modelMatrix));
glm::vec3 axis_in_object_coord = camera2object * axis_in_camera_coord;
//apply rotation to cube's matrix
cube.modelMatrix = glm::rotate(cube.modelMatrix, angle, axis_in_object_coord);
How can I make the clicked point remain under the mouse?
Set the arcball radius to the distance between the point clicked and the center of the object. In other words the first point is a raycast on the cube and the subsequent points will be raycasts on an imaginary sphere centered on the object and with the above mentioned radius.
P.S.: Check out my arcball rotation code on codereview.SE. It doesn't mess with acos and axis-angle and only normalizes twice.