What's wrong with multiple addChild/removeChild on rotated object?

I call addChild and removeChild several times in a row. I thought it’s ok but looks it’s not :smiley: Could you please explain why does this happen?
I was expected that should change nothing… Here is: Playground

Looks this happen because of rotationQuaternion line. Without it all works fine. But what’s wrong with this line? Euler rotation also breaks parenting like this.
I need a rotation :smiley:

1 Like

Let me check that :slight_smile:

Ok it seems like we have a bug in the matrix.decompose function
Investigating…

1 Like

addChild(plane)/removeChild(plane) appears to do the same as setParent(box0)/setParent(null).

Another method is to assign to parent:

plane.parent = plane.parent?null: box0

This retains the child’s local matrix so will appear to change in world space when the parent changes.

Using setParent(newparent) will modify the child’s local matrix, so the mesh will not move in world space. My guess is that setParent(null) and removeChild(child) do not reverse the effect.

2 Likes

they should and technically they are. set Parent(null) build the world matrix and try to decompose it into position, rotation and scaling

Unfortunately, because of rounding errors we end up adding small variations on the quaternion computation

More details:

You can see on this one that even the setParent has a SLIGHT change:
addChild issue | Babylon.js Playground (I recreated the code inside the PG to make it easier to grasp)

And here if we want to get back to origin with setParent(null), the small errors got amplified:

Yes. I see the decompose on line 828. I wonder if the inverse applied on line 826 is backwards? (I always have trouble remembering the necessary order of mulitplication, especially when trying to “undo” a matrix).

I know you got this, I’m just trying to follow along to see if I can learn something. I hope my comments aren’t wasting your time.

Well I don’t know what to do next. I think our maths are correct but the precision is the issue here

I’m still looking. I’m seeing much too big of a difference to attribute precision. Note the large repeated change in OP playground. Your playground also demonstrates it if you include a setInterval. I’m confident the bug is in removeParent() (the removeChild() equivalent in your playground). Will post when I find the issue.

1 Like

Appreciated! I was not able to find a problem (I even tried to use a Trace algorithm to get the quaternion at the end)

2 Likes

So is it a bug or it’s just normal behaviour and I should look for a another way of doing this?

I’m still looking through source code. I suspect it’s a bug. I note that it appears to occur only when both scaling and rotationQuaternion are non-identity values. @Deltakosh 's suspicion that it’s a bug in the Matrix.decompose() function appears to be correct. I’m sifting through web searches to find where the calculation is faulty. Currently, I’m thinking it’s in the decomposition specifically during the rotation or scaling calculations.

The expected outcome with mesh.setParent() or (equivalently) with mesh.addChild()/removeChild() is that the child mesh does not move or change in World Space.

Edit to add: it appears to be incorrect when scaling is non-uniform.

1 Like

cc @Cedric as I think he ran into smthg similar with Gizmo for none uniform scaled decomposition ?

For now, you can try forcing a uniform scaling in box2. One way to do this is baking the transform (after scaling only), into box0:

There is a lot of info about decomposing a matrix when scaling is non-uniform. Other graphics systems have the same limitation. The primary method I’ve seen to decompose a matrix that incorporates non-uniform scaling is using a Ken Shoemake’s decomposition algorithm. Here’s the best overview for using Shoemake’s method

This type of decomposition is explained in detail in Shoemake and Duff’s Matrix Animation and Polar Decomposition paper, as well as Shoemake’s chapter on Polar Matrix Decomposition in Graphics Gems IV.

and here’s source code in C to implement it.

Note that it is fairly expensive (and iterative) operation. So even if it were available in Babylon, you’d likely want to avoid it if possible.

1 Like

Could you please tell me what are pros of baking a transform to vertices? What’s the limitations?

Baking applies the transform to the vertices themselves as they exist on the Mesh VertexData, then modifies the corresponding individual mesh transform values (scale, position, rotation, and maybe pivot?) into identity values. Here, we’re only baking the scale, while the other transform values can be further modified and reflected in the WorldMatrix of each mesh. You can’t then apply the non-uniform scaling to any children by simple parenting. If you always use setParent(), addChild(), and removeChild() and you don’t have non-uniform scaling in any mesh, I don’t think you’ll notice any limitations.

As indicated in other forum posts, the non-uniform scaling requirement that I’m suggesting here applies to imported meshes and to gizmos as well, especially in the case of re-parenting any children after import.

1 Like

I’ll note here that three.js has a similar restriction in decompose.

From the pull request

Note: Not all matrices are decomposable in this way. For example, if an object has a non-uniformly scaled parent, then the object’s world matrix may not be decomposable, and this method may not be appropriate.

1 Like

I may be at the end of my research on this.

I believe it is impossible. The persuasive comment on stackexchange.

I found the above linked answer to the following question:

How do I combine (in what order) the two sets of transformations into a third set t3, r3 and s3, such that mat4(t1, r1, s1) * mat4(t2, r2, s2) == mat4(t3, r3, s3), without actually converting to matrices?

To save you a click to the answer:

This is not possible in the general case when you have both non-90-degree rotations on the “first” transformation and non-uniform scale on the second transformation. This can result in a squash or stretch being applied diagonal to the coordinate axes, which a vec3 of axis-aligned scale values is insufficient to describe. See discussion in this Q&A for more details. [emphasis added]

I was thinking we could reduce the problem to reparenting the child to it’s grandparent. This way, we already have the decomposed values of both the child’s and parent’s local matrix. If the parent’s local transformations could be applied to the child’s already‐decomposed values, then the child’s parent is assigned to it’s grandparent. Done (but not possible in all cases).

My research adventure included reading mathematical descriptions to general problems regarding the theoretical math and generalized matrices that included shear. Those solutions (including Shoemake’s decomposition, polar decomposition (might be the same as polar decomposition), Gram -Schmidt orthogonalization, and QR decomposition) solved the mathematical problem, but the result cannot (for cases mentioned above) be expressed as only including scale, rotation, and translation in that order of application. My previous assertion that shear can be decomposed into rotation and non-uniform scaling overlooked the fact(?) that shear can be expressed as an initial rotation, then scaling, followed by another rotation. Given that Babylon (as well as the other popular 3D web-based frameworks) only represent transformations as SRT (also referred to as TRS representing the order of matrix multiplications?) and do NOT include shear NOR an additional pre-rotation, decomposing a matrix that is composed with transformations described in the above answer into Babylon-supported SRT is not possible.

I think this leads to a potential way forward, if the Babylon community wished to better accomodate non-uniform scaling. What if BABYLON supported shear as a fundamental transformation side by side with scale, rotation, and translation? The core GPU performance would be unmodified, I think, because, in the cases I’m familiar with, the GPU represents transformations as either translation (“position” vector in the case of vertices and PointsCloudSystem particles) or a full 16-float matrix (mesh/geometry, ThinInstance). If it’s true that the GPU does not ever use rotations or scale directly, then incorporating shear into tranformations is only on the CPU side when calculating the matrix to send to the GPU. This may have an adverse impact on some custom (vertex?) shaders, though, that assume or use decomposed SRT only.

It’s unclear to me how deep the assumption of “only SRT transformations” is. Perhaps it’s abstracted/isolated sufficiently in Matrix4 and TransformNode that making additions in these core functions is plausible. However, I do think dependant code (such as mesh.setParent()) will not be a small change. I wonder if abstracting Matrix components would be beneficial. Instead of decompose, for example, writing to three separate objects, it could write to an object like {scale,rotation,translation} then adding shear might be less traumatic.

If adding full support of non-uniform scales and shear constitutes a fully generalized matrix transformation/decomposition from/into a single set of transformations in a fixed order, then I think adding this support could set BABYLON apart from other frameworks.

1 Like

Yes, I ran into such issue with gizmo. Basically, depending of order of operation, nonuniform scaling over an oriented matrix gives a squashed matrix.
The operation is not allowed in the gizmo.
But rotating a non uniform matrix is fine.
Some 3D engine do not allow non uniform scaling and negative scaling (like Valve Source IIRC).
More on non uniform scaling is normal transformation that need to be adapted.
https://nicolbolas.github.io/oldtut/Illumination/Tut09%20Normal%20Transformation.html

Does this normal transformation need to be repeated if there is a different non-uniform scaling or a different subsequent rotation?

Ii’s interesting that the operation for normals also includes a similar decompose operation semi-relevant to reparenting with non-uniform scales.

In this case, SVD decomposes any matrix, including those with shear, into Rotation-Scale-Rotation. The resulting component “Scale” here can be non uniform in the general case.