/* QGL Transform Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "gltransform.h" #include "gltypes.h" #include inline void composeQMatrix4x4(const QVector3D & position, const QVector3D & orientation, const QVector3D & scale, QMatrix4x4 &m) { const QMatrix3x3 rot3x3(Transform::toRotationMatrix(orientation)); // set up final matrix with scale, rotation and translation m(0, 0) = scale.x() * rot3x3(0, 0); m(0, 1) = scale.y() * rot3x3(0, 1); m(0, 2) = scale.z() * rot3x3(0, 2); m(0, 3) = position.x(); m(1, 0) = scale.x() * rot3x3(1, 0); m(1, 1) = scale.y() * rot3x3(1, 1); m(1, 2) = scale.z() * rot3x3(1, 2); m(1, 3) = position.y(); m(2, 0) = scale.x() * rot3x3(2, 0); m(2, 1) = scale.y() * rot3x3(2, 1); m(2, 2) = scale.z() * rot3x3(2, 2); m(2, 3) = position.z(); // no projection term m(3, 0) = 0.0f; m(3, 1) = 0.0f; m(3, 2) = 0.0f; m(3, 3) = 1.0f; } inline void decomposeQMatrix3x3(const QMatrix3x3 & m, QMatrix3x3 & Q, QVector3D & D, QVector3D & U) { // Factor M = QR = QDU where Q is orthogonal, D is diagonal, // and U is upper triangular with ones on its diagonal. // Algorithm uses Gram-Schmidt orthogonalization (the QR algorithm). // // If M = [ m0 | m1 | m2 ] and Q = [ q0 | q1 | q2 ], then // q0 = m0/|m0| // q1 = (m1-(q0*m1)q0)/|m1-(q0*m1)q0| // q2 = (m2-(q0*m2)q0-(q1*m2)q1)/|m2-(q0*m2)q0-(q1*m2)q1| // // where |V| indicates length of vector V and A*B indicates dot // product of vectors A and B. The matrix R has entries // // r00 = q0*m0 r01 = q0*m1 r02 = q0*m2 // r10 = 0 r11 = q1*m1 r12 = q1*m2 // r20 = 0 r21 = 0 r22 = q2*m2 // // so D = diag(r00,r11,r22) and U has entries u01 = r01/r00, // u02 = r02/r00, and u12 = r12/r11. // Q = rotation // D = scaling // U = shear // D stores the three diagonal entries r00, r11, r22 // U stores the entries U[0] = u01, U[1] = u02, U[2] = u12 // build orthogonal matrix Q float invLen = 1.0f / std::sqrt(m(0, 0) * m(0, 0) + m(1, 0) * m(1, 0) + m(2, 0) * m(2, 0)); Q(0, 0) = m(0, 0) * invLen; Q(1, 0) = m(1, 0) * invLen; Q(2, 0) = m(2, 0) * invLen; float dot = Q(0, 0) * m(0, 1) + Q(1, 0) * m(1, 1) + Q(2, 0) * m(2, 1); Q(0, 1) = m(0, 1) - dot * Q(0, 0); Q(1, 1) = m(1, 1) - dot * Q(1, 0); Q(2, 1) = m(2, 1) - dot * Q(2, 0); invLen = 1.0f / std::sqrt(Q(0, 1) * Q(0, 1) + Q(1, 1) * Q(1, 1) + Q(2, 1) * Q(2, 1)); Q(0, 1) *= invLen; Q(1, 1) *= invLen; Q(2, 1) *= invLen; dot = Q(0, 0) * m(0, 2) + Q(1, 0) * m(1, 2) + Q(2, 0) * m(2, 2); Q(0, 2) = m(0, 2) - dot * Q(0, 0); Q(1, 2) = m(1, 2) - dot * Q(1, 0); Q(2, 2) = m(2, 2) - dot * Q(2, 0); dot = Q(0, 1) * m(0, 2) + Q(1, 1) * m(1, 2) + Q(2, 1) * m(2, 2); Q(0, 2) -= dot * Q(0, 1); Q(1, 2) -= dot * Q(1, 1); Q(2, 2) -= dot * Q(2, 1); invLen = 1.0f / std::sqrt(Q(0, 2) * Q(0, 2) + Q(1, 2) * Q(1, 2) + Q(2, 2) * Q(2, 2)); Q(0, 2) *= invLen; Q(1, 2) *= invLen; Q(2, 2) *= invLen; // guarantee that orthogonal matrix has determinant 1 (no reflections) const float det = Q(0, 0) * Q(1, 1) * Q(2, 2) + Q(0, 1) * Q(1, 2) * Q(2, 0) + Q(0, 2) * Q(1, 0) * Q(2, 1) - Q(0, 2) * Q(1, 1) * Q(2, 0) - Q(0, 1) * Q(1, 0) * Q(2, 2) - Q(0, 0) * Q(1, 2) * Q(2, 1); if (det < 0.0f) Q *= -1.0f; // build "right" matrix R QMatrix3x3 R(Qt::Uninitialized); R(0, 0) = Q(0, 0) * m(0, 0) + Q(1, 0) * m(1, 0) + Q(2, 0) * m(2, 0); R(0, 1) = Q(0, 0) * m(0, 1) + Q(1, 0) * m(1, 1) + Q(2, 0) * m(2, 1); R(1, 1) = Q(0, 1) * m(0, 1) + Q(1, 1) * m(1, 1) + Q(2, 1) * m(2, 1); R(0, 2) = Q(0, 0) * m(0, 2) + Q(1, 0) * m(1, 2) + Q(2, 0) * m(2, 2); R(1, 2) = Q(0, 1) * m(0, 2) + Q(1, 1) * m(1, 2) + Q(2, 1) * m(2, 2); R(2, 2) = Q(0, 2) * m(0, 2) + Q(1, 2) * m(1, 2) + Q(2, 2) * m(2, 2); // the scaling component D[0] = R(0, 0); D[1] = R(1, 1); D[2] = R(2, 2); // the shear component U[0] = R(0, 1) / D[0]; U[1] = R(0, 2) / D[0]; U[2] = R(1, 2) / D[1]; } inline bool hasScale(const QMatrix4x4 &m) { // If the columns are orthonormal and form a right-handed system, then there is no scale float t(m.determinant()); if (!qFuzzyIsNull(t - 1.0f)) return true; t = m(0, 0) * m(0, 0) + m(1, 0) * m(1, 0) + m(2, 0) * m(2, 0); if (!qFuzzyIsNull(t - 1.0f)) return true; t = m(0, 1) * m(0, 1) + m(1, 1) * m(1, 1) + m(2, 1) * m(2, 1); if (!qFuzzyIsNull(t - 1.0f)) return true; t = m(0, 2) * m(0, 2) + m(1, 2) * m(1, 2) + m(2, 2) * m(2, 2); if (!qFuzzyIsNull(t - 1.0f)) return true; return false; } inline void decomposeQMatrix4x4(const QMatrix4x4 & m, QVector3D & position, QVector3D & orientation, QVector3D & scale) { Q_ASSERT(m.isAffine()); const QMatrix3x3 m3x3(m.toGenericMatrix<3, 3>()); QMatrix3x3 rot3x3(Qt::Uninitialized); if (hasScale(m)) { decomposeQMatrix3x3(m3x3, rot3x3, scale, position); } else { // we know there is no scaling part; no need for QDU decomposition scale = QVector3D(1.0f, 1.0f, 1.0f); rot3x3 = m3x3; } orientation = Transform::fromRotationMatrix(rot3x3); position = QVector3D(m(0, 3), m(1, 3), m(2, 3)); } Transform::Transform(): m_scale(1.0f, 1.0f, 1.0f), m_translation(), m_eulerRotationAngles(), m_matrixDirty(false) { } Transform & Transform::operator =(const Transform & t) { m_scale = t.m_scale; m_translation = t.m_translation; m_eulerRotationAngles = t.m_eulerRotationAngles; m_matrixDirty = true; return *this; } void Transform::setMatrix(const QMatrix4x4 & m) { if (m != matrix()) { m_matrix = m; m_matrixDirty = false; QVector3D s; QVector3D t; QVector3D r; decomposeQMatrix4x4(m, t, r, s); m_scale = s; m_translation = t; m_eulerRotationAngles = r; } } void Transform::setRotationX(float r) { if (m_eulerRotationAngles.x() == r) return; m_eulerRotationAngles.setX(r); m_matrixDirty = true; } void Transform::setRotationY(float r) { if (m_eulerRotationAngles.y() == r) return; m_eulerRotationAngles.setY(r); m_matrixDirty = true; } void Transform::setRotationZ(float r) { if (m_eulerRotationAngles.z() == r) return; m_eulerRotationAngles.setZ(r); m_matrixDirty = true; } QMatrix4x4 Transform::matrix() const { buildMatrix(); return m_matrix; } QMatrix4x4 Transform::matrixRotate() const { buildMatrix(); return m_matrixR; } QMatrix4x4 Transform::matrixScale() const { buildMatrix(); return m_matrixS; } QMatrix4x4 Transform::matrixRotateScale() const { buildMatrix(); return m_matrixWT; } QVector3D Transform::direction() const { return matrixRotate().mapVector(QVector3D(0,0,-1)).normalized(); } void Transform::buildMatrix() const { if (m_matrixDirty) { composeQMatrix4x4(m_translation, m_eulerRotationAngles, m_scale, m_matrix); composeQMatrix4x4(QVector3D(), m_eulerRotationAngles, m_scale, m_matrixWT); composeQMatrix4x4(QVector3D(), m_eulerRotationAngles, QVector3D(1,1,1), m_matrixR); composeQMatrix4x4(QVector3D(), QVector3D(), m_scale, m_matrixS); m_matrixDirty = false; } } float Transform::rotationX() const { return m_eulerRotationAngles.x(); } float Transform::rotationY() const { return m_eulerRotationAngles.y(); } float Transform::rotationZ() const { return m_eulerRotationAngles.z(); } void Transform::setScale(const QVector3D & s) { if (s != m_scale) { m_scale = s; m_matrixDirty = true; } } void Transform::setScaleX(float s) { if (s != m_scale.x()) { m_scale.setX(s); m_matrixDirty = true; } } void Transform::setScaleY(float s) { if (s != m_scale.y()) { m_scale.setY(s); m_matrixDirty = true; } } void Transform::setScaleZ(float s) { if (s != m_scale.z()) { m_scale.setZ(s); m_matrixDirty = true; } } QVector3D Transform::scale3D() const { return m_scale; } float Transform::scale() const { return m_scale.x(); } void Transform::setRotation(const QVector3D & r) { if (r != m_eulerRotationAngles) { m_eulerRotationAngles = r; m_matrixDirty = true; } } QVector3D Transform::rotation() const { return m_eulerRotationAngles; } void Transform::setTranslation(const QVector3D & t) { if (t != m_translation) { m_translation = t; m_matrixDirty = true; } } void Transform::setTranslationX(float t) { if (t != m_translation.x()) { m_translation.setX(t); m_matrixDirty = true; } } void Transform::setTranslationY(float t) { if (t != m_translation.y()) { m_translation.setY(t); m_matrixDirty = true; } } void Transform::setTranslationZ(float t) { if (t != m_translation.z()) { m_translation.setZ(t); m_matrixDirty = true; } } QVector3D Transform::translation() const { return m_translation; } QQuaternion Transform::fromAxisAndAngle(const QVector3D & axis, float angle) { return QQuaternion::fromAxisAndAngle(axis, angle); } QQuaternion Transform::fromAxisAndAngle(float x, float y, float z, float angle) { return QQuaternion::fromAxisAndAngle(x, y, z, angle); } QQuaternion Transform::fromAxesAndAngles(const QVector3D & axis1, float angle1, const QVector3D & axis2, float angle2) { const QQuaternion q1 = QQuaternion::fromAxisAndAngle(axis1, angle1); const QQuaternion q2 = QQuaternion::fromAxisAndAngle(axis2, angle2); return q2 * q1; } QQuaternion Transform::fromAxesAndAngles(const QVector3D & axis1, float angle1, const QVector3D & axis2, float angle2, const QVector3D & axis3, float angle3) { const QQuaternion q1 = QQuaternion::fromAxisAndAngle(axis1, angle1); const QQuaternion q2 = QQuaternion::fromAxisAndAngle(axis2, angle2); const QQuaternion q3 = QQuaternion::fromAxisAndAngle(axis3, angle3); return q3 * q2 * q1; } QQuaternion Transform::fromAxes(const QVector3D & xAxis, const QVector3D & yAxis, const QVector3D & zAxis) { return QQuaternion::fromAxes(xAxis, yAxis, zAxis); } QVector3D Transform::fromDirection(QVector3D d, float pitch) { QVector3D ret; d.normalize(); ret[0] = M_PI - acos(d.z()); ret[1] = pitch * deg2rad; ret[2] = -atan2(d.x(), d.y()); normalizeAngleRad(ret[0]); normalizeAngleRad(ret[2]); return ret * rad2deg; } QVector3D Transform::fromRotationMatrix(const QMatrix3x3 & m) { //return QQuaternion::fromRotationMatrix(m); float sy = sqrt(m(0,0) * m(0,0) + m(1,0) * m(1,0)); bool singular = sy < 1.E-6; // If float x, y, z; if (!singular) { x = atan2( m(2,1), m(2,2)); y = atan2(-m(2,0), sy); z = atan2( m(1,0), m(0,0)); } else { x = atan2(-m(1,2), m(1,1)); y = atan2(-m(2,0), sy); z = 0.; } return QVector3D(x, y, z) * rad2deg; } QMatrix3x3 Transform::toRotationMatrix(const QVector3D & r) { QMatrix4x4 m; if (r.z() != 0.f) m.rotate(r.z(), 0., 0., 1.); if (r.y() != 0.f) m.rotate(r.y(), 0., 1., 0.); if (r.x() != 0.f) m.rotate(r.x(), 1., 0., 0.); return m.toGenericMatrix<3,3>(); } QMatrix4x4 Transform::rotateAround(const QVector3D & point, float angle, const QVector3D & axis) { QMatrix4x4 m; m.translate(point); m.rotate(angle, axis); m.translate(-point); return m; } QMatrix4x4 Transform::rotateFromAxes(const QVector3D & xAxis, const QVector3D & yAxis, const QVector3D & zAxis) { return QMatrix4x4(xAxis.x(), yAxis.x(), zAxis.x(), 0.0f, xAxis.y(), yAxis.y(), zAxis.y(), 0.0f, xAxis.z(), yAxis.z(), zAxis.z(), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f); }