I am trying to fix an animated model with negative scales (maybe a bad idea, I know). While looking at the transforms in max script, I notice something funny, which might be connected to the internal use of the left-handed coordinate system in 3ds max. I compared the transform of a node to the PRS values of the node: My expectation was that by multiplying the PRS-value, I should also get the transform. However, this is not the case if the object is rotated or mirrored, see:
$.transform =
row1 [0.866,-0.500,0.000]
row2 [-0.500,-0.866,0.000]
row3 [0.000,0.000,1.000]
row4 [13.000,-3.000,1.000]
...scale * ...rotation * ...pos =
row1 [-0.866,0.500,0.000]
row2 [-0.500,-0.866,0.000]
row3 [0.000,0.000,-1.000]
row4 [13.000,-3.000,1.000]
...transform.scalepart * ...transform.rotationpart * ...transform.translationpart =
row1 [-0.866,0.500,0.000]
row2 [0.500,0.866,0.000]
row3 [0.000,0.000,-1.000]
row4 [13.000,-3.000,1.000]
Any ideas, why these transforms are not the same? I am trying to understand how 3ds max works under the hood. Thank you very much for any insight!
A few things are going on here.
First, it appears the MaxScript $.transform.scalepart
and $.transform.rotationpart
are bugged and don't support inverse scale. These are probably implemented similarly to the first answer on this post, which always gives positive scale coordinates, and wrong answers for negative scale matrices.
Second, when asking for $.scale.controller.value
and $.rotation.controller.value
the rotation part is also bugged and returns the same value as before mirroring, while the scale returns a mirrored value. If you think about how the (scale * rotation * position) matrix composition works, you can see that, for example, a mirror in X requires inverting the first component of scale but also reversing all rotations around the X axis.
Apparently (I'm speculating here) the mirror mode does two things when applied. (1) it activates special-case handling inside the PRS controller, such that, when composing the scale/rotation/transform subcontroller values into a matrix, it flips some coordinates without doing a true matrix composition. For example, a mirror in X inverts the first coordinate of the each of the first three rows of the transform matrix, but otherwise the PRS controller expects the pre-mirrored rotation value as input. (2) it flips components of scale subcontroller value. For example, a mirror in X inverts the first coordinate. But the rotation subcontroller in unaffected.
The result of all this is that you obtain an incorrect maxtrix composition when asking MaxScript for the transform parts. And you also get an incorrect composition when directly combining the position/rotation/scale subcontroller values.
What you need is to a better way to decompose the final (correct) transform in position/rotation/scale components. Here is a MaxScript based on the algorithm shown here. It returns an array with ScaleMatrix, RotationMatrix, PositionMatrix, so the original transform can be obtained again with (result[1] * result[2]) * result[3]
. Pass $.transform
as input.
fn matrixDecompose t =
(
trans = t.pos
trn = ( matrix3 [1,0,0] [0,1,0] [0,0,1] trans )
scaleX = length [ t[1][1], t[2][1], t[3][1] ]
scaleY = length [ t[1][2], t[2][2], t[3][2] ]
scalez = length [ t[1][3], t[2][3], t[3][3] ]
tempZ = cross t[1] t[2]
if( (dot tempZ t[3]) < 0 ) then
(
scaleX = -scaleX
t[1] = -t[1]
)
scl = ( matrix3 [scaleX,0,0] [0,scaleY,0] [0,0,scaleZ] [0,0,0] )
t[1] = normalize t[1]
t[2] = normalize t[2]
t[3] = normalize t[3]
rot = t.rotationPart as matrix3
#( scl, rot, trn )
)