Sign in to follow this  
kosmon_x

Question about good class heirarchies

Recommended Posts

kosmon_x    205
Ok, so I'm tooling around with DirectX a little and have built a small framework for myself that creates a Direct3D device, etc and now I want to start making a simple object heirarchy that can be used for renderable objects in my game. But, I'm not quite sure how to go about it -- I'm not sure what's the best place for different data, or how to organize it most efficiently. For example, Does my IObject base class contain the position, velocity, world matrix, ID3DXMesh*, etc of my renderable objects? Or does the IObject base class simply define some simple methods that all renderables will use (stuff like "render()," "update()"). Then have a wrapper class around ID3DXMesh that contains the position and matrices defining the object, then have a discrete class that inherits from IObject and -contains- a renderable object via composition? I've read a little bit about the Model-View-Controller pattern, but I'm not sure how I would break down the individual parts specifically, or if it's even necessarly or good to use. And, it's the "model" part that I'm concerned about most. Which subclass / composition element holds the actual 'data' about the mesh and its state in the world? How does everyone do it? I want to keep my options open for easy sorting of similar meshes and stuff like that to be able to optimize rendering efficiency. And, of course, I want to keep the solution clean and easy to extend :) Any help would be greatly appreciated ^^

Share this post


Link to post
Share on other sites
Zanthos    300
All of the objects being rendered will have a common set of methods which can be called:


class IObject
{
virtual int Render() = 0;
virtual int Update() = 0;
}



Interfaces are exactly that, you shouldn't put any class attributes in, and obviously all functions are virtual.

Next, the base class for all visible meshes, containing attributes for rendering the object(aswell as updating it's position)


class TObjectPosition
{
private:
Vector3D* pos;
Vector3D* vel;
Vector3D* accel;
Vector3D* rotation;
LPD3DXMATRIX worldmat;
double friction; // Friction coefficient
void RecalculateMatrix();
public:
void UpdateMovement(); // Calculate new position and velocity
void ApplyWorldMatrix();
/* various methods for updating speed/acceleration, etc */
}



The vectors are stored in TObjectPosition so we can recalculate the world matrix when the object moves around/gets rotated, etc. RecalculateMatrix() is called by ApplyWorldMatrix just before D3D is instructed to apply our world matrix to the scene.

TSimpleMesh is an implementation of an IObject as a simple mesh(ie, no LOD). The implementation class shouldn't really contain any more public methods unless you use multiple inheritance and specify them in another interface class.


class TSimpleMesh : public IObject
{
private:
LPD3DXMESH mesh;
{ .. etc .. }
}
TSimpleMesh::Render() { return 1; }
TSimpleMesh::Update() { return 1; }




Hopefully this will help in some way.

Share this post


Link to post
Share on other sites
Illco    928
Adding to that, consider what you intend to do with your code base in the future. If it is purely about rendering objects this is ok. If you consider adding sounds, autonomous entities (characters and vehicles), etc. you should consider adding an extra layer.

What I mean is that those kind of things will have positions in the world but not necessarily a world matrix or the ability to be rendered. For example, it is convenient for (3D) sounds to inherit from the basic object, but it is useless to 'render' a sound. Typically, most objects do acknowledge the benefit of an update fucntion. Basically, consider already the fact that not every object is a mesh.

Also, if you really intend to get together a small engine it is convenient to separate resource data from their instances. In the case of meshes, you probably want to store its internal data somewhere (lets say MeshResource) but use instances at various specific locations that refer to the same data (lets say MeshInstance). This separation is vital for an efficient approach and it is good advice to make this distinction from the start.

Greetz,

Illco

Share this post


Link to post
Share on other sites
kosmon_x    205
Using those classes, how would you set up a Tank object?
Would the Tank class inherit from IObject and contain a TSimpleMesh object, which also inherits from IObject, and contain a TObjectPosition object? Or would you add another layer called class XYZ that inherits from IObject and contains both a TSimpleMesh and an TObjectPosition object, then have the tank class, which also inherits from IObject, contain XYZ as a data element?

It seems like I would just want one of the classes to inherit from IObject (probably Tank), and then pass down the calls to the data members it contains, but I'm not sure...

Share this post


Link to post
Share on other sites
Telastyn    3777
I wouldn't assume that a tank is going to be rendered. Or rendered as a mesh.

Game Objects and Render Objects should [in nearly every case] be completely seperate [edit: imo]. One of the benefits of making Zanthos' TObject above is that a Tank can hold a simple TObject pointer and thus ignore any changes you make as to how a Tank is presented to the world.

Share this post


Link to post
Share on other sites
kosmon_x    205
So, I should never have to call Tank->render()? Instead, the Tank will contain a TMesh (or whatever) that will add itself to the scene management. Then the scene management will call render directly on the TMesh object? And all the Tank does is handle input updates and frame updates to change the position / orientation of the TMesh? (Am I getting this somewhat correct?)

Share this post


Link to post
Share on other sites
Telastyn    3777
Quote:
Original post by kosmon_x
So, I should never have to call Tank->render()? Instead, the Tank will contain a TMesh (or whatever) that will add itself to the scene management. Then the scene management will call render directly on the TMesh object? And all the Tank does is handle input updates and frame updates to change the position / orientation of the TMesh? (Am I getting this somewhat correct?)


That's the idea. The Tank object would focus on the actual gameplay implications of it being a tank, and the Mesh object would focus on rendering meshes the best it can. That makes it much easier to do game play calculation without rendering [which is useful for testing/simulation, and needed for a dedicated server scenario]. Plus, the Mesh class can then be re-used elsewhere, when spaceships or something similarly different from tanks are being used.

I am no expert though, and you should come to your own conclusion. I know this sort of setup works well for me, and [for me] making game objects into code objects leads to many design problems. I wouldn't make Tank it's own class for example. I'd make a more generic class [mobile_unit? user_controllable?], which took Tank as a parameter since the object being a Tank is a property of the object rather than being a defining function of it. But that's just me, and there's plenty of other ways of doing it.

Share this post


Link to post
Share on other sites
Nathan Baum    1027
Quote:
Original post by kosmon_x
So, I should never have to call Tank->render()? Instead, the Tank will contain a TMesh (or whatever) that will add itself to the scene management. Then the scene management will call render directly on the TMesh object? And all the Tank does is handle input updates and frame updates to change the position / orientation of the TMesh? (Am I getting this somewhat correct?)

That's a possible approach.

I'd have the renderer call Tank->render() and then have the default implementation of Object::render() just pass that directly to the model.

However, there will be many instances where you cannot simply put a model directly in the scene graph. Most obviously, if you use a model more than once, it couldn't directly be in more than one place in the graph -- you'd have to put it in a container.

With simple static meshes, this container will be a bit dull. But if we introduce interpolated meshes, skeletal meshes and skinned meshes, things may get more interesting. It's very unlikely that all instances of a particular interpolated or skeletal mesh in your scene will have exactly the same pose. So the container will have to record what frame the interpolated mesh is on, or what the configuration of the bones of the skeletal mesh is. If you have a skinned mesh where face textures may change to reflect damage, then the container will need to note which textures to display on which faces.

This complicated container describes the view of the model. Hence, it is the View of your Model-View-Controller pattern.

The Controller is what I'd call an Actor. It defines the behaviour of the object in the scene, but is as distanced as possible from its appearance: it contains a reference to the View via which it updates the details of how the Model should be rendered.

Note that if the Controller needs to have carnal knowledge about the details of the Model -- for example if the Controller needs to know that the Model is a skeletal model -- then the Controller/View distinction disappears.

A better, although more complex, design is for the Controller to need to know nothing about the Model whatsoever. This means that the Controller will have to notify the View of anything that might effect how the Model is rendered. For example, if the View is in rag-doll mode, it will need notifications of any forces which are applied to the object, since it is only the View which would have the knowledge necessary to actually make the limbs move.


class Actor
{
// The default actor doesn't have a behavioural response to being
// touched. Just pass it on.
virtual void touch (Actor * toucher, Vector3f point, Vector3f normal)
{
view->touch(force, origin);
}
ModelView * view;
};

class ModelView
{
// Really stupid way of responding to forces, for example's sake:
// In reality, the default implementation would deal with the angle
// of impact and angular effects.
virtual void touch (Actor * toucher, Vector3f point, Vector3f normal)
{
this->applyForce(toucher.linearVelocity());
}
};

class Model
{
};


Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this