Monday 13 February 2012

Getting Started with the Programming Assignment Pt 1

It's programming assignment time again, and I've decided this year to write a simple game to demonstrate some of the features of NGL and to show you how to go from the initial design stuff into code.

For this game I've decided to do a simple 3D style asteroid game which uses a number of obj meshes to draw the different asteroids and the ship.

The main focus on these blog posts will be the sharing of data across different classes and the stepwise refinement from the class design to writing the code.

My initial design for the system is shown below


I'm going to concentrate of the basic design of each of these classes, then implement and test them separately. The first class will be the models class.

Models
The models class is a container for the meshes, and allows us to share meshes for each of the other classes. For example in this game we will have a number of Asteroid objects, however each one will only have a single mesh. We could load a mesh for each Asteroid object we create, however this is going to be slow and wasteful of resources as we only have a limited amount of GPU memory and texture memory. Using the Models class we load all our models and textures at program startup, then each of our classes that need access to the models will have a pointer to this class and access models via it.

To store the Obj meshes I decided to use std::string name lookup and the easiest way of implementing this is to use the std::map container and use the build in iterators to access things. Another concern when designing is to ensue we have const correctness built in from the outset. In this case as we only have one method that doesn't mutate the class (the draw method) it is quite easy to do this. 

Finally I wish to ensure full documentation of the class, I will be using Doxygen to comment everything and as usual it is best to write this at the time of creating the class. The code below shows the fully .h file for the class
#ifndef __MODELS_H__
#define __MODELS_H__

#include <string>
#include <map>
#include <ngl/Obj.h>

/// @file Models.h
/// @brief a class to contain all models used in game
/// @author Jonathan Macey
/// @version 1.0
/// @date 10/2/12
/// @class Models
/// @brief this class contains all meshes required for the game level
/// we use this as we only need to load meshes once but may need to attach
/// to many objects in our game

class Models
{
public :
  /// @brief our ctor
  Models(){;}
  /// @brief our dtor this will clear the models and remove
  /// all of the meshes created
  ~Models();
  /// @brief add a mesh with no texture
  /// @param[in] the name of the model we wish to use for lookup
  /// @param[in] the path / name of the mesh
  void addModel(
                const std::string &_name,
                const std::string &_mesh
              );
  /// @brief add a mesh with a texture
  /// @param[in] the name of the model we wish to use for lookup
  /// @param[in] the path / name of the mesh
  /// @param[in] the path / name of the texture
  void addModel(
                  const std::string &_name,
                  const std::string &_mesh,
                  const std::string &_texture
               );
  /// @brief accesor to the model, incase the caller
  /// wishes to modify the mesh etc
  ngl::Obj *getModel(std::string _name);
  /// @brief method to draw the mesh, all tx must be executed before
  /// the call to draw
  /// @param[in] the name of the mesh to draw
  void draw(std::string _name) const;
private :
  /// @brief a map to hold our meshes by name
  std::map < std::string, ngl::Obj *>m_models;

};


#endif
As you can see the constructor doesn't do anything in the class, however we need to implement a destructor to call the ngl::Obj destructor and clear out all the mesh data etc.
Models::~Models()
{
  std::map <std::string,ngl::Obj *>::iterator pbegin=m_models.begin();
  std::map <std::string,ngl::Obj *>::iterator pend=m_models.end();
  while(pbegin != pend)
  {
    std::cout <<"deleting "<<pbegin->first<<"\n";
    delete pbegin->second;
    ++pbegin;
  }
}
The destructor uses the std::map::iterator to grab the front and the back of the map, we then loop through and use the ->second accessor to delete the ngl::Obj * class. The rest of the class is fairly simple, to add a mesh we use the following code
void Models::addModel(
                       const std::string &_name,
                       const std::string &_mesh,
                       const std::string &_texture
                      )
{
  ngl::Obj *mesh = new ngl::Obj(_mesh,_texture);
  mesh->createVAO(GL_STATIC_DRAW);
  mesh->calcBoundingSphere();
  m_models[_name]=mesh;
}
Finally as the draw method is const we need to use a std::map::const_iterator to access the data for drawing
void Models::draw(std::string _name) const
{
  std::map <std::string,ngl::Obj *>::const_iterator pbegin=m_models.begin();
  std::map <std::string,ngl::Obj *>::const_iterator model=m_models.find(_name);

  if(pbegin!=m_models.end() && model !=m_models.end())
  {
    model->second->draw();
  }
}
Testing
To test the class I've used one of the basic ngl:: demo programs, and added to the GLWindow.h class a simple Models m_model; object.

To load the meshes we need to first have a valid OpenGL context
m_model.addModel("bigrock","models/RockBig.obj","textures/rock_texture.bmp");
m_model.addModel("spike","models/RockSpike.obj","textures/rock_texture.bmp");
m_model.addModel("ship","models/SpaceShip.obj","textures/spaceship.bmp");
Then to draw we can use the following
switch (m_whichModel)
{
  case 0 : m_model.draw("bigrock"); break;
  case 1 : m_model.draw("spike"); break;
  case 2 : m_model.draw("ship"); break;
}
Which gives us the following


You can download the test code and class here 

Reflections on initial design
It has occurred to me, whilst writing this up that using a std::map may not be the fasted way of using this  class as we need to iterate to search by name. It may actually be better to use a std::vector and have some form of index based enumerated type to search for the models. Once I start testing the rest of the classes I will do some comparisons for speed, which will test this.

1 comment: