Mesh.addChild() with negative scaling transforms child rotation incorrectly

Hi guys, I’m new to this forum.

Not sure if this is by design, or a bug, but when adding a Mesh/TransformNode with negative scale as child to another Mesh, its scaling gets flipped along different axis and rotation added along z axis.

Given: childMesh with scaling {x: -2, y: 1, z: 1} and default rotation
when: parentMesh.addChild(childMesh)
and: parentMesh has default rotation and scaling
then: childMesh scaling and rotation should remain the same.

Expected: childMesh with scaling {x: -2, y: 1, z: 1}
Received: childMesh with scaling {x: 2 y: -1 z: 1} and rotation {x: 0 y: 0 z: 3.141592653589793}

https://playground.babylonjs.com/#RH34UQ#8 (check console for warnings)

Visually, it transforms everything correctly, but in my use case, using React Redux with unidirectional data flow, I need all internal transform vectors to sync correctly, regardless if the Mesh is attached to a parent, or not.

The broken behavior happens even after releasing the childMesh.

1 Like

Hey there, welcome to the forum. :slightly_smiling_face: I’m not sure if this is by design in this case either, but you can work around the issue by manually assigning the mesh’s parent, like below. setParent maintains the child’s absolute position in addition to setting the parent, but you might not need that.

child.parent = parent;

https://playground.babylonjs.com/#RH34UQ#7

Hi @Blake, I’m aware of the difference of child.parent = parent; method. However, it’s not suitable in this case, because I need the gizmo mesh position (pivot point) to match with selected (childMesh) position.

Babylon gizmo currently cannot work with negative scales, but I already had a work around for that (sanitizing gizmo scaling to always be positive, then on child mesh release, apply back flipped scale axis).

The biggest problem is that I don’t even know how to get the correct rotationQuaternion after flipping a mesh because its rotation got messed up.

And it gets even more confusing when you have multiple meshes selected at once, then perform flip or rotate, because selected Meshes would have very different rotation in World Space, including position changes, which are not easily computed manually.

1 Like

Okay, sounds like you’ve already found a good work-around then. :+1: IDK if it’s a bug or a limitation, but I’m sure others will - it might take a while longer thou, being the weekend. :slightly_smiling_face: :beers:

Must be a bug, because childMesh transform is messed up even after releasing with removeChild.
https://playground.babylonjs.com/#RH34UQ#8

Still no clean solution to get correct rotationQuaternion after attaching the mesh to parent.

Hi @ecoin and welcome.

Out of interest why do you want to apply a negative scaling rather than a positive one?

Also have you tried using a pivot rather than a parent to see if this behaves as you want it to?

Understanding and visualising rotations is always trick. In the following PG the child is a group of merged meshes that more clearly show the orientaion of the mesh. The child also displays its local axes, red x axis, green y axis, blue z axis.

https://playground.babylonjs.com/#RH34UQ#9

Perhaps you could use this to demonstrate what goes wrong with rotations following negative scaling.

For example by commenting out or not, various combinations of lines 20 and 28 in this https://playground.babylonjs.com/#RH34UQ#10 you could show the difference between what you get and what you expect.

Hi JohnK,

Answer to your question: like in the screenshot I attached in the previous reply, I need negative scaling for flip (for example: an L shaped sofa needs to be mirrored to be Г). I cannot use Pivot. Please correct me if I’m wrong, but the Gizmo tool can only attach to a Mesh or TransformNode.

Perhaps this illustration will make it clear for you the use case for using specifically parent.addChild() method, and not any other approaches.

In the app, users can drag objects to the scene, then select them (with gizmo tool) and perform different actions: position, rotate, scale, flip (i.e. negative scale), apply material, copy, delete. Then Undo/Redo each action. All with the use of Gizmo tool to select objects (which can be Meshes, or TransformNodes with meshes inside).

These transforms get saved to user’s project. When loading the same project next time, or when deselecting objects, those same transforms need to be applied, but without the Gizmo tool. Thus, I cannot have two different transforms when objects are within a parent gizmo mesh, and another when they are not. It needs to always be the same.

Internal rotation vectors also need to be correct, so it can show to user the correct rotation angle, like the 30° shown in the screenshot.

Everything works very well, even with Undo/Redo. Except when flipping begins :frowning_face:

What do I expect?
Attaching a mesh to a parent using setParent or addChild and then releasing with removeChild without applying any transforms in between, does not modify the mesh’s transform.

Currently this behavior only happens when the mesh has positive scaling, but is broken with negative scaling.

A workaround might be to bake the current transformation into the mesh whenever you apply a transformation. Although this makes undo more complicated.

https://playground.babylonjs.com/#RH34UQ#11

Baking is not an option because of performance and architectural reasons.

The app has complex Item → Part → Mesh structure, where every object you see is an Item that is built by combining Parts, which in return are made of Meshes. The same Mesh is reused across different Parts, which in return are reused by different Items, like how objects are built in the real world. User can apply different Materials to each Mesh (the two towers you see are the same Part, with varying Materials). So I cannot merge meshes for baking.

I know that Babylon encourages people to build games with its engine, but if you look into the market of games, there are plenty more specialized engines built for it, and people don’t usually play in the browser. With games you rarely need negative scaling.

If we are being more practical, and not talking about pet projects, then web based apps is where Babylon will truly shine. And flipping things is a common feature.

I believe this is a critical bug that needs to be fixed. Not being able to do negative scaling properly is a big blocker.

Adding @Cedric to have a look at negative scaling.

1 Like

When parenting node, the child postion/rotation/scaling is recomputed . This is done by transform its world matrix by inverse parent world matrix.

Resulting matrix is then decomposed and child PRS is updated.
Matrix decomposition returns a positive scaling, for each axis:

Because there is no way to know for each axis if scaling should be negative or not. Once scaling is computed, the rotation is computed, taking into account the scaling.

That’s why at the end of parenting scaling is positive, possibily negative on Y axis and rotation is changed.

For any orthogonal matrix, it’s not possible to determine is axis should be negative or if it’s the result of a rotation.

Now, this works perfectly well for positive scaling and gives visually correct rendering.

I understand it’s not what you are expecting with negative scaling. It’s a critical bug for you. Many products have been shipped without being an issue.

This said, I think it’s possible to make it better in the parenting code by dealing with component: inverting quaternion, multiplying scaling by inv parent, etc

But, I have doubt with LH/RH gltf. when importing .gltf, 1 axis has negative scaling. with current code, parenting a mesh to one gltf node, everything will be rendered correctly.

If inverting components, child mesh will have negative scaling and rendering might not be correct.
I’m a bit scared (to say the least) to change parenting code to fix your issue while, at the same time, breaking apps for dozen of people.
EDIT: as suggested by @JohnK , this can be enabled with a flag set to false by default.

The problem is with setPosition uses the decompose function. This obtains from a matrix the scale, quaternion rotation and the position.

As you would expect two meshes given different negative scaling will look different after rotation as in Babylon.js Playground

What you might expect is that if you give a mesh a scaling, find its matrix, decompose the matrix to obtain the scale and reapply the scale the mesh would be in the same orientation. This is not true as in https://playground.babylonjs.com/#7IMYNL#1.

This is because the decomposition code uses positive square roots to obtain the positive scale values and then uses the determinate to determine negativity. So if the determinant is negative this means that one or three scaling values must be negative. For the form of the mesh to be scaled as before it is sufficient that one value is made negative and this is what is done in the code; ie if the determinant is negative the make scale of y negative. What this then does not do is scale the local axes correctly.

My solution would be to replace

scale = scale || MathTmp.Vector3[0];
scale.x = Math.sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]);
scale.y = Math.sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]);
scale.z = Math.sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]);

if (this.determinant() <= 0) {
      scale.y *= -1;
}

with

scale = scale || MathTmp.Vector3[0];
signX = Math.sign(node.scaling.x);
signY = Math.sign(node.scaling.y);
signZ = Math.sign(node.scaling.z);

scale.x = Math.sqrt(m[0] * m[0] + m[1] * m[1] + m[2] * m[2]);
scale.y = Math.sqrt(m[4] * m[4] + m[5] * m[5] + m[6] * m[6]);
scale.z = Math.sqrt(m[8] * m[8] + m[9] * m[9] + m[10] * m[10]);

scale.x *= SignX;
scale.y *= SignY;
scale.z *= SignZ;

However this is a breaking change and would then require a boolean option retain to be added to decompose with true using the signX type option, false (the default) to use the existing option.

This is a decision for the likes of @Deltakosh @sebavan @Evgeni_Popov @Cedric

EDIT Change in solution code to correct an error, code would need fully checking still.

1 Like

@JohnK yes, having a parameter to change type of matrix computation is the way to go.
I don’t know if doing that in decompose is better than doing it in setparenting with decomposition per component.

@ecoin In your project, is it possible to add a new parameter to setparent/addChild without too much work?

If possible I would change decompose I would expect the result of obtaining and applying scale from decompose NOT to make any changes as per https://playground.babylonjs.com/#7IMYNL#1 and https://playground.babylonjs.com/#7IMYNL#2

3 Likes

alright. Let me try to do a PR.

PR is live. Added optional preserveScalingSign to addChild and setParent to keep sign by CedricGuillemet · Pull Request #11403 · BabylonJS/Babylon.js · GitHub

5 Likes

Hi Cedric, yes, that would be super easy to set additional flag for addChild, it’s only called 3 times in the entire project.

Would be a good idea to add this to official docs regarding parenting, IMHO.

Thanks @JohnK , for the PR, will test it out and let you guys know if anything breaks.

2 Likes

Just to let you know it was @Cedric who did the PR, my suggested code had some holes in it that he corrected. Changes will be available after next update. Changes in docs will follow.

doc PR preserve sign doc by CedricGuillemet · Pull Request #378 · BabylonJS/Documentation · GitHub

4 Likes