I've started getting my scene graph class working. Here's a random example class diagram:
ESceneObject -(has a)-> ESceneObjectContext | | | PSceneObjectContext | | / | / | / | / |ESprite EMesh PSpriteContext PMeshContext
Each ESceneObject (Engine-level object, P* is a platform level (D3D) object) has a ESceneObjectContext context class pointer as a member. I'm still not 100% sure where to create that pointer, since I'd like to be able to create ESprite's for instance in the game code, and I don't really want to expose the PSprite layer to ESprite. But I might have to.Anyway, every ESceneObjectContext is actually a PSceneObjectContext in disguise, and the renderer calls the pure virtual functions BeginBatchRender(), EndBatchRender() and Render(). BeginBatchRender() and EndBatchRender() are used as effectively static functions; all they do is optionally set up the D3D state for rendering this object type. For instance, PSpriteContext::BeginBatchRender() would call ID3DXSprite::Begin() (or rather my sprite layer equivalent), PSpriteContext::EndBatchRender() would call ID3DXSprite::End(), and PSpriteContext::Render() would call ID3DXSprite::Draw(), passing in the details for the owner of the context.
The reason I've made the context class instead of making a PSprite class is that I want the engine and game code to be able to create an ESprite by doing ESprite* pSprite = new ESprite; and not have to go through the graphics class to create a PSprite internally. Unfortunately, as I said above, I'm not quite sure where to create the context. I might have a factory method in my renderer (Although I'd rather not, that drags in all context headers to my main graphics class), I could have a factory method in PSceneObjectContext (Which is looking most feasable), or I could create the relevant context in the ESprite / EMesh / EWhatever constructor, which I don't like because it brings a platform header into engine code.
And while I'm here, here's my ideal render loop (Which is how it is at the moment, but we'll see how long that lasts:
void PGraphics::RenderSceneGraph(const ESceneMgr::ObjectList& objects){ ESceneObject::Type eCurrType = ESceneObject::TYPE_Count; PSceneObjectContext* pLastContext = NULL; for(ESceneMgr::ObjectListConstIter it=objects.begin(); it!=objects.end(); ++it) { ESceneObject::Type eType = (*it)->GetType(); if(eCurrType != eType) { // Setup batch rendering if(pLastContext) pLastContext->FinishBatchRender(); pLastContext = (PSceneObjectContext*)(*it)->GetContext(); if(!pLastContext) pLastContext = ConstructContextForObject(**it); if(pLastContext->BeginBatchRender()) { eCurrType = eType; } else { // Batch render begin failed, skip this object Assert(false, "PSceneObjectContext::BeginBatchRender failed"); continue; } } // Render PSceneObjectContext* pContext = (PSceneObjectContext*)(*it)->GetContext(); pContext->Render(); } if(pLastContext) pLastContext->FinishBatchRender();}
I'd like to get rid of that ConstructContextForObject() call, but it might just have to stay there. I doubt it'll be that big a deal. I can always have a TouchScene() function or whatever, which will go and create the contexts of all objects in the scene.
Ideally I should be able to destroy a context and cause D3D resources to be destroyed, so resetting the device could be as simple as destroying all contexts. As it is, I have a std::vector of PGraphicsObject pointers, and every graphics object (Like textures, ID3DXSprites, vertex buffers, etc) should inherit from that class. I then have a common interface to reset everything. Again, in theory I should be able to Release() it all too, but that's not going to be very worthwhile, and will cause headaches.
Anyway. It's 10am, I should get some work done...
EDIT: Sodding code tags don't like backslashes...