Monday, 13 February 2012

Getting Started with the Programming Assignment Pt 3 Linking Things Together

In the previous post I created a simple SpaceShip class, to contain the state data for the Ship, I also outlined different  methods. In this post I'm going to discuss different ways of associating the classes with each other. In this case we have the SpaceShip class which contains the state data, and the Models class which will contain the actual Mesh to be draw.

I'm going to then highlight three different ways of associating the classes so the SpaceShip class can access the model and draw.

In all these version we will have an instance of the SpaceShip class created in the GLWindow class called m_spaceShip, and we need to pass the global ngl::Camera to the SpaceShip class as well as the instance of the Model class.

Pass By Reference version
In this version we are going to pass into the draw method both the model and the camera, we declare the draw prototype as follows
/// @brief draw the ship
/// @param[in] _model a reference to the model loader
/// @param[in] _cam a reference to the global camera
void draw(
          const Models &_model,
          const ngl::Camera &_cam
         );
You will now note that this method is no longer marked as const, this is because in the loadMatricesToShader method we need to access the m_transform object and grab the current matrix. This will force the transform object to calculate the transforms and mutate itself, and hence is now a non const method.
/// @brief load our matrices to OpenGL shader
/// @param[in] _cam a reference to the camera use to grab the 
/// VP part of the Matrix for drawing
void loadMatricesToShader(
                           const ngl::Camera &_cam
                         );
So the draw method will basically load the Model View Projection matrix to the shader by calling the method above we then need to access the model and call the draw method, this is shown in the following code
void SpaceShip::draw(const Models &_model, const ngl::Camera &_cam)
{
  loadMatricesToShader(_cam);
  _model.draw("spaceship");

}

void SpaceShip::loadMatricesToShader(const ngl::Camera &_cam)
{
  ngl::ShaderLib *shader=ngl::ShaderLib::instance();
  (*shader)["TextureShader"]->use();
  const ngl::Matrix MVP=m_transform.getMatrix()*_cam.getVPMatrix();
  shader->setShaderParamFromMatrix("MVP",MVP);
}
In the loadMatricesToShader method we have already loaded the shaders to the ShaderLib class and we then just call it when required. We can now use the following code in the GLWindow class to draw our SpaceShip
m_spaceShip.draw(m_model,*m_cam);
Which gives us the following
As you can see the orientation is wrong at present but I will fix that later.

Class Global Pointer Version
The next version is going to have two global pointers one for the Camera and one for the Models, these will be set in the spaceship class on construction and then used in the draw methods.

Firstly we need to add them to the SpaceShip class as follows
/// @brief the global camera
const ngl::Camera *m_camera;
/// @brief the global model store
const Models *m_models;
We now need to set these value, the best way to do this is by using the constructor, as we can guarantee that this will always be called.
/// @brief the ctor
/// @param[in] _cam the global camera
/// @param[in] _m the models 
SpaceShip(
           const ngl::Camera *_cam,
           const Models *_m
         );
If we only implement the parameterized constructor above we will force the object to be constructed using these values. In the constructor we just need to copy these values
SpaceShip::SpaceShip(
                      const ngl::Camera *_cam,
                      const Models *_m
                     )
{
  m_camera=_cam;
  m_models=_m;
  m_pos.set(0,0,0);
  m_rotation.set(0,0,0);
  m_numLives=3;
  m_shieldStrength=100;
}
Now the draw and loadMatrices to shader method signatures can change to have no parameters as shown
void SpaceShip::draw()
{
  loadMatricesToShader();
  m_models->draw("spaceship");

}

void SpaceShip::loadMatricesToShader()
{
  ngl::ShaderLib *shader=ngl::ShaderLib::instance();
  (*shader)["TextureShader"]->use();
  const ngl::Matrix MVP=m_transform.getMatrix()*m_camera->getVPMatrix();
  shader->setShaderParamFromMatrix("MVP",MVP);
}
The code to generate the Ship is now different, firstly the SpaceShip class in GLWindow.h must now be a pointer and we need to build the object once we have created our camera and models as shown below
m_cam= new ngl::Camera(From,To,Up,ngl::PERSPECTIVE);
// set the shape using FOV 45 Aspect Ratio based on Width and Height
// The final two are near and far clipping planes of 0.5 and 10
m_cam->setShape(45,(float)720.0/576.0,0.05,350,ngl::PERSPECTIVE);
 

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("spaceship","models/SpaceShip.obj","textures/spaceship.bmp");


m_spaceShip = new SpaceShip(m_cam,&m_model);
Both these methods work in more or less the same way, not passing the parameters to the draw method does reduce some of the stack overhead with accessing the data however simple timing tests give both methods the same speed of approximately 60FPS
Game State Object
The final method I'm going to describe is going to use a global game object. This object will be a singleton class, it will have public attributes for the camera and the models, which must be set when the class is first used.
The SpaceShip class will then access this when required.
#ifndef __GLOBALGAMEOBJECT_H__
#define __GLOBALGAMEOBJECT_H__

#include "Models.h"
#include <ngl/Camera.h>

/// @file GlobalGameObject.h
/// @brief we use this object to pass around global game data this is a singleton
/// @author Jonathan Macey
/// @version 1.0
/// @date 13/2/12
/// @class Models
/// @brief we only put const global references in this class to pass around data used
/// by the game


class GlobalGameObject
{
  public :
    /// @brief this is a singleton class this get the current instance
    static GlobalGameObject * instance();
    /// @brief our model made public for ease of access
    const Models *m_models;
    /// @brief the global camera made public for ease of access
    const ngl::Camera *m_camera;
  private :
    /// @brief hide away the ctor as this is a singleton
    GlobalGameObject(){;}
    /// @brief hide the copy ctor
    GlobalGameObject(const GlobalGameObject &_g){Q_UNUSED(_g);}
    /// @brief hide the dtor
    ~GlobalGameObject();
    /// @brief hide the assignment operator
    GlobalGameObject operator =(const GlobalGameObject &_r){Q_UNUSED(_r);}
    /// @brief our instance pointer
    static GlobalGameObject *m_instance;
};
#endif
The main C++ code looks like this and is a standard singleton, in this case we don't have to worry about managing the lifetime of the class as it will only contain const pointers and the other classes will manage their own lifetimes
#include "GlobalGameObject.h"

GlobalGameObject* GlobalGameObject::m_instance = 0;// initialize pointer

GlobalGameObject* GlobalGameObject::instance()
{
  if (m_instance == 0)  // is it the first call?
  {
    m_instance = new GlobalGameObject; // create sole instance
  }
  return m_instance; // address of sole instance
}
To use this class we must create it in GLWindow and attach the pointers as shown
GlobalGameObject *game=GlobalGameObject::instance();
game->m_camera=m_cam;
game->m_models=&m_model;
Now in the SpaceShip class we use the following to access the models and the camera
void SpaceShip::draw()
{
  loadMatricesToShader();
  GlobalGameObject *game=GlobalGameObject::instance();
  game->m_models->draw("spaceship");
}

void SpaceShip::loadMatricesToShader()
{
  GlobalGameObject *game=GlobalGameObject::instance();
  ngl::ShaderLib *shader=ngl::ShaderLib::instance();
  (*shader)["TextureShader"]->use();
  const ngl::Matrix MVP=m_transform.getMatrix()*game->m_camera->getVPMatrix();
  shader->setShaderParamFromMatrix("MVP",MVP);
}
As you can see this method breaks some of the OO rules about encapsulation but will require less re-working of the code if we wish to change game elements or add / remove elements to be shared with the rest of the game objects. Another speed test puts this at about the same speed as the others. So there is no clear winner at present for any of the methods. All of the code for the three examples can be found here, for now I think I'm going to use the GlobalGame object method as it will make the development cycle quicker as I only need to add things to the singleton class.

Next how to move the ship

Getting Started with the Programming Assignment Pt 2 The SpaceShip

For part one see here

The main premise of the game is we are going to have a read view of a SpaceShip which the player can move around the screen in x and y. The user can also rotate the ship around the different axis.

The sketch above shows the view of the ship and the axis of rotation, the class sketch below show the initial design of the SpaceShip class

The SpaceShip will have a shield with initial value of 100% which will be changed based on the game play mechanic (for example collisions will reduce the shield and I may update the value based on collecting assets)

By default the player will have 3 ships which will be decreased based on gameplay / collisions and there will be opportunities to add more ships based on score etc.

The following class diagram is a more formal version of the above
And in code looks like this
#ifndef __SPACESHIP_H__
#define __SPACESHIP_H__

#include <ngl/Vec3.h>
#include <ngl/Transformation.h>


/// @file SpaceShip.h
/// @brief the basic spaceship class used for the game
/// @author Jonathan Macey
/// @version 1.0
/// @date 13/2/12
/// @class SpaceShip
/// @brief this class encapsulates the spaceship state, and also controls, it does
/// not have a mesh assosiated with it this is stored in the Models class

class SpaceShip
{
  public :
    /// @brief the ctor
    SpaceShip();
    /// @brief the dtor
    ~SpaceShip();
    /// @brief move this will set the position of the ship
    /// @param[in] _dx the change in the x position
    /// @param[in] _dy the change in the y position
    void move(
               float _dx,
               float _dy
             );
    /// @brief rotate the ship in the three axis
    /// @param[in] _dx the change in the x rotation
    /// @param[in] _dy the change in the y rotation
    /// @param[in] _dz the change in the z rotation
    void rotate(
                 float _dx,
                 float _dy,
                 float _dz
                );
    /// @brief draw the ship
    void draw() const;
    /// @brief get the life value
    inline int getLife() const {return m_numLives;}
    /// @brief remove life
    inline void removeLife(){m_numLives-=1;}
    /// @brief get the sheild strength
    inline int getShieldStrength()const {return m_shieldStrength;}
    /// @brief reduce the sheild strength
    inline void reduceShieldStrength(int _s){ m_shieldStrength-=_s;}

  private :
    /// @brief the position of the ship
    ngl::Vec3 m_pos;
    /// @brief the x,y,z rotation values of the ship
    ngl::Vec3 m_rotation;
    /// @brief our transformation used to load to the matrix
    ngl::Transformation m_transform;
    /// @brief load our matrices to OpenGL shader
    void loadMatricesToShader();
    /// @brief the number of lives for the ship
    /// this is set to 3 initially and will then change as the game progresses
    int m_numLives;
    /// @brief the strength of the sheild starts at 100% and reduces during the game
    /// based on collisions
    int m_shieldStrength;
};


#endif
We can now write most of the methods if we ignore the drawing elements of the class. These are as follows
SpaceShip::SpaceShip()
{
  m_pos.set(0,0,0);
  m_rotation.set(0,0,0);
  m_numLives=3;
  m_shieldStrength=100;
}


SpaceShip::~SpaceShip()
{

}

void SpaceShip::move(float _dx, float _dy)
{
  m_pos.m_x+=_dx;
  m_pos.m_y+=_dy;
}

void SpaceShip::rotate(float _dx, float _dy, float _dz)
{
  m_rotation.m_x+=_dx;
  m_rotation.m_y+=_dy;
  m_rotation.m_z+=_dz;
}
It is now possible to construct a simple SpaceShip class in the main GLWindow and use it for storing the data values for the ship However drawing the mesh for the class is going to be a lot more complex and this will be put into the next post. For now here is the basic code and part three is here

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.

Monday, 30 January 2012

Maya Alembic Export

Whilst this blog is mainly concerned with setting up alembic maya export within the University network, most of this is also applicable to other systems. You just need to change all the paths to the corresponding ones on your system.

First you need to download and build your alembic maya plugins. These come as part of the Alembic package and you should follow the build instructions. Once everything is built you will have a directory within the source tree called maya/plug-ins.

On the University system this is in the directory /public/bin/alembic/maya/plug-ins and you should see the two plugins AbcExport.so  AbcImport.so

University Setup
In the root of your home directory (type cd and press enter) execute the following commands

cd
mkdir MayaPlug
mkdir MayaScript
cp /public/bin/alembic/maya/plug-ins/* ~/MayaPlug
This will copy the Alembic plugins into a pre-defined directory which we will tell maya to search when starting up. This is controlled by the file Maya.env. Again the location of this file will differ depending upon the install, but in the University this is located here $HOME/maya/2011-x64/ If this file doesn't exist in the directory you can create your own using the following command
cd $HOME/maya/2011-x64/
touch Maya.env
gedit Maya.env
This will open the file and allow us to edit the maya environment variables used when maya starts. We need to add to this file the following
MAYA_PLUG_IN_PATH=/home/jmacey/MayaPlug
MAYA_SCRIPT_PATH=/home/jmacey/MayaScripts
PYTHONPATH=MAYA_SCRIPT_PATH
In the above example you will need to change the /home/jmacey to your own home directory path. This will then setup two areas that maya will search when looking for plugins ( $HOME/MayaPlug ) and scripts ( $HOME/MayaScript ) when you now start maya you should get the following list when opening the Menu Windows->Settings / Preferences -> Plugin-Manager
You should now see the AbcExport.so ( this screen shot from my mac is different as it uses a .bundle) and AbcImport.so. If you click on the Loaded button it will load the plugin and you should be able to type AbcExport -h in the Mel tab of the script editor as shown below
For more info on this read the blog post here. As the command line is a little bit complex, I decided to create a simple GUI to make life easier. The main design for this came from the output of AbcExport -h and all the options printed in the help that actually work have been translated into gui items.

AlembicExport.py

The AlembicExport.py script can be downloaded from here and it should be saved in the $HOME/MayaScripts directory.

When using the script you will need to select all the geometry you wish to export (if you select all Alembic will attempt to export all it can ) and type the following in the python script editor
from AlembicExport import *
AlembicExport()
This will give you the following GUI
The current frame range is selected and by default uv's and normals will be exported. Other options are available and you should read the AlembicExport help for more details. 

The actual alembic jobstring and command line is placed in the job string text field so you can copy this if you wish to use the command line at a later date.

Code outline
The code is fairly self explanatory, however I will outline a couple of areas.
First we check to see if the AlembicExport plugin is installed. This is done with the following code
# check to see if plugin is loaded
plugs=cmds.pluginInfo( query=True, listPlugins=True )
if "AbcExport" not in plugs :
  print "AbcExport not loaded please load it"
To build up the jobstring for the actual export we use the following code
jobstring="AbcExport "
if self.verbose == True :
  jobstring+=" -v "
jobstring+="-j \" -fr %d %d -s %d" %(self.start,self.end,self.steps)
.....
This will build up a complete export command which we will then execute using the eval command as follows
mel.eval(jobstring)

Friday, 27 January 2012

Maya standalone python

I've just had an email from an ex student asking about automating the export process from maya as at present they load each scene by hand and then use a particular plugin to export in a new format.

My suggestion was to use the standalone maya python interpretor and try to semi automate the process, to do this I wrote a simple proof of concept as follows.

The following script scans the current directory for any maya ASCII files, opens the file and selects all. Then exports this as an obj file.

First we need to enable the maya standalone system

import maya.standalone
import maya.cmds as cmds
import os

maya.standalone.initialize(name='python')

The line above imports the maya.standalone module, next we need to initialise this and tell it which interpretor we are using with the name='python' command. We now have an empty maya environment which should have read our Maya.env so all the paths etc are setup. However none of our default auto-loaded plugins are loaded. In this case I wish to use the obj export plugin so need to load it. As this is a simple proof of concept I don't do any checking to make sure it is loaded etc.

cmds.loadPlugin("objExport")

The next batch of code scans the current directory and checks for .ma files then does the export
files = os.listdir(".")
for mayafile in files :
  if mayafile.endswith(".ma") :
    cmds.file(mayafile,o=True)
    cmds.select(all=True)
    newFile="%s.obj" %(mayafile)
    cmds.file(newFile,type="OBJexport",pr=True,es=True)

The rest of the code uses the standard maya.cmds module to first open the file then select all elements.

Next I create a new filename by adding ".obj" to the end of the file loaded and export with the file command.

To run the script we need to use the mayapy command. This should be in the same directory as the rest of the maya executables. On my mac this is /Applications/Autodesk/maya2011/Maya.app/Contents/bin/mayapy but you will need to add it to your path

To run I've saved the file as export.py and run mayapy export.py

Friday, 20 January 2012

ngl::Matrix vs Imath::Matrix44

As I've been working with the Alembic file I/O system for a while I've been using some of the IMath functions as Alembic is build upon IMath / OpenEXR base code. IMath is a templated maths library that, to quote the website, "Imath, a math library with support for matrices, 2d- and 3d-transformations, solvers for linear/quadratic/cubic equations, and more".

Half way through using this I started wanting to use my own ngl::Matrix library as this is integrated into my code base, it when that I discovered that the two were not fully compatible.

Whilst both have very similar functions, the one core difference was how the matrix*matrix multiplication worked, (pre / post multiplication of values). In the end I decided to modify how the ngl::Matrix * operator worked so that it is compatible with IMath::Matrix44

Using IMath in ngl::
To start using IMath in ngl (or other programs) we need to set the compiler include paths to the correct place. By default they are installed in /usr/local (with the headers being in a directory OpenEXR)

A number of the classes in IMath are templated header only files so we don't need to add any additional libs, however some of the functions may also need the additional libImath.so (or static .a version)

To add these in a Qt project file add the following lines

INCLUDEPATH+=/usr/include/
LIBS +=-lImath

Once these have been added to the project we need to add the following header to the program
#include <OpenEXR/ImathMatrix.h>
#include <OpenEXR/ImathVec.h>

The IMath::Matrix44 class is a templated class so we need to construct it to be compatible with the ngl::Matrix class using a float. The following code is going to construct both an ngl::Matrix and a IMath::Matrix44
float xRotation=45.0f;
// Imath
Imath::Matrix44 <float> iXMatrix;
iXMatrix.setAxisAngle(Imath::Vec3<float>(1,0,0),ngl::radians(xRotation));
// ngl
ngl::Matrix nXMatrix;
nXMatrix.rotateX(xRotation);

In the above example the Imath matrix is constructed and will be set to the identity matrix as default. We then use the setAxisAngle method to set the matrix as a rotation around the x axis by xRotation degrees. This method is passed a vector for the axis to rotate around and a value for the rotation which must be converted into radians.

The ngl::Matrix class is also set to the identity when it's constructed, and to set the rotation value we use the rotateX method (which expects the rotation values in degrees).

We can check the output of this by using the overloaded << operators as shown

std::cout<<"X rotation "<<xRotation<<"\n"<<nXMatrix<<"\n"<<iXMatrix<<"\n";


X rotation 45
[+1.0000000000000000,+0.0000000000000000,+0.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,-0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +1.0000000000000000   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   -0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)
Another useful feature of the ngl::Matrix class is that is can be constructed from a float [4][4] as shown here
Matrix::Matrix(Real _m[4][4])
{
  for(int y=0; y<4; ++y)
  {
    for(int x=0; x<4; ++x)
    {
      m_m[y][x]=_m[y][x];
    }
  }

}
This means we can construct an ngl::Matrix from an Imath matrix as shown
  
Imath::Matrix44 <float> iZMatrix;
iZMatrix.setAxisAngle(Imath::Vec3<float>(0,0,1),ngl::radians(zRotation));
// alternativly we can construct from an Matrix.x array
ngl::Matrix nZMatrix(iZMatrix.x);
The listing below show the complete program and the output
int main()
{
  float xRotation=45.0f;
  float yRotation=35.0f;
  float zRotation=15.0f;


  Imath::Matrix44 <float> iXMatrix;
  iXMatrix.setAxisAngle(Imath::Vec3<float>(1,0,0),ngl::radians(xRotation));
  ngl::Matrix nXMatrix;
  nXMatrix.rotateX(xRotation);

  Imath::Matrix44 <float> iYMatrix;
  iYMatrix.setAxisAngle(Imath::Vec3<float>(0,1,0),ngl::radians(yRotation));
  ngl::Matrix nYMatrix;//(iYMatrix.x);
  nYMatrix.rotateY(yRotation);

  Imath::Matrix44 <float> iZMatrix;
  iZMatrix.setAxisAngle(Imath::Vec3<float>(0,0,1),ngl::radians(zRotation));
  // alternativly we can construct from an Matrix.x array
  ngl::Matrix nZMatrix(iZMatrix.x);

  std::cout<<"X rotation "<<xRotation<<"\n"<<nXMatrix<<"\n"<<iXMatrix<<"\n";
  std::cout<<"y rotation "<<yRotation<<"\n"<<nYMatrix<<"\n"<<iYMatrix<<"\n";
  std::cout<<"z rotation "<<zRotation<<"\n"<<nZMatrix<<"\n"<<iZMatrix<<"\n";

  std::cout<<"ngl mult y*x \n"<<nYMatrix*nXMatrix<<"\n";
  std::cout<<"iMath mult y*x \n"<<iYMatrix*iXMatrix<<"\n";
  ngl::Matrix nxyz=nXMatrix*nYMatrix*nZMatrix;
  std::cout<<"ngl mult x*y*z \n"<<nxyz<<"\n";
  Imath::Matrix44 <float> ixyz=iXMatrix*iYMatrix*iZMatrix;
  std::cout<<"iMath mult x*y*z \n"<<ixyz<<"\n";

  Imath::Matrix44 <float> iInverse=ixyz.inverse();
  std::cout<<"inverse \n"<<iInverse<<"\n";
  ngl::Matrix nInverse=nxyz.inverse();
  std::cout<<"inverse \n"<<nInverse<<"\n";

  Imath::Vec3<float> iPos(1,2,3);
  std::cout<<"i V*M "<<iPos*ixyz<<"\n";
  ngl::Vector nPos(1,2,3,1);
  std::cout<<"n V*M"<<nPos*nxyz<<"\n";
  std::cout<<"n M*V"<<nxyz*nPos<<"\n";
}
Which gives the following output
X rotation 45
[+1.0000000000000000,+0.0000000000000000,+0.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,-0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +1.0000000000000000   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   -0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

y rotation +35.0000000000000000
[+0.8191520571708679,+0.0000000000000000,-0.5735764503479004,+0.0000000000000000]
[+0.0000000000000000,+1.0000000000000000,+0.0000000000000000,+0.0000000000000000]
[+0.5735764503479004,+0.0000000000000000,+0.8191520571708679,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +0.8191520571708679   +0.0000000000000000   -0.5735764503479004   +0.0000000000000000
   +0.0000000000000000   +1.0000000000000000   +0.0000000000000000   +0.0000000000000000
   +0.5735764503479004   +0.0000000000000000   +0.8191520571708679   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

z rotation +15.0000000000000000
[+0.9659258127212524,+0.2588190436363220,+0.0000000000000000,+0.0000000000000000]
[-0.2588190436363220,+0.9659258127212524,+0.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+1.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +0.9659258127212524   +0.2588190436363220   +0.0000000000000000   +0.0000000000000000
   -0.2588190436363220   +0.9659258127212524   +0.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

ngl mult y*x 
[+0.8191520571708679,+0.4055798053741455,-0.4055798053741455,+0.0000000000000000]
[+0.0000000000000000,+0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.5735764503479004,-0.5792279839515686,+0.5792279839515686,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

iMath mult y*x 
(  +0.8191520571708679   +0.4055798053741455   -0.4055798053741455   +0.0000000000000000
   +0.0000000000000000   +0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.5735764503479004   -0.5792279839515686   +0.5792279839515686   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

ngl mult x*y*z 
[+0.7912400960922241,+0.2120121568441391,-0.5735764503479004,+0.0000000000000000]
[+0.2087472975254059,+0.7879844307899475,+0.5792279839515686,+0.0000000000000000]
[+0.5747727155685425,-0.5780408978462219,+0.5792279839515686,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

iMath mult x*y*z 
(  +0.7912400960922241   +0.2120121568441391   -0.5735764503479004   +0.0000000000000000
   +0.2087472975254059   +0.7879844307899475   +0.5792279839515686   +0.0000000000000000
   +0.5747727155685425   -0.5780408978462219   +0.5792279839515686   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

inverse 
(  +0.7912400960922241   +0.2087472826242447   +0.5747727155685425   +0.0000000000000000
   +0.2120121717453003   +0.7879844903945923   -0.5780409574508667   +0.0000000000000000
   -0.5735764503479004   +0.5792279243469238   +0.5792278647422791   +0.0000000000000000
   +0.0000000000000000   -0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

inverse 
[+0.7912402153015137,+0.2087473124265671,+0.5747727751731873,+0.0000000000000000]
[+0.2120122015476227,+0.7879846096038818,-0.5780410170555115,+0.0000000000000000]
[-0.5735765099525452,+0.5792279839515686,+0.5792279243469238,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

i V*M (+2.9330530166625977 +0.0538582801818848 +2.3225636482238770)
n V*M[+2.9330530166625977,+0.0538582801818848,+2.3225636482238770,+1.0000000000000000]
n M*V[-0.5054649114608765,+3.5224001407623291,+1.1563749313354492,+1.0000000000000000]

The main upshot from these changes occur in the ngl demos where the code to load the matrices to the shader have been modified as shown in the code here
ngl::Matrix MV;
ngl::Matrix MVP;
ngl::Mat3x3 normalMatrix;
ngl::Matrix M;

// load matrix to shader before changes to ngl::Matrix

M=_tx.getCurrentTransform().getMatrix();
MV=m_cam->getViewMatrix() *_tx.getCurrAndGlobal().getMatrix();
MVP=m_cam->getProjectionMatrix()*MV*;

// new version with compatible matrix
M=_tx.getCurrentTransform().getMatrix();
MV=_tx.getCurrAndGlobal().getMatrix()*m_cam->getViewMatrix() ;
MVP=MV*m_cam->getProjectionMatrix();

As you can see when we calculated the previous MVP matrix it was done using the P*V*M calculation order, now we use M*V*P instead. If you are having any issues just swap the matrix order in these type of functions.

Monday, 16 January 2012

Pipeline Stuff

As we are starting the group project section of the Masters term I thought it would be a good idea to do some basic pipeline stuff.

This example is going to write some simulation data created in a C++ program and then we will write some simple Python scripts to load this data into Maya and Houdini.

The following video shows the program in action as well as the Maya and Houdini versions.

As this is an ad-hoc system the output file format is very simple, the C++ program writes out the following data :-


NumParticles 500
Frame 0
P0 64.301 -57.7284 49.8516
....
Frame 1
....

Where the particle data consists of The name of the Particle (in this case P0,P1....Pn) and the x,y,z positions of the particle per frame.

Maya Version
The maya version of the program will popup a dialog box to prompt the user to select an input file, it will then parse the file and create a locator for each of the particle names found.
Then for every frame a keyframe is created for the locator replicating the animation from the C++ simulation.
import maya.OpenMaya as OM
import maya.OpenMayaAnim as OMA
import maya.cmds as cmds
The code above loads in the maya elements we need, OpenMaya contains the core maya elements we need, the OpenMayaAnim namespace has the controls for all the animation transports such as setting the current frame and maya.cmds gives us access to all the maya cmds similar to the mel commands printed in the console when we generate things.

def createLocator(_name,_x,_y,_z) :
    cmds.spaceLocator( name=_name)
    cmds.move(_x,_y,_z)
    cmds.setKeyframe()

The createLocator function creates a simple space locator then moves it to the current x,y,z position we then set the keyframe to make the initial key at the currently set frame (more on this later)

def moveLocator(_name,x,y,z) :
    cmds.select(_name)
    cmds.move(x,y,z)
    cmds.setKeyframe()

The move locator function, first selects the locator (based on the name passed in), it then calles the move command which will move the currently selected object, finally we set the keyframe.
def importParticleFile() :
    basicFilter = "*.out"

    fileName=cmds.fileDialog2(caption="Please select file to import",fileFilter=basicFilter, fm=1)
    if fileName[0] !=None :

 file=open(str(fileName[0]))
 frame=0
 numParticles=0
 #set to frame 0
 animControl=OMA.MAnimControl()
 animControl.setCurrentTime(OM.MTime(frame))

 for line in file :
  line=line.split(" ")
  if line[0]=="NumParticles" :
   numParticles=int(line[1])
  elif line[0]=="Frame" :
   frame=int(line[1])
   animControl.setCurrentTime(OM.MTime(frame))
  else :
   name=line[0]
   x=float(line[1])
   y=float(line[2])
   z=float(line[3])
   if frame==0 :
    #we need to create our initial locators
    createLocator(name,x,y,z)
   else :
    moveLocator(name,x,y,z)
Houdini Version
The houdini version of the script will generate a null which is the houdini equivalent of a locator. By default a houdini null doesn't contain any geometry so we also need to parent this to some axis (houdini call these controls).


To start with we need to get the name of the file, this is done using the getAbsoluteFilename function described here http://jonmacey.blogspot.com/2011/01/houdini-python-ascode.html


def createNull(parent,_name,x,y,z) :
 #create a null this will set loads of default values
 null = parent.createNode("null", _name, run_init_scripts=False, load_contents=True)
 # set the x,y,z values
 null.parm("tx").set(x)
 null.parm("ty").set(y)
 null.parm("tz").set(z)
 # now add a control to the null so we have something to visualise
 null.createNode("control", "ctrl"+_name, run_init_scripts=False, load_contents=True)
 # now grab the keyframe
 setKey = hou.Keyframe()
 # set to frame 0
 setKey.setFrame(0)
 # now key the tx/y and z values
 setKey.setValue(x)
 null.parm("tx").setKeyframe(setKey)
 setKey.setValue(y)
 null.parm("ty").setKeyframe(setKey)
 setKey.setValue(z)
 null.parm("tz").setKeyframe(setKey)
 # now add to our network node
This function is passed the parent node, which in this case will be the houdini path "/obj/" from this we create a new node called a "null" and set it's parameters. In Houdini all parameters can be access using the parm(...) method of the object passing in the name of the parameter we wish to access, in this case "tx/ty/tz". Next we set the keyframes using the hou.keyframe class.
def moveNull(_name,frame,x,y,z) :
 null=hou.node("/obj/"+_name)
 setKey = hou.Keyframe()
 setKey.setFrame(frame)
 setKey.setValue(x)
 null.parm("tx").setKeyframe(setKey)
 setKey.setValue(y)
 null.parm("ty").setKeyframe(setKey)
 setKey.setValue(z)
 null.parm("tz").setKeyframe(setKey)
In this function we grab the object by name then set the keyframes using the same method as above. Finally we are going to create our nodes and import the file, the file reading code is exactly the same, however we create a subNetwork first to make the houdini scene neater.
def importParticleFile() :

 fileName=GetAbsoluteFileName("Select particle File","*.out",hou.fileType.Any)
 # get the the object leve as our parent
 if locals().get("hou_parent") is None:
  parent = hou.node("/obj")

 # make sure we got a filename
 if fileName !=None :
  subNetName=hou.ui.readInput("Please Enter name for the subnet")
  ## @brief we now copy this to a new string
  subNetName=subNetName[1]

  subNet=parent.createNode("subnet", subNetName, run_init_scripts=False, load_contents=True)
  #open the file (could do a proper check here)
  file=open(fileName)
  frame=0
  numParticles=0
  # now process the file and create the nulls
  for line in file :
   line=line.split(" ")
   if line[0]=="NumParticles" :
    numParticles=int(line[1])
   elif line[0]=="Frame" :
    frame=int(line[1])
   else :
    name=line[0]
    x=float(line[1])
    y=float(line[2])
    z=float(line[3])
    if frame==0 :
     #we need to create our initial locators
     createNull(subNet,name,x,y,z)
    else :
     moveNull(subNetName+"/"+name,frame,x,y,z)


To grab the full code use the following

DemoProgram (requires NGL)
particles.out (the output of the program which the python files read)
Houdini Script
Maya Script