MOGRE InstancedGeometry        

This is a class I made to handle creation of instanced geometry.

You'll need a couple of things for it before you start:

  • instancing.material, instancing.cg, and instancing.glsl from the Ogre samples
  • a model to make instances of



Start by making a singleton class, however you normally would do it in your game. In mine, I have something called an LKernel that manages all of the singletons.

using System.Collections.Generic;
using Mogre;

	public class InstancedGeometryManager {
		// uses mesh name as a key
		IDictionary<string, InstancedGeometry> igeoms;
		// uses mesh name as a key
		IDictionary<string, IList<Transform>> transforms;
		// uses mesh name as a key
		IDictionary<string, Entity> ents;

		public InstancedGeometryManager() {
			igeoms = new Dictionary<string, InstancedGeometry>();
			transforms = new Dictionary<string, IList<Transform>>();
			ents = new Dictionary<string, Entity>();
		}

		// change this method's arguments to whatever you use to notify objects that you're unloading a level
		void OnLevelUnload(LevelChangedEventArgs eventArgs) {

		}

		// This is the part where we'll be adding an entity to the instanced geometry. The three arguments I have here just hold data from my files as to where stuff should go, what mesh it should use, what material it should have, etc. Change them as appropriate
		public void Add(ModelComponent mc, ThingBlock template, ModelBlock def) {
			
		}

		// this should be called whenever we're finished loading the entire level
		public void Build() {

		}

		// and this is a little class to hold where all of the entities are going to go.
		class Transform {
			public Vector3 Position { get; set; }
			public Quaternion Orientation { get; set; }
			public Vector3 Scale { get; set; }
		}
	}


This is the template we'll be using. Change everything appropriately so it works with the rest of your game first, before you start adding any more code in!

We'll start with the level unloading since that's easiest. Paste this into the OnLevelUnload method or whatever you called it.

ents.Clear();
			transforms.Clear();
			
			// this is just my way of getting the scene manager
			var sceneMgr = LKernel.GetG<SceneManager>();
			foreach (InstancedGeometry ig in igeoms.Values) {
				sceneMgr.DestroyInstancedGeometry(ig);
				ig.Dispose();
			}
			igeoms.Clear();


Should be pretty straightforward to figure out what that does.

Next up is the part where we "add" an entity. Now what's actually going on here is first we check to see whether we've already created an entity using the mesh you want. If we haven't, we create one. If we have, we don't do anything.

Next we get the position, orientation, and scale that the object will have when it's placed. These are stored in that Transform class so we can use them later.

Lastly we just add this Transform to our dictionary.

var sceneMgr = LKernel.GetG<SceneManager>();

			string meshName = def.GetStringProperty("mesh", null);

			// create our entity if it doesn't exist
			if (!ents.ContainsKey(meshName)) {
				Entity ent = sceneMgr.CreateEntity(mc.Name + mc.ID, meshName);
				ent.SetMaterialName(def.GetStringProperty("Material", ""));
				// then add it to our dictionary
				ents.Add(meshName, ent);
			}

			// get our transforms
			Vector3 pos = def.GetVectorProperty("position", Vector3.ZERO) + template.VectorTokens["position"];
			Quaternion orient = def.GetQuatProperty("orientation", Quaternion.IDENTITY) * template.GetQuatProperty("orientation", Quaternion.IDENTITY);
			Vector3 sca = def.GetVectorProperty("scale", Vector3.UNIT_SCALE);

			// put them in one class
			Transform trans = new Transform {
				Position = pos,
				Orientation = orient,
				Scale = sca,
			};

			// if the transforms dictionary doesn't contain the mesh yet, add a new one
			if (!transforms.ContainsKey(meshName)) {
				transforms.Add(meshName, new List<Transform>());
			}
			// then put our transform into the dictionary
			transforms[meshName].Add(trans);


And the last bit of code is probably the most important - the part where everything comes together, the Build method!

So here's what's going on:

  1. Get the entity
  2. Create the instanced geometry
  3. Add the entity 80 times or less to the instanced geometry
  4. Build the instanced geometry
  5. Add as many batches as you need for the geometry
  6. Go through the instanced objects of the batches and adept the position, scale and orientation
  7. Release the entity


Remember that an instanced geometry's batch can only do a maximum of 80 objects! If you have more than 80 objects, we'll need multiple batches.

var sceneMgr = LKernel.GetG<SceneManager>();

			// first create all of the InstancedGeometry objects
			foreach (var ent in ents) {
				// its name is the mesh name
				InstancedGeometry igeom = sceneMgr.CreateInstancedGeometry(ent.Key);
				igeom.CastShadows = false;

				// add the entities to our batch
				int numEnts = transforms[ent.Key].Count;

				// add the entity 80 times or less to the geometry
				for (int a = 0; a < (numEnts > 80 ? 80 : numEnts); a++) {
					igeom.AddEntity(ent.Value, Vector3.ZERO);
				}

				igeom.Origin = Vector3.ZERO;
				igeom.Build();

				// number of batch instances we need is number of entities / 80, since that's the maximum number of instances a batch can do
				for (int a = 0; a < (int) (numEnts / 80f); a++) {
					igeom.AddBatchInstance();
				}

				// now we need to go through each instanced object and update its transform
				int transformIndex = 0;
				var batchIterator = igeom.GetBatchInstanceIterator();
				// first we go through each batch
				foreach (InstancedGeometry.BatchInstance batch in batchIterator) {
					IList<Transform> entTransforms = transforms[ent.Key];

					// then go through each object in the batch
					var objectIterator = batch.GetObjectIterator();

					foreach (InstancedGeometry.InstancedObject obj in objectIterator) {
						if (transformIndex >= entTransforms.Count) {
							obj.Dispose();
							continue;
						}

						// get the transform we'll use for this object
						Transform t = entTransforms[transformIndex];

						// update the object with the transform
						obj.Position = t.Position;
						obj.Orientation = t.Orientation;
						obj.Scale = t.Scale;

						// then increment the index we use to get transforms
						transformIndex++;
					}
				}
				sceneMgr.DestroyEntity(ent.Value);
				ent.Value.Dispose();
			}


That's it for the code!

Now instancing itself doesn't work out of the box - you need to set up a shader to go with it. Luckily, ogre's already provided that for you, so all you need to do is point to the shader in your material.

Any entity that wants to use instancing needs to have this line in every pass of its material:

vertex_program_ref Instancing { }


That's it, you're done!