2

I am trying to animate an object in Qt3D to rotate around a specific axis (not the origin) while performing other transformations (e.g. scaling and translating).

The following code rotates the object as I want it but without animation yet.

QMatrix4x4 mat = QMatrix4x4();
mat.scale(10);
mat.translate(QVector3D(-1.023, 0.836, -0.651));
mat.rotate(QQuaternion::fromAxisAndAngle(QVector3D(0,1,0), -20));
mat.translate(-QVector3D(-1.023, 0.836, -0.651));
//scaling here after rotating/translating shifts the rotation back to be around the origin (??)

Qt3DCore::QTransform *transform = new Qt3DCore::QTransform(root);
transform->setMatrix(mat);
//...
entity->addComponent(transform);   //the entity of the object i am animating

I did not manage to incorporate a QPropertyAnimation as I desire with this code. Only animating the rotationY property does not let me include the rotation origin, so it rotates around the wrong axis. And animating the matrix property produces the end result but rotates in a way that is not desired/realistic in my scenario. So how can I animate this rotation to rotate around a given axis?

EDIT: There is a QML equivalent to what I want. There, you can specify the origin of the rotation and just animate the angle values:

Rotation3D{
  id: doorRotation
  angle: 0
  axis: Qt.vector3d(0,1,0)
  origin: Qt.vector3d(-1.023, 0.836, -0.651)
}

NumberAnimation {target: doorRotation; property: "angle"; from: 0; to: -20; duration: 500}

How can I do this in C++?

Mariam
  • 342
  • 3
  • 18
  • _Apparently the order of scaling and rotating changes the behaviour of the rotation_ Yepp. Rotations are not commutative i.e. the order counts essentially (except in special cases). One rotation and uniform scaling should work in any order. If scaling is not uniform - it's a difference if you turn something and then distort it or first distort and then rotate it. – Scheff's Cat Aug 08 '18 at 09:57
  • @Scheff yeah makes sense, but in my example the scaling _is_ uniform. It's just that when the rotation is not around the origin but a different axis the order becomes relevant. – Mariam Aug 08 '18 at 09:59
  • If you want to know more about this, you may google for e.g. "affine transformation computer graphics". Thus, I found [SE: What are Affine Transformations?](https://computergraphics.stackexchange.com/q/391) but there are surely tons of docs and tutorials out there. – Scheff's Cat Aug 08 '18 at 09:59
  • `rotateAround()` is not a pure rotation (as rotations are always about origin). It does translations as well (to move rotation center). Involving translations, it's again not commutative anymore. First uniform scale and then translate is different than first translate and then uniform scale... ;-) – Scheff's Cat Aug 08 '18 at 10:01
  • yeah, it basically does what I do manually in the first snippet. But then is there a way to scale before calling rotateAround()? Because the function just returns the final matrix @Scheff – Mariam Aug 08 '18 at 10:08
  • I'm not that experienced in `Qt3d` (but in other scene graphs including our own): I guess, you have to nest multiple transformation nodes to isolate the rotation (you want to animate) in one. The rendering will do the proper combination/chaining with the rest. – Scheff's Cat Aug 08 '18 at 10:17
  • How about `QVector3D t(-1.023, 0.836, -0.651); QMatrix4x4 matS, matTBack, matR, matTThere; matS.scale(10.0); matTThere.translate(-t); matTBack.translate(t); matR.rotateAround(-20, 0,1,0); QTransform xform(matTBack * matR * matTThere * matS);` (I'm sure that I swapped things accidentally - mostly I do. So, you might fix the order to get it properly.) – Scheff's Cat Aug 08 '18 at 10:27
  • yes, this is also a third way to implement the static rotation, but then how can I animate `matR` alone? this is what I am trying to achieve. @Scheff and thank you btw for trying to help – Mariam Aug 08 '18 at 11:58
  • I just clicked a bit through the Qt3d doc.: Is it possible to add multiple instances of `Qt3DCore::QTransforms` to the components of one entity? If so, do they accumulate? (If yes and yes, my earlier approach of nested transformation nodes could be applied (removing the "nested").) – Scheff's Cat Aug 08 '18 at 12:17
  • The answers are yes and no. From trying it out I saw that the second transform just overwrites the first one :/ @Scheff – Mariam Aug 08 '18 at 12:36
  • Damn. Well, I know my stuff from old-fashioned scene graphs (where this would be easy). Qt3d seems to follow a modern [Component Entity System](https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system) approach - something else I've planned to learn ASAP (if I find the time for this). – Scheff's Cat Aug 08 '18 at 12:39
  • I googled a bit and found [Node's parent-child relationship clarification, please?](http://lists.qt-project.org/pipermail/interest/2015-October/019084.html) linking to [Entity Component System and Parent Relations](https://www.gamedev.net/forums/topic/654122-entity-component-system-and-parent-relations/). I didn't get it fully at the first glance but I feel it provides the answer to How to translate nested transformations in a scenegraph to modern ECS relations. May be, it helps. – Scheff's Cat Aug 08 '18 at 12:47
  • I feel like Qt3D just boiled everything down to entities and components. I don't see any differences when entities are children of others etc. If they share the same components, they behave the same. But that doesn't really help me. I am surprised this problem is so hard to solve.. – Mariam Aug 08 '18 at 13:53
  • There is a Qt3d example: [Qt 3D: Planets QML Example](https://doc.qt.io/qt-5.11/qt3d-planets-qml-example.html). There is a lot of QML and js stuff which I understand only partly. However, the IMHO interesting files are `SolarSystem.qml` and `planets.js`. (Search for any occurrence of `centerOfOrbit`.) To make it short, accumulated transformations (e.g. rotation of moon about center of earth which itself rotates about center of sun) is done in code. Hence, I believe you have to prepare the transformation in code (e.g. by a timer) and feed the entity transform with the resulting matrix. – Scheff's Cat Aug 08 '18 at 15:17
  • My main and only problem is that I don't know how to set the rotation origin or `centerOfOrbit` in Qt C++, so no matter how many timers I set or transformations I accumulate, I still cannot make them rotate around the right axis or am I missing something? @Scheff – Mariam Aug 09 '18 at 07:53
  • 1
    Can't the [Qt 3D: Simple C++ Example](https://doc.qt.io/qt-5/qt3d-simple-cpp-example.html) help? With a modification like this in the `updateMatrix()` method in `orbittransformcontroller.cp`: `QVector3D translationVector3D(20, 0, 0); m_matrix.translate(translationVector3D); m_matrix.rotate(m_angle, QVector3D(0.0f, 1.0f, 0.0f)); m_matrix.translate(-translationVector3D);` – Julien Déramond Sep 03 '18 at 12:30
  • @ju_ I think this might be a working idea! Do you mind formulating it as an answer? – Mariam Sep 03 '18 at 14:26

1 Answers1

1

I think it is possible to use the Qt 3D: Simple C++ Example to obtain what it is looked for by simply modifying the updateMatrix() method in orbittransformcontroller.cpp:

void OrbitTransformController::updateMatrix()
{
    m_matrix.setToIdentity();
    // Move to the origin point of the rotation
    m_matrix.translate(40, 0.0f, -200);
    // Infinite 360° rotation
    m_matrix.rotate(m_angle, QVector3D(0.0f, 1.0f, 0.0f));
    // Radius of the rotation
    m_matrix.translate(m_radius, 0.0f, 0.0f);
    m_target->setMatrix(m_matrix);
} 

Note: It is easier to change the torus into a small sphere to observe the rotation.


EDIT from question asker: This idea is indeed a very good way of solving the problem! To apply it specifically to my scenario the updateMatrix() function has to look like this:

void OrbitTransformController::updateMatrix()
{
    //take the existing matrix to not lose any previous transformations
    m_matrix = m_target->matrix();
    // Move to the origin point of the rotation, _rotationOrigin would be a member variable
    m_matrix.translate(_rotationOrigin);
    // rotate (around the y axis)
    m_matrix.rotate(m_angle, QVector3D(0.0f, 1.0f, 0.0f));
    // translate back
    m_matrix.translate(-_rotationOrigin);
    m_target->setMatrix(m_matrix);
} 

I have made _rotationOrigin also a property in the controller class, which can then be set externally to a different value for each controller.

Mariam
  • 342
  • 3
  • 18
Julien Déramond
  • 544
  • 8
  • 17
  • The second translation should be the negative vector of the first one for it to work. – Mariam Sep 04 '18 at 07:23
  • To complete the answer: I also added two additional properties to the controller: `_rotationPoint` and `_rotationVector` that I can set externally to make the controller be animatable around an arbitrary rotation point and vector. – Mariam Sep 04 '18 at 07:26
  • @Mariam Should the second translation be the negative vector of the first one in your case or even in the [Qt 3D: Simple C++ Example](https://doc.qt.io/qt-5/qt3d-simple-cpp-example.html)? It seemed to work but I'm far to be an expert in 3D :) – Julien Déramond Sep 04 '18 at 10:29
  • 1
    in my case it should be the negative vector, yes. that's how you rotate around a different axis: translate to the rotation point, rotate, translate back where you came from. In the example it is different because they had a different aim. But thanks a lot either way, your simple idea was enough for me to develop this further to fit my case! I can edit your answer with the corrections. – Mariam Sep 04 '18 at 11:18
  • OK, thank you for the explanation :) You can edit my answer maybe, I don't know, by adding a second part with the modification corresponding to the real answer to the problem and by letting the first part for a more general answer to the problem. – Julien Déramond Sep 04 '18 at 11:53
  • Thank you @Mariam – Julien Déramond Sep 06 '18 at 07:43