[Closed] Matrices and coordinate systems
I’m typing this a bit quickly as I’m getting a bit desperate and didn’t have a lot of time to write this. I might be able to provide some visual information later on.
I'm working on a script, with both maxscript and C#, to parse an LDraw text file (3D LEGO) to import parts from my own library and assemble a LEGO model. However, LDraw uses a different coordinate system, -Z forward and -Y up. I've looked up references to adjust one transformation matrix to another. I have been able to set it up with the following formula:
LDMatrix = (matrix3 [1,0,0] [0,0,-1] [0,1,0] [0,0,0])
--LDMatrix = (matrix3 [1,0,0] [0,0,1] [0,-1,0] [0,0,0])
$.transform = LDMatrix * matrixT * inverse LDMatrix
I got the LDMatrix values by creating a point helper with axis tripod and rotate it until the Z and Y axes were correct. The top variable has -Z forward and -Y up while the second one has +Y up. For the top matrix, I mirrored the point helper so the Y axis would invert. The "matrixT" variable comes from the C# library which is the unedited transformation matrix extracted from the LDraw file.
The strange thing is that certain models import correctly, while others, especially larger, more complex ones, have parts rotated incorrectly.
[img] http://www.ldraw.org/uploads/images/Articles/LDrawCoordsSystem.png [/img]
^This is the coordinate system used by LDraw. In the software I use to build LEGO models (LDCad) the -Z axis is considered forward.
Are the ones that get messed up part of a hierarchy? Are you parsing/branching down the hierarchy while you do your coordinate transformation?
No, neither in the LDraw file, nor in Max. Some bricks are part of a “submodel” (basically a group, similar to Xref in Max), but this also happens to ungrouped parts. It also happens mostly with the rotations, it rarely, if ever, happens with positions. As far as I encountered it, I have to rotate a part either 180 degrees on one axis, or 90 degrees on two axes each. This is not a huge problem, but this is a pain for larger or complex models.
Right now, I’ve managed to fix the position problems by inverting one axis. Only the rotations are a problem, and it only happens in certain situations. The models themselves are correct, as I’ve compared them using the identity matrices. If I place the parts in simple positions and rotations, there’s no problem and when I parse the file in Max, all parts match.
Your coordinate transformation is correct (Basically a -90 degree rotation about the +X axis). My suggestion would be to try and create the smallest grouping/hierarchy that causes this issue and try and understand it. In 3ds Max a Group is like parenting parts to an invisible dummy helper. Are the transformations being done on the hidden group master node? I’m sure you’re aware that children nodes inherit the transforms of their parent nodes.
I wrote a script that would fix a skeletal hierarchy after importing; because the importer plugin did not do a proper coordinate transformation– it used a +Y axis up, +Z forward coordinate system (Basically a +90 degree rotation about the +X axis).
Here is a link to that script and you could see (with minor modifications to the transform matrix, etc.) if it works for you.
jkhub.org/files/file/2230-softimage-dotxsi-import-v17-plug-in-cleaner/
I’ve uploaded an example. There is no hierarchy at all, so no parent/child relationships, neither in the LDraw program, nor in Max. It appears that for some parts, the X and Y rotation axes are swapped, but not for all parts, yet the pivot points are correct. The notable parts are the 1×2 tiles.
it would be more helpful if you uploaded an example max file instead of only pictures. (max 2014 version please)
Before I do so, I want to ask what would be the point? Once the tool is done with assembly, there’s nothing wrong in Max itself. The problem occurs during the transition between two separate programs. I’ve uploaded pictures to show a comparison. If you want to see the problem for yourself, you’d need to install the LDraw tools, my custom parts library and my importer tool. The Max file itself is useless.
On that note, as I’ve stated above (ignore my ramblings) I’ve analyzed some parts and it appears that the world rotations are applied locally in Max, which suggests that Max is applying the transform in local space rather than world space. How can I change that?
That’s the odd thing, they’re not. Every part is modeled on the same axes. Comparing the front, top and side axes in LDraw, then translating that to Max. I also import the parts from LDraw to .obj to Max as a guide to make my own optimized versions. While the shapes, placements and scales are correct, the geometry itself is corrupt as faces are flipped and vertices/edges aren’t welded, as well as inconsistencies with polycount, which means it’s easier to just remodel them.
It appears, though I’m not sure as I’m far too confused, that there might be an issue with gimbal rotations. It would take too much pictures to actually show it in detail, but when rotating a part in LDraw in a certain way, it won’t rotate properly in Max, causing different angles in LDraw (like rotating a part 180 degrees) to appear the same as before, but with a 180 degree around its local Z axis. Or it would rotate around a different axis, such as Z to X, X to Y or Y to Z, or it would invert the rotation, so 90 becomes -90, thus rotating in the opposite direction.
I’m not sure if this can be solved so easily as I would hope, but it’s also very confusing for me to explain, even with pictures.
EDIT: I don’t know if would make it clearer, but it appears rotations from the world in LDraw are applied locally in Max, rather than the world, so a world X rotation in LDraw would be local X rotation in Max.
Excerpt from an older 3ds Max SDK Reference manual section “The Node and Object Offset Transformations”:
To retrieve what is considered the local transformation (the transformation of a node relative to its parent) you must perform some matrix arithmetic on the nodes world space transformation (NodeTM). This is because there is not a local transformation matrix of a node stored by 3ds Max.
What is often considered the local transformation is the transformation matrix of the node relative to its parent. However, the transformation of a node relative to its parent may be some function of the node’s parent’s transform. For example, a transform controller takes the parent’s TM and modifies it. When a node evaluates itself it takes the parent’s TM and passes it as an argument when it calls GetValue() on its transform controller. The task of a transform controller in its implementation of GetValue() is to modify this matrix. It applies its transformation to the parent transformation passed in.
In some cases this modification may be just a simple pre-multiplication by what is considered the local transformation matrix. But it may be some other more-complicated process. For example, if rotation or scale inheritance is turned off, the transform controller takes the parent’s matrix and perhaps removes rotation from it, or removes scaling from it, and then applies itself. As another example, it may use the parent’s position as a function to derive rotation – as in a Look At controller. Thus, what is considered the local transformation is a function of the transform controller and is not stored by 3ds Max.
What 3ds Max does store is the node’s world space transformation matrix (NodeTM). This matrix includes the parent’s transformation. To understand how the local transformation can be extracted from the world space transformation consider the following:
Any transformation is made up by starting with the node, and then multiply it by its parent, and then by its parent, and so on, all the way through the ancestors. This is shown below:
NodeWorldTM = NodeLocalTM * ParentLocalTM * ParentLocalTM * ParentLocalTM, etc.
The parents world transformation is equal to the product of all its ancestors, so:
ParentWorldTM = ParentLocalTM * ParentsLocalTM * ParentsLocalTM, etc.
The nodes world transform can then be expressed as:
NodeWorldTM = NodesLocalTM * ParentWorldTM.
If we multiply both sides of this equation by the Inverse of the ParentsWorldTM and simplify we get:
NodeLocalTM = NodeWorldTM * Inverse(ParentWorldTM)
So, to retrieve what is considered the local transformation you must get the parent’s transform and multiply the node’s transform by the inverse of the parent.
I recommend reading the entire section and also the following section “Node Linking and Grouping.” The Maxscript reference has a similar discussion but it’s not as in-depth as in the SDK.
Edit: I have also attached a PDF file (created by Kees Rijnen) discussing 3ds Max Rotations.
Well, here’s news: I did it.
Short explanation: Basically, something is off with the X axis. Previously, I already had to invert the X position to get the positions of the part correct. It turns out that I also had to invert the X rotation.
Full explanation: So I decided to experiment with the matrix and break it down into pieces to test each individual piece. First I took out the position and assigned it to the part’s position. Then, I took out the rotation and assigned that separately. This messed everything up, so I assigned the rotation first, and then the position. Somehow, the rotations were off compared to before. I inverted the rotation, which resulted again in the original problem. I then broke the rotation down into its components. I inverted each axis; X and Y did nothing, but Z did. I then turned back and inverted the Y axis too, which fixed it. I rewrote everything and inverted the rotation entirely, then inverted the X axis back and finally assign the rotation to the part, followed by the position.
Now, no matter what model I throw at it, all parts are positioned and rotated correctly, from the simple test models I made for this, to the complex spaceship I made before.
Since I had to invert the X axis of both the position and rotation, and changing the matrix itself didn’t work, it looks like something is wrong with the X axis. If possible, can anyone know why this would happen? Has it something to do with the fact that LDraw has negative Y up?
Regardless, the problem is fixed now.
Are any of those lego parts, mirrored in LDRAW, i.e., have negative scale part [-1,-1,-1] in 3ds Max (or does any axis have negative scaling)?
Look at that Kees’ PDF file I attached… it states:
Left-handed vs. right-handed
From the online help:
Rotation properties and in fact all rotation-related functions in
MAXScript reflect the direction convention used in the 3ds max user
interface. This convention is the right-hand rule, namely, positive
angles rotate counter-clockwise about positive axes. Internally,
however, 3ds max stores rotations in node matrices using the left-hand
rule. It is important to remember this inversion when working directly
with node transform matrices using the transform and objectTransform
properties. Youll need to invert rotations (for example, by multiplying
axes by [-1,-1,-1] or using the inverse() function on quaternions) when
mixing them with 3ds max and MAXScript standard rotations
e.g.r = $foo.transform.rotationPart
$baz.rotation = inverse rSo, in a nutshell:
- All UI-related rotation values are right-handed (take your right hand,
stick your thumb up like the divers’ sign for going to the surface, curl
your fingers: your thumb will be the rotation axis, your fingers will
point in the positive direction of rotation)- Internal rotations of 3dsmax (e.g. node.transform.rotation) are
left-handed- Use the inverse to go from one to the other
So is that what you’re saying you did to fix the rotations?
As far as I understand those explanations, yes I did. Except only on one axis instead of all three.
You didn’t answer my first question… were any parts mirrored into place in LDRAW? Do any of those “offending” imported parts have negative scaling on them (any or all axes) (Before applying your recent script fixes…)?
$.scale = [?,?,?]
Oh, sorry. No, none of them were mirrored. Better yet, you can’t mirror parts in LDraw, at least, you don’t have scale or mirror options and I didn’t see any inverted scaling (this would also cause errors in the program as it is written to avoid such things). And my own parts have their xforms reset before exporting them to .obj’s.