GLASS tutorial

A GLASS model

A GLASS model is a structured 3D model. Its shape is made up of surfaces, which are the solid parts of the model. These are linked together by a structure which is fixed when it is created (by hand or using an editor), but the models shape can be modified by using variables, that change "joints" in the model. Feedback is available by using active points that give the position and direction of specific places in the model.

These three usable parts, the surfaces, variables, and active points are explained in more detail below.

Surfaces

A surface is a collection of triangles. It is the smallest unit in a GLASS model, and is what is linked together to make the model. The triangles are stored internally in the GLASS model structure, or by using openGL display lists. Each surface has a unique name, allowing its visibility to be modified, or for it to be drawn on its own.

Variables

Variables control the transforms that link the surfaces together. Each variable has a unique name, a value, and the minimum and maximum values the variable can take. By modifying one variable, the shape of the GLASS model can be changed.

Active Points

Active points can be present in the object. An active point allows the program using the GLASS object to find the position and direction of a point in the object, after it has been moved by the various transforms. Each active point has a unique name.

Using GLASS in a project

To use a GLASS object the following things must be done:
  • Set up openGL
  • Get the version of GLASS
  • Load the model
  • Find the surfaces/variables/active points
  • Modify variables
  • Modify surface visibility
  • Draw the surfaces/model
  • Read active points
  • Remove the model
  • Optionally, libraries may be used.

    Setting up openGL

    For GLASS to work, openGL must be correctly set up. The display must be set up (using GLUT for instance), lighting must be prepared if it is to be used, depth testing must be enabled, back face culling (if wanted), and the desired blending function set up. As is with openGL if the display is correctly set up then the default settings will enable most GLASS models to be displayed fine, but you will want to consider these settings for efficient/correct display.

    Getting the version of GLASS

    The version of glass can be obtained using the function:

    const char *glassGetVersion(void);
    

    This returns a string that describes the current version of glass. For example, for version 1.0.0 this returns "1.0.0" (like you'd never expect). The string is used internally by GLASS so don't free it!

    Loading a GLASS model

    First your program will need to include the GLASS header file glass.h, with the following standard command.

    #include <glass.h>
    

    A GLASS model is stored in the GlassModel structure. The internals of the structure are not relevant, all that is required is that this structure is passed to the GLASS functions. An empty GLASS model is then declared as follows:

    GlassModel *foo = NULL
    

    Where foo is the name of the model you want to create.

    To actually load a glass model all you need to use the first GLASS function. Keep in mind that loading the model will create textures, and display lists.

    The prototype for the loading function is:

    GlassModel *glassLoadModel(const char *fname)
    

    Where fname is the path of the model file. This returns a pointer to the GLASS model.

    Using the previous example, the GLASS model in the file models/hello.glm would be loaded using:

    #include <glass.h>
    
    int main(int argc, char **argv)
    {
      GlassModel *hello = NULL;
    
      hello = glassLoadModel("models/hello.glm");
      if(hello == NULL)
        printf("Error loading GLASS model!\n");
    }
    

    So now you have a loaded GLASS model (assuming the model file was valid).

    GLASS Libraries

    Models can be grouped together into libraries, which allow models to share textures/materials/surfaces/variables/active points. The prototype of the function that creates GLASS libraries is:

    GlassLibrary *glassLibraryNew(const char *name)
    

    Where name is the name of the new library. This returns a pointer to the GLASS library. The GLASS library pointer is dealt with in the same way as a GLASS model pointer.

    To add models to this library, the following function is used:

    GlassModel *glassLibraryLoadModel(GlassLibrary *library, const char *fname)
    

    Where library is the pointer to the library created (as above), fname is the path of the GLASS model file. This returns a pointer to the added GLASS model.

    The difference between this function, and the previous loading function is that the loaded model is added to the library. When the model is assembling itself while loading, it first looks inside itself for items (variables, active points etc), and then in the other models in the library. This means it is important that models are loaded into the library in the correct order, with models not linking to anything else first.

    Libraries are useful in programs where the GLASS models have an underlining theme. For example in a space game a fleet of ships might all have the same gun turrets. By having the turret surfaces stored in a model file, and this being part of a library, many GLASS models can access this surface without it having to be defined in each model. This also means you can change it once, and all ships in the fleet will have the new turret automatically.

    Finding Surfaces/Variables/Active Points

    For surfaces, variables or active points to be modified/accessed, they first need to be found. Each of these have a unique name, and it is assumed you know what these are (for a particular model). Each surface/variable/active point has an integer index which is what you want to know. The functions that turn the names into indices are:

    int glassFindSurface     (GlassModel *model, const char *name)
    int glassFindVariable    (GlassModel *model, const char *name)
    int glassFindActivePoint (GlassModel *model, const char *name)
    

    Where model is a valid GLASS model, and name is the name of the surface/variable/active point. The return values are the index of that surface/variable/active point. -1 is returned if the surface/variable/active point doesn't exist. The names are case sensitive.

    All indicies used by surfaces, variables and active points are numbered from zero to the number of each part + 1. The number of each part can be found with:

    int glassGetNumSurfaces     (GlassModel *model)
    int glassGetNumVariables    (GlassModel *model)
    int glassGetNumActivePoints (GlassModel *model)
    

    For example if you had loaded a model of a tank, which was made up of two surfaces, the hull, and the turret, which were linked by a rotation in the y-axis controlled by a variable turret heading, and the end of the turret had an active point, barrel; These would be found by:

    #include <glass.h>
    
    GlassModel *tank = NULL;
    int num_surfaces;
    int surf_hull, surf_turret, surf_fish; /* The surfaces */
    int var_turret_head;                   /* The variables */
    int apt_barrel;                        /* The active points */
    
    int main(int argc, char **argv)
    {
      tank = glassLoadModel("models/tank.glm");
    
      num_surfaces = glassGetNumSurfaces(tank);
      surf_hull = glassFindSurface(tank, "hull");
      surf_turret = glassFindSurface(tank, "turret");
      surf_fish = glassFindSurface(tank, "fish");
    
      var_turret_head = glassFindVariable(tank, "turret heading");
    
      apt_barrel = glassFindActivePoint(tank, "barrel");
    }
    

    The values of the variables would all be >= zero, but the value for surf_fish would be -1, since there is no surface fish in the model. num_surfaces would be 2 assuming there are only the two surfaces "hull" and "turret".

    Modifying Variables

    Each variable has a value, and a maximum, and minimum value. The value of a variable may be set to a particular value, or increased by a delta value. GLASS will clip these values internally so the value lies in the range min <= value <= max.

    The prototypes for retrieving the properties of a variable are:

    GLfloat glassGetVariableValue(GlassModel *model, int variable)
    const GLfloat *glassGetVariableValuev(GlassModel *model, int variable)
    GLfloat glassGetVariableMin(GlassModel *model, int variable)
    GLfloat glassGetVariableMax(GlassModel *model, int variable)
    

    Where model is a valid GLASS model, and variable is a valid index of a variable in that model. The first and last two functions return the value, minimum value, and maximum value respectively for that variable. The values are stored in the GLfloat format to be compatible with openGL. If the variable doesn't exist the returned values are undefined. The second function returns a pointer to the value of the variable. This is particularly useful as this is a pointer to the actual stored value, and so changes as the variable changes. Since it is used internally it shouldn't be freed or modified. If variable doesn't exist in this case the function returns NULL.

    The prototypes for the functions that modify variables are:

    int glassSetVariable(GlassModel *model, int variable, GLfloat value)
    int glassIncVariable(GlassModel *model, int variable, GLfloat dvalue)
    

    Where model is a valid GLASS model and variable is the index of a variable for that model. value is the new value of the variable, and dvalue is the change in value for a variable. Both functions return FALSE if the variable doesn't exist, and TRUE otherwise.

    For example with the tank, the following code would rotate the turret by 5 degrees:

    void animate(void)
    {
      ...
    
      glassIncVariableInc(tank, var_turret_head, 5.0);
    
      ...
    }
    

    Modifying a surfaces visibility

    A surface can be hidden, or shown. Note that this affects all uses of this surface, as surfaces can be reused in one or more models. The prototype is:

    int glassSetSurfaceVisibility(GlassModel *model, int surface, int visible)
    

    Where model is a valid GLASS model, surface is the index of a surface in that model, and visible is either TRUE or FALSE and is the new visibility state of that surface. This function returns FALSE if the surface doesn't exist, otherwise TRUE.

    For example if the tanks turret exploded, so it was no longer visible, but you still wanted to draw the tank model, the following code would be used:

    void explode_tank(void)
    {
      ...
    
      glassSetSurfaceVisibility(tank, surf_turret, FALSE);
    
      ...
    }
    

    Drawing surfaces/models

    Either single surfaces can be drawn, or the whole model. Drawing a single surface displays only that surface, without any transforms. Drawing the model draws the surfaces, by transforming as the structure and variables define, and updating the active points position and direction (if they have changed). These functions just draw triangles to openGL, so make sure that the screen is cleared etc. The prototypes are:

    int glassDrawSurface(GlassModel *model, int surface)
    int glassDrawModel(GlassModel *model)
    

    Where model is a valid GLASS model and surface is the index of a surface in that model. The first function returns FALSE if the surface doesn't exist, and the second returns FALSE if an error occurs while drawing the model.

    With the tank again... Say that the turret had exploded (see above), and you wanted to draw the tank, with the turret rising above the tank, then you would do the following:

    void display(void)
    {
      GLfloat explosion_height;
      ...
    
      glassDrawModel(tank);
      if(tank_exploded)
      {
        glTranslatef(0.0, explosion_height, 0.0);
        glassDrawSurface(tank, surf_turret);
      }
    
      ...
    }
    

    Reading active points

    Active points are only updated when the model is drawn, so you'll want to access them after that has happened. There are two functions for active points, one to return its current position, and one to return the current direction. The prototypes for these are:

    GLfloat *glassGetActivePointPos(GlassModel *model, int apoint)
    GLfloat *glassGetActivePointDir(GlassModel *model, int apoint)
    

    Where model is a valid GLASS model and apoint is the index of an active point in that model. These functions return an array of 3 GLfloats for the current position, or direction of the active point. They both return FALSE if the active point doesn't exist. These arrays (like for the variables), are used internally by GLASS and shouldn't be freed or modified, they do however continue to be updated, and can be reused like for the variables.

    With the tank example, active points are useful to find where the end of the turrets barrel is, so projectiles can be created at the correct position, and fired in the correct direction. By getting the position of the active point apt_barrel, the position of the barrel (relative to the tank) is found. And the direction of the active point is the direction the barrel is pointing. So when the tank is fired:

    /* The position of the tank (in the world) */
    GLfloat tank_pos[3];
    
    void fire_tank(void)
    {
      GLfloat *barrel_pos, *barrel_dir;
      /* The position and velocity of a new projectile */
      GLfloat projectile_pos[3], projectile_vel[3];
      int i;
      ...
    
      barrel_pos = glassGetActivePointPos(tank, apt_barrel);
      barrel_dir = glassGetActivePointDir(tank, apt_barrel);
    
      /* Create a projectile at the end of the barrel, with a speed
         of 10 m/s */
      for(i = 0; i < 3; i++)
      {
        projectile_pos[i] = barrel_pos[i] + tank_pos[i];
        projectile_dir[i] = 10.0 * barrel_dir[i];
      }
      ...
    }
    

    Removing GLASS models/Libraries

    When you've finished with a GLASS model, it can be freed using the following function:

    void glassFreeModel(GlassModel *model);
    

    Where model is a valid GLASS model.

    This function removes the model, and everything it used (e.g. textures). Note that now model is an invalid pointer, so setting it to NULL would be a good idea.

    To free a library, use:

    void glassFreeLibrary(GlassLibrary *library);
    

    This will remove the library, and all the models who are members of it.

    And that's it!

    That's all there is, as you can see (unless this isn't very obvious... :) ), the idea was to keep it simple. If you have any questions/comments feel free to email me at bob27@users.sourceforge.net. All feedback is appreciated. Feel free to modify the tutorial if you think it necessary, and give me the new version.
    Robert Cleaver Ancell
    Last modified: Thu Mar 13 11:07:42 NZDT 2003