This is another update for the cloud code, a lot of lines but this time I have started to add the doxygen doc.
Misc
====
- corrected a bug when RTT is not available, the current rendering context was
altered
- if RTT is not available then 3d clouds are not drawn at all
- impostors lighting is now recomputed when the sun changes position
- distant objects are no more seen in front of clouds
- blending of distant clouds is a bit better now
- litle optimization of code (uses a less cpu time)
- use layer wind speed and direction (no more hardcoded wind)
- fov is no more hardcoded
Changes
=======
- clouds (cu only) are dissipating/reforming (experimental)
- compute a turbulence factor that depends on surrounding clouds and type of
clouds (experimental)
- clouds shapes are defined in cloudlayers.xml
- type of clouds present in a layer is also defined in cloudlayers.xml
- cloud layers are generated from metar and other misc. data (in progress)
- added a rain effect around the viewer (enabled in the rendering dialog and
when the metar property says so)
- added a lightning effect (enabled in the rendering dialog) : cb clouds spawn
new lightnings
- added a dialog to select from different weather source : metar/property,
a 'fair weather' environment and a 'thunderstorm' environment.
//
//
+#ifdef HAVE_CONFIG_H
+# include <simgear_config.h>
+#endif
+
+#include <plib/sg.h>
+#include <simgear/constants.h>
+#include <simgear/math/sg_random.h>
+#include <simgear/math/sg_geodesy.hxx>
+#include <simgear/math/point3d.hxx>
+#include <simgear/math/polar3d.hxx>
+#include <simgear/sound/soundmgr_openal.hxx>
#include <simgear/scene/sky/cloudfield.hxx>
#include <simgear/scene/sky/newcloud.hxx>
#include "visual_enviro.hxx"
+#include <vector>
+
+SG_USING_STD(vector);
+
+
+typedef struct {
+ Point3D pt;
+ int depth;
+ int prev;
+} lt_tree_seg;
+
+#define MAX_RAIN_SLICE 200
+static float rainpos[MAX_RAIN_SLICE];
+#define MAX_LT_TREE_SEG 400
+
+/**
+ * A class to render lightnings.
+ */
+class SGLightning {
+public:
+ /**
+ * Build a new lightning.
+ * The lightning has a limited life time. It will also play a thunder sounder once.
+ * @param lon lon longitude in degree
+ * @param lat lat latitude in degree
+ * @param alt asl of top of lightning
+ */
+ SGLightning(double lon, double lat, double alt);
+ ~SGLightning();
+ void lt_Render(void);
+ void lt_build(void);
+ void lt_build_tree_branch(int tree_nr, Point3D &start, float energy, int nbseg, float segsize);
+
+ // contains all the segments of the lightning
+ lt_tree_seg lt_tree[MAX_LT_TREE_SEG];
+ // segment count
+ int nb_tree;
+ // position of lightning
+ double lon, lat, alt;
+ int sequence_count;
+ // time to live
+ double age;
+};
+
+typedef vector<SGLightning *> list_of_lightning;
+static list_of_lightning lightnings;
+
SGEnviro sgEnviro;
SGEnviro::SGEnviro(void) :
view_in_cloud(false),
- precipitation_enable_state(false),
+ turbulence_enable_state(false),
+ precipitation_enable_state(true),
+ lightning_enable_state(false),
+ soundMgr(NULL),
+ snd_active(false),
+ snd_dist(0.0),
+ last_cloud_turbulence(0.0),
+ cloud_turbulence(0.0),
+ elapsed_time(0.0),
+ dt(0.0),
+ min_time_before_lt(0.0),
+ fov_width(55.0),
+ fov_height(55.0),
precipitation_density(100.0)
{
+ for(int i = 0; i < MAX_RAIN_SLICE ; i++)
+ rainpos[i] = sg_random();
+
}
SGEnviro::~SGEnviro(void) {
+ list_of_lightning::iterator iLightning;
+ for( iLightning = lightnings.begin() ; iLightning != lightnings.end() ; iLightning++ ) {
+ delete (*iLightning);
+ }
+ lightnings.clear();
}
-void SGEnviro::startOfFrame(void) {
+void SGEnviro::startOfFrame( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double delta_time) {
view_in_cloud = false;
+ // ask the impostor cache to do some cleanup
if(SGNewCloud::cldCache)
SGNewCloud::cldCache->startNewFrame();
+ last_cloud_turbulence = cloud_turbulence;
+ cloud_turbulence = 0.0;
+ elapsed_time += delta_time;
+ min_time_before_lt -= delta_time;
+ dt = delta_time;
+
+ sgMat4 T1, LON, LAT;
+ sgVec3 axis;
+
+ sgMakeTransMat4( T1, p );
+
+ sgSetVec3( axis, 0.0, 0.0, 1.0 );
+ sgMakeRotMat4( LON, lon, axis );
+
+ sgSetVec3( axis, 0.0, 1.0, 0.0 );
+ sgMakeRotMat4( LAT, 90.0 - lat, axis );
+
+ sgMat4 TRANSFORM;
+
+ sgCopyMat4( TRANSFORM, T1 );
+ sgPreMultMat4( TRANSFORM, LON );
+ sgPreMultMat4( TRANSFORM, LAT );
+
+ sgCoord pos;
+ sgSetCoord( &pos, TRANSFORM );
+
+ sgMakeCoordMat4( transform, &pos );
+ last_lon = lon;
+ last_lat = lat;
+ last_alt = alt;
}
void SGEnviro::endOfFrame(void) {
}
+double SGEnviro::get_cloud_turbulence(void) const {
+ return last_cloud_turbulence;
+}
+
// this can be queried to add some turbulence for example
bool SGEnviro::is_view_in_cloud(void) const {
return view_in_cloud;
return SGCloudField::get_enable3dClouds();
}
+bool SGEnviro::get_turbulence_enable_state(void) const {
+ return turbulence_enable_state;
+}
+
void SGEnviro::set_CacheResolution(int resolutionPixels) {
SGCloudField::set_CacheResolution(resolutionPixels);
}
void SGEnviro::set_clouds_enable_state(bool enable) {
SGCloudField::set_enable3dClouds(enable);
}
-
+void SGEnviro::set_turbulence_enable_state(bool enable) {
+ turbulence_enable_state = enable;
+}
// rain/snow
float SGEnviro::get_precipitation_density(void) const {
return precipitation_density;
// others
bool SGEnviro::get_lightning_enable_state(void) const {
- return false;
+ return lightning_enable_state;
}
void SGEnviro::set_lightning_enable_state(bool enable) {
+ lightning_enable_state = enable;
+ if( ! enable ) {
+ // TODO:cleanup
+ }
}
+void SGEnviro::setLight(sgVec4 adj_fog_color) {
+ sgCopyVec4( fog_color, adj_fog_color );
+ if( false ) {
+ // ssgGetLight( 0 ) -> setColour( GL_DIFFUSE, l->scene_diffuse() );
+ }
+}
+
+void SGEnviro::callback_cloud(float heading, float alt, float radius, int familly, float dist) {
+ // send data to wx radar
+ // compute turbulence
+ // draw precipitation
+ // draw lightning
+ // compute illumination
+
+ // http://www.pilotfriend.com/flight_training/weather/THUNDERSTORM%20HAZARDS1.htm
+ double turbulence = 0.0;
+ if( dist < radius * radius * 2.25f ) {
+ switch(familly) {
+ case SGNewCloud::CLFamilly_st:
+ turbulence = 0.2;
+ break;
+ case SGNewCloud::CLFamilly_ci:
+ case SGNewCloud::CLFamilly_cs:
+ case SGNewCloud::CLFamilly_cc:
+ case SGNewCloud::CLFamilly_ac:
+ case SGNewCloud::CLFamilly_as:
+ turbulence = 0.1;
+ break;
+ case SGNewCloud::CLFamilly_sc:
+ turbulence = 0.3;
+ break;
+ case SGNewCloud::CLFamilly_ns:
+ turbulence = 0.4;
+ break;
+ case SGNewCloud::CLFamilly_cu:
+ turbulence = 0.5;
+ break;
+ case SGNewCloud::CLFamilly_cb:
+ turbulence = 0.6;
+ break;
+ }
+ // full turbulence inside cloud, half in the vicinity
+ if( dist > radius * radius )
+ turbulence *= 0.5;
+ if( turbulence > cloud_turbulence )
+ cloud_turbulence = turbulence;
+ // we can do 'local' precipitations too
+ }
+
+ // convert to LWC for radar (experimental)
+ // http://www-das.uwyo.edu/~geerts/cwx/notes/chap08/moist_cloud.html
+ double LWC = 0.0;
+ switch(familly) {
+ case SGNewCloud::CLFamilly_st:
+ LWC = 0.29;
+ break;
+ case SGNewCloud::CLFamilly_cu:
+ LWC = 0.27;
+ break;
+ case SGNewCloud::CLFamilly_cb:
+ LWC = 2.0;
+ break;
+ case SGNewCloud::CLFamilly_sc:
+ LWC = 0.44;
+ break;
+ case SGNewCloud::CLFamilly_ci:
+ LWC = 0.03;
+ break;
+ // no data
+ case SGNewCloud::CLFamilly_cs:
+ case SGNewCloud::CLFamilly_cc:
+ case SGNewCloud::CLFamilly_ac:
+ case SGNewCloud::CLFamilly_as:
+ LWC = 0.03;
+ break;
+ case SGNewCloud::CLFamilly_ns:
+ LWC = 0.29*2.0;
+ break;
+ }
+ // TODO:send data to radar antenna
+ // NB:data valid only from cockpit view
+
+ // spawn a new lightning
+ if(min_time_before_lt <= 0.0 && (familly == SGNewCloud::CLFamilly_cb) &&
+ dist < 15000.0 * 15000.0 && sg_random() > 0.9f) {
+ double lat, lon;
+ Point3D orig, dest;
+ orig.setlat(last_lat * SG_DEGREES_TO_RADIANS );
+ orig.setlon(last_lon * SG_DEGREES_TO_RADIANS );
+ orig.setelev(0.0);
+ dist = sgSqrt(dist);
+ dest = calc_gc_lon_lat(orig, heading, dist);
+ lon = dest.lon() * SG_RADIANS_TO_DEGREES;
+ lat = dest.lat() * SG_RADIANS_TO_DEGREES;
+ addLightning( lon, lat, alt );
+
+ // reset timer
+ min_time_before_lt = 5.0 + sg_random() * 30;
+ // DEBUG only
+// min_time_before_lt = 1.0;
+ }
+}
+
+// precipitation rendering code
+void SGEnviro::DrawCone2(float baseRadius, float height, int slices, bool down, double rain_norm, double speed) {
+
+ sgVec3 light;
+ sgVec3 min_light = {0.35, 0.35, 0.35};
+ sgAddVec3( light, fog_color, min_light );
+ float da = SG_PI * 2.0f / (float) slices;
+ // low number = faster
+ float speedf = 2.5f - speed / 200.0;
+ if( speedf < 1.0f )
+ speedf = 1.0f;
+ float lenf = 0.03f + speed / 2000.0;
+ if( lenf > 0.10f )
+ lenf = 0.10f;
+ float t = fmod((float) elapsed_time, speedf) / speedf;
+// t = 0.1f;
+ if( !down )
+ t = 1.0f - t;
+ float angle = 0.0f;
+ glColor4f(1.0f, 0.7f, 0.7f, 0.9f);
+ glBegin(GL_LINES);
+ int rainpos_indice = 0;
+ for( int i = 0 ; i < slices ; i++ ) {
+ float x = cos(angle) * baseRadius;
+ float y = sin(angle) * baseRadius;
+ angle += da;
+ sgVec3 dir = {x, -height, y};
+
+ // rain drops at 2 different speed to simulate depth
+ float t1 = (i & 1 ? t : t + t) + rainpos[rainpos_indice];
+ if(t1 > 1.0f) t1 -= 1.0f;
+ if(t1 > 1.0f) t1 -= 1.0f;
+
+ // distant raindrops are more transparent
+ float c = (i & 1 ? t1 * 0.5f : t1 * 0.9f);
+ glColor4f(c * light[0], c * light[1], c * light[2], c);
+ sgVec3 p1, p2;
+ sgScaleVec3(p1, dir, t1);
+ // distant raindrops are shorter
+ float t2 = t1 + (i & 1 ? lenf : lenf+lenf);
+ sgScaleVec3(p2, dir, t2);
+
+ glVertex3f(p1[0], p1[1] + height, p1[2]);
+ glVertex3f(p2[0], p2[1] + height, p2[2]);
+ if( ++rainpos_indice >= MAX_RAIN_SLICE )
+ rainpos_indice = 0;
+ }
+ glEnd();
+}
+
+// TODO:check alt vs layer
+void SGEnviro::drawRain(double pitch, double roll, double speed, double rain_norm) {
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glDisable(GL_DEPTH_TEST);
+ glShadeModel(GL_SMOOTH);
+ glEnable(GL_BLEND);
+ glBlendFunc( GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ glDisable( GL_FOG );
+ glDisable(GL_LIGHTING);
+
+ int slice_count = (40.0 + rain_norm*150.0)* precipitation_density / 100.0;
+
+ float angle = speed;
+ if( angle > 90.0 )
+ angle = 90.0;
+
+ glPushMatrix();
+ // TODO:find the real view orientation, not the AC one
+ // the cone rotate with speed
+ angle = -pitch - angle;
+ glRotatef(angle, 1.0, 0.0, 0.0);
+ glRotatef(roll, 0.0, 1.0, 0.0);
+
+ // up cone
+ DrawCone2(15.0, 30.0, slice_count, true, rain_norm, speed);
+ // down cone (usually not visible)
+ if(angle > 0.0)
+ DrawCone2(15.0, -30.0, slice_count, false, rain_norm, speed);
+
+ glPopMatrix();
+
+ glEnable(GL_LIGHTING);
+ glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
+ glEnable( GL_FOG );
+ glEnable(GL_DEPTH_TEST);
+
+}
+
+void SGEnviro::set_soundMgr(SGSoundMgr *mgr) {
+ soundMgr = mgr;
+}
+
+void SGEnviro::drawPrecipitation(double rain_norm, double snow_norm, double hail_norm, double pitch, double roll, double speed) {
+ // TODO:check alt with right layer (wich layer ?)
+ if( precipitation_enable_state && rain_norm > 0.0)
+ drawRain(pitch, roll, speed, rain_norm);
+}
+
+
+SGLightning::SGLightning(double _lon, double _lat, double _alt) :
+ lon(_lon),
+ lat(_lat),
+ alt(_alt),
+ age(1.0 + sg_random() * 4.0),
+ nb_tree(0)
+{
+// sequence_count = 1 + sg_random() * 5.0;
+ lt_build();
+}
+
+SGLightning::~SGLightning() {
+}
+
+// lightning rendering code
+void SGLightning::lt_build_tree_branch(int tree_nr, Point3D &start, float energy, int nbseg, float segsize) {
+
+ sgVec3 dir, newdir;
+ int nseg = 0;
+ Point3D pt = start;
+ if( nbseg == 50 )
+ sgSetVec3( dir, 0.0, -1.0, 0.0 );
+ else {
+ sgSetVec3( dir, sg_random() - 0.5f, sg_random() - 0.5f, sg_random() - 0.5f);
+ sgNormaliseVec3(dir);
+ }
+ if( nb_tree >= MAX_LT_TREE_SEG )
+ return;
+
+ lt_tree[nb_tree].depth = tree_nr;
+ nseg = 0;
+ lt_tree[nb_tree].pt = pt;
+ lt_tree[nb_tree].prev = -1;
+ nb_tree ++;
+
+ // TODO:check agl
+ while(nseg < nbseg && pt.y() > 0.0) {
+ int prev = nb_tree - 1;
+ nseg++;
+ // add a branch
+ if( energy * sg_random() > 0.8f )
+ lt_build_tree_branch(tree_nr + 1, pt, energy * 0.9f, nbseg == 50 ? 10 : nbseg * 0.4f, segsize * 0.7f);
+
+ if( nb_tree >= MAX_LT_TREE_SEG )
+ return;
+ sgSetVec3(newdir, (sg_random() - 0.5f), (sg_random() - 0.5f) - (nbseg == 50 ? 0.5f : 0.0), (sg_random() - 0.5f));
+ sgNormaliseVec3(newdir);
+ sgAddVec3( dir, newdir);
+ sgNormaliseVec3(dir);
+ sgVec3 scaleDir;
+ sgScaleVec3( scaleDir, dir, segsize * energy * 0.5f );
+ pt[PX] += scaleDir[0];
+ pt[PY] += scaleDir[1];
+ pt[PZ] += scaleDir[2];
+
+ lt_tree[nb_tree].depth = tree_nr;
+ lt_tree[nb_tree].pt = pt;
+ lt_tree[nb_tree].prev = prev;
+ nb_tree ++;
+ }
+}
+
+void SGLightning::lt_build(void) {
+ Point3D top;
+ nb_tree = 0;
+ top[PX] = 0 ;
+ top[PY] = alt;
+ top[PZ] = 0;
+ lt_build_tree_branch(0, top, 1.0, 50, top[PY] / 8.0);
+ if( ! sgEnviro.soundMgr )
+ return;
+ Point3D start( sgEnviro.last_lon*SG_DEGREES_TO_RADIANS, sgEnviro.last_lat*SG_DEGREES_TO_RADIANS, 0.0 );
+ Point3D dest( lon*SG_DEGREES_TO_RADIANS, lat*SG_DEGREES_TO_RADIANS, 0.0 );
+ double course = 0.0, dist = 0.0;
+ calc_gc_course_dist( dest, start, &course, &dist );
+ if( dist < 10000.0 && ! sgEnviro.snd_playing && (dist < sgEnviro.snd_dist || ! sgEnviro.snd_active) ) {
+ sgEnviro.snd_timer = 0.0;
+ sgEnviro.snd_wait = dist / 340;
+ sgEnviro.snd_dist = dist;
+ sgEnviro.snd_pos_lat = lat;
+ sgEnviro.snd_pos_lon = lon;
+ sgEnviro.snd_active = true;
+ sgEnviro.snd_playing = false;
+ }
+}
+
+
+void SGLightning::lt_Render(void) {
+ float flash = 0.5;
+ if( fmod(sgEnviro.elapsed_time*100.0, 100.0) > 50.0 )
+ flash = sg_random() * 0.75f + 0.25f;
+ float h = lt_tree[0].pt[PY];
+ sgVec4 col={0.62f, 0.83f, 1.0f, 1.0f};
+ sgVec4 c;
+
+#define DRAW_SEG() \
+ {glBegin(GL_LINES); \
+ glColor4fv(c); \
+ glVertex3f(lt_tree[n].pt[PX], lt_tree[n].pt[PZ], lt_tree[n].pt[PY]); \
+ glVertex3f(lt_tree[lt_tree[n].prev].pt[PX], lt_tree[lt_tree[n].prev].pt[PZ], lt_tree[lt_tree[n].prev].pt[PY]); \
+ glEnd();}
+
+ glDepthMask( GL_FALSE );
+ glEnable(GL_BLEND);
+ glBlendFunc( GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glDisable(GL_LIGHTING);
+ glDisable( GL_FOG );
+ glPushMatrix();
+ sgMat4 modelview, tmp;
+ ssgGetModelviewMatrix( modelview );
+ sgCopyMat4( tmp, sgEnviro.transform );
+ sgPostMultMat4( tmp, modelview );
+ ssgLoadModelviewMatrix( tmp );
+
+ Point3D start( sgEnviro.last_lon*SG_DEGREES_TO_RADIANS, sgEnviro.last_lat*SG_DEGREES_TO_RADIANS, 0.0 );
+ Point3D dest( lon*SG_DEGREES_TO_RADIANS, lat*SG_DEGREES_TO_RADIANS, 0.0 );
+ double course = 0.0, dist = 0.0;
+ calc_gc_course_dist( dest, start, &course, &dist );
+ double ax = 0.0, ay = 0.0;
+ ax = cos(course) * dist;
+ ay = sin(course) * dist;
+
+ glTranslatef( ax, ay, -sgEnviro.last_alt );
+// glTranslatef( ax, ay, 0 );
+
+ for( int n = 0 ; n < nb_tree ; n++ ) {
+ if( lt_tree[n].prev < 0 )
+ continue;
+
+ float t1 = sgLerp(0.5, 1.0, lt_tree[n].pt[PY] / h);
+ t1 *= flash;
+ if( lt_tree[n].depth >= 2 ) {
+ glLineWidth(3);
+ sgScaleVec4(c, col, t1 * 0.6f);
+ DRAW_SEG();
+ } else {
+ if( lt_tree[n].depth == 0 ) {
+ glLineWidth(12);
+ sgScaleVec4(c, col, t1 * 0.5f);
+ DRAW_SEG();
+
+ glLineWidth(6);
+ sgScaleVec4(c, col, t1);
+ DRAW_SEG();
+ } else {
+ glLineWidth(6);
+ sgScaleVec4(c, col, t1 * 0.7f);
+ DRAW_SEG();
+ }
+
+ if( lt_tree[n].depth == 0 )
+ glLineWidth(3);
+ else
+ glLineWidth(2);
+
+ sgSetVec4(c, t1, t1, t1, t1);
+ DRAW_SEG();
+ }
+
+ }
+ glLineWidth(1);
+ glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
+ glPopMatrix();
+ glDepthMask( GL_TRUE );
+ glEnable( GL_FOG );
+ glEnable(GL_LIGHTING);
+}
+
+void SGEnviro::addLightning(double lon, double lat, double alt) {
+ if( lightnings.size() > 10)
+ return;
+ SGLightning *lt= new SGLightning(lon, lat, alt);
+ lightnings.push_back(lt);
+}
+
+void SGEnviro::drawLightning(void) {
+ list_of_lightning::iterator iLightning;
+ // play 'thunder' for lightning
+ if( snd_active )
+ if( !snd_playing ) {
+ // wait until sound has reached us
+ snd_timer += dt;
+ if( snd_timer >= snd_wait ) {
+ snd_playing = true;
+ // compute relative position of lightning
+ Point3D start( sgEnviro.last_lon*SG_DEGREES_TO_RADIANS, sgEnviro.last_lat*SG_DEGREES_TO_RADIANS, 0.0 );
+ Point3D dest( snd_pos_lon*SG_DEGREES_TO_RADIANS, snd_pos_lat*SG_DEGREES_TO_RADIANS, 0.0 );
+ double course = 0.0, dist = 0.0;
+ calc_gc_course_dist( dest, start, &course, &dist );
+ double ax = 0.0, ay = 0.0;
+ ax = cos(course) * dist;
+ ay = sin(course) * dist;
+ SGSoundSample *snd = soundMgr->find("thunder");
+ if( snd ) {
+ ALfloat pos[3]={ax, ay, -sgEnviro.last_alt };
+ snd->set_source_pos(pos);
+ snd->play_once();
+ }
+ }
+ } else {
+ if( !soundMgr->is_playing("thunder") ) {
+ snd_active = false;
+ snd_playing = false;
+ }
+ }
+
+ if( ! lightning_enable_state )
+ return;
+
+ for( iLightning = lightnings.begin() ; iLightning != lightnings.end() ; iLightning++ ) {
+ if( dt )
+ if( sg_random() > 0.95f )
+ (*iLightning)->lt_build();
+ (*iLightning)->lt_Render();
+ (*iLightning)->age -= dt;
+ if( (*iLightning)->age < 0.0 ) {
+ delete (*iLightning);
+ lightnings.erase( iLightning );
+ break;
+ }
+ }
+
+}
+
+
+void SGEnviro::setFOV( float w, float h ) {
+ fov_width = w;
+ fov_height = h;
+}
+
+void SGEnviro::getFOV( float &w, float &h ) {
+ w = fov_width;
+ h = fov_height;
+}
#ifndef _VISUAL_ENVIRO_HXX
#define _VISUAL_ENVIRO_HXX
-class SGEnviro {
+#include <simgear/compiler.h>
+#include STL_STRING
+
+SG_USING_STD(string);
+class SGLightning;
+class SGSoundMgr;
+
+/**
+ * Visual environment helper class.
+ */
+class SGEnviro {
+friend SGLightning;
private:
+ void DrawCone2(float baseRadius, float height, int slices, bool down, double rain_norm, double speed);
+ void lt_update(void);
+
bool view_in_cloud;
bool precipitation_enable_state;
float precipitation_density;
+ bool turbulence_enable_state;
+ double last_cloud_turbulence, cloud_turbulence;
+ bool lightning_enable_state;
+ double elapsed_time, dt;
+ sgVec4 fog_color;
+ sgMat4 transform;
+ double last_lon, last_lat, last_alt;
+ SGSoundMgr *soundMgr;
+ bool snd_active, snd_playing;
+ double snd_timer, snd_wait, snd_pos_lat, snd_pos_lon, snd_dist;
+ double min_time_before_lt;
+
+ float fov_width, fov_height;
public:
SGEnviro();
~SGEnviro();
- void startOfFrame(void);
+ /**
+ * Forward a few states used for renderings.
+ */
+ void startOfFrame( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double delta_time);
+
void endOfFrame(void);
+ /**
+ * Whenever a cloud is drawn we check his 'impact' on the environment.
+ * @param heading direction of cloud in radians
+ * @param alt asl of cloud in meters
+ * @param radius radius of cloud in meters
+ * @param familly cloud familly
+ * @param dist squared dist to cloud in meters
+ */
+ void callback_cloud(float heading, float alt, float radius, int familly, float dist);
+
+ void drawRain(double pitch, double roll, double speed, double rain_norm);
+ /**
+ * Draw rain or snow precipitation around the viewer.
+ * @param rain_norm rain normalized intensity given by metar class
+ * @param snow_norm snow normalized intensity given by metar class
+ * @param hail_norm hail normalized intensity given by metar class
+ * @param pitch pitch rotation of viewer
+ * @param roll roll rotation of viewer
+ * @param speed moving speed of viewer in kt
+ */
+ void drawPrecipitation(double rain_norm, double snow_norm, double hail_norm,
+ double pitch, double roll, double speed);
+
+ /**
+ * Draw the lightnings spawned by cumulo nimbus.
+ */
+ void drawLightning(void);
+
+ /**
+ * Forward the fog color used by the rain rendering.
+ * @param adj_fog_color color of the fog
+ */
+ void setLight(sgVec4 adj_fog_color);
+
// this can be queried to add some turbulence for example
bool is_view_in_cloud(void) const;
void set_view_in_cloud(bool incloud);
+ double get_cloud_turbulence(void) const;
// Clouds
// return the size of the memory pool used by texture impostors
float get_clouds_visibility(void) const;
float get_clouds_density(void) const;
bool get_clouds_enable_state(void) const;
+ bool get_turbulence_enable_state(void) const;
+ /**
+ * Set the size of the impostor texture cache for 3D clouds.
+ * @param sizeKb size of the texture pool in Kb
+ */
void set_clouds_CacheSize(int sizeKb);
+ /**
+ * Set the resolution of the impostor texture for 3D clouds.
+ * @param resolutionPixels size of each texture in pixels (64|128|256)
+ */
void set_CacheResolution(int resolutionPixels);
+ /**
+ * Set the maximum range used when drawing clouds.
+ * Clouds are blended from totaly transparent at max range to totaly opaque around the viewer
+ * @param distance in meters
+ */
void set_clouds_visibility(float distance);
+ /**
+ * Set the proportion of clouds that will be rendered to limit drop in FPS.
+ * @param density 0..100 no clouds drawn when density == 0, all are drawn when density == 100
+ */
void set_clouds_density(float density);
+ /**
+ * Enable or disable the use of 3D clouds.
+ * @param enable when false we draw the 2D layers
+ */
void set_clouds_enable_state(bool enable);
+ /**
+ * Enable or disable the use of proximity cloud turbulence.
+ * @param enable when true the turbulence is computed based on type of cloud around the AC
+ */
+ void set_turbulence_enable_state(bool enable);
// rain/snow
float get_precipitation_density(void) const;
bool get_precipitation_enable_state(void) const;
void set_precipitation_density(float density);
+ /**
+ * Enable or disable the rendering of precipitation around the viewer.
+ * @param enable when true we will draw precipitation depending on metar data
+ */
void set_precipitation_enable_state(bool enable);
// others
bool get_lightning_enable_state(void) const;
+ /**
+ * Enable or disable the rendering of lightning and the thunder sound.
+ * @param enable when true we will draw lightning spwaned by cumulonimbus
+ */
void set_lightning_enable_state(bool enable);
+
+ /**
+ * Spawn a new lighning at specified lon/lat.
+ * @param lon position of the new lightning
+ * @param lat position of the new lightning
+ * @param alt asl of the starting point of the lightning in meters
+ */
+ void addLightning(double lon, double lat, double alt);
+
+ /**
+ * Forward the sound manager instance to be able to play samples.
+ * @param mgr a running sound manager
+ */
+ void set_soundMgr(SGSoundMgr *mgr);
+
+ void setFOV( float w, float h );
+ void getFOV( float &w, float &h );
+
};
extern SGEnviro sgEnviro;
-#endif // _VISUAL_ENVIRO_HXX
\ No newline at end of file
+#endif // _VISUAL_ENVIRO_HXX
if( bbListCount ) {
for(int i = 0 ; i < bbListCount ; i++) {
+ bbList[i].cldID = 0;
if(bbList[i].texID)
glDeleteTextures(1, & bbList[i].texID);
}
cacheSizeKb = (textureDimension * textureDimension * 4);
cacheSizeKb *= cacheCount;
cacheSizeKb /= 1024;
- if(rt) {
- rt->BeginCapture();
- glViewport(0, 0, textureDimension, textureDimension);
- rt->EndCapture();
+ if(rtAvailable) {
+ if( rt->BeginCapture() ) {
+ glViewport(0, 0, textureDimension, textureDimension);
+ rt->EndCapture();
+ }
}
return true;
}
rt->Reset("rgba tex2D ctt");
// rt->Reset("rgba tex2D");
if( rt->Initialize(256, 256, true) ) {
- rtAvailable = true;
if (rt->BeginCapture())
{
+ rtAvailable = true;
glViewport(0, 0, 256, 256);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
bbList[i].angleX = -999;
bbList[i].angleY = -999;
bbList[i].frameUsed = 0;
+ bbList[i].needRedraw = true;
return i;
}
}
// bbList[bbId].angleY = angleY;
bbList[bbId].frame = frameNumber;
bbList[bbId].frameUsed = frameNumber;
+ bbList[bbId].needRedraw = false;
builtBBCount ++;
builtBBframe ++;
}
if( builtBBframe >= maxImpostorRegenFrame )
return true;
- if( fabs(angleY - bbList[bbId].angleY) >= 4.0 )
- return false;
+ if( bbList[bbId].needRedraw )
+ return false;
+
+// if( fabs(angleY - bbList[bbId].angleY) >= 4.0 )
+// return false;
- if( fabs(angleX - bbList[bbId].angleX) >= 4.0 )
- return false;
+// if( fabs(angleX - bbList[bbId].angleX) >= 4.0 )
+// return false;
bbList[bbId].frameUsed = frameNumber;
return true;
void SGBbCache::startNewFrame(void) {
builtBBframe = 0;
// TOTO:find reasonable value
- int minFrameNumber = frameNumber - 500;
+ int minFrameNumber = frameNumber - 100;
frameNumber++;
// cleanup of unused enties
for( int bbId = 0 ; bbId < bbListCount ; bbId++)
bbList[bbId].cldID = 0;
}
}
+
+// force all impostors to be rebuilt, this will enventually be done over several frames
+void SGBbCache::invalidateCache(void) {
+
+ for( int bbId = 0 ; bbId < bbListCount ; bbId++)
+// bbList[bbId].cldID = 0;
+ bbList[bbId].needRedraw = true;
+}
+
+// flag the impostor for a lazy update
+void SGBbCache::invalidate(int cldId, int bbId) {
+ if( bbId < 0 || bbId >= bbListCount )
+ return;
+ if( bbList[bbId].cldID != cldId )
+ return;
+ bbList[bbId].needRedraw = true;
+}
+
#include <simgear/screen/extensions.hxx>
#include <simgear/screen/RenderTexture.h>
+/**
+ * Billboard helper class.
+ */
class SGBbCache {
private:
- typedef struct {
+ /**
+ * storage class for impostors state.
+ */
+ class bbInfo {
+ public:
+ /// the texture used by this impostor
GLuint texID;
+ /// the cloud owning this impostor
int cldID;
float angleX, angleY;
// creation frame number for debug only
int frame;
- // last time this entry was used
+ /// last time this entry was used
int frameUsed;
- } bbInfo;
+ /// dirty flag for lazy rebuild of impostor
+ bool needRedraw;
+ };
void freeTextureMemory(void);
+ /**
+ * Allocate and initialize the texture pool.
+ * @param count the number of texture to build
+ * @param textureDimension size in pixel of each texture
+ */
bool allocTextureMemory(int count, int textureDimension);
// a list of impostors
SGBbCache(void);
~SGBbCache(void);
- // call this first to initialize everything, cacheCount is the number of texture to allocate
+ /**
+ * Call this first to initialize the cache.
+ * @param cacheCount the number of texture to allocate
+ */
void init(int cacheCount);
- // free one cache slot, usualy when the cached object is destroyed
+ /**
+ * Free one cache slot, usualy when the cached object is destroyed.
+ * @param bbId the impostor slot
+ * @param cldId the cloud identifier
+ */
void free(int bbId, int cldId);
- // allocate a new texture, return an index in the cache
+ /**
+ * Allocate a new impostor.
+ * @param cldId the cloud identifier
+ * @return an impostor slot
+ */
int alloc(int cldId);
- // give the texture name to use
+ /**
+ * Query the texture name associated with this cloud.
+ * @param bbId the impostor slot
+ * @param cldId the cloud identifier
+ * @return a texture name
+ */
GLuint QueryTexID(int cldId, int bbId);
- // save the rendered texture from the current context to a new texture
+ /**
+ * Save the rendered texture from the current context to a new texture.
+ * @param bbId the impostor slot
+ */
void setTextureData(int bbId);
- // start the rendering of a billboard in the RTT context
+ /**
+ * Start the rendering of a billboard in the RTT context.
+ */
void beginCapture(void);
- // adjust the projection matrix of the RTT context to the size of the object
+ /**
+ * Adjust the projection matrix of the RTT context to the size of the object.
+ * @param radius radius in meters of the object to draw
+ * @param dist_center distance between viewer and object
+ */
void setRadius(float radius, float dist_center);
- // forget the RTT and go back to the previous rendering context
+ /**
+ * Forget the RTT and go back to the previous rendering context.
+ */
void endCapture(void);
- // for debugging only, give the number of frames since the inpostor was built
+ /**
+ * For debugging only, give the number of frames since the impostor was built.
+ * @param bbId the impostor slot
+ */
int queryImpostorAge(int bbId);
- // can we still use this impostor ?
+ /**
+ * Can we still use this impostor ? Check versus view angles and load.
+ * @param bbId the impostor slot
+ * @param cloudId the cloud identifier
+ * @param angleY rotation needed to face the impostor
+ * @param angleX rotation needed to face the impostor
+ */
bool isBbValid( int cloudId, int bbId, float angleY, float angleX);
- // save view angles of this billboard
+ /**
+ * Save view angles of this billboard.
+ * @param bbId the impostor slot
+ * @param cloudId the cloud identifier
+ * @param angleY rotation needed to face the impostor
+ * @param angleX rotation needed to face the impostor
+ */
void setReference( int cloudId, int bbId, float angleY, float angleX);
- // prepare the cache for the rendering of a new frame
+ /**
+ * Prepare the cache for the rendering of a new frame.
+ * Do some garbage collect of unused impostors
+ */
void startNewFrame(void);
- // alloc the impostors texture memory given the size of the memory pool
- // if sizeKb == 0 then the memory pool is freed and impostors are disabled
+ /**
+ * Alloc the impostors texture memory given the size of the memory pool.
+ * If sizeKb == 0 then the memory pool is freed and impostors are disabled
+ * @param sizeKb size of the texture pool in K
+ */
bool setCacheSize(int sizeKb);
- // alloc the impostors texture memory given the count and size of texture
- // if count == 0 then the memory pool is freed and impostors are disabled
+ /**
+ * Alloc the impostors texture memory given the count and size of texture.
+ * If count == 0 then the memory pool is freed and impostors are disabled
+ * @param count number of texture to allocate
+ * @param textureDimension size of each texture in pixels
+ */
bool setCacheSize(int count, int textureDimension);
- // return the size of the memory pool used by texture impostors
+ bool isRttAvailable(void) { return rtAvailable; }
+
+ /**
+ * Force all impostors to be rebuilt.
+ */
+ void invalidateCache(void);
+
+ /**
+ * Flag the impostor for a lazy update.
+ * @param bbId the impostor slot
+ * @param cldId the cloud identifier
+ */
+ void invalidate(int cldId, int bbId);
+
+ /**
+ * Return the size of the memory pool used by texture impostors.
+ * @return size of the memory pool in Kb
+ */
int queryCacheSize(void);
+ /**
+ * Maximum number of impostor to regen each frame.
+ * If we can't update all of them we will do that in the next frame
+ */
int maxImpostorRegenFrame;
};
last_lat = lat;
}
- layer3D->reposition( p, up, lon, lat, alt, dt);
+ layer3D->reposition( p, up, lon, lat, alt, dt, direction, speed);
return true;
}
void SGCloudLayer::draw( bool top ) {
if ( layer_coverage != SG_CLOUD_CLEAR ) {
- if ( SGCloudField::enable3D )
+ if ( SGCloudField::enable3D && layer3D->is3D())
layer3D->Render();
else
if ( bump_mapping && enable_bump_mapping ) {
static bool enable_bump_mapping;
+ /** return the 3D layer cloud associated with this 2D layer */
+ SGCloudField *get_layer3D(void) { return layer3D; }
+
private:
struct CloudVertex {
// visibility distance for clouds in meters
float SGCloudField::CloudVis = 25000.0f;
-bool SGCloudField::enable3D = true;
+bool SGCloudField::enable3D = false;
// fieldSize must be > CloudVis or we can destroy the impostor cache
// a cloud must only be seen once or the impostor will be generated for each of his positions
-double SGCloudField::fieldSize = 27000.0;
+double SGCloudField::fieldSize = 50000.0;
float SGCloudField::density = 100.0;
+double SGCloudField::timer_dt = 0.0;
+sgVec3 SGCloudField::view_vec;
+
static int last_cache_size = 1*1024;
static int cacheResolution = 64;
+static sgVec3 last_sunlight={0.0f, 0.0f, 0.0f};
int SGCloudField::get_CacheResolution(void) {
return cacheResolution;
int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
if(count == 0)
count = 1;
-// SGNewCloud::cldCache->setCacheSize(sizeKb);
SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
}
}
return;
enable3D = enable;
if(enable) {
- SGNewCloud::cldCache->setCacheSize(last_cache_size);
+ int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
+ if(count == 0)
+ count = 1;
+ SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
} else {
SGNewCloud::cldCache->setCacheSize(0);
}
}
// reposition the cloud layer at the specified origin and orientation
-void SGCloudField::reposition( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double dt) {
+void SGCloudField::reposition( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double dt, float direction, float speed) {
sgMat4 T1, LON, LAT;
sgVec3 axis;
this->alt = alt;
// simulate clouds movement from wind
- double speed = 10.0f;
- double direction = 45.0;
double sp_dist = speed*dt;
if (sp_dist > 0) {
double bx = cos((180.0-direction) * SGD_DEGREES_TO_RADIANS) * sp_dist;
// correct the frustum with the right far plane
ssgContext *context = ssgGetCurrentContext();
frustum = *context->getFrustum();
- frustum.setFOV(55.0,0);
+
+ float w, h;
+ sgEnviro.getFOV( w, h );
+ frustum.setFOV( w, h );
frustum.setNearFar(1.0, CloudVis);
+ timer_dt = dt;
}
SGCloudField::SGCloudField() :
+ draw_in_3d(true),
last_density(0.0),
deltax(0.0),
deltay(0.0),
}
+void SGCloudField::clear(void) {
+ list_of_Cloud::iterator iCloud;
+ for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
+ delete iCloud->aCloud;
+ }
+ theField.clear();
+ // force a recompute of density on first redraw
+ last_density = 0.0;
+ // true to come back in set density after layer is built
+ draw_in_3d = true;
+}
+
// use a table or else we see poping when moving the slider...
static int densTable[][10] = {
{0,0,0,0,0,0,0,0,0,0},
iCloud->visible = false;
}
last_density = density;
+ draw_in_3d = ( theField.size() != 0);
}
// add one cloud, data is not copied, ownership given
void SGCloudField::addCloud( sgVec3 pos, SGNewCloud *cloud) {
Cloud cl;
- sgCopyVec3( cl.pos, pos );
cl.aCloud = cloud;
cl.visible = true;
cloud->SetPos( pos );
+ sgCopyVec3( cl.pos, *cloud->getCenter() );
theField.push_back( cl );
}
for( int z = -5 ; z <= 5 ; z++) {
for( int x = -5 ; x <= 5 ; x++ ) {
- SGNewCloud *cloud = new SGNewCloud;
+ SGNewCloud *cloud = new SGNewCloud(SGNewCloud::CLFamilly_cu);
cloud->new_cu();
sgVec3 pos = {(x+Rnd(0.7)) * s, 750.0f, (z+Rnd(0.7)) * s};
addCloud(pos, cloud);
sgCopyVec3( tmp.eyePos, eyePos );
// save distance for later sort, opposite distance because we want back to front
tmp.dist = - squareDist;
+ tmp.heading = -SG_PI/2.0 - atan2( dist[0], dist[2] ); // + SG_PI;
+ tmp.alt = iCloud->pos[1];
inViewClouds.push_back(tmp);
if( squareDist - radius*radius < 100*100 )
sgEnviro.set_view_in_cloud(true);
applyDensity();
}
- // ask the impostor cache to do some cleanup
- // TODO:don't do that for every field
- SGNewCloud::cldCache->startNewFrame();
+ if( ! draw_in_3d )
+ return;
+
+ if( ! SGNewCloud::cldCache->isRttAvailable() )
+ return;
inViewClouds.clear();
sgVec3 lightVec;
ssgGetLight( 0 )->getPosition( lightVec );
sgXformVec3( lightVec, invtrans );
- sgCopyVec3( SGNewCloud::modelSunDir, lightVec );
+
sgSetVec3( SGNewCloud::modelSunDir, lightVec[0], lightVec[2], lightVec[1]);
- // try to find the lighting data (buggy)
+ // try to find the lighting data (not accurate)
sgVec4 diffuse, ambient;
ssgGetLight( 0 )->getColour( GL_DIFFUSE, diffuse );
ssgGetLight( 0 )->getColour( GL_AMBIENT, ambient );
- sgScaleVec3 ( SGNewCloud::sunlight, diffuse , 0.70f);
- sgScaleVec3 ( SGNewCloud::ambLight, ambient , 0.60f);
+ sgScaleVec3 ( SGNewCloud::sunlight, diffuse , 1.0f);
+ sgScaleVec3 ( SGNewCloud::ambLight, ambient , 1.0f);
+
+ sgVec3 delta_light;
+ sgSubVec3(delta_light, last_sunlight, SGNewCloud::sunlight);
+ if( (fabs(delta_light[0]) + fabs(delta_light[1]) + fabs(delta_light[2])) > 0.05f ) {
+ sgCopyVec3( last_sunlight, SGNewCloud::sunlight );
+ // force the redraw of all the impostors
+ SGNewCloud::cldCache->invalidateCache();
+ }
// voodoo things on the matrix stack
ssgGetModelviewMatrix( modelview );
// cloud fields are tiled on the flat earth
// compute the position in the tile
- relx = -fmod( deltax + relative_position[SG_X] + tmp[3][0], fieldSize );
- rely = -fmod( deltay + relative_position[SG_Y] + tmp[3][1], fieldSize );
+ relx = fmod( deltax + relative_position[SG_X] + tmp[3][0], fieldSize );
+ rely = fmod( deltay + relative_position[SG_Y] + tmp[3][1], fieldSize );
- sgSetVec3( eyePos, -relx, alt, -rely);
+ relx = fmod( relx + fieldSize, fieldSize );
+ rely = fmod( rely + fieldSize, fieldSize );
+ sgSetVec3( eyePos, relx, alt, rely);
+ sgCopyVec3( view_vec, tmp[1] );
tmp[3][2] = 0;
tmp[3][0] = 0;
for( iCloud = inViewClouds.begin() ; iCloud != inViewClouds.end() ; iCloud++ ) {
// iCloud->aCloud->drawContainers();
iCloud->aCloud->Render(iCloud->eyePos);
+ sgEnviro.callback_cloud(iCloud->heading, iCloud->alt,
+ iCloud->aCloud->getRadius(), iCloud->aCloud->getFamilly(), - iCloud->dist);
}
glBindTexture(GL_TEXTURE_2D, 0);
SGNewCloud *aCloud;
sgVec3 eyePos;
float dist;
+ float heading;
+ float alt;
bool operator<(const culledCloud &b) const {
- return this->dist < b.dist;
+ return (this->dist < b.dist);
}
};
typedef vector<culledCloud> list_of_culledCloud;
+/**
+ * A layer of 3D clouds.
+ */
class SGCloudField {
private:
SGNewCloud *aCloud;
sgVec3 pos;
bool visible;
-// float dist;
-// bool culled;
-
-// bool operator<(const Cloud &b) {
-// return this->dist < b.dist;
-// }
};
double last_lon, last_lat, last_course;
float last_density;
+ bool draw_in_3d;
public:
SGCloudField();
~SGCloudField();
+ void clear(void);
+
// add one cloud, data is not copied, ownership given
void addCloud( sgVec3 pos, SGNewCloud *cloud);
void Render(void);
// reposition the cloud layer at the specified origin and orientation
- void reposition( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double dt);
+ void reposition( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double dt, float direction, float speed);
+
+ bool is3D(void) { return draw_in_3d; }
// visibility distance for clouds in meters
static float CloudVis;
- static float density;
+ static sgVec3 view_vec;
+ static float density;
+ static double timer_dt;
static double fieldSize;
static bool enable3D;
bool SGNewCloud::useAnisotropic = true;
SGBbCache *SGNewCloud::cldCache = 0;
static bool texturesLoaded = false;
+static float minx, maxx, miny, maxy, minz, maxz;
+
float SGNewCloud::nearRadius = 3500.0f;
bool SGNewCloud::lowQuality = false;
sgVec3 SGNewCloud::sunlight = {0.5f, 0.5f, 0.5f};
sgVec3 SGNewCloud::modelSunDir = {0,1,0};
-// constructor
-SGNewCloud::SGNewCloud() :
- bbId(-1),
-// rank(-1),
- minx(999), miny(999), minz(999), maxx(-999), maxy(-999), maxz(-999)
-
-{
+void SGNewCloud::init(void) {
+ bbId = -1;
+ fadeActive = false;
+ duration = 100.0f;
+ fadetimer = 100.0f;
+ pauseLength = 0.0f;
+ last_step = -1.0f;
+ familly = CLFamilly_nn;
cloudId = (int) this;
sgSetVec3(center, 0.0f, 0.0f, 0.0f);
sgSetVec3(cloudpos, 0.0f, 0.0f, 0.0f);
+ radius = 0.0f;
+ delta_base = 0.0f;
list_spriteContainer.reserve(8);
list_spriteDef.reserve(40);
-// if( ! texturesLoaded ) {}
+
if( cldCache == 0 ) {
cldCache = new SGBbCache;
cldCache->init( 64 );
}
}
+// constructor
+SGNewCloud::SGNewCloud(CLFamilly_type classification)
+{
+ init();
+ familly = classification;
+}
+
+SGNewCloud::SGNewCloud(string classification)
+{
+ init();
+ if( classification == "cu" )
+ familly = CLFamilly_cu;
+ else if( classification == "cb" )
+ familly = CLFamilly_cb;
+ else if( classification == "st" )
+ familly = CLFamilly_st;
+ else if( classification == "ns" )
+ familly = CLFamilly_ns;
+ else if( classification == "sc" )
+ familly = CLFamilly_sc;
+ else if( classification == "as" )
+ familly = CLFamilly_as;
+ else if( classification == "ac" )
+ familly = CLFamilly_ac;
+ else if( classification == "ci" )
+ familly = CLFamilly_ci;
+ else if( classification == "cc" )
+ familly = CLFamilly_cc;
+ else if( classification == "cs" )
+ familly = CLFamilly_cs;
+}
+
SGNewCloud::~SGNewCloud() {
list_spriteDef.clear();
list_spriteContainer.clear();
}
void SGNewCloud::startFade(bool direction, float duration, float pauseLength) {
+ if(duration <= 0.0) {
+ fadeActive = false;
+ return;
+ }
+ this->direction = direction;
+ fadetimer = 0.0;
+ this->duration = duration;
+ this->pauseLength = pauseLength;
+ last_step = -1.0;
+ fadeActive = true;
}
void SGNewCloud::setFade(float howMuch) {
+ duration = 100.0;
+ fadetimer = howMuch;
+ fadeActive = false;
+ last_step = -1.0;
}
-static float rayleighCoeffAngular(float cosAngle) {
+static inline float rayleighCoeffAngular(float cosAngle) {
return 3.0f / 4.0f * (1.0f + cosAngle * cosAngle);
}
// compute the light for a cloud sprite corner
// from the normal and the sun, scaled by the Rayleigh factor
// and finaly added to the ambient light
-static void lightFunction(sgVec3 normal, sgVec4 light, float pf) {
+static inline void lightFunction(sgVec3 normal, sgVec4 light, float pf) {
float cosAngle = sgScalarProductVec3( normal, SGNewCloud::modelSunDir);
- float vl = (1.0f - 0.1f + cosAngle / 10.0f) * pf;
- sgScaleVec3( light, SGNewCloud::sunlight, vl );
+ float vl = (1.0f - 0.5f + cosAngle * 0.5f) * pf;
+ sgScaleVec3( light, SGNewCloud::sunlight, 0.25f + 0.75f * vl );
sgAddVec3( light, SGNewCloud::ambLight );
// we need to clamp or else the light will bug when adding transparency
if( light[0] > 1.0 ) light[0] = 1.0;
// compute the light for a cloud sprite
// we use ambient light and orientation versus sun position
-// TODO:check sun pos and check code
void SGNewCloud::computeSimpleLight(sgVec3 FakeEyePos) {
// constant Rayleigh factor if we are not doing Anisotropic lighting
float pf = 1.0f;
- const float ang = 45.0f * SG_PI / 180.0f;
+
list_of_spriteDef::iterator iSprite;
for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
if( useAnisotropic ) {
float cosAngle = sgScalarProductVec3(eyeDir, modelSunDir);
pf = rayleighCoeffAngular(cosAngle);
}
- // compute the vector going from the container box center to the sprite
- // TODO : this is a constant except for cloudpos, compute the normal in setpos function
- sgVec3 normal;
- spriteContainer *thisSpriteContainer = &list_spriteContainer[iSprite->box];
- sgSubVec3(normal, iSprite->pos, thisSpriteContainer->pos);
- sgSubVec3(normal, thisSpriteContainer->center);
- sgSubVec3(normal, cloudpos);
- sgNormaliseVec3(normal);
- if( lowQuality ) {
- // juste use the traditional normal to compute some lightning
- sgVec4 centerColor;
- lightFunction(normal, centerColor, pf);
- sgCopyVec4(iSprite->l0, centerColor);
- sgCopyVec4(iSprite->l1, centerColor);
- sgCopyVec4(iSprite->l2, centerColor);
- sgCopyVec4(iSprite->l3, centerColor);
+ lightFunction(iSprite->n0, iSprite->l0, pf);
+ lightFunction(iSprite->n1, iSprite->l1, pf);
+ lightFunction(iSprite->n2, iSprite->l2, pf);
+ lightFunction(iSprite->n3, iSprite->l3, pf);
- } else {
- // use exotic lightning function, this will give more 'relief' to the clouds
- // compute a normal for each vextex this will simulate a smooth shading for a round shape
- sgVec3 polar, cart, pt;
- // I suspect this code to be bugged...
- CartToPolar3d(normal, polar);
-
- // offset the normal vector by some angle for each vertex
- sgSetVec3(pt, polar[0] - ang, polar[1] - ang, polar[2]);
- PolarToCart3d(pt, cart);
- lightFunction(cart, iSprite->l0, pf);
- sgSetVec3(pt, polar[0] + ang, polar[1] - ang, polar[2]);
- PolarToCart3d(pt, cart);
- lightFunction(cart, iSprite->l1, pf);
- sgSetVec3(pt, polar[0] + ang, polar[1] + ang, polar[2]);
- PolarToCart3d(pt, cart);
- lightFunction(cart, iSprite->l2, pf);
- sgSetVec3(pt, polar[0] - ang, polar[1] + ang, polar[2]);
- PolarToCart3d(pt, cart);
- lightFunction(cart, iSprite->l3, pf);
- }
}
}
cont.cont_type = type;
sgSetVec3( cont.center, 0.0f, 0.0f, 0.0f);
list_spriteContainer.push_back( cont );
+ // don't place cloud below his base
+ if( y - r*0.50 < delta_base )
+ delta_base = y - r*0.50;
}
// add a sprite inside a box
void SGNewCloud::addSprite(float x, float y, float z, float r, CLbox_type type, int box) {
spriteDef newSpriteDef;
int rank = list_spriteDef.size();
- sgSetVec3( newSpriteDef.pos, x, y, z);
+ sgSetVec3( newSpriteDef.pos, x, y - delta_base, z);
newSpriteDef.box = box;
newSpriteDef.sprite_type = type;
newSpriteDef.rank = rank;
sgSubVec3( deltaPos, newSpriteDef.pos, thisBox->pos );
sgAddVec3( thisBox->center, deltaPos );
- r = r * 0.6f; // 0.5 * 1.xxx
+ r = r * 0.65f; // 0.5 * 1.xxx
if( x - r < minx )
minx = x - r;
if( y - r < miny )
void SGNewCloud::genSprites( void ) {
float x, y, z, r;
int N, sc;
+ minx = miny = minz = 99999.0;
+ maxx = maxy = maxz = -99999.0;
+
N = list_spriteContainer.size();
for(int i = 0 ; i < N ; i++ ) {
spriteContainer *thisBox = & list_spriteContainer[i];
// the type defines how the sprites can be positioned inside the box, their size, etc
switch(thisBox->cont_type) {
case CLbox_sc:
- for( sc = 0 ; sc <= 4 ; sc ++ ) {
- r = thisBox->r + Rnd(0.2f);
- x = thisBox->pos[SG_X] + Rnd(thisBox->r);
- y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.2f);
- z = thisBox->pos[SG_Z] + Rnd(thisBox->r);
- addSprite(x, y, z, r, thisBox->cont_type, i);
- }
+ sc = 1;
+ r = thisBox->r + Rnd(0.2f);
+ x = thisBox->pos[SG_X] + Rnd(thisBox->r * 0.75f);
+ y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.75f);
+ z = thisBox->pos[SG_Z] + Rnd(thisBox->r * 0.75f);
+ addSprite(x, y, z, r, thisBox->cont_type, i);
break;
case CLbox_stratus:
sc = 1;
radius /= 2.0f;
sgSetVec3( center, (maxx + minx) / 2.0f, (maxy + miny) / 2.0f, (maxz + minz) / 2.0f );
-/* fadingrank = 0
-' fadingrank = UBound(tbSpriteDef()) * 10
- fadingdir = 0*/
- // TODO : compute initial sprite normals for lighting function
+ const float ang = 45.0f * SG_PI / 180.0f;
+
+ // compute normals
+ list_of_spriteDef::iterator iSprite;
+ for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
+ sgVec3 normal;
+ spriteContainer *thisSpriteContainer = &list_spriteContainer[iSprite->box];
+ if( familly == CLFamilly_sc || familly == CLFamilly_cu || familly == CLFamilly_cb) {
+ sgSubVec3(normal, iSprite->pos, center);
+ } else {
+ sgSubVec3(normal, iSprite->pos, thisSpriteContainer->pos);
+ sgSubVec3(normal, thisSpriteContainer->center);
+ sgSubVec3(normal, cloudpos);
+ }
+ if( normal[0] == 0.0f && normal[1] == 0.0f && normal[2] == 0.0f )
+ sgSetVec3( normal, 0.0f, 1.0f, 0.0f );
+ sgNormaliseVec3(normal);
+ // use exotic lightning function, this will give more 'relief' to the clouds
+ // compute a normal for each vextex this will simulate a smooth shading for a round shape
+ sgVec3 polar, pt;
+ // I suspect this code to be bugged...
+ CartToPolar3d(normal, polar);
+ sgCopyVec3(iSprite->normal, normal);
+
+ // offset the normal vector by some angle for each vertex
+ sgSetVec3(pt, polar[0] - ang, polar[1] - ang, polar[2]);
+ PolarToCart3d(pt, iSprite->n0);
+ sgSetVec3(pt, polar[0] + ang, polar[1] - ang, polar[2]);
+ PolarToCart3d(pt, iSprite->n1);
+ sgSetVec3(pt, polar[0] + ang, polar[1] + ang, polar[2]);
+ PolarToCart3d(pt, iSprite->n2);
+ sgSetVec3(pt, polar[0] - ang, polar[1] + ang, polar[2]);
+ PolarToCart3d(pt, iSprite->n3);
+ }
+
+ // experimental : clouds are dissipating with time
+ if( familly == CLFamilly_cu ) {
+ startFade(true, 300.0f, 30.0f);
+ fadetimer = sg_random() * 300.0f;
+ }
}
}
sgAddVec3( center, deltaPos );
sgSetVec3( cloudpos, newPos[SG_X], newPos[SG_Y], newPos[SG_Z]);
- // TODO : recompute sprite normal so we don't have to redo that each frame
}
// render the cloud on screen or on the RTT texture to build the impostor
void SGNewCloud::Render3Dcloud( bool drawBB, sgVec3 FakeEyePos, sgVec3 deltaPos, float dist_center ) {
-/* int clrank = fadingrank / 10;
- int clfadeinrank = fadingrank - clrank * 10;*/
- float CloudVisFade = 1.0 / (1.5 * SGCloudField::get_CloudVis());
+ float step = ( list_spriteDef.size() * (direction ? fadetimer : duration-fadetimer)) / duration;
+ int clrank = (int) step;
+ float clfadeinrank = (step - clrank);
+ last_step = step;
+
+ float CloudVisFade = 1.0 / (0.7f * SGCloudField::get_CloudVis());
+ // blend clouds with sky based on distance to limit the contrast of distant cloud
+ float t = 1.0f - dist_center * CloudVisFade;
+// if ( t < 0.0f )
+// return;
computeSimpleLight( FakeEyePos );
// view point sort, we sort because of transparency
sortSprite( FakeEyePos );
+ float dark = (familly == CLFamilly_cb ? 0.9f : 1.0f);
+
GLint previousTexture = -1, thisTexture;
list_of_spriteDef::iterator iSprite;
for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
+ // skip this sprite if faded
+ if(iSprite->rank > clrank)
+ continue;
// choose texture to use depending on sprite type
switch(iSprite->sprite_type) {
case CLbox_stratus:
}
sgVec3 translate;
- if( drawBB ) {
- sgCopyVec3( translate, iSprite->pos);
- sgSubVec3( translate, iSprite->pos, deltaPos );
- }
- else
- sgSubVec3( translate, iSprite->pos, deltaPos);
+ sgSubVec3( translate, iSprite->pos, deltaPos);
// flipx and flipy are random texture flip flags, this gives more random clouds
float flipx = (float) ( iSprite->rank & 1 );
float flipy = (float) ( (iSprite->rank >> 1) & 1 );
// cu texture have a flat bottom so we can't do a vertical flip
- if( iSprite->sprite_type == CLbox_cumulus || iSprite->sprite_type == CLbox_stratus )
+ if( iSprite->sprite_type == CLbox_cumulus )
flipy = 0.0f;
- if( iSprite->sprite_type == CLbox_stratus )
- flipx = 0.0f;
+// if( iSprite->sprite_type == CLbox_stratus )
+// flipx = 0.0f;
// adjust colors depending on cloud type
// TODO : rewrite that later, still experimental
switch(iSprite->sprite_type) {
case CLbox_cumulus:
// dark bottom
- sgScaleVec3(iSprite->l0, 0.6f);
- sgScaleVec3(iSprite->l1, 0.6f);
+ sgScaleVec3(iSprite->l0, 0.8f * dark);
+ sgScaleVec3(iSprite->l1, 0.8f * dark);
+ sgScaleVec3(iSprite->l2, dark);
+ sgScaleVec3(iSprite->l3, dark);
break;
case CLbox_stratus:
// usually dark grey
- sgScaleVec3(iSprite->l0, 0.8f);
- sgScaleVec3(iSprite->l1, 0.8f);
- sgScaleVec3(iSprite->l2, 0.8f);
- sgScaleVec3(iSprite->l3, 0.8f);
+ if( familly == CLFamilly_st ) {
+ sgScaleVec3(iSprite->l0, 0.8f);
+ sgScaleVec3(iSprite->l1, 0.8f);
+ sgScaleVec3(iSprite->l2, 0.8f);
+ sgScaleVec3(iSprite->l3, 0.8f);
+ } else {
+ sgScaleVec3(iSprite->l0, 0.7f);
+ sgScaleVec3(iSprite->l1, 0.7f);
+ sgScaleVec3(iSprite->l2, 0.7f);
+ sgScaleVec3(iSprite->l3, 0.7f);
+ }
break;
default:
// darker bottom than top
sgCopyVec4 ( l2, iSprite->l2 );
sgCopyVec4 ( l3, iSprite->l3 );
if( ! drawBB ) {
- // blend clouds with sky based on distance to limit the contrast of distant cloud
- float t = 1.0f - dist_center * CloudVisFade;
- if ( t < 0.0f )
- t = 0.0f; // no, it should have been culled
// now clouds at the far plane are half blended
sgScaleVec4( l0, t );
sgScaleVec4( l1, t );
sgScaleVec4( l2, t );
sgScaleVec4( l3, t );
}
+ if( iSprite->rank == clrank ) {
+ sgScaleVec4( l0, clfadeinrank );
+ sgScaleVec4( l1, clfadeinrank );
+ sgScaleVec4( l2, clfadeinrank );
+ sgScaleVec4( l3, clfadeinrank );
+ }
// compute the rotations so that the quad is facing the camera
sgVec3 pos;
sgSetVec3( pos, translate[SG_X], translate[SG_Z], translate[SG_Y] );
sgCopyVec3( translate, pos );
sgNormaliseVec3( translate );
+#if 0
+ // change view angle when near a sprite
+ sgVec3 trans={translate[0], translate[2], translate[1]};
+ float angle = sgScalarProductVec3( SGCloudField::view_vec, trans );
+ if( fabs(angle) < 0.85f ) {
+ // view not ok from under
+ sgSetVec3( translate, -SGCloudField::view_vec[0],-SGCloudField::view_vec[2],-SGCloudField::view_vec[1] );
+// sgSetVec3( l0,1,0,0 );
+// sgSetVec3( l1,1,0,0 );
+// sgSetVec3( l2,1,0,0 );
+// sgSetVec3( l3,1,0,0 );
+ }
+#endif
sgVec3 x, y, up = {0.0f, 0.0f, 1.0f};
sgVectorProductVec3(x, translate, up);
- sgNormaliseVec3(x);
- sgScaleVec3(x, r);
sgVectorProductVec3(y, x, translate);
- sgNormaliseVec3(y);
+ sgScaleVec3(x, r);
sgScaleVec3(y, r);
sgVec3 left, right;
}
// draw a cloud but this time we use the impostor texture
-void SGNewCloud::RenderBB(sgVec3 deltaPos, float angleY, float angleX, float dist_center) {
- // TODO:glrotate is not needed
- glPushMatrix();
- glTranslatef(center[SG_X] - deltaPos[SG_X], center[SG_Z] - deltaPos[SG_Z], center[SG_Y] - deltaPos[SG_Y]);
- glRotatef(angleY, 0.0f, 0.0f, 1.0f);
- glRotatef(angleX, 1.0f, 0.0f, 0.0f);
-
+void SGNewCloud::RenderBB(sgVec3 deltaPos, bool first_time, float dist_center) {
+
+ sgVec3 translate;
+ sgSubVec3( translate, center, deltaPos);
+
// blend clouds with sky based on distance to limit the contrast of distant cloud
- float CloudVisFade = (1.5 * SGCloudField::get_CloudVis());
+ float CloudVisFade = (1.0f * SGCloudField::get_CloudVis());
- float t = 1.0f - dist_center / CloudVisFade;
- // err the alpha value is not good for impostor, debug that
- t *= 1.65;
+ float t = 1.0f - (dist_center - 1.0*radius) / CloudVisFade;
if ( t < 0.0f )
- t = 0.0f;
-
+ return;
+ if( t > 1.0f )
+ t = 1.0f;
+ if( t > 0.50f )
+ t *= 1.1f;
glColor4f(t, t, t, t);
float r = radius;
+ // compute the rotations so that the quad is facing the camera
+ sgVec3 pos;
+ sgSetVec3( pos, translate[SG_X], translate[SG_Z], translate[SG_Y] );
+ sgCopyVec3( translate, pos );
+ sgNormaliseVec3( translate );
+ sgVec3 x, y, up = {0.0f, 0.0f, 1.0f};
+ sgVectorProductVec3(x, translate, up);
+ sgVectorProductVec3(y, x, translate);
+ if(first_time) {
+ sgCopyVec3( rotX, x );
+ sgCopyVec3( rotY, y );
+ } else if(fabs(sgScalarProductVec3(rotX, x)) < 0.93f || fabs(sgScalarProductVec3(rotY, y)) < 0.93f ) {
+ // ask for a redraw of this impostor if the view angle changed too much
+ sgCopyVec3( rotX, x );
+ sgCopyVec3( rotY, y );
+ cldCache->invalidate(cloudId, bbId);
+ }
+ sgScaleVec3(x, r);
+ sgScaleVec3(y, r);
+
+ sgVec3 left, right;
+ sgCopyVec3( left, pos );
+ sgSubVec3 (left, y);
+ sgAddVec3 (right, left, x);
+ sgSubVec3 (left, x);
+
glBegin(GL_QUADS);
- glTexCoord2f(0.0f, 1.0f);
- glVertex2f(-r, r);
- glTexCoord2f(1.0f, 1.0f);
- glVertex2f(r, r);
- glTexCoord2f(1.0f, 0.0f);
- glVertex2f(r, -r);
glTexCoord2f(0.0f, 0.0f);
- glVertex2f(-r, -r);
+ glVertex3fv(left);
+ glTexCoord2f(1.0f, 0.0f);
+ glVertex3fv(right);
+ sgScaleVec3( y, 2.0 );
+ sgAddVec3( left, y);
+ sgAddVec3( right, y);
+ glTexCoord2f(1.0f, 1.0f);
+ glVertex3fv(right);
+ glTexCoord2f(0.0f, 1.0f);
+ glVertex3fv(left);
glEnd();
#if 0 // debug only
#endif
- glPopMatrix();
-
}
// determine if it is a good idea to use an impostor to render the cloud
void SGNewCloud::Render(sgVec3 FakeEyePos) {
sgVec3 dist;
-
sgVec3 deltaPos;
sgCopyVec3( deltaPos, FakeEyePos);
sgSubVec3( dist, center, FakeEyePos);
float dist_center = sgLengthVec3(dist);
-
+ if( fadeActive ) {
+ fadetimer += SGCloudField::timer_dt;
+ if( fadetimer > duration + pauseLength ) {
+ // fade out after fade in, and vice versa
+ direction = ! direction;
+ fadetimer = 0.0;
+ }
+ }
if( !isBillboardable(dist_center) ) {
// not a good candidate for impostors, draw a real cloud
Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
} else {
GLuint texID = 0;
+ bool first_time = false;
// lets use our impostor
if( bbId >= 0)
texID = cldCache->QueryTexID(cloudId, bbId);
// allocate a new Impostor
bbId = cldCache->alloc(cloudId);
texID = cldCache->QueryTexID(cloudId, bbId);
+ first_time = true;
}
if( texID == 0 ) {
// no more free texture in the pool
Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
} else {
- float angleX, angleY;
- CalcAngles(center, FakeEyePos, &angleY, &angleX);
- if( ! cldCache->isBbValid( cloudId, bbId, angleY, angleX) ) {
+ float angleX=0.0f, angleY=0.0f;
+
+ // force a redraw of the impostor if the cloud shape has changed enought
+ float step = ( list_spriteDef.size() * (direction ? fadetimer : duration-fadetimer)) / duration;
+ if( fabs(step - last_step) > 0.5f )
+ cldCache->invalidate(cloudId, bbId);
+
+ if( ! cldCache->isBbValid( cloudId, bbId, angleY, angleX)) {
// we must build or rebuild this billboard
// start render to texture
cldCache->beginCapture();
}
// draw the newly built BB or an old one
glBindTexture(GL_TEXTURE_2D, texID);
- RenderBB(deltaPos, angleY, angleX, dist_center);
+ RenderBB(deltaPos, first_time, dist_center);
}
}
SG_USING_STD(string);
SG_USING_STD(vector);
+/**
+ * 3D cloud class.
+ */
class SGNewCloud {
public:
- SGNewCloud();
+ enum CLFamilly_type {
+ CLFamilly_cu = 0,
+ CLFamilly_cb,
+ CLFamilly_st,
+ CLFamilly_ns,
+ CLFamilly_sc,
+ CLFamilly_as,
+ CLFamilly_ac,
+ CLFamilly_ci,
+ CLFamilly_cc,
+ CLFamilly_cs,
+ CLFamilly_nn
+ };
+ SGNewCloud(CLFamilly_type classification=CLFamilly_nn);
+ SGNewCloud(string classification);
~SGNewCloud();
enum CLbox_type {
CLTexture_stratus = 2,
CLTexture_max
};
+
private:
class spriteDef {
float r;
CLbox_type sprite_type;
sgVec4 l0, l1, l2, l3;
+ sgVec3 normal, n0, n1, n2, n3;
int rank;
int box;
float dist; // distance used during sort
bool operator<(const spriteDef &b) const {
- return this->dist < b.dist;
+ return (this->dist < b.dist);
}
};
typedef vector<spriteDef> list_of_spriteDef;
typedef vector<spriteContainer> list_of_spriteContainer;
+ void init(void);
+
void computeSimpleLight(sgVec3 eyePos);
void addSprite(float x, float y, float z, float r, CLbox_type type, int box);
void CalcAngles(sgVec3 refpos, sgVec3 eyePos, float *angleY, float *angleX);
// draw a cloud but this time we use the impostor texture
- void RenderBB(sgVec3 deltaPos, float angleY, float angleX, float dist_center);
+ void RenderBB(sgVec3 deltaPos, bool first_time, float dist_center);
// determine if it is a good idea to use an impostor to render the cloud
bool isBillboardable(float dist);
int cloudId, bbId;
+ sgVec3 rotX, rotY;
+
// int rank;
sgVec3 cloudpos, center;
+ float delta_base;
list_of_spriteDef list_spriteDef;
list_of_spriteContainer list_spriteContainer;
- float minx, maxx, miny, maxy, minz, maxz;
float radius;
+ CLFamilly_type familly;
// fading data
bool direction, fadeActive;
- float duration, pauseLength;
- // need timer here
+ float duration, pauseLength, fadetimer;
+ float last_step;
public:
// add a new box to the cloud
void setFade(float howMuch);
inline float getRadius() { return radius; }
+ inline sgVec3 *getCenter() { return ¢er; }
+
+ inline CLFamilly_type getFamilly(void) { return familly; }
// load all textures used to draw cloud sprites
static void loadTextures( const string &tex_path );
#include <simgear/math/sg_random.h>
#include "sky.hxx"
-
+#include "cloudfield.hxx"
// Constructor
SGSky::SGSky( void ) {
// in cloud layer
// bail now and don't draw any clouds
+ if( cloud_layers[i]->get_layer3D()->is3D() && SGCloudField::enable3D )
+ continue;
in_cloud = i;
} else {
// above cloud layer
ratio = 1.0;
}
- if ( cloud_layers[i]->getCoverage() == SGCloudLayer::SG_CLOUD_CLEAR ) {
+ if ( cloud_layers[i]->getCoverage() == SGCloudLayer::SG_CLOUD_CLEAR ||
+ cloud_layers[i]->get_layer3D()->is3D() && SGCloudField::enable3D) {
// do nothing, clear layers aren't drawn, don't affect
// visibility andn dont' need to be faded in or out.
} else if ( (cloud_layers[i]->getCoverage() ==
IMAGE_SERVER_SRCS =
endif
-noinst_HEADERS = colours.h RenderTexture.h
+noinst_HEADERS = colours.h GLBitmaps.h
include_HEADERS = \
colors.hxx \
$(IMAGE_SERVER_INCL) \
screen-dump.hxx \
extensions.hxx \
+ RenderTexture.h \
tr.h
libsgscreen_a_SOURCES = \
texture.cxx \
- GLBitmaps.cxx GLBitmaps.h \
+ GLBitmaps.cxx \
$(IMAGE_SERVER_SRCS) \
screen-dump.cxx \
tr.cxx \