4

I am using legacy OpenGL to draw a mesh. I am now trying to implement an arcball class to rotate the object with the mouse. However, when i move the mouse, the object either doesn't rotate or rotates by way too big an angle.

This is the method that is called when the mouse is clicked:

void ArcBall::startRotation(int xPos, int yPos) {
    int x = xPos - context->getWidth() / 2;
    int y = context->getHeight() / 2 - yPos;
    startVector = ArcBall::mapCoordinates(x, y).normalized();
    endVector = startVector;
    rotating = true;
}

This method is meant to simply map the mouse coordinates to be centered at the center of the screen and map them to the bounding sphere, resulting in a starting vector

This is the method that is called when the mouse moves:

void ArcBall::updateRotation(int xPos, int yPos) {
    int x = xPos - context->getWidth() / 2;
    int y = context->getHeight() / 2 - yPos;
    endVector = mapCoordinates(x, y).normalized();
    rotationAxis = QVector3D::crossProduct(endVector, startVector).normalized();
    angle  = (float)qRadiansToDegrees(acos(QVector3D::dotProduct(startVector, endVector)));
    rotation.rotate(angle, rotationAxis.x(), rotationAxis.y(), rotationAxis.z());
    startVector = endVector;
}

This method is again meant to map the mouse coordinates to be centered t the middle of the screen, then compute the new vector and compute a rotation axis and angle based on these two vectors.

I then use

glMultMatrixf(ArcBall::rotation.data());

to apply the rotation

Rabbid76
  • 202,892
  • 27
  • 131
  • 174

1 Answers1

2

I recommend to do store the mouse position at the point where you initially click in the view. Calculate the amount of the mouse movement in window coordinates. The distance of the movement has to be mapped to an angle. The rotation axis is perpendicular (normal) to the direction of the mouse movement. The result is a rotation of an object similar to this WebGL demo.

Store the current mouse position in startRotation. Note store the coordinates of the position mouse position not normalized vector:

// xy normalized device coordinates:
float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
float ndcY = 1.0 - 2.0f * yPos / context->getHeight();

startVector = QVector3D(ndcX, ndcY, 0.0);

Get the current position in updateRotation:

// xy normalized device coordinates:
float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
float ndcY = 1.0 - 2.0f * yPos / context->getHeight();

endVector = QVector3D(ndcX, ndcY, 0.0);

Calculate the vector from the start position to the end position:

QVector3D direction = endVector - startVector;

The rotation axis is normal to the direction of movement:

rotationAxis = QVector3D(-direction.y(), direction.x(), 0.0).normalized();

Note even if the type of direction is QVector3D, it is still a 2 dimensional vector. It is a vector in the XY plane of the viewport representing the mouse movement on the viewport. The z coordinate is 0. A 2 dimensional vector (x, y), can be 90 degrees counter clockwise rotated, by (-y, x).

The length of the direction vector represents tha angle of rotation. A mouse motion over the entire screen results in a vector with length 2.0. So if a dragging over the full screen should result in a full rotation, the length of the vector has to be multiplied by PI. If the a hlf rotation should be performed, then by PI/2:

angle = (float)qRadiansToDegrees(direction.length() * 3.141593);

Finally the new rotation has to be applied to the existing rotation and not to the model:

QMatrix4x4 addRotation;
addRotation.rotate(angle, rotationAxis.x(), rotationAxis.y(), rotationAxis.z());
rotation = addRotation * rotation; 

Final code listing of the methods startRotation and updateRotation:

void ArcBall::startRotation(int xPos, int yPos) {

    // xy normalized device coordinates:
    float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
    float ndcY = 1.0 - 2.0f * yPos / context->getHeight();

    startVector = QVector3D(ndcX, ndcY, 0.0);
    endVector   = startVector;
    rotating    = true;
}
void ArcBall::updateRotation(int xPos, int yPos) {

    // xy normalized device coordinates:
    float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
    float ndcY = 1.0 - 2.0f * yPos / context->getHeight();

    endVector = QVector3D(ndcX, ndcY, 0.0);

    QVector3D direction = endVector - startVector;
    rotationAxis        = QVector3D(-direction.y(), direction.x(), 0.0).normalized();
    angle               = (float)qRadiansToDegrees(direction.length() * 3.141593);

    QMatrix4x4 addRotation;
    addRotation.rotate(angle, rotationAxis.x(), rotationAxis.y(), rotationAxis.z());
    rotation = addRotation * rotation; 

    startVector = endVector;
}

If you want a rotation around the upwards axis of the object a tilting the object along the view space x axis, then the calculation is different. First apply the rotation matrix around the y axis (up vector) then the current view matrix and finally the rotation on the x axis:

view-matrix = rotate-X * view-matrix * rotate-Y

The function update rotation has to look like this:

void ArcBall::updateRotation(int xPos, int yPos) {

    // xy normalized device coordinates:
    float ndcX = 2.0f * xPos / context->getWidth() - 1.0f;
    float ndcY = 1.0 - 2.0f * yPos / context->getHeight();

    endVector = QVector3D(ndcX, ndcY, 0.0);

    QVector3D direction = endVector - startVector;

    float angleY = (float)qRadiansToDegrees(-direction.x() * 3.141593);
    float angleX = (float)qRadiansToDegrees(-direction.y() * 3.141593);

    QMatrix4x4 rotationX;
    rotationX.rotate(angleX, 1.0f 0.0f, 0.0f);

    QMatrix4x4 rotationUp;
    rotationX.rotate(angleY, 0.0f 1.0f, 0.0f);

    rotation = rotationX * rotation * rotationUp; 

    startVector = endVector;
}
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • I have implemented this however it looks as if my mouse picking code is somehow interfering with this. You helped me solve a probelm with my mouse picking code in this question https://stackoverflow.com/questions/54462354/opengl-moving-vertices-with-mouse. The rotation seems to happen correctly when I click on a vertex and then move the mouse but not when I click anywhere else – Callum Perks Feb 03 '19 at 15:13
  • @CallumPerks Actually I don't know what you mean. But have a look at the answer to to the question [`Qmatrix4x4 translate does not take any effect`](https://stackoverflow.com/questions/54491410/qmatrix4x4-translate-does-not-take-any-effect/54491988#54491988), may be this can help you, too. – Rabbid76 Feb 03 '19 at 15:25
  • i am no longer using that method. I am just mapping the coordinates to betwene 0 and 1 with the following lines of code – Callum Perks Feb 03 '19 at 15:26
  • float mappedX = ((2.0f * x) / context->width() - 1.0f); float mappedY = -((2.0f * y) / context->height() - 1.0f); – Callum Perks Feb 03 '19 at 15:27
  • Im still not seeing a rotation. Every so often the model will just jump to a different orientation – Callum Perks Feb 03 '19 at 15:41
  • @CallumPerks What does it mean *"it doesnt feel very natural"*? Do you want that the up-vector of the object always points upwards? The following question is related to python, but possibly this is want you want - [How to implement alt+MMB camera rotation like in 3ds max?](https://stackoverflow.com/questions/54400422/how-to-implement-altmmb-camera-rotation-like-in-3ds-max/54411580#54411580) – Rabbid76 Feb 03 '19 at 16:32