7
\$\begingroup\$

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.

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ For those interested in knowing how the quaternion interpolation solution works, see here. \$\endgroup\$
    – legends2k
    Commented Dec 27, 2014 at 15:38
2
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Jun 4, 2015 at 23:15
  • \$\begingroup\$ I see; my answer on the scaling part doesn't apply, removed it. \$\endgroup\$
    – legends2k
    Commented Jun 5, 2015 at 13:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.