Questions and comments to hal {at} artofmagic.co.uk

2.3 Models and Entities

2.2 Loading Models -> Models and Entities -> 2.4 Local and Global Transforms

Now of course what we really want to do is load and render a model. The models that we shall be using are made of multiple bits that can independently move.

There is in fact a hierarchy of meshes in the model. Each one can have different materials, effects and so on. Lets set up a small bit of code that rotates the head of our robot. The guns and eyes are also attached to the head so we would expect everything to spin around.

We add a private field:

 ModelBone hipBone;
 

We also need to assign that variable immediately after we load the mode:

if (loadAllContent)
{
    myModel = content.Load<Model>("Content/Models/Machines/Aggressor/Knight/Level3/agk3a38");
    hipBone = myModel.Bones["x3ds_head"];
}

The x3ds_head is the .x file frame that the head mesh, guns and eyes hang off from. Changing the transform of this bone will cause teh head to spin. Sorry about the duff variable name ("hipBone" you would have thought would be the "hip").

In the update we'll add some really noddy code to rotate the hipBone around the "Z" or upwards pointing axis. Now you're going to say "but surely Y is upwards and Z is into the screen?" well you're right of course. Our issue is that the model has to be transformed to make it stand upright. X is forwards, Y is right and Z is up in the local frame for the machine. We need to fix this problem later on properly!

hipBone.Transform = hipBone.Transform * Matrix.CreateRotationZ((float)gameTime.ElapsedGameTime.TotalMilliseconds * MathHelper.ToRadians(0.1f));

Well that's great - spinning head!

That gives us a nice spinning head. The CopyAbsoluteBoneTransformsTo function ensures that the correct parent transforms are also applied and so the top of the model spins as expected.  

The other important concept here is to split model and entity. Entity is going to be my name for "something in the world". It could be a piece of terrain, one of the robots, a missile, etc.

To start with we'll keep it simple. Entities will have a position, a rotation around each axis and a model. This last may be revoked - but for now all entities have a model!

Of course, a position and rotation around all axis can be encapsulated with a Transform.

class Entity
{
    private Matrix transform_;
    private Model model_;
}

We'll also provide a render method on the entity, a constructor and that'll do. The entire entity class looks like this:

class Entity
{
    private Matrix transform_;
    private Model model_;
    public Entity(Matrix transform, Model m)
    {
        transform_ = transform;
        model_ = m;
    }
    public Matrix Transform
    {
        get { return transform_; }
        set { transform_ = value; }
    }
    public void render(Vector3 cameraPosition, float aspectRatio)
    {
        // Copy any parent transforms.
        Matrix[] transforms = new Matrix[model_.Bones.Count];
        // The CopyAbsolute expands all the local transforms to global transforms.
        model_.CopyAbsoluteBoneTransformsTo(transforms);

        // Draw the model. A model can have multiple meshes, so loop.
        foreach (ModelMesh mesh in model_.Meshes)
        {
            // This is where the mesh orientation is set, as well as our camera and projection.
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();
                effect.World = transforms[mesh.ParentBone.Index]
                    * Matrix.CreateRotationZ(MathHelper.PiOver2)
                    * Matrix.CreateRotationX(MathHelper.PiOver2)
                    * transform_;
                effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
                effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
                    aspectRatio, 1.0f, 10000.0f);
            }
            // Draw the mesh, using the effects set above.
            mesh.Draw();
        }
    }
}

If we alter our main class to create two of these side by side and render them what do we see. Oh dear. An instancing bug! BOTH heads spin.

Why is that?

The answer is of course that both entities have a reference to the SAME model. Any change to one and the other changes as well.

What do we do about that? The key of course is to not change the underlying data in the model class. That should be considered "static" and to apply transforms as we render individual meshes/bones.

It's time to re-arrange our Entity and what an entity means.

 Next: 2.4 Local and Global Transforms

(c) Hal Angseesing 2007 All Rights Reserved.