Parent-Child Math

Hi. I want to learn how this process is calculated. I have 2 object. “parent” and “child”

Parent Position: (0.75, 0, 0)
Parent Rotation: (0, 0, 0)
Parent Scale: (1, 1, 1)

Parent Model Matrix(localToWorldMatrix):
1.00000 0.00000 0.00000 0.75000
0.00000 1.00000 0.00000 0.00000
0.00000 0.00000 1.00000 0.00000
0.00000 0.00000 0.00000 1.00000

Child Position: (-1.5, 0, 0) (not local)
Child Rotation: (0, 0, 0) (not local)
Child Scale: (1, 1, 1) (not local)

Child Model Matrix(localToWorldMatrix):
1.00000 0.00000 0.00000 -1.50000
0.00000 1.00000 0.00000 0.00000
0.00000 0.00000 1.00000 0.00000
0.00000 0.00000 0.00000 1.00000

If i rotate parent object by 90 degree on y axis, new child matrix would be:
0.00000 0.00000 1.00000 0.75000
0.00000 1.00000 0.00000 0.00000
-1.00000 0.00000 0.00000 2.25000
0.00000 0.00000 0.00000 1.00000

new parent matrix:
0.00000 0.00000 1.00000 0.75000
0.00000 1.00000 0.00000 0.00000
-1.00000 0.00000 0.00000 0.00000
0.00000 0.00000 0.00000 1.00000

How is the last child matrix formed? I know that to compute the matrix of child object we need to do childModelMatrix * parentModelMatrix. However, when we do this, it turns out like this:
0.00000 0.00000 1.00000 -0.75000
0.00000 1.00000 0.00000 0.00000
-1.00000 0.00000 0.00000 0.00000
0.00000 0.00000 0.00000 1.00000

Am i missing something? I tried worldToLocalMatrix instead of localToWorldMatrix but nothing ‘importantly’ changed.

I think it’s the other way: parentMatrix*childMatrix. That applies the child rotation using the parent’s local axis. You can see that’s how it works by rotating a child by hand – spinning on y is on the parent’s y. The other way rotated the child around the global axis.

1 Like

parentMatrix*childMatrix result:
0.00000 0.00000 1.00000 0.75000
0.00000 1.00000 0.00000 0.00000
-1.00000 0.00000 0.00000 1.50000
0.00000 0.00000 0.00000 1.00000

Still i dont understand value of 2.25 :eyes:

That 2.25 in the “expected” matrix looks wrong. The 4th column are offsets… You can’t take an arrow going 0.75 in one direction and 1.5 in another, spin it, and have it go 2.25 somehow.

Just checking: this is for fun, fight? I can’t think of anything in Unity you need matrixes for. It does all of that for you, and if you need to fake a child, quaternions are more common.

When you ROTATE the parent, the child MOVES position, because the child had an offset from the parent
2.25 is the difference between .75 and -1.5

5 Likes

The child offset is from the parent, I think (I haven’t used rotation matrixes in forever, since you don’t need them in Unity) I know you wrote “not local” but the matrix math assumes it is. In that huge picture the red dot should be -1.5 from the blue one, at -0.75.

Thinking of it another way. The child -1.5 was along x. After a 90-degree spin, it’s now 1.5 along z – which is what your last matrix says. Except you got +1.5 instead of negative? Hmmm…Unity’s left-handed rotations? Or is +z towards us (I have to look it up every time)?.

Your question seems a bit confusing. You said your parent rotation is (0,0,0) but the parent matrix you showed is already rotated 90°. What’s also not clear, is your parent-child relation setup before you do the rotation or do you parent the child after the rotation of the parent. Unity automatically recalculates the child position / rotation when you parent / unparent a child. So your information is at least inconsistent and not complete to actually answer your question ^^. Also we would assume that the parent is a root object, right? So no further parents? Keep in mind that the localToWorld matrix brings you from local space to worldspace. Also note that the object itself lives inside it’s own local space at position (0,0,0).

Like others have said you can’t really arrive at the outcome you presented, at least not with the information provided. So there’s either something missing, or you somehow got the wrong numbers, or both.

My guess is that you originally had the child and parent seperate. So the child as well as the parent are defined in worldspace. So both matrices represent their local matrices. When you parent the child to the parent, the child does not keep it’s local matrix. Unity recalculates the childs position, rotation ans scale in order to keep the same worldspace position and orientation. That means in order to get the childs new local position and rotation we have to use the inverse matrix of the parent in order to figure out where the child should be inside the local space of the parent after the parenting.

Unity now has an explicit SetParent method which allows you to disable this recalculation. If you use this method with worldPositionStays set to false the child would keep it’s local matrix and the result would be what you would expect. Of course when you parent a child that way, the child would change it’s actual worldspace position and rotation since you just switched the “universe” the child lives in.

Here’s a breakdown of what’s happening when you parent your child to your parent:

   // calculate inverse of the parent
   0   0   1   0.75     1   0   0   0 // setup augmented matrix
   0   1   0   0        0   1   0   0 // use gaussian elimination
  -1   0   0   0        0   0   1   0
   0   0   0   1        0   0   0   1
   ----------------------------------
   1   0   0   0        0   0  -1   0 // swapped #1 and #3 to get
   0   1   0   0        0   1   0   0 // upper triangle matrix
   0   0   1   0.75     1   0   0   0
   0   0   0   1        0   0   0   1
   ----------------------------------
   1   0   0   0        0   0  -1   0
   0   1   0   0        0   1   0   0
   0   0   1   0        1   0   0  -0.75 // subtracted #4 multiplied by 0.75
   0   0   0   1        0   0   0   1

   // so the inverse of the parent matrix is

   ( 0   0  -1   0    )
   ( 0   1   0   0    )
   ( 1   0   0  -0.75 )
   ( 0   0   0   1    )

   // calculate new local child matrix by doing
   // P' * C
   ( 0   0  -1   0    )   ( 1   0   0  -1.5 )   ( 0   0  -1   0    )
   ( 0   1   0   0    ) * ( 0   1   0   0   ) = ( 0   1   0   0    )
   ( 1   0   0  -0.75 )   ( 0   0   1   0   )   ( 1   0   0  -2.25 )
   ( 0   0   0   1    )   ( 0   0   0   1   )   ( 0   0   0   1    )

   // this gives us the new local child matrix. In other words to counteract
   // the parent rotation of 90° clockwise, the child need to be rotated 90°
   // counter clockwise. Also in order for the child to keep it's worldspace
   // position it need to be positioned inside the parent at -2.25.

So the new local space child position is (0, 0, -2.25) inside the local space of the parent. If you now multiply the original parent matrix with the new local child matrix, you get the same local to world matrix as before (which is the expected result since the child hasn’t been moved or rotated at all relative to worldspace).

   // doing P * newC == oldC
   (  0   0   1   0.75 )   ( 0   0  -1   0    )   ( 1   0   0  -1.5 )
   (  0   1   0   0    ) * ( 0   1   0   0    ) = ( 0   1   0   0   )
   ( -1   0   0   0    )   ( 1   0   0  -2.25 )   ( 0   0   1   0   )
   (  0   0   0   1    )   ( 0   0   0   1    )   ( 0   0   0   1   )
1 Like

If you have trouble understanding linear algebra, I recommend to watch the 3b1b series on linear algebra. Of course it would take some time and you may need to rewatch some of them or maybe skip some depending on your pre-knowledge and level of understanding. While the concepts of vectors an matrices stay the same no matter the rank of the matrix, he doesn’t specifically mention 4x4 matrices and homogeneous coordinates. This is of course essential to actually represent translation or perspective projection with a matrix.

Though the concept of homogeneous coordinates is actually extremely simple. There are two ways you can view them. First we just imagine that we work in a 4d extension of the 3d space. So all of our 3d space is simply offset exactly 1 unit from the origin in the new “w” direction. So everything happens in a 3d hyperplane. You can easily imagine the 2d equivalent where the 2d space we’re working in is offset in 3d space by one unit along z. A “translation” in 3d space can be represented by a shear transformation in 4d space. That’s what’s actually happening mathematically.

However you could of course just ignore the whole “4d” thing and just think about w and the 4th column as an “extension”. By setting the w component of our vectors to 1 we can simply abuse the nature of matrix multiplication to allow translation since the last column is multiplied by that “1” and is simply added to the result which essentially allows us to do a “vector addtion” with a matrix. Though when it comes to perspective projection it becomes a bit more tricky to visualize what’s happening. I’ve written this matrix crash course on UA which explains the basics of matrices and how the projection works.

1 Like

This is not for fun, i’m making my own engine. At one point I got stuck. I created a post in StackOverflow, but no one writes. That’s why I’m trying to understand from unity myself.

https://stackoverflow.com/questions/64650049/opengl-parent-child-system

@Owen-Reynolds
@Bunny83

Sorry for misunderstanding. Parent matrix I wrote is rotated 90 degrees. More clearly:
Parent Matrix:
1.00000 0.00000 0.00000 0.75000
0.00000 1.00000 0.00000 0.00000
0.00000 0.00000 1.00000 0.00000
0.00000 0.00000 0.00000 1.00000

Child Matrix:
1.00000 0.00000 0.00000 -1.50000
0.00000 1.00000 0.00000 0.00000
0.00000 0.00000 1.00000 0.00000
0.00000 0.00000 0.00000 1.00000

If i rotate parent object by 90 degree on y axis, new parent matrix:
0.00000 0.00000 1.00000 0.75000
0.00000 1.00000 0.00000 0.00000
-1.00000 0.00000 0.00000 0.00000
0.00000 0.00000 0.00000 1.00000

So calculations should be made on ‘new parent matrix’.

I’m gonna edit post

Thank you for the explanation. I did the parent-child calculations as you said before.
https://github.com/requizm/cs2d_vscode/blob/b76cc901ad71cb5d8639ea92cf553a6fe94e744b/src/Models/GameObject.cpp#L79

And i did it. But when I removed that parent link, I could not leave the child object in its current position, it would revert to its old form. My knowledge of that time was not enough for this. It happened 1 year ago. I understand many things better now. Thanks again. I probably do the parent-child system using the ‘inverse matrix’ method.

Anyway, i wanted to do it another way. Most sources on the internet said, multiply two matrices directly. So i tried but i didn’t.

Yes, that’s what you do when you have a normal kinematic chain. However if you parent a child afterwards you have defined your child in worldspace and want it to be defined in a different space after the parenting. As I said SetParent(parentTransform, false) does what you had in mind. It literally just takes the current position, rotation and scale and uses them as local space coordinates in the new parent. However that means that the child will of course change position, rotation and scale depending on the state of the parent.

If an object “A” is located at (0,0,5) in worldspace and you have an object B at (0,0,0) that is rotated 90° clockwise, after parenting A to B (so A becomes a child of B), when you use “false” in SetParent the child would move to the worldspace position (-5,0,0). However the child still has the local space position of (0,0,5), just that it’s now relative to the new parent and not worldspace.

On the other hand if you use SetParent with “true” the object A would keep it’s worldspace position, but it’s local position would change to (5,0,0).

Keep in mind that Unity does track the objects position rotation and scale actually through the localPosition, localRotation, localScale and “parent” properties. Those actually define where the object is located and how it’s rotated. As already mentioned when you parent an object with worldPositionStays set to true those properties will actually change

1 Like

I figured out the flipped sign on z in that last 4x4 matrix. It’s because +y goes down, not up. I regular coordinate system has x&y forming a flat L with +z going up. When you spin that to x&z lying flat in an L, +y happens to be going down. That means a 90-degees rotation on y spins the opposite direction, clockwise (not counterclockwise in the picture, which is what would happen if +y were going up).

It’s confusing since Unity has +y going up, not down, in that situation. But Unity uses left-handed coordinates.

That’s has got me wondering how much extra help Unity will be. Ideally you’d make a parent and child. Then you’d reproduce that in matrix math (can create the matrixes from parent’s rotation quaternion and position and again for the child’s locals)., If they exactly overlap, you got it right. Except I’m not sure if Unity’s left-handed coordinates would break that. Unity also rotates clockwise, from the top, but maybe the quaternion to matrix3x3 handles that?. Basically, it’s the problem you already have: if your guess and what Unity says don’t match, you won’t know which one is wrong (or if they both are).