Simple Scene Management

A common question on game programming forums is something like:

I have a bunch of objects moving around the world; how do I render them? Do I have a Draw() function on each of them and let them call glVertexPointer() themselves?

Here's my proto-typical answer, assuming you know about the Interface way of structuring code, and some other common software patterns:

You typically want objects implementing your Viewable interface to go into some scene graph (like an octree).

Then, you use a Visitor to visit all Viewables that intersect the viewing frustum.

The protocol for Viewable is typically to return a description of some sort (i e, group of geometry, material, and pose/matrix state) which the renderer uses to actually render the thing.

The reason you don't want objects calling renderer->setTexture() and similar functions directly is that then, if you change your rendering to require multi-passing (for shadows, reflection, etc) then you have to update ALL the objects, if the objects do the rendering.

Having objects describe themselves with multiples of geometry-material-state is much more flexible, as you can easily throw that at mirroring, cube mapping, shadow maps, shadow volumes, cel shading, and whatever else you care about implementing.

// An array of struct Mesh is how a Viewable describes what it looks like.
struct Mesh {
  double time;
  void const * vertexData;
  unsigned int vertexFormat;
  unsigned int numVertices;
  unsigned short const * indexData;
  unsigned int numIndices;
  Texture * texture;
  Matrix43 objectPosition;
  Matrix43 * skeletonPose;
  unsigned int numBones;
};

class IAllViewables;

class IViewable {
public:
  // AllViewables may want to tell viewables about their presence/absence in 
  // the scene graph.
  virtual void onAdded( AllViewables * av ) = 0;
  virtual void onRemoved( AllViewables * av ) = 0;
  // I'm assuming the AllViewables interface does bounding-sphere culling
  virtual void location( Vector3 & outPos, float & outRadius ) = 0;
  // The AllViewables interface will call the ViewableVisitor so that it can call 
  // this function on the Viewable, for all Viewables visited.
  virtual void geometryAtTime( double time, Mesh const *& outMeshes, int & outCount ) = 0;
};

class IViewableVisitor;

struct Frustum {
  Plane left, right, top, bottom, hither, yon;
};

class IAllViewables {
public:
  // Viewables will call back to let the AllViewables interface know when 
  // they move.
  virtual void onMoved( Viewable * v ) = 0;
  // Users of AllViewables will add and remove Viewers as appropriate.
  virtual void add( Viewable * v ) = 0;
  virtual void remove( Viewable * v ) = 0;
  // Someone wanting to view the entire scene, pass in a visitor to 
  // call back with all viewables that intersect the frustum.
  virtual void visit( ViewableVisitor * vv, Frustum const & f ) = 0;
};

class IViewableVisitor {
public:
  // This function is called by AllViewables for each Viewable 
  virtual void visit( Viewable * v ) = 0;
};

You would create one ViewableVisitor instance for rendering the scene from the point of view of a light into a light map; another for rendering the scene from the point of view of the camera into a glow buffer; a third for stencil volumes, etc.

The first extension you want to make is probably to wrap the concept of index buffers and vertex buffers into two explicit interfaces. Changing from Texture to some higher-order Material interface would also be on the list. Notifying the AllViewables when the location/radius of the Viewable changes would be needed if AllViewables keeps an acceleration structure -- the point is that this structure can grow pretty well from a simple beginning, in the direction you need it to go. Meanwhile, it separates the graphics API calls from the content objects.

This combination of patterns is not quite a full-fledged scene graph, but it's a good start. The implementation of AllViewables can be as simple as a linked list, or as complex as a loose, balanced kd-tree -- although anything more than a loose octree is probably overkill.