User controlled rotation with arcBall

I am trying to visualize a pointcloud centered around the origin. I also want to have a user controlled rotation for which I found the arcball.

rMat is the model matrix, passed into the shader.

inline QVector3D getArcballVector(const QPoint& pt, int width, int height)
{
QVector3D P = QVector3D(1.0*pt.x ()/width * 2 - 1.0,
1.0*pt.y ()/height * 2 - 1.0,
0);
P.setY (-P.y ());

float OP_squared = P.lengthSquared ();
if (OP_squared <= 1)
P.setZ (std::sqrt(1 - OP_squared));  // Pythagore
else
P.normalize ();  // nearest point
return P;
}

void PointCloud::mouseMoveEvent(QMouseEvent * e)
{
if(e->buttons ()==Qt::LeftButton)
{
if(!rotPos.isNull () && rotPos!=e->pos ())
{
//rotate using an arcBall for freeform rotation
QVector3D vec1 = getArcballVector (rotPos, width (), height ());
QVector3D vec2 = getArcballVector (e->pos (), width (), height ());

//use bisector to get half-angle cos and sin from dot and cross product
// for quaternion initialisation
QVector3D vec3 = vec1+vec2;
vec3.normalize ();
QVector3D rotaxis = QVector3D::crossProduct (vec1, vec3);
double cos = QVector3D::dotProduct (vec1, vec3);

QQuaternion quat (cos,rotaxis);
quat.normalize ();
//we want to left-multiply rMat with quat but that isn't available
//so we'll have to do it the long way around
QMatrix4x4 rot;
rot.rotate (quat);
rMat=rot*rMat;

updateGL ();
}
rotPos=e->pos ();
}
}

void PointCloud::mousePressEvent(QMouseEvent *e)
{
if(e->buttons ()==Qt::LeftButton)
{
rotPos=e->pos ();
}
}

One point to make is that most arcball examples use acos to get the rotation angle for the axis-angle representation and use that to create the matrix and only mention using quaternions in passing (and if they do use quaternions they still end up using axis-angle). I found that I can derive the quaternion values directly using the dot and cross products of the first vector with the bisector of the 2 vectors (vec3).

Are there any glaring inefficiencies? I could move the update code to a QTimer activated slot but the usage of the program has the user mostly ignoring the screen while concentrating on the road.

After some research I found that it's slightly more efficient to get the half quaternion directly from the double quaternion:

QVector3D rotaxis = QVector3D::crossProduct (vec1, vec2);
double cos = QVector3D::dotProduct (vec1, vec2);

// rotaxis and cos describe the double quat
// add 1 to cos so normalizing will 0.5 lerp between the null quat and the double quat
QQuaternion quat (1+cos,rotaxis);
quat.normalize ();

This saves the normalization of the bisector.

The rotation matrix can be constructed from a non-unit quaternion without first normalizing it. You do have to provide your own function to do so because the Qt algorithm assumes a unit quaternion:

inline QMatrix4x4 quatToMat(QQuaternion q)
{
//based on algorithm on wikipedia
// http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
float w = q.scalar ();
float x = q.x();
float y = q.y();
float z = q.z();

float n = q.lengthSquared();
float s =  n == 0?  0 : 2 / n;
float wx = s * w * x, wy = s * w * y, wz = s * w * z;
float xx = s * x * x, xy = s * x * y, xz = s * x * z;
float yy = s * y * y, yz = s * y * z, zz = s * z * z;

float m = { 1 - (yy + zz),         xy + wz ,         xz - wy ,0,
xy - wz ,    1 - (xx + zz),         yz + wx ,0,
xz + wy ,         yz - wx ,    1 - (xx + yy),0,
0 ,               0 ,               0 ,1  };
QMatrix4x4 result =  QMatrix4x4(m,4,4);
result.optimize ();
return result;
}

Using that eliminates all normalization from in mouseMoveEvent except to get the arcballVectors themselves.

• For those interested in knowing how the quaternion interpolation solution works, see here. – legends2k Dec 27 '14 at 15:38

The quaternion formed using the bisector would already be of unit length, thus normalizing it again is redundant. From the definition of cross product we know that the result would be a unit vector perpendicular to the operands scaled by the product of the magnitudes of the operands and sine of angle between them (θ½ here). However, that's exactly what we need in this case i.e. |1 * 1 * sin(θ½)| A, where A is the unit axis of rotation. A quaternion formed using a unit axis and some angle would be a unit quaternion:

q = [cos θ½, sin θ½ A]
‖q‖ = √(cos² θ½ + sin² θ½ ‖A‖²)
‖q‖ = 1
//use bisector to get half-angle cos and sin from dot and cross product
// for quaternion initialisation
QVector3D vec3 = vec1+vec2;
vec3.normalize ();
QVector3D rotaxis = QVector3D::crossProduct (vec1, vec3);
double cos = QVector3D::dotProduct (vec1, vec3);

QQuaternion quat (cos,rotaxis);
// quat.normalize ();

In the double quaternion lerping solution you'd normalize the quaternion while in the bisector solution you'd normalize the bisector. Thus both methods are almost equal, except that the latter method may slightly be slower since it involves vector addition as opposed to just the scalar addition in the former method.

• The function Qt provides doesn't actually create a correct matrix when the quat is non-unit, the algorithm uses 1 where it should be using lengthSquared. – ratchet freak Jun 4 '15 at 23:15
• I see; my answer on the scaling part doesn't apply, removed it. – legends2k Jun 5 '15 at 13:16