Extending the ContentManager for Models

I don't know about everyone else, but usually the first thing I do after loading models is loop over their effects and set up their lighting. (Check Shawn's blog to read more about the standard lighting rig and per pixel lighting. ) The other day I thought of a neat way to tuck this code away a little, so I thought I'd share.

A subclass of ContentManager could easily handle the model setup code for us automatically. It could check what kind of content it's being asked to load, and if the content type is a Model, it can set up the model automatically.

first, make a subclass of ContentManager. I've called mine SmarterContentManager, because I couldn't think of a better name. 

     public class SmarterContentManager : ContentManager
    {
        public SmarterContentManager(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
        }
    } 

The next step is to override the Load function, and do the extra bit of set up for models. 

     public override T Load<T>(string assetName)
    {
        T t = base.Load<T>(assetName);
        Model model = t as Model;
        if (model != null)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (Effect e in mesh.Effects)
                {
                    BasicEffect basic = e as BasicEffect;
                    if (basic != null)
                    {
                        basic.EnableDefaultLighting();
                        basic.PreferPerPixelLighting = true;
                    }
                }
            }
        }
        return t;
    }

This might not be the best design for larger projects, because it ties the content loading part of your engine to graphics rendering. You might argue that a better place to set up this stuff is in some other class that manages lights and rendering state or something. But for me, this works just fine. Most of my XNA games just use BasicEffect with the default lighting rig.

One more cool thing you can put in this content manager is a bit of code to calculate the absolute bone transforms. (Shawn writes in this post about models and bones and what absolute bone transforms are.) For static models that don't animate, the bones won't change. This means that rather than recalculating the bone transforms every frame, I can calculate them once and save the results. You can do this by adding this function to the SmarterContentManager:

     public Model LoadModel(string assetName, out Matrix[] absoluteBoneTransforms)
    {
        Model model = Load<Model>(assetName);
        absoluteBoneTransforms = new Matrix[model.Bones.Count];
        model.CopyAbsoluteBoneTransformsTo(absoluteBoneTransforms);

        return model;
    }

Now, if I change my game to use the SmarterContentManager, I can just load my models like this:

 Model model;
Matrix[] bones;
 model = content.LoadModel("models\\asteroid1", out bones);

and draw them like this:

     foreach (ModelMesh mesh in model.Meshes)
    {
        foreach (BasicEffect basic in mesh.Effects)
        {
            basic.Projection = projection;
            basic.View = view;
            basic.World = bones[mesh.ParentBone.Index] * world;
        }
        mesh.Draw();
    }

Shawn's probably going to hassle me for linking to his blog three times in one post (What's the matter - don't you have any content of your own?) but oh well. Maybe someone will find this interesting.

Comments

  • Anonymous
    August 27, 2007
    Was just about to post saying there's no PreferPerPixelLighting on BasicEffect, but I see Shawn says it's in XNA 2. Thanks for the post, interesting stuff.