HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

User controlled rotation with arcBall

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withuserrotationarcballcontrolled

Problem

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 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

Solution

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[16] = { 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.

Code Snippets

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 ();
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[16] = { 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;
}

Context

StackExchange Code Review Q#51205, answer score: 4

Revisions (0)

No revisions yet.