SSG Auxiliary Libraries.

by Steve Baker

Introduction

PLIB/ssgAux is a suite of auxiliary libraries that build higher level classes on top of the basic SSG classes.

ssgAux screenshot
This image was created entirely from ssgAux primitives.

Conventions

ssgAux is installed and named with the same conventions as the base PLIB libraries.
ie:
   /usr/include/plib/ssgAux.h
   /usr/lib/libplibssgaux.a

PLIB/ssgAux functions, classes and constants are all named with an 'ssga' or 'SSGA' prefix.
eg:

   class ssgaShape ;
   SSGA_TYPE_CUBE

ssgaShape

This is an abstract base class for many of the more complex classes in the ssgAux 'collection'. The idea is that ssgaShape's are really ssgBranch nodes that create their own child nodes - but they behave in such a way that you are never concerned with the nodes they create beneath themselves.

class ssgaShape : public ssgBranch
{
  ssgaShape (void) ;
  ssgaShape ( int numtris ) ;
 
  float *getCenter  () { return center     ; }
  float *getSize    () { return size       ; }
  int    getNumTris () { return ntriangles ; }
 
  void setColour  ( sgVec4 c ) { sgCopyVec4 ( colour, c ) ; regenerate () ; }
  void setCenter  ( sgVec3 c ) { sgCopyVec3 ( center, c ) ; regenerate () ; }
  void setSize    ( sgVec3 s ) { sgCopyVec3 ( size  , s ) ; regenerate () ; }
  void setSize    ( float  s ) { sgSetVec3  ( size,s,s,s) ; regenerate () ; }
  void setNumTris ( int ntri ) { ntriangles = ntri ; regenerate () ; }
 
  void setKidState    ( ssgState *s ) ;
  void setKidCallback ( int cb_type, ssgCallback cb ) ;
 
  virtual void regenerate () = 0 ;                                              
}
The constructor allows you to specify the approximate number of triangles you'd like the shape to consume. For some shapes, this will be ignored (eg the Cube has 12 triangles - no matter how many you ask for) - for others, it's honored only approximately (eg the Sphere can only generate certain specific numbers of triangles) - and for others, it may be honored exactly.

You can also set the colour of all the polygons and the ssgState and callbacks that are applied to all of the child nodes.

setCenter allows you to position the center point of the object in 3D space - and setSize lets you determine the overall dimensions of the object (this too may also be approximate - the 'size' of a teapot is hard to specify in any useful way). The version of 'setSize' that takes an sgVec3 allow you to generate cuboids from cubes and sphereoids from spheres...but not all shapes allow this.

The 'regenerate' call allows the complete child structure of the object to be deleted and then recreated. This is occasionaly useful if (for example) you have walked down into the child structures and modified them - and you'd like the shape back into it's pristine conditions.

ssgaCube


class ssgaCube : public ssgaShape
{
  ssgaCube (void) ;
  ssgaCube ( int numtris ) ;
  virtual void regenerate () ;
} ;
 
This is the simplest of ssgaShapes - it defines nothing new over and above the standard ssgaShape.

ssgaPatch

 
 
class ssgaPatch : public ssgaShape
{
  ssgaPatch (void) ;
  ssgaPatch ( int numtris ) ;
 
  void setControlPoint ( int s, int t, sgVec3 xyz, sgVec2 uv, sgVec4 rgba ) ;
  void setControlPoint ( int s, int t,
                         float x, float y, float z,
                         float u, float v,
                         float r, float g, float b, float a ) ;
  void getControlPoint ( int s, int t, sgVec3 xyz, sgVec2 uv, sgVec4 rgba ) ;
 
  virtual void regenerate () ;
} ;
 
This implements a basic spline patch - with a 4x4 grid of 'control points' that define its shape. 'setContolPoint' allows you to set the spatial position, the texture coordinate and the colour at any given (s,t) position. (s and t each must be in the range 0..3).

It's necessary to call the 'regenerate' function whenever you have added or changed one or more control points.

ssgaTeapot

 
class ssgaTeapot : public ssgaShape
{
  ssgaTeapot (void) ;
  ssgaTeapot ( int numtris ) ;
  virtual void regenerate () ;
} ;
 
This creates the 'classic' Martin Newell teapot model.

ssgaSphere

  
class ssgaSphere : public ssgaShape
{
  ssgaSphere (void) ;
  ssgaSphere ( int numtris ) ;
 
  void setLatLongStyle ( int ll ) ;
  int  isLatLongStyle  () ;
  virtual void regenerate () ;
} ;

There are two ways to generate a sphere model - one is to produce a fairly uniform grid of triangles that are as nearly equilateral as possible. The alternative is to generate more nearly right-triangles in cylindrical strips that are stacked on top of each other with a circular disk at top and bottom.

The function 'setLatLongStyle(ll)' allows you to choose which you want, call regenerate after you change types!

ssgaCylinder

 
class ssgaCylinder : public ssgaShape
{
  ssgaCylinder (void) ;
  ssgaCylinder ( int numtris ) ;
 
  void makeCapped ( int c ) ;
  int  isCapped   () ;
  virtual void regenerate () ;
} ;                                                                             

Cylinders also come in two forms - capped and uncapped. Once again, if you change the capped versus uncapped status, call regenerate to make the new version.

ssgaWaveSystem

This class is for handling water waves. It generates a large polygonal mesh which it distorts in realtime to simulate water waves.

The simulation uses between one and sixteen "Wave Trains" - each using a class:


class ssgaWaveTrain
{
  ssgaWaveTrain () ;
 
  float getSpeed      () ;
  float getLength     () ;
  float getLambda     () ;
  float getHeading    () ;
  float getWaveHeight () ;

  void  setSpeed      ( float s ) ;
  void  setLength     ( float l ) ;
  void  setLambda     ( float l ) ;
  void  setHeading    ( float h ) ;
  void  setWaveHeight ( float h ) ;
} ;                                                                             

So, you create a bunch of these trains (typically, two or three is plenty to generate 'interesting' motion). Each wave train has a direction in which the waves are travelling, a speed, a wave length (which also affects the shape of the wave), the height of the wave and a 'lambda' term (which controls how much the tops of the waves bend over).

Next, generate the ssgaWaveSystem:

 
class ssgaWaveSystem : public ssgaShape
{
  ssgaWaveSystem ( int ntri ) ;
 
  virtual void        regenerate  () ;
 
  ssgaWSDepthCallback getDepthCallback () ;
  void                setDepthCallback ( ssgaWSDepthCallback cb ) ;
 
  ssgaWaveTrain *getWaveTrain ( int i ) ;
  void           setWaveTrain ( int i, ssgaWaveTrain *t ) ;
 
  float getWindSpeed   () ;
  float getWindDirn    () ;
  float getEdgeFlatten () ;
  float getTexScaleU   () ;
  float getTexScaleV   () ;
 
  void  setWindSpeed   ( float speed      ) ;
  void  setWindDirn    ( float dirn       ) ;
  void  setEdgeFlatten ( float dist       ) ;
  void  setTexScale    ( float u, float v ) ;
 
  void updateAnimation ( float t ) ;
} ;

The appearance of waves varies greatly with the depth of the water - and this simulation produces the correct effects. There is an optional user-callback that can be used to feed water depth values into the simulation. The wave system will likely call this function many thousands of times per frame - so make sure your depth function is very efficient. If you don't provide a depth function then the water will be assumed to be infinitely deep.

You can determine the number of polygons used to render the patch of waves, the size of the wave patch and the amount of texture repetition. The 'EdgeFlatten' setting determines to what distance from the edge of the patch the waves are gradually flattened out. This is useful because it allows you to keep all those expensive wave polygons close to the camera - and feather them out over range so that they blend gently into a larger flat ocean.

Designing waves by calling the ssgaWaveSystem API is quite difficult. You are advised to use the wave designer program in the PLIB examples package. This program lets you adjust wave parameters until it looks how you'd like - then hit the 'Write C++ code' to write out a source code snippet that you can cut and paste into your program.

There is a 'README' in the 'water' source directory that explains how to use the program in a little more detail. That's all the wave system documentation I have time to write just now - sorry.

ssgaParticleSystem

This is a very flexible implementation of particle systems - it's not as efficient as you might want for thousands of particles - this is intended for high flexibility and relatively fewer numbers of particles.

All of that flexibility is evident in the complex constructor function:


  class ssgaParticleSystem : public ssgVtxArray
  {
    ssgaParticleSystem ( int max ,          /* Max Number of particles      */
                         int init,          /* Number to launch initially   */
                       float add ,          /* Max number to create per sec */
                         int turn_to_face,  /* Turn to face camera?         */
                       float size,          /* Size of particles            */
                       float bsphere_rad,   /* Size of bounding sphere      */
      ssgaParticleCreateFunc create,        /* Initial create fn.           */
      ssgaParticleUpdateFunc update = NULL, /* Update function              */
      ssgaParticleDeleteFunc del     = NULL /* Remove function              */
                      ) ;

    void  setSize ( float sz ) ;
    float getSize () ;

    update ( float dt ) ;

    int getNumActiveParticles () ;
  } ;

There are three callback functions - with the following types:
 
  typedef void (* ssgaParticleCreateFunc) ( ssgaParticleSystem *ps,
					    int index,
					    ssgaParticle *p ) ;
 
  typedef void (* ssgaParticleUpdateFunc) ( float deltaTime,
					    ssgaParticleSystem *ps,
					    int index,
					    ssgaParticle *p ) ;
 
  typedef void (* ssgaParticleDeleteFunc) ( ssgaParticleSystem *ps,
					    int index,
					    ssgaParticle *p ) ;

There is also a public class for a single particle:
 
class ssgaParticle
{
public:
 
  sgVec4 col ;
  sgVec3 pos ;
  sgVec3 vel ;
  sgVec3 acc ;
 
  float size ;
  float time_to_live ;
  void *userData ;
 
  ssgaParticle () ;
 
} ;                                                                             

This is best explained with an example. Here is an example of a fountain model:

  fountain = new ssgaParticleSystem ( 2000, /* Max Number of particles */
                                         2, /* Number to launch initially */
                                       200, /* Max number to create per sec */
                                      TRUE, /* Turn to face? */
                                       0.2, /* Size of particles */
                                      20.0, /* Size of bounding sphere */
                           fountain_create, /* Initial create fn. */
                           fountain_update  /* Update function */ ) ;
 
This creates a system that has the capability to draw 2000 particles (each is a quadrilateral). Two particles are launched initially and 200 more are added gradually every second. You can choose to have each particle turn to face the camera - or to be oriented in the X/Z plane, mine are set to turn-to-face. The particles are 0.2 OpenGL units across.

This fountain is a continuous effect - so the inital number of particles to launch is small - and the 'per second' creation rate determines the flow. If you wanted something more like an explosion, have a larger number of initial particles and none launched per-second after that.

One messiness is that we don't know how big the fountain may become over time - and it's VERY inefficient to recompute the bsphere every frame. Hence you have to tell the class what the maximum bounding sphere of the fountain is at the outset so we can field-of-view cull it - this could be a near infinite radius (MAX_FLOAT say) if you truly don't know how big it could get - but that's inefficient because all 2,000 particles must be sent to OpenGL even if the fountain is 20 miles away behind the camera - so do your best to come up with some kind of a reasonable guess.

fountain_create and fountain_update are user-defined callback functions, we could also have defined a 'fountain_delete' function - but many applications won't need this.

You have to have a 'create' function - but the other two are optional.

These callbacks are passed the address of the particle system, the index number and address of the particle that's being created/updated/deleted - and (for the 'update' function only) - the amount of elapsed time since this particle was last updated.

The callbacks can set, read or change any or all aspects of the particle... it's colour, position, velocity, accelleration, size and 'time to live' (in seconds). There is also a user data pointer - so you can hang your own data onto each individual particle.

The particle system automatically moves each particle according to the usual laws of motion and decrements it's time-to-live - so in many cases, you can just set the initial velocity, the force due to gravity and a reasonable time to live - and let the particle system code do the rest.

My 'fountain_create' function looks like this:


void fountain_create ( ssgaParticleSystem *, int, ssgaParticle *p )
{
  sgSetVec4 ( p -> col, 1, 1, 1, 1 ) ;  /* initially white */
  sgSetVec3 ( p -> pos, 0, 0, 0 ) ;     /* start off on the ground */
  sgSetVec3 ( p -> vel, (rand()%1000-500)/300.0f,
                        (rand()%1000-500)/300.0f, 50.0f ) ;
                                        /* Shoot up and out */
  sgSetVec3 ( p -> acc, 0, 0, -9.8f ) ; /* Gravity */
  p -> time_to_live = 5 ;               /* Droplets evaporate after 5 seconds */
}

It populates the particle with an initial colour, position, velocity and accelleration and gives it a 'time to live' of five seconds. The velocity is randomised a bit to make a nicer looking fountain.

You can do all sorts of fancy things to the colour, position, velocity and accelleration in your 'update' function.


void fountain_update ( ssgaParticle *p )
{
  if ( p -> pos [ 2 ] < 0 )
    p -> time_to_live = -1 ;
 
  p -> col [ 2 ] = (p -> time_to_live > 2) ? 1 : (p -> time_to_live/2.0f) ;
}

...this one just erases particles that go below zero altitude (by setting their time-to-live variable to -1) and makes them go a pretty shade of yellow for the last two seconds before they die.

The 'delete' function is principally useful for freeing up any user-data you allocated in the 'create' function or during 'update's. Don't delete the particle though - the particle system recycles it to avoid doing too much dynamic memory allocation.

All that remains is to call the ssgaParticleSystem::update function every frame with a parameter that tells the system how much time has elapsed since the last call. This allows you to easily speed up, slow down or pause the particles (or even run them backwards if you want).

I call this every frame:


void updateFountain ()
{
  static ulClock ck ;

  ck . update () ;
 
  if ( fountain != NULL )
    fountain -> update ( ck.getDeltaTime () ) ;
}
                                                                                
This function could also check the number of particles currently in flight and delete the fountain when there are none left - that might be useful for an explosion or something where you'd want to save CPU time by not running the explosion particle system when all the pieces have landed and 'gone away'.

The size of each particle is the size field of the individual particle MULTIPLIED by the number you pass into the size parameter of the ssgaParticleSystem constructor (or set with setSize()). This allows you to cheaply set the particle size for all the particles at once - or to tweak the size of each one in turn. The number in the particle structure defaults to 1.0 so you can ignore it and just set the size in the particle system overall.

ssgaParticleSystem is derived from ssgVtxArray - so you can apply textures and other state things using the usual ssgVtxArray::setState(ssgSimpleState*) call. I applied a texture with a fuzzy alpha-blended circle.

While debugging this, I got a bug which caused a picture of Tux to be applied instead of the droplet texture - it was absolutely hilarious to see 2000 tiny penguins shooting up in a fountain and falling gently to earth!

ssgaFire

This class is actually a highly specialised particle system. You have to construct it quite carefully in order to get a nice looking fire:

  class ssgaFire : public ssgaParticleSystem
  {
    ssgaFire ( int num_tris,
               float radius = 1.0f,
               float height = 5.0f,
               float speed  = 2.0f ) ;
 
    virtual ~ssgaFire () ;
 
    virtual void update ( float t ) ;
 
    void setUpwardSpeed ( float spd )
    void setHeight      ( float hgt )
    void setRadius      ( float rad )
    void setHotColour   ( sgVec4 col )
  } ;

In the constructor, you set the number of triangles you wish to generate, the radius and height of the approximately cylindrical fire and the speed at which the flames head up towards the sky.

The number of polygons you use tends to be rather critical - too many and your fire will look too 'smooth' and will be mostly white-hot. Too few and it'll look like a number of detached reddish blobs floating upwards. For the default radius, between 100 and 200 triangles seems to look good - for a 10 meter patch of fire, you may need as many as 2000 triangles to make it look effective. All three parameters (speed, height and radius) can be manipulated in realtime - but since the number of polygons is fixed, there is a limit to the amount of realtime tweaking you can do without making it look silly.

The 'setHotColour' function allows you to set the colour of the hottest flames (at the base of the fire). Since many layers of polygons add up to form the colour, you'll tend to want to use a primary colour with about 10% of one or two of the other primaries. The default colour is (1.0, 0.2, 0.1, 1.0) which produces red flames with yellow and white in the hotter regions.

ssgaLensFlare

When you view the sun (or some other very bright light source) through the imperfect lens of a camera, you get a row of bright circles and rings in a line from the light source, through the center of the lens. This is called a 'lens flare' and it can be very effective in computer graphics in depicting a light source that's brighter than the computer screen can display. It almost makes you want to squint because of the brightness.

The ssgaLensFlare object is a special kind of ssgaShape that can be positioned beneath the same transform as the source of the light - and it 'just works'. You don't need to set any parameters - just add it into the SSG scene graph in the right place.

eg:


   ssgTransform *myTransform ;
   ssgBranch    *myLightSource ;

   /* Set up myTransform and myLightsource */

   ...

   myTransform -> addKid ( myLightSource ) ;
   myTransform -> addKid ( new ssgaLensFlare ) ;

It contains a (hard-coded) 256x128 texture map which is compiled into the code and shared between however many lens flares there are in the scene.

ssgaSky

The ssgaSky class models a blended sky dome, with methods to add celestial bodies, for example a haloed sun or a textured moon, and methods to add clouds, stars and planets.

WARNING - Solaris C/C++ compilers (from Sun Microsystems Inc) have some nasty '#define's for the word 'SUN' and 'sun'. If you are writing portable software, you might want to pick an alternative word! PLIB uses 'Sol', the formal astronomical name for the star that happens to be our sun.

The sky implements various time of day lighting effects, it plays well with fog and visibility effects, and implements scudded cloud fly-through effects. Additionally, you can wire in the output of the SimGear SGEphemeris class to accurately position all the objects in the sky.

This sky dome code was based on the SimGear sky code, written by Curtis Olson, which is used in FlightGear. The code was moved into PLIB for easier access, STL was removed and 'flat earth' methods added. The main reason the code was moved here was to allow the PLIB community to add their own extensions (not relevant to FlightGear), to build for example, 'un-realistic' sky domes, with green skies, purple clouds or multiple suns. This has not yet fully been achieved, but hopefully when someone finds the time or has the need, these features will be added.

ssgaSky screenshots
ssgaSky screenshots
class ssgaSky
{
public:

  ssgaSky( void );
  ~ssgaSky( void );

  void build( double h_radius, double v_radius,
	  int nplanets, sgdVec3 *planet_data,
	  int nstars, sgdVec3 *star_data);

  ssgaCelestialBody* addBody( const char *body_tex_path, const char *halo_tex_path, double size, double dist, bool sol = false );
  ssgaCelestialBody* addBody( ssgSimpleState *orb_state, ssgSimpleState *halo_state, double size, double dist, bool sol = false );
  ssgaCelestialBody* getBody(int i) { return bodies.get(i); }
  int getBodyCount() { return bodies.getNum(); }

  ssgaCloudLayer* addCloud( const char *cloud_tex_path, float span, float elevation, float thickness, float transition );
  ssgaCloudLayer* addCloud( ssgSimpleState *cloud_state, float span, float elevation, float thickness, float transition );
  ssgaCloudLayer* getCloud(int i) { return clouds.get(i); }
  int getCloudCount() { return clouds.getNum(); }

  bool repositionFlat( sgVec3 view_pos, double spin, double dt );
  bool reposition( sgVec3 view_pos, sgVec3 zero_elev, sgVec3 view_up,
                   double lon, double lat, double alt, double spin, double gst, double dt );

  bool repaint( sgVec4 sky_color, sgVec4 fog_color, sgVec4 cloud_color, double sol_angle,
	  int nplanets, sgdVec3 *planet_data,
	  int nstars, sgdVec3 *star_data );

  void modifyVisibility( float alt, float time_factor );

  void preDraw();
  void postDraw( float alt );

  void enable();
  void disable();

  float getVisibility();
  void setVisibility( float v );
} ;

Building the sky

Once you have created an instance of ssgaSky you must call the build() method. The arguments you pass to the build() method allow you to specify the size of your sky dome, a number of planets, and a multitude of stars. For the planets and stars you pass in an array of right ascensions, declinations, magnitudes, and the distance from the view point.

Celestial Bodies

Celestial bodies (eg. sun, moon or even mars if you like) can be added or modified individually. To add a body use the addBody() method. The arguments allow you to specify the body texture, the halo texture, the size of the body, the body distance (usually the sky dome size) and a boolean flag to indicate if the body should be used as a reference to 'spin' the dome based on the bodies rotation for correct sunrise and sunset effects (nb. the sun reference is only used by repositionFlat() so the user does not need to calculate the spin parameter). There is an additional form of this method that allows you to specify your own ssgSimpleState for drawing the body and halo texture.

Body accessor methods are available to:

note: body angle and rotation are calculated internally (by ssgaSky::repositionFlat() only) for sunrise and sunset effects.
class ssgaCelestialBody
{
...
  void getPosition ( sgCoord* p );

  void setAngle ( double angle );
  double getAngle ();

  void setRotation ( double rotation );
  double getRotation ();

  void setRightAscension ( double ra );
  double getRightAscension ();

  void setDeclination ( double decl );
  double getDeclination ();

  void setDist ( double dist );
  double getDist ();

  float *getColor();
} ;

Cloud Layers

Cloud layers can be added or modified individually. To add a cloud layer use the addCloud() method. The arguments allow you to specify cloud texture, the size of the cloud object, base height above sea level, layer thickness and a transition zone for entering/leaving the cloud layer. There is an additional form of this method that allows you to specify your own ssgSimpleState for drawing the cloud layer texture.

Cloud accessor methods are available to:

class ssgaCloudLayer
{
...
  void enable();
  void disable();
  bool isEnabled();

  float getElevation ();
  void  setElevation ( float elevation );

  float getThickness ();
  void  setThickness ( float thickness );

  float getTransition ();
  void  setTransition ( float transition );

  float getSpeed ();
  void  setSpeed ( float val );

  float getDirection ();
  void  setDirection ( float val );
} ;

Repainting the Sky

As the sun circles the globe, you can call the repaint() method to re-color the sky objects to simulate sunrise and sunset effects, visibility, and other lighting changes. The arguments allow you to specify a base sky color (for the top of the dome), a fog color (for the horizon), a cloud color (for blending clouds with the fog color), the sun angle with the horizon (for sunrise/sunset effects), and new star and planet data so that we can optionally change the magnitude of these (for day/night transitions).

Positioning Sky Objects

As time progresses and as you move across the surface of the earth, the apparent position of the objects and the various lighting effects can change. At this point you may wish to call celestial body setRightAscension() and setDeclination() methods to update the position of your bodies (SimGear SGEphemeris can be used if you wish to place bodies correctly).

Once you have specified the positions of all the sky objects, you must call the repositionFlat() or reposition() method to allow you to specify your view position.

The repositionFlat() arguments allow you to specify view position for a 'flat earth' model. A 'spin' angle can be specified for orienting the sky with the sun position so sunset and sunrise effects look correct. If you specified a body to be the sun reference on addBody() you do not need to specify the 'spin' angle. You may specify the amount of elapsed time since the sky was last updated (used to animate cloud movement).

The reposition() arguments allow you to specify your view position in world Cartesian coordinates, the zero elevation position in world Cartesian coordinates (your longitude, your latitude, sea level), the 'up' vector in world Cartesian coordinates, current longitude, latitude, and altitude. A 'spin' angle can be specified for orienting the sky with the sun position so sunset and sunrise effects look correct. You must specify GMT side real time. You may specify the amount of elapsed time since the sky was last updated (used to animate cloud movement).

Rendering the Sky

The sky is designed to be rendered in two stages. The first stage renders the parts that form your back drop - the sky dome, the stars, the planets and the celestial bodies. These should be rendered before the rest of your scene by calling the preDraw() method. The second stage renders the clouds which are likely to be translucent and should be drawn after your scene has been rendered. Use the postDraw() method to draw the second stage of the sky.

A typical application might do the following:

thesky->preDraw(); 
ssgCullAndDraw ( myscene ) ; 
thesky->postDraw( my_altitude ); 
The current altitude in meters is passed to the postDraw() method so the clouds layers can be rendered correctly from most distant to closest.

Visibility Effects

Visibility and fog is important for correctly rendering the sky. You can inform ssgaSky of the current visibility by calling the setVisibility() method.

When transitioning through clouds, it is nice to pull in the fog as you get close to the cloud layer to hide the fact that the clouds are drawn as a flat polygon. As you get nearer to the cloud layer it is also nice to temporarily pull in the visibility to simulate the effects of flying in and out of the puffy edge of the cloud. These effects can all be accomplished by calling the modifyVisibility() method. The arguments allow you to specify your current altitude (which is then compared to the altitudes of the various cloud layers). You can also specify a time factor which should be the length in seconds since the last time you called modifyVisibility(). The time_factor value allows the puffy cloud effect to be calculated correctly.

The modifyVisibility() method alters the ssgaSky's internal idea of visibility, so you should subsequently call getVisibility() to get the actual modified visibility. You should then make the appropriate glFog() calls to setup fog properly for your scene.

Accessor Methods

Once an instance of ssgaSky has been successfully initialized, there are a couple accessor methods you can use such as getBodyCount() to return the number of celestial bodies, getBody(i) to return body number i, getCloudCount() to return the number of cloud layers, getCloud(i) to return cloud layer number i, getVisibility() to return the actual visibility as modified by the sky/cloud model.


Valid HTML 4.0!
Steve J. Baker. <sjbaker1@airmail.net>