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.
2 Answers 2
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.
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 (θ1⁄2 here). However, that's exactly what we need in this case i.e. |1 * 1 * sin(θ1⁄2)| 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 θ1⁄2, sin θ1⁄2 A] ‖q‖ = √(cos2 θ1⁄2 + sin2 θ1⁄2 ‖A‖2) ‖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.
-
\$\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\$ratchet freak– ratchet freak2015年06月04日 23:15:41 +00:00Commented Jun 4, 2015 at 23:15 -
\$\begingroup\$ I see; my answer on the scaling part doesn't apply, removed it. \$\endgroup\$legends2k– legends2k2015年06月05日 13:16:18 +00:00Commented Jun 5, 2015 at 13:16