Use base class or not?

Started by
6 comments, last by cppcdr 18 years ago
I am making a game engine and I am wondering what would be the best choice between 1)having a base class and having a class for Md2, for Md3, for 3ds, etc... 2)having a single class that implements everything with functions (loadMD2 (), loadMd3 (), load3DS (), etc...) Thanks for your advice.
Advertisement
Quote:Original post by cppcdr
I am making a game engine and I am wondering what would be the best choice between
1)having a base class and having a class for Md2, for Md3, for 3ds, etc...
2)having a single class that implements everything with functions (loadMD2 (), loadMd3 (), load3DS (), etc...)
It's been a while since I've written a 3DS loader, but I remember there being a lot of fairly complicated support code. For this reason and others it would probably be worthwhile to implement each format as a class derived from a base 'mesh' class; a single class implementing all this functionality would probably be somewhat bloated and hard to maintain.
There are other ways of modularity than inheritance. Having a base model class and many subclasses that differ only in their loading methods might not be ideal. Functions (friend or normal, depending if your class is open or not) can be used to populate or return newly allocated and populated models. This really is a highly subjective design decision, so... it's up to you.

The only thing I would recommend against is having a model class and methods like loadMD3, load3DS, etc. That isn't easy to change, it's not just dropping in a new header and source file and blammo, you can support a new model format, you have to change the code for your base class.

Though you might want to make your Model class aware of the functions or subclasses and what types of models they can support. A static list of these formats and a way to construct objects for them in the Model class (along with static member support functions, if necessary) can provide transparent support in application code. That means things like "new Model("model.md3")" instead of "new MD3_Mode("model.md3")" so changes in your resources won't affect changes in your code.
Your mesh class should be logical, not physical. Having one mesh class per file format implies a close relationship with the physical structure of the file on disk... not a good idea for a number of reasons.

First, it means you have potentially multiple code paths for performing operations on each different type of mesh (such as rendering or updating, et cetera. Second, the format in which something is stored on disk is often not ideal for maximal render performance.

Separate responsibiliy. Build a single mesh class that stores mesh information in whatever form is most convient for you to render and work with. Provide loading methods or helper classes, derived from a common ModelLoader interface, that you can subclass to load each different type of model format and convert it to your mesh class that you use at runtime.

For example:
You have a MeshLoader base class that provides a virtual function LoadMesh() that takes a filename and returns a Mesh object. Then you subclass this as MeshLoaderMD3, MeshLoader3DS, MeshLoaderX, et cetera and overload LoadMesh for each file type. Every implementation of LoadMesh() simply returns a newly constructed Mesh object; it uses the functionality that Mesh exposes to add, remove or change vertex, normal, texture coordinate information, and so on.

This allows you to support new file formats by writing only the loader and without modifying existing classes, it allows you to simplicity of working with / rendering only a single type of object, it allows you to make more aggressive optimizations to your render pipeline, and if the interface of Mesh itself is well-designed, you can even change the underlying storage format of Mesh itself without having to rewrite your loaders.
you could try using the strategy pattern:

http://www.codeproject.com/cpp/strategy.asp
Joel Martinez
http://codecube.net
[twitter]joelmartinez[/twitter]
The idea of using interfaces is that you'll use them to access the different derived class instances.

This is, if you have your source code files infested with pointers to CMesh3DS, everytime you change something in it, you'll have to recompile everything that uses it.

Instead, you instance all your concrete classes in one place, and access them trough their interface everywhere else.

IRenderer - RenderMesh ( IMesh* );IResource - LoadMeshes ( ); // IMesh::Load();IPhysics - MakeBody ( IMesh* ); // IMesh::GetBoundingBox();IFactory //Abstract  - IMesh* MakeMesh()=0;     o-> CFactory          - IMesh* MakeMesh(); // IMesh p = new CMesh3DS; return p;
[size="2"]I like the Walrus best.
A class that does what?

You need to capture the different kind of things you need to do to geometry, such as storing it, rendering it, processing it, animating it, analyzing it ...

These operations need some kind of interfaces (be they pure abstract base classes, or common calling conventions for template binding, or whatever).

Then, for each piece of functionality, implement the appropriate interfaces for the appropriate underlying structures.

For example, you might define an interface for a mesh with vertices, indices, and material information; a hierarchy of transformations (vector+quaternion+scale), an interface of data types that vary over time, and a list of meshes. Loading an animated mesh would then manufacture instances of the transformation hierarchies, of the time-varying values, and a list of meshes; into this list, it would put meshes for each "piece" of the animated mesh.

Loading a non-animating mesh would probably just manufacture a mesh instance (none of the other interfaces).
enum Bool { True, False, FileNotFound };
Thanks for everything.

This topic is closed to new replies.

Advertisement