0
\$\begingroup\$

My problem

I'm working with Qt3D and my problem is almost exactly like this one:

https://stackoverflow.com/q/60995155/3405291

Suggested solution

A solution is suggested here:

https://stackoverflow.com/a/61315454/3405291

Understanding the solution

I have a problem understanding the suggested solution. Specifically:

The problem is that the QTransform node does not store the transformation as a general 4x4 matrix. Rather is decomposes the matrix into a 3 transformations that are applied in fixed order:

S - a diagonal scaling matrix

R - the rotation matrix

T - translation

and then applies it in the order T * R * S * X to a point X.

So when the transformation on the parent is M = T * R * S, then the inverse on the child will be M^-1 = S^-1 * R^-1 * T^-1. Setting the inverse on the child QTransform will attempt to decompose it in the same way:

M^-1 = T_i * R_i * S_i = S^-1 * R^-1 * T^-1

That doesn't work, because particularly S and R don't commute like this.

I'm looking for the phenomenon happening here and why it happens. Does it happen in other 3D engines? How is it resolved in other 3D engines?

\$\endgroup\$
2
  • \$\begingroup\$ Can you clarify what it is you don't understand about this? \$\endgroup\$
    – DMGregory
    Feb 26, 2021 at 18:45
  • 1
    \$\begingroup\$ We have some past Q&A here about related effects in other engines, which might give you some intuition for why this happens: 1 2 3 4 \$\endgroup\$
    – DMGregory
    Feb 26, 2021 at 19:08

1 Answer 1

3
\$\begingroup\$

A 4x4 homogeneous matrix can represent any affine transformation. That is, any combination of:

  • Translation by any offset
  • Rotation around any axis by any angle
  • Scale along multiple arbitrary axes by any scale factor
  • Shearing in any plane by any factor

Including any arbitrary composition of the above, in any order - like rotating then scaling then rotating and scaling again.

But there are good reasons why we don't want to use a 4x4 matrix as the "source of truth" for the transformations of objects in our game scene.

  • Repeated transformations of a matrix can allow rounding errors to creep in and distort our objects, or even flatten them to a plane/line/point!

  • Decomposing a matrix, like if we wanted to isolate and read or change just the rotation component, is somewhat expensive.

    It's also ambiguous: we can't tell the difference between an object that's been rotated 180 degrees, versus one that's been scaled by a factor of -1 on two axes: the net transformed result is the same. So we lose some information when we compose transformations into a matrix.

For this reason, game engines often store the "source of truth" for an object's transformation as separate components, and generate the corresponding matrix on demand from that source. Frequently these source components will be:

  • Translation vector
  • Rotation quaternion or Euler angle triplet
  • Scale factors along the object's 3 local coordinate axes

(Note that this is also just 9-10 floats, a little lighter than the full 16 needed for a 4x4 matrix)

When we need to update the matrix for rendering, we can build it by applying these three components in order from right to left:

$$M = T \times R \times S$$

Applying this matrix to a vector \$\vec v\$ is the same as first multiplying that vector's x, y, and z components by the axis-aligned scale factors, then rotating the result around the origin, then adding the translation vector to that rotated result.

$$M \times \vec v = T \times R \times S \times \vec v$$

(See this answer that goes into more depth about constructing and using matrices like this)

This covers the set of transformations we most frequently use in 3D games, but it's not as expressive as a 4x4 matrix. We can't use a single set of the components above to represent shearing transformations, or scaling along a diagonal axis.

That means we cannot make every matrix \$ M\$ in this way.

In environments like this, the object's transformation matrix will either be read-only (like in Unity), or if you're allowed to "set" it, what you're really doing is requesting a transformation as close as possible to the value you provide. The framework will then try to find some combination of translation, rotation, and axis-aligned scale that comes closest to matching the matrix you provided, maybe using an algorithm like this one.

That latter seems to be what Qt3D is doing, according to the answer you linked. So when you set a matrix, what you're really doing is asking the framework to calculate the best matching \$T\$ \$R\$ and \$S\$, and then assign the matrix \$M = T \times R \times S\$, even if that \$M\$ doesn't exactly match the matrix you provided.

In particular, if you have a parent object with a non-uniform scale applied, and a child object that is rotated diagonally to that parent, then the net effect of that transformation is a shear, or a scale along a diagonal axis of the child.

As we saw above, we can't represent a diagonal scale/shear in TRS form. So if you make an inverse matrix that cancels out the diagonal scale, and try to assign it to the child, the framework has no valid choice of \$S\$ that exactly matches the diagonal scale you're trying to apply. It has to compromise and pick the closest axis-aligned scale instead.

The best fix for this is to avoid applying non-uniform scales to parent objects. Consider making an invisible parent with only uniform scale, then give it two children: one with non-uniform scale representing the original parent's visual, and the second representing the original child. By keeping non-uniform scale out of the parent chain, you ensure you don't get these shear transforms that we cannot correct by applying the inverse to child objects.

\$\endgroup\$
2
  • \$\begingroup\$ In 3D app, I'm going to limit scale to uniform. It works as far as I tested. It might be the easiest/cleanest approach. \$\endgroup\$
    – Megidd
    Feb 28, 2021 at 7:33
  • \$\begingroup\$ By the way, your explanation was awesome =) \$\endgroup\$
    – Megidd
    Feb 28, 2021 at 10:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .