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();
}
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.