Search code examples
c++qtlinear-algebra

How to get scaling, rotation, and translation from a transformation matrix


I want to get scaling, rotation, and translation from a transformation matrix in Qt C++ OpenGL ES 2.0 to make a keyframe animation with linear interpolation for Android and WebAssembly. I have the next example in JavaScript with the glMatrix (https://glmatrix.net/) library that creates the transformation matrix and gets scaling, rotation, and translation from the transformation matrix and prints them to the console:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>How to get scaling, rotation, and translation from a transformation matrix using glMatrix and JavaScript</title>
</head>

<body>
    <!-- Since import maps are not yet supported by all browsers, its is
        necessary to add the polyfill es-module-shims.js -->
    <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js">
    </script>

    <script type="importmap">
        {
            "imports": {
                "gl-matrix": "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
            }
        }
    </script>

    <script type="module">
        import { mat4, quat, vec3 } from "gl-matrix";

        // Create a transformation matrix
        const matrix = mat4.create();
        mat4.translate(matrix, matrix, [20, 80, 0]);
        mat4.rotate(matrix, matrix, -90 * Math.PI / 180, [0, 0, 1]);
        mat4.scale(matrix, matrix, [20, 20, 1]);
        console.log("Transformation matrix: " + matrix);
        // Output:  0, -20, 0, 0,
        //         20,   0, 0, 0,
        //          0,   0, 1, 0,
        //         20,  80, 0, 1
        // "mat4" has a column-major order

        // Get scaling
        const scaling = vec3.create();
        mat4.getScaling(scaling, matrix);
        console.log(`Scaling: (sx: ${scaling[0]},` +
            ` sy: ${scaling[1]}, sz${scaling[2]})`);
        // Output: (sx: 20, sy: 20, sz: 1)

        // Get rotation
        const rotation = quat.create();
        mat4.getRotation(rotation, matrix);
        console.log(`Rotation: (rx: ${rotation[0]}, ry: ${rotation[1]}`,
            ` rz: ${rotation[2]}, rw: ${rotation[3]})`);
        // Output: (rx: 0, ry: 0  rz: -0.7071067690849304, rw: 0.7071067690849304)

        // Get translation
        const translation = vec3.create();
        mat4.getTranslation(translation, matrix);
        console.log(`Translation: (tx: ${translation[0]},` +
            ` ty: ${translation[1]}, tz: ${translation[2]})`);
        // Output: (tx: 20, ty: 80, tz: 0)
    </script>

</body>

</html>

I want to rewrite it to Qt. I read a documentation and tried to google but I didn't find how to extract scaling from a transformation matrix.

#include <QtGui/QMatrix4x4>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QQuaternion>
#include <QtMath>
#include <QtOpenGL/QOpenGLWindow>
#include <QtWidgets/QApplication>
#include <QtWidgets/QWidget>

class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions
{
public:
    OpenGLWindow()
    {
        setTitle("OpenGL ES 2.0, Qt6, C++");
        resize(350, 350);

        QMatrix4x4 m;
        m.translate(20.f, 80.f, 0.f);
        m.rotate(-90.f, QVector3D(0.f, 0.f, 1.f));
        m.scale(20.f, 20.f);
        qDebug() << "Transformation matrix: " << m;
        // Output:  0,  20, 0, 20,
        //        -20,   0, 0, 80,
        //          0,   0, 1,  0,
        //          0,   0, 0,  1
        // "QMatrix4x4" has a row-major order
    }

    void initializeGL() override
    {
        initializeOpenGLFunctions();
        glClearColor(0.2f, 0.2f, 0.2f, 1.f);
    }

    void paintGL() override
    {
        glClear(GL_COLOR_BUFFER_BIT);
    }
};

int main(int argc, char *argv[])
{
    QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL);
    QApplication app(argc, argv);
    OpenGLWindow w;
    w.show();
    return app.exec();
}

Solution

  • I opened a source code of getScaling and saw how scaling is calculated there: https://glmatrix.net/docs/mat4.js.html#line1197

    export function getScaling(out, mat) {
      let m11 = mat[0];
      let m12 = mat[1];
      let m13 = mat[2];
      let m21 = mat[4];
      let m22 = mat[5];
      let m23 = mat[6];
      let m31 = mat[8];
      let m32 = mat[9];
      let m33 = mat[10];
      out[0] = Math.hypot(m11, m12, m13);
      out[1] = Math.hypot(m21, m22, m23);
      out[2] = Math.hypot(m31, m32, m33);
      return out;
    }
    

    I made the same with qHypot

    #include <QtGui/QMatrix3x3>
    #include <QtGui/QMatrix4x4>
    #include <QtGui/QOpenGLFunctions>
    #include <QtGui/QQuaternion>
    #include <QtMath>
    #include <QtOpenGL/QOpenGLWindow>
    #include <QtWidgets/QApplication>
    #include <QtWidgets/QWidget>
    
    class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions
    {
    public:
        OpenGLWindow()
        {
            setTitle("OpenGL ES 2.0, Qt6, C++");
            resize(350, 350);
    
            QMatrix4x4 matrix;
            matrix.translate(20.f, 80.f, 0.f);
            matrix.rotate(-90.f, QVector3D(0.f, 0.f, 1.f));
            matrix.scale(20.f, 20.f);
            qDebug() << "Transformation matrix: " << matrix;
            // Output:  0,  20, 0, 20,
            //        -20,   0, 0, 80,
            //          0,   0, 1,  0,
            //          0,   0, 0,  1
            // "QMatrix4x4" has a row-major order
    
            // Get scaling
            float sx = qHypot(matrix.row(0)[0], matrix.row(1)[0], matrix.row(2)[0]);
            float sy = qHypot(matrix.row(0)[1], matrix.row(1)[1], matrix.row(2)[1]);
            float sz = qHypot(matrix.row(0)[2], matrix.row(1)[2], matrix.row(2)[2]);
            QVector3D scaling(sx, sy, sz);
            qDebug() << "Scaling:" << scaling;
            // Output: QVector3D(20, 20, 1)
    
            // Get rotation
            QMatrix3x3 rotationMatrix;
            rotationMatrix.data()[0] = matrix.column(0)[0] / sx;
            rotationMatrix.data()[1] = matrix.column(0)[1] / sy;
            rotationMatrix.data()[2] = matrix.column(0)[2] / sz;
            rotationMatrix.data()[3] = matrix.column(1)[0] / sx;
            rotationMatrix.data()[4] = matrix.column(1)[1] / sy;
            rotationMatrix.data()[5] = matrix.column(1)[2] / sz;
            rotationMatrix.data()[6] = matrix.column(2)[0] / sx;
            rotationMatrix.data()[7] = matrix.column(2)[1] / sy;
            rotationMatrix.data()[8] = matrix.column(2)[2] / sz;
            QQuaternion rotation = QQuaternion::fromRotationMatrix(rotationMatrix);
            qDebug() << "Rotation:" << rotation;
            // Output: QQuaternion(scalar:0.707107, vector:(0, 0, -0.707107))
    
            // Get translation
            float tx = matrix.row(0)[3];
            float ty = matrix.row(1)[3];
            float tz = matrix.row(2)[3];
            QVector3D translation(tx, ty, tz);
            qDebug() << "Translation:" << translation;
            // Output: QVector3D(20, 80, 0)
        }
    
        void initializeGL() override
        {
            initializeOpenGLFunctions();
            glClearColor(0.2f, 0.2f, 0.2f, 1.f);
        }
    
        void paintGL() override
        {
            glClear(GL_COLOR_BUFFER_BIT);
        }
    };
    
    int main(int argc, char *argv[])
    {
        QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL);
        QApplication app(argc, argv);
        OpenGLWindow w;
        w.show();
        return app.exec();
    }
    
    QT += core gui opengl widgets
    
    win32: LIBS += -lopengl32
    
    CONFIG += c++17
    
    SOURCES += \
        main.cpp
    

    P.S. The QMatrix4x4 constructor uses row-major order. The glMatrix fromValues method uses column-major order.