Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect result with non-uniform scale on axis not-aligned to local axis #981

Open
Ben-Mack opened this issue Jul 1, 2021 · 8 comments
Labels
Milestone

Comments

@Ben-Mack
Copy link

Ben-Mack commented Jul 1, 2021

This is the MaxScript to create a reproducable case:

tea = Teapot isSelected:on radius:10
tea.rotation.x = 0.2
scale tea [1,1,5] -- scale in world coordinate (not local coord)

Original obj in 3ds Max:
image

Exported result (I can see the z scale is applied to obj's local z axis):
image

Run resetXForm does help the exporter exported correctly, but in my case I needed this for instancing, and resetXForm will break instancing.

@pandaGaume
Copy link
Contributor

Hello,
Actually, the transformation matrix returned by the API is giving the following transformation values, but it's not obvious to know if it has to be taked in account at global or local level
image
The reason is the actual code is decompose these values using it own math, and therefore it's work for local transform only.

        private void exportTransform(BabylonNode babylonAbstractMesh, IIGameNode maxGameNode)
        {
            // Position / rotation / scaling
            var localTM = maxGameNode.GetLocalTM(0);

            // use babylon decomposition, as 3ds max built-in values are no correct
            var tm_babylon = new BabylonMatrix();
            tm_babylon.m = localTM.ToArray();

            var s_babylon = new BabylonVector3();
            var q_babylon = new BabylonQuaternion();
            var t_babylon = new BabylonVector3();

            tm_babylon.decompose(s_babylon, q_babylon, t_babylon);

            if (ExportQuaternionsInsteadOfEulers)
            {
                // normalize quaternion
                var q = q_babylon;
                float q_length = (float)Math.Sqrt(q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W);
                babylonAbstractMesh.rotationQuaternion = new[] { q_babylon.X / q_length, q_babylon.Y / q_length, q_babylon.Z / q_length, q_babylon.W / q_length };
            }
            else
            {
                babylonAbstractMesh.rotation = q_babylon.toEulerAngles().ToArray();
            }
            babylonAbstractMesh.scaling = new[] { s_babylon.X, s_babylon.Y, s_babylon.Z };
            babylonAbstractMesh.position = new[] { t_babylon.X, t_babylon.Y, t_babylon.Z };

            // Apply unit conversion factor to meter
            babylonAbstractMesh.position[0] *= scaleFactorToMeters;
            babylonAbstractMesh.position[1] *= scaleFactorToMeters;
            babylonAbstractMesh.position[2] *= scaleFactorToMeters;
        }

I will have a look on re-introduce native max build in values for scale, pos and rotation (keeping in mind that native max value are for a normalized vector). Keep u posted.

@pandaGaume
Copy link
Contributor

pandaGaume commented Jul 2, 2021

@bghgary This issue highlight one behavior (with Max2021):
Using existing code give an incorrect result in matrix decompose.
image

switching to use the native decompose from max it give the correct scale and rotation values
image

However, as you can see on the code above, the previous developper state that
// use babylon decomposition, as 3ds max built-in values are no correct
So i do not know how to act on it.

BTW the correct code appear to be

        private void exportTransform(BabylonNode babylonAbstractMesh, IIGameNode maxGameNode)
        {
            var localTM = maxGameNode.GetLocalTM(0);

            if (ExportQuaternionsInsteadOfEulers)
            {
                 // 3DSMax Quaternions use the right-hand rule for determining positive rotation.
                // for Babylon, we use left-hand rule so we reverse the rotation
                var q = localTM.Rotation.Inverse;
                float q_length = (float)Math.Sqrt(q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W);
                babylonAbstractMesh.rotationQuaternion = new[] { q.X / q_length, q.Y / q_length, q.Z / q_length, q.W / q_length };
            }
            else
            {
                var q = localTM.Rotation.ToEulerAngles();
                babylonAbstractMesh.rotation = q.ToArray();
            }
            babylonAbstractMesh.scaling = localTM.Scaling.ToArray();
            // note : Apply unit conversion factor to meter
            babylonAbstractMesh.position = localTM.Translation.MultiplyBy(scaleFactorToMeters).ToArray();
        }

@Ben-Mack I'm agree this not solve the initial issue topic but I beleive we have no way to recompose the behavior and the intend of the scripting tools. You MAY add additional node as teapot parent which take the Z scale in account to act as global transform.

@Ben-Mack
Copy link
Author

Ben-Mack commented Jul 2, 2021

Thank you for such quick investigation and the workaround!

The maxscript I created is simplified just for reproduce purpose, unfortunately my actual case is not that simple, the 3D model provided by artist is already scaled in some arbitrary axis like that, so currently I have no way to know how it was scaled.

Are there anyway to detect this kind of scaling? Can we noticed it from the transform matrix somehow?
I need to detect this kind of transform to make a workaround.

@pandaGaume
Copy link
Contributor

pandaGaume commented Jul 2, 2021

Nope i dont think so you can detect it, but i may be wrong, i do not know everyting about 3DSMax.
In pure math point of view, this is not possible, but max may save some information somewhere.
for understanding, you can see that the order of the transformation applied by script is important

tea = Teapot isSelected:on radius:10
scale tea [1,1,5]
tea.rotation.x = 0.2

In other hand, the render engine in max is taking this in account, so it might be a way.
I will have a look on the resetXForm, to see what he is doing to create the a new xForm modifier.
Did you try to use Reference instead of Instance then beeing able to use the xForm modifier ?

@pandaGaume
Copy link
Contributor

Hey guy's,
here for infos, some values extracted from the different transformation order.
First

tea = Teapot isSelected:on radius:10
tea.rotation.x = 0.2
scale tea [1,1,5]

give

Local TM
1	0			0				0	
0	4.61538458	-0.384615541	0	
0	1.923077	0.923076868		0	
0	0			0				1	

3ds Rotation
		X	-0.196116179
		Y	0
		Z	0
		W	0.9805806	
3ds Scaling
		Length	5.196152
		X	0.192450091	
		Y	0.9622505
		Z	0.192450061

Decomposed by Babylon (actual method)

-		rotationQuaternion
		[0]	-0.2759583
		[1]	0
		[2]	0
		[3]	0.9611696
		
-		scaling	
		[0]	1
		[1]	4.63138247
		[2]	2.13314223 

[Note] there is an invert of the rotation beetwen max UI and SDK
[Note] there is a large difference beetween Max and decomposed value of the rotation
[Note] the returned scalling is about Y and not Z.

1 & 3 are due to the fact we define the CoordSystem at the begining of the export session using DirectX (which is Babylon norm)

gameConversionManger.CoordSystem = Autodesk.Max.IGameConversionManager.CoordSystem.D3d;

then

tea = Teapot isSelected:on radius:10
scale tea [1,1,5]
tea.rotation.x = 0.2

give

Local TM
1	0			0			0
0	4.61538458	-1.92307711	0
0	0.384615421	0.9230769	0
0	0			0			1

3ds Rotation
		X	-0.196116179
		Y	0
		Z	0
		W	0.9805806
3ds Scaling
		Length	5.196152
		X	0.192450091
		Y	0.9622505
		Z	0.192450091

Decomposed by Babylon (actual method)
-		rotationQuaternion
		[0]	-0.196116149
		[1]	0
		[2]	0
		[3]	0.9805807
-		scaling	
		[0]	1
		[1]	5
		[2]	1

according in max coordinate system (as stated into the doc)

scale 1 1 5 => 1 0 0 0
                        0 1 0 0
                        0 0 5 0
                        0 0 0 1 

and

rotation.x 0.2 => 1 0     0    0
                            0 cos  sin  0
                            0 -sin cos 0
                            0 0     0    1

using
RotValue = quatToEuler tea.rotation
we get
(eulerAngles -22.6199 0 0)

so

cos =0.92307668778
sin=-0.38461594932

finally

rotation.x=0.2 => 1 0               0              0
                            0 0.923076 -0.384615 0
                            0 0.384615  0.923076  0
                            0 0               0              1

To be continued.

@pandaGaume
Copy link
Contributor

@bghgary So the 3DS max transform matrix, when set into script, is logically build keeping the order of applied transform operations. Here for the particular case R*S*T (while when decompose and set as local babylon transformation the order is become S*R*T by Babylon order).
We can detect that the transform matrix is NOT the S*R*T one by computing the theorical matrix, but once done, the question is what to do with this information ??
Parent the mesh and then scale the parent ?? this seems overkill, especially for instances .. Any thoughts ??
Also, what to do with Matrix which are composed of more than one scaling in different order such RSR*R...??
Max is applied the matrix, not the unit operation (this is how xFormReset work, similar as bakeTransformIntoVertices(matrix) in Babylon )

@pandaGaume
Copy link
Contributor

@bghgaryOn this case one proposal may be to save the matrix information into the BabylonMesh as ObjectTransformation, reset the node transform to identity, then "bake" the vertices with this matrix, additionally of the MaxOffset transformation.
This is the only way we have to convey the information along the pipeline, while babylon just support scale, rotate, translation and not matrix set.

@Ben-Mack
Copy link
Author

Ben-Mack commented Jul 22, 2021

Just found some MaxSDK documentations that might help:

World Space Modifiers and Object Transformations:
https://help.autodesk.com/view/3DSMAX/2016/ENU/?guid=__files_GUID_2E4E41D4_1B52_48C8_8ABA_3D3C9910CB2C_htm

INode Class Reference (see the parts about getObjectTm and getNodeTM):
https://help.autodesk.com/view/3DSMAX/2016/ENU/?guid=__cpp_ref_class_i_node_html

Transformation and Rotation:
https://help.autodesk.com/view/3DSMAX/2016/ENU/?guid=__files_GUID_62FCAAB5_1499_4D23_A5CB_05E3209CDC55_htm

Seems like MaxSDK's document explains things in much more details than MXS document.

@thomlucc thomlucc added this to the Future milestone Jan 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants