A Child Component

If you learn vector math now, the rest is super easy – crashovrd, 7/Aug/11

It’s been a perilous journey through which I have learnt an immense amount; I’m still in the “I know I’m ignorant phase” but working on becoming proficient. The reality for 3D Games Programming is, that Vector & Matrix Math (or Linear Algebra or perhaps just consider them the Vector Math Libraries)  is important and having an understanding of what’s going on is really important but it wont seem that way until you encounter this:

image

When you do encounter a wall like this, it can be a really trying time. This is especially true when you think it sounds super easy to begin with. Painfully, I knew that it really couldn’t be that many lines of code to actually achieve what I wanted to do but there in lies the rub of all programming, which lines and what goes in them.

Problem

  • ‘Group’ a child object to a parent object.
    • The child needs to be able to be positioned through the editor and then grouped when it’s at the desired location. (Positioning in the editor is done in world space)
    • The child needs to have a maintained distance from the origin of the parent.
    • The child should do everything that the parent does, think a fixed cannon on a space ship (originally I was describing a wheel and a car but that is probably a flawed analogy as you normally want wheels to move).
    • If the parent rotates, the child should rotate too. The relative distance needs to be maintained – again the gun on a space ship should always be in the same spot on the space ship.
    • If the parent moves, the child should move, at the same relative position.

Background

You know how there is this thing called world space:

image

There is normally lots of stuff in world space and each of the objects in world space, for a 3D game, can be thought of as having a location (Translation), which is a set of X, Y, Z floats.

Lets have a look at two objects in world space:

image

 

For the purposes of explanation we are going to assume that the Green arrow is Y and it represents up, Blue is Z and Red is X. These two objects currently have no rotation value specified. (As an aside, Blender actually has these directions swapped around; the colours for the arrows are the same but Blender uses Z as the direction away from the floor.)

What does that mean? Obviously it means that they have no rotation specified but this is because they align with the world space. So if we were to decompose the Matrix of one of these cubes we could then just put it back in to the same spot, facing the same direction, without including the rotation in the equation. Lets have a look at an example component that does just that.

//Rebuilds the objects world matrix, without rotation, which aligns the child with the world origin [Serializable]
public class Component_LockToWorldRotation : BaseComponentAutoSerialization<ISceneEntity>
{
    public override void OnUpdate(GameTime gameTime)
    {
        // Get a sceneobject from the ParentObject SceneObject sceneobject = (SceneObject)ParentObject;

        // Decompose Child Matrix Quaternion childRotation = new Quaternion();
        Vector3 childLocation = new Vector3();
        Vector3 childScale = new Vector3();
        sceneobject.World.Decompose(out childScale, out childRotation, out childLocation);

        // Rebuild the matrix without the rotation sceneobject.World = Matrix.CreateScale(childScale) * (Matrix.CreateTranslation(childLocation));
    }
}

So what happens when you put this component on an object? Well if the object isn’t aligned to the world origin, it will rotate the object back to the orientation in line with the world. If the cubes were already aligned to the world orientation, then from a user perspective nothing changes – but if you try and rotate the object in the editor you will now notice that you can’t – because every update any rotation on the child Matrix is wiped out and the child is realigned to the world Matrix.

Distance between 2 Objects in World Space

To get the distance between 2 Objects in World Space, we just need to subtract the Translation of the two Objects.

SceneObject childObject = (SceneObject)ParentObject;
Vector3 _parentOffset;
_parentOffset = childObject.World.Translation - parentObject.World.Translation;

The _parentOffset now stores the difference between the parent and child objects (as a world space set of coordinates). With this we can now build a Child Matrix, that includes the offset value in world space.

// Decompose Child Matrix Quaternion childRotation = new Quaternion();
Vector3 childLocation = new Vector3();
Vector3 childScale = new Vector3();
childObject.World.Decompose(out childScale, out childRotation, out childLocation);

// Decompose Parent Matrix Quaternion parentQ = new Quaternion();
Vector3 parentLocation = new Vector3();
Vector3 parentScale = new Vector3();
parentObject.World.Decompose(out parentScale, out parentQ, out parentLocation);

// Multiple the offset with the parents location to get the product Matrix offsetWorldLocation = Matrix.CreateTranslation(_parentOffset) * Matrix.CreateTranslation(parentLocation);

// Rebuild the Matrix for the child with the new location sceneobject.World = Matrix.CreateScale(childScale) * (Matrix.CreateFromQuaternion(childRotation) * (offsetWorldLocation));

If you run this bit of code up and see what’s happening, you will notice that if we move a parent object around on the screen, the child will just follow at the same distance, in world space. There in lies the rub, see the child only knows that it is a fixed world distance away based on our X,Y,Z location – what’s not included in the equation is any information about the parents rotation. Therefore, if the parent rotates to the left and moves forward, the child will follow the same movement of the parent, it will remain the same distance from the parent in world space but it will not be offset by the parents rotation – this means that if the gun on the space ship was originally pointing out the front of the space ship and the space ship then rotated left, the gun would then be pointing out the right hand side of the space ship.

I guess, maybe, a useful scenario for knowing this could be a gun turret. You want the gun on the turret to stay attached to the turret and move around with the turret but you want it to have it’s own independent rotation. Currently the above code would only work if the parent star ships origin and main gun turret lined up (their centre’s were at the same position in world space).

Object (Local) Space

We have covered how objects in world space relate to each other but that doesn’t help us in too many situations  & it certainly doesn’t help when we want a child object to always have the same relative distance and location to a parent object (ship’s gun on the right, drivers side wheel, sword being held by a person, etc).

To be able to achieve this, what we need to know is: where is the child object located, in the parents object space?

image

Two Cubes, again. This time we have them selected & highlighting their local space orientation. Notice that the cube on the left is still oriented in line with world space (it’s not moved). However the cube on the right has now been rotated & no longer aligns with world space (observe how the red and blue arrows don’t line up).

To get the distance from the left cube to the right cube, we can just use the process above – as the distance in world space would be the same as the distance in the object (local) space.

But for the cube on the right, this isn’t the same case. The cube on the right’s orientation means that from a local (object) space, any world space measurement for the distance to the left cube is only correct while the cube on the right doesn’t rotate.

What? Slow Down.

Someone (as in a single person, not every one) who reads this may ask:

“If we get a distance in world space, you showed us how to do that already, why would the world distance change (from the Cube on the right to the Cube on the left) if the cube on the right rotates; isn’t that exactly what you don’t want to happen, you want to maintain the same distance as the parent object rotates?”

Of course we want to maintain the same distance but the issue is the measurement of this distance, in local object space, is different to the measurement from world space, for any object which isn’t aligned to the world space orientation.

Since we are just working with a rotation around a single axis (in the example above, rotating around the Y) we can look at this in the bounds of a 2 dimensional problem to start with. In this example below, the Red is X and the Green is Y, so we are going to assume that we are actually rotating around Z.

image

This image shows 2 cubes, the blue cube at position 1.5x 1.5y (We measure to the origin of the object) and the red cube is at 3.5x 4.5y. So the world distance between the two objects is the same and is just a straight subtraction.

World Distance:
3.5 – 1.5 = x;
4.5 – 1.5 = y;

But if you want to measure the distance and store it as an offset, between the red object to the blue object, the order of the subtraction is important.

An Offset

I would like to take the brief moment to describe an offset in the way that I am using the term. I am refereeing to a set of numbers one each for X,Y & Z that will store a relative distance from one object to another. The offset is relative based on which object the offset is calculated from. Lets call the object we are calculating the offset from as the parent and the second object the child. Once we have the offset calculated, as a measure from the Parent to the Child, we can then repeatedly find out where the child is by using the parents location (X,Y&Z) and adding the offset to this location.

So our example would go like this:

Blue object as the parent; the relative red offset (this is the same as the world offset):
3.5 – 1.5 = 2;
4.5 – 1.5 = 3;

Red object as the parent; the relative blue offset:
1.5 – 3.5 = –2;
1.5 – 4.5 = –3;

So in the example where the Red object is the parent, we can find out where the blue object is by just adding the offset to the red object, with the result being blues location in world space.

e.g. If we move the Red object by an additional 2X & 1Y (Red’s new location would be 5.5x 5.5y), the Blues object location would be:
5.5 + –2 = 3.5;
5.5 + –3 = 2.5;

Nothing difficult so far but this only covers the scenario where the parent moves around and keeps the same orientation (Forwards, backwards and strafe, with no mouse look)

Rotation as any other name

If we rotate the objects and try to use the Math above we will find that our solution above isn’t going to work. Lets assume that the Blue object is the parent and if we rotate it by 45 degrees we would see this:

image

The Blue object hasn’t moved location, it’s just rotated. The Blue object hasn’t moved so adding the world calculated offset to the blue object would see the red object in the same location, with the blue object just being rotated.

What we want is this:

image

The red object has moved the correct relative distance – however if we we measure the distance that the object has moved in world space – it’s not the same as the offset amount.

Back to the World of 3D

The answer is: Matrix.Invert
Let’s have a look at how to use it and store the offset in a Matrix:

// Get the distance between the child and the parent which we keep as the offset // Inversing the ParentMatrix and multiplying it by the child's matrix gives an offset // The offset is stored as a relative xyz, based on the parents object space _ParentMatrixOffset = childObject.World * Matrix.Invert(parentObject.World);

_ParentMatrixOffset now stores a relative offset for the child, from the parent.

Using the offset now to position the child object, based on the parents location and rotation is as simple as:

// Move the child to result of the Matrix Offset (in coordinates relevant to the parents // object space) multiplied with the parents world. childObject.World = Matrix.Multiply(_ParentMatrixOffset, parentObject.World);

Seriously, that’s it.

Using this we can now build a component that we can add to any child, specify the Parent Objects name and have the child maintain the offset each update, keeping it in sync with the parent.

A Child Component

Add this component to a child, type the name of the parent and then use the group setup mode to switch between a setup mode or maintaining the offset.

[Serializable]
[EditorCreatedObject]
public class Component_Child_AnyParent : BaseComponentAutoSerialization<ISceneEntity>
{
    // Where the offset in the parents object space is stored Matrix _ParentMatrixOffset;

    bool _GroupSetupMode = false;
    string _ParentName = "";

    [SerializeMember]
    [EditorProperty(true, Description = "Group Setup Mode", ToolTipText = "This is to allow turning on or off grouping setup. False, the group moves based on setup distance. True, no children moves will be actioned.")]
    public bool GroupSetupMode
    {
        get { return _GroupSetupMode; }
        set { _GroupSetupMode = value; }
    }

    [SerializeMember]
    [EditorProperty(true, Description = "Parent Name", ToolTipText = "The name of the parent model that we are trying to connect to.")]
    public string ParentName
    {
        get { return _ParentName; }
        set {_ParentName = value;
                SceneObject copyBase;
                SceneInterface.ActiveSceneInterface.ObjectManager.Find(_ParentName, out copyBase);
                if (copyBase == null)
                {
                    SystemConsole.AddMessage("Could not connect to " + _ParentName, 1);
                }
        }
    }

    public override void OnUpdate(GameTime gameTime)
    {
        // Get a sceneobject from the ParentObject SceneObject sceneObject = (SceneObject)ParentObject;

        // A SceneObject to store the parents information for this update SceneObject parentObject = null;

        // Make sure we have specified a parent if (_ParentName != null)
        {
            // Get the parent object based on the name SceneInterface.ActiveSceneInterface.ObjectManager.Find(_ParentName, out parentObject);
        }

        // This will switch between getting the offset and saving or updating the child. // It will always execute the first time to get the current relevant offset and then // use that from then on (the comparison against a new matrix). // Or it will save the offset if the object is currently in Group Setup Mode if (_GroupSetupMode || _ParentMatrixOffset == new Matrix())
        {
            // Check that we have been able to connect to and find the correct parent. if (parentObject != null)
            {
                // Get the distance between the child and the parent which we keep as the offset // Inversing the ParentMatrix and multiplying it by the child's matrix gives an offset // The offset is stored as a relative xyz, based on the parents object space _ParentMatrixOffset = sceneObject.World * Matrix.Invert(parentObject.World);
            }   

        }
        else {
            // Check that we have been able to connect to and find the correct parent. if (parentObject != null)
            {
                // Move the child to result of the Matrix Offset (in coordinates relevant to the parents // object space) multiplied with the parents world. sceneObject.World = Matrix.Multiply(_ParentMatrixOffset, parentObject.World);
            }
        }
    }
}

Copy and past this in to your project and try it out for yourself.

A Little Demo

Lets have a look at how this works:

The Child Component in Action

 

Special Thanks

A big thanks to Flashed from #XNA on EFNet for patiently helping me out through this one & his insight to Matrix.Invert

Leave a Reply

Your email address will not be published. Required fields are marked *