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

Wednesday, 7 December 2011

Getting Started with Alembic

I've just started looking at the Alembic interchange format which looks like a really promising system for passing baked animation data plus a whole lot more around. With big players like ILM, Sony Image Works, The Foundry etc all using it I thought it would be good to get started with implementing my own wrapper for the ngl:: library and just have a general look at what it can do so I can do a lecture or two on it.

Installation is fairly straight forward, you need boost++,  HDF5 and cmake and the OpenEXR framework libraries ( ILMBase which has IMath etc).  On the mac the latest version get placed in /usr/local/alembic-1.0.3 and this will have the static libs, headers and maya plugins needed to get started.

I'm not going to dwell too long on the structures etc of Alembic at present and just have a look at getting started on some very basic loading of meshes etc. A good overview can be found here.

Getting some geometry

To get some sample geometry to test I decided to generate a very simple scene of primitives in maya


I have named each of the primitives to I can see how they fit into the alembic structure, also as transforms are present within the alembic structure, I decided to duplicate and transform some of the elements to see how this worked.

To export the data we can use the maya exported which comes with Alembic. This is called AbcExport and can be run from the Mel script window. I've not really managed to figure out how selection / grouping works with the exporter as yet, and it seems best to make each individual element in the scene a root and export in turn. This can be done with the following command.

AbcExport -v -j " -ws -root pasted__Platonic -root pasted__Helix -root pasted__Pipe -root pasted__Solid -root pasted__Torus -root pasted__Cone -root pasted__Cylinder -root pasted__Sphere -root pasted__Plane -root Platonic -root Helix -root Pipe -root Solid -root Torus -root Cone -root Cylinder -root Sphere -root Plane -file /Users/jmacey/teaching/AlembicTests/MayaTest.abc";

You will see that the -j option is basically a command string that specifies the job, in this case we are exporting world space root nodes (-ws) where -root  indicates each of the objects to store.

Finally I'm writing to the file MayaTest.abc with an absolute directory.


Basic Qt Project

To get started I've created a basic Qt project based on the installation directory  /usr/local/alembic-1.0.3 to build and link agains the libs using a qt program can be done using the following

INCLUDEPATH+=/usr/local/alembic-1.0.3/include/
INCLUDEPATH+=/usr/local/include/OpenEXR
LIBS+=-L/usr/local/alembic-1.0.3/lib/static/
LIBS+=-lAlembicAbc
LIBS+=-lAlembicAbcGeom
LIBS+=-lAlembicAbcCoreAbstract
LIBS+=-lAlembicUtil
LIBS+=-lAlembicAbcCoreHDF5
LIBS+=-lImath
LIBS+=-lHalf
LIBS+=-lIex
LIBS+=-lhdf5
LIBS+=-lhdf5_hl

You will notice that we need to include both the hdf5 and ILM libs as well as the static alembic libraries.

Basic File read / echo
The first program I wrote was a basic traversal of the structure looking for nodes / meshes.  From initial reading of the docs It seems that the internal structure of an alembic file closely resembles as unix file system. With a root node / then nodes following down a tree structure. Each of these nodes is a distinct object that we can access and gather attribute values from.

In most of the sample code / demos a recursive visitor pattern seems to have been used for initial test I wanted a simple iterative solution for quick testing and debugging so I decided to use static loops instead. 

The following code is the basic opening of a Alembic archive.
#include <Alembic/AbcGeom/All.h>
#include <Alembic/AbcCoreAbstract/All.h>
#include <Alembic/AbcCoreHDF5/All.h>
#include <Alembic/Abc/ErrorHandler.h>

using namespace Alembic::AbcGeom; 
...
IArchive  archive( Alembic::AbcCoreHDF5::ReadArchive(),argv[1] );

One of the key design philosophies behind Alembic is that the API is split into In and Out classes similar to iostream. This is summed up well by the following quote in the documents

"Because Alembic is intended as a caching system, and not a live scenegraph, the API is split into two roughly symmetrical halves: one for writing, and one for reading. At the Abc and AbcGeom layers, classes that start with ‘O’ are for writing (or “output”), and classes that start with ‘I’ are for reading (or “input”). This is analogous to the C++ iostreams conceptual separation of istreams and ostreams."

So in the above we are opening an input (I) archive.

The next section of code will get the top of the archive and see how many children there are 
std::cout<<"traversing archive for elements\n";
IObject obj=archive.getTop();
unsigned int numChildren=obj.getNumChildren();
std::cout<< "found "<<numChildren<<" children in file\n";
Once we have the number of children we can iterate for each child in the node and traverse the tree (it must be noted in this example I know the tree is of a set depth, in reality we would need to traverse using recursion / visitor pattern to cope with more complex scene / geometry data however for a proof of concept this is fine)

for(int i=0; i<numChildren; ++i)
{
 std::cout<<obj.getChildHeader(i).getFullName()<<"\n";
 IObject child(obj,obj.getChildHeader(i).getName());
 
 std::cout<<"Children "<<child.getNumChildren()<<"\n";
 const MetaData &md = child.getMetaData();
 std::cout<<md.serialize() <<"\n";
 
 for(int x=0; x<child.getNumChildren(); x++)
 {
  IObject child2(child,child.getChildHeader(x).getName());
  const MetaData &md2 = child2.getMetaData();
  if( IPolyMeshSchema::matches( md2 ) || ISubDSchema::matches( md2 ))
  {
   std::cout<<"Found a mesh "<<child2.getName()<<"\n"; 
  }
 }
}

The code above grabs the child header of the current object and prints out the name. We then create a new IObject from the current object branch (getChildHeader(i).getName() ), this is the next object in the tree and we can then traverse this.

To access all of the data we can grab the meta data and call the serialize method, followed by traversing the data to see if we have a mesh and seeing what the name is. The output on the scene above looks like the following ( a partial listing )


traversing archive for elements
found 18 children in file

/Plane
Children 1
schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform
Found a mesh plane
/Platonic
Children 1
schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform
Found a mesh icosa
/Solid
Children 1
schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform
Found a mesh buckyball
/Sphere
Children 1
schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform
Found a mesh sphere
/Torus
Children 1
schema=AbcGeom_Xform_v3;schemaObjTitle=AbcGeom_Xform_v3:.xform
Found a mesh torus


Getting to the Points
Once I have the ability to grab the mesh in the file structure I can now look at accessing the point data and rendering it. This is a two stage process with the maya output as we have a top level transform node and then the point data. We need to get the transform matrix, then we need to multiply the points by it to get them in the correct world space.

Using the previous code frame work, we can grab the transform using the following

IXform x( child, kWrapExisting );
XformSample xs;
x.getSchema().get( xs );
M44d mat=xs.getMatrix();

You will see that this returns a M44d which is from the IMath library and is a 4x4 Matrix. We can also access the XformSample elements in a number of ways such as getTranslation, get[X/Y/Z]Rotation, getScale which will actually fit in well with the ngl::Transform class once I get to that stage. Initially I'm just going to use the raw matrix and use IMath to do the multiplication with the point.

We now do the check to see if we have a mesh as outlined above, If we do we can built a MeshObject and access the data as show below
// we have a mesh so build a mesh object
IPolyMesh mesh(child,child2.getName());
// grab the schema for the object
IPolyMeshSchema schema=mesh.getSchema();

// now grab the time sample data for the object

IPolyMeshSchema::Sample mesh_samp;
schema.get( mesh_samp );
// get how many points (positions) there are
uint32_t size=mesh_samp.getPositions()->size();

Another important concept of Alembic is the idea of sampling, again for the documentation

"Because Alembic is a sampling system, and does not natively store things like animation curves or natively provide interpolated values, there is a rich system for recording and recovering the time information associated with the data stored in it. In the classes AbcA::TimeSamplingType and AbcA::TimeSampling, you will find that interface."


We access the mesh sample at the current time value (0 as we've not set any frame data yet) and we can get the positions (and size) at this temporal sample.

The following code will now loop for each of the positions in the sample and multiply them by the current transform. I then save each in my own format Vec3
for(uint32_t m=0; m<size; ++m)
{
  // get the current point
  V3f p= mesh_samp.getPositions()->get()[m];
  // multiply by transform
  p=p*mat;
  // store for later use
  data.push_back(ngl::Vec3(p.x,p.y,p.z));
}
In the case of this demo I just create a point cloud into a Vertex Array object and draw using my ngl:: framework. This can be seen in the following video

A lot of this post is supposition /  basic rantings about what I've done so far, as this is a very new API and there is very little solid documentation at the moment, I think this post may well be updated / superseded very soon. It is my intention to fully integrate the mesh and possibly lights / cameras as much as I can into ngl, and we also intend to use this as a core data exchange format at the NCCA, so I hope to have much more detail about all this very soon.

Thursday, 10 November 2011

iNGL

I've just release the initial version and a demo of the iNGL library for iOS devices (it's designed for iPhone 3Gs and above and both iPads as it uses OpenGLES 2.0)

You can see more here and I will do many a blog post about it soon

http://nccastaff.bournemouth.ac.uk/jmacey/GraphicsLib/iNGL/index.html

Thursday, 3 November 2011

What's changed in NGL 4.5

I'm just about to put the feature freezes on NGL 4.5 and this blog post is going to highlight the many changes from version 4.0 which many of you may have used in last years assignments.

The Big Picture
Perhaps the biggest change in the whole library is the conversion OpenGL 3.2 core profile only.  This design decision was made for a number of reason, primarily for speed and compatibility across platforms, but also so I can teach the very latest versions of OpenGL and CGI techniques. The only major issue you may encounter with this change is that only Mac OSX Lion is now supported.

The main reason for this decision was so we could use GLSL 150 which is only available under mac using Lion and also makes it easier to port all the code base to OpenGL ES ( there is an iPhone port of NGL which will be released soon). If you don't wish to upgrade to Lion then you will have to use the legacy version or develop under another platform.

This also means that all immediate mode OpenGL has gone, (under Lion they are not present as it uses two different libraries for GL2.0 and GL3.2 see here ) so it means you can no longer use functions like glBegin, glVertex, glRotate, glTranslate, glScale etc. It also means that the whole OpenGL lighting and Material subsystem has also gone and we need to use shaders to do everything. I've already mentioned some of these things in previous posts.

It has now also been tested to work with boost 1.4.7 and the Windows version seems to be a lot more stable (but will still be the last version to be developed as I hardly ever use windows)

Changes
The following list shows the major changes to NGL from 4.0 to 4.5

  1. ngl::Clip class has been removed as the student version of Houdini no longer support ASCII clip export, this has been replaced with the NCCAPointBake system and eventually Alembic support will be added
  2. ngl::ShaderManager has been replaced with ngl::ShaderLib, this is mainly a name change after some healthy in class discussions with Ian about class naming. Also all calls to the shader subsystem no longer need the name of the shader passed as a parameter. It is assumed that the currently active shader is the one to set parameters for.
  3. ngl::VBOPrimitives has been replaced with ngl::VAOPrimitives, the class is effectively the same, however we now use Vertex Array Objects to store our primitives, this also has advantages for porting to OpenGL ES 2.0 
  4. ngl::VertexArrayObject class was added in 4.0 but is now used internally to create all geometry required in any of the classes. This has made the removal of any immediate mode OpenGL much easier.
  5. ADDLARGEMODELS define has been added to the NGL.pro file, if defined it will add 4 new primitives to the VAOPrimitives class these are the NCCA Troll, Stanford Bunny, Buddha and Dragon. These models are very large and should only be enabled if required. The image below shows them in action on the iPhone 4s

New Classes
The following new classes have been added, some are to help with GLSL compatibility and some because of the changes to core profile required it.
  1. ngl::Mat3x3 a simple 3x3 matrix class which can be created from the ngl::Matrix class. This is generally used to calculate the normal matrix for GLSL shaders and lighting calculations.
  2. ngl::Vec3 is a simple container for a GLSL vec3 type, it is basically a subset of the ngl::Vector class and is used internally for passing vertices etc to the GLSL
  3. ngl::Vec2 is a simple container for a GLSL vec2 type and works similarly to above.
  4. ngl::Text this class is described in detail here but is needed as we can no longer use the Qt renderText method as it relies on immediate mode OpenGL
Demo Programs
I'm currently in the process of converting the demo programs to work on the new Version of NGL and this is taking a little time as there are quite a lot of changes in the way the shaders work and this does take some time.

The following videos talk through how the SimpleNGL demo works