Use base class or not?
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.
Quote:Original post by cppcdrIt'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.
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...)
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.
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.
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.
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.
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;
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).
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).
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement