1 // a layer of 3d clouds
3 // Written by Harald JOHNSEN, started April 2005.
5 // Copyright (C) 2005 Harald JOHNSEN - hjohnsen@evc.net
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 # include <simgear_config.h>
27 #include <simgear/compiler.h>
30 #include <simgear/math/sg_random.h>
31 #include <simgear/math/sg_geodesy.hxx>
32 #include <simgear/math/polar3d.hxx>
34 #include STL_ALGORITHM
39 #include <simgear/environment/visual_enviro.hxx>
41 #include "newcloud.hxx"
42 #include "cloudfield.hxx"
44 #if defined(__MINGW32__)
45 #define isnan(x) _isnan(x)
48 #if defined (__FreeBSD__)
49 # if __FreeBSD_version < 500000
51 inline int isnan(double r) { return !(r <= 0 || r >= 0); }
57 #if defined (__CYGWIN__)
61 static list_of_culledCloud inViewClouds;
63 // visibility distance for clouds in meters
64 float SGCloudField::CloudVis = 25000.0f;
65 bool SGCloudField::enable3D = false;
66 // fieldSize must be > CloudVis or we can destroy the impostor cache
67 // a cloud must only be seen once or the impostor will be generated for each of his positions
68 double SGCloudField::fieldSize = 50000.0;
69 float SGCloudField::density = 100.0;
70 double SGCloudField::timer_dt = 0.0;
71 sgVec3 SGCloudField::view_vec, SGCloudField::view_X, SGCloudField::view_Y;
73 static int last_cache_size = 1*1024;
74 static int cacheResolution = 64;
75 static sgVec3 last_sunlight={0.0f, 0.0f, 0.0f};
77 int SGCloudField::get_CacheResolution(void) {
79 return cacheResolution;
84 void SGCloudField::set_CacheResolution(int resolutionPixels) {
86 if(cacheResolution == resolutionPixels)
88 cacheResolution = resolutionPixels;
90 int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
93 SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
98 int SGCloudField::get_CacheSize(void) {
100 return SGNewCloud::cldCache->queryCacheSize();
105 void SGCloudField::set_CacheSize(int sizeKb) {
107 // apply in rendering option dialog
108 if(last_cache_size == sizeKb)
113 last_cache_size = sizeKb;
115 int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
118 SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
122 void SGCloudField::set_CloudVis(float distance) {
124 if( distance <= fieldSize )
125 SGCloudField::CloudVis = distance;
128 void SGCloudField::set_density(float density) {
130 SGCloudField::density = density;
133 void SGCloudField::set_enable3dClouds(bool enable) {
135 if(enable3D == enable)
139 int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
142 SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
144 SGNewCloud::cldCache->setCacheSize(0);
149 // reposition the cloud layer at the specified origin and orientation
150 void SGCloudField::reposition( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double dt, float direction, float speed) {
155 sgMakeTransMat4( T1, p );
157 sgSetVec3( axis, 0.0, 0.0, 1.0 );
158 sgMakeRotMat4( LON, lon * SGD_RADIANS_TO_DEGREES, axis );
160 sgSetVec3( axis, 0.0, 1.0, 0.0 );
161 sgMakeRotMat4( LAT, 90.0 - lat * SGD_RADIANS_TO_DEGREES, axis );
165 sgCopyMat4( TRANSFORM, T1 );
166 sgPreMultMat4( TRANSFORM, LON );
167 sgPreMultMat4( TRANSFORM, LAT );
170 sgSetCoord( &layerpos, TRANSFORM );
172 sgMakeCoordMat4( transform, &layerpos );
177 // simulate clouds movement from wind
178 double sp_dist = speed*dt;
180 double bx = cos((180.0-direction) * SGD_DEGREES_TO_RADIANS) * sp_dist;
181 double by = sin((180.0-direction) * SGD_DEGREES_TO_RADIANS) * sp_dist;
182 relative_position[SG_X] += bx;
183 relative_position[SG_Y] += by;
186 if ( lon != last_lon || lat != last_lat || sp_dist != 0 ) {
187 Point3D start( last_lon, last_lat, 0.0 );
188 Point3D dest( lon, lat, 0.0 );
189 double course = 0.0, dist = 0.0;
191 calc_gc_course_dist( dest, start, &course, &dist );
192 // if start and dest are too close together,
193 // calc_gc_course_dist() can return a course of "nan". If
194 // this happens, lets just use the last known good course.
195 // This is a hack, and it would probably be better to make
196 // calc_gc_course_dist() more robust.
197 if ( isnan(course) ) {
198 course = last_course;
200 last_course = course;
203 // calculate cloud movement due to external forces
204 double ax = 0.0, ay = 0.0;
207 ax = cos(course) * dist;
208 ay = sin(course) * dist;
219 // correct the frustum with the right far plane
220 ssgContext *context = ssgGetCurrentContext();
221 frustum = *context->getFrustum();
224 sgEnviro.getFOV( w, h );
225 frustum.setFOV( w, h );
226 frustum.setNearFar(1.0, CloudVis);
231 SGCloudField::SGCloudField() :
239 sgSetVec3( relative_position, 0,0,0);
240 theField.reserve(200);
241 inViewClouds.reserve(200);
242 sg_srandom_time_10();
248 SGCloudField::~SGCloudField() {
250 list_of_Cloud::iterator iCloud;
251 for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
252 delete iCloud->aCloud;
259 void SGCloudField::clear(void) {
261 list_of_Cloud::iterator iCloud;
262 for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
263 delete iCloud->aCloud;
266 // force a recompute of density on first redraw
268 // true to come back in set density after layer is built
273 // use a table or else we see poping when moving the slider...
274 static int densTable[][10] = {
275 {0,0,0,0,0,0,0,0,0,0},
276 {1,0,0,0,0,0,0,0,0,0},
277 {1,0,0,0,1,0,0,0,0,0},
278 {1,0,0,0,1,0,0,1,0,0}, // 30%
279 {1,0,1,0,1,0,0,1,0,0},
280 {1,0,1,0,1,0,1,1,0,0}, // 50%
281 {1,0,1,0,1,0,1,1,0,1},
282 {1,0,1,1,1,0,1,1,0,1}, // 70%
283 {1,1,1,1,1,0,1,1,0,1},
284 {1,1,1,1,1,0,1,1,1,1}, // 90%
285 {1,1,1,1,1,1,1,1,1,1}
288 // set the visible flag depending on density
289 void SGCloudField::applyDensity(void) {
291 int row = (int) (density / 10.0);
295 list_of_Cloud::iterator iCloud;
296 for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
299 if( densTable[row][col] ) {
300 iCloud->visible = true;
301 fieldBox.extend( *iCloud->aCloud->getCenter() );
303 iCloud->visible = false;
305 last_density = density;
306 draw_in_3d = ( theField.size() != 0);
308 sgSubVec3( center, fieldBox.getMax(), fieldBox.getMin() );
309 sgScaleVec3( center, 0.5f );
311 field_sphere.setCenter( center );
312 field_sphere.setRadius( fieldSize * 0.5f * 1.414f );
316 // add one cloud, data is not copied, ownership given
317 void SGCloudField::addCloud( sgVec3 pos, SGNewCloud *cloud) {
322 cloud->SetPos( pos );
323 sgCopyVec3( cl.pos, *cloud->getCenter() );
324 theField.push_back( cl );
329 static float Rnd(float n) {
330 return n * (-0.5f + sg_random());
334 // build a field of cloud of size 25x25 km, its a grid of 11x11 clouds
335 void SGCloudField::buildTestLayer(void) {
337 const float s = 2250.0f;
339 for( int z = -5 ; z <= 5 ; z++) {
340 for( int x = -5 ; x <= 5 ; x++ ) {
341 SGNewCloud *cloud = new SGNewCloud(SGNewCloud::CLFamilly_cu);
343 sgVec3 pos = {(x+Rnd(0.7)) * s, 750.0f, (z+Rnd(0.7)) * s};
344 addCloud(pos, cloud);
351 // cull all clouds of a tiled field
352 void SGCloudField::cullClouds(sgVec3 eyePos, sgMat4 mat) {
354 list_of_Cloud::iterator iCloud;
356 sgSphere tile_sphere;
357 tile_sphere.setRadius( field_sphere.getRadius() );
359 sgSubVec3( tile_center, field_sphere.getCenter(), eyePos );
360 tile_sphere.setCenter( tile_center );
361 tile_sphere.orthoXform(mat);
362 if( frustum.contains( & tile_sphere ) == SG_OUTSIDE )
365 for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
368 if( ! iCloud->visible )
370 sgSubVec3( dist, iCloud->pos, eyePos );
371 sphere.setCenter(dist[0], dist[2], dist[1] + eyePos[1]);
372 float radius = iCloud->aCloud->getRadius();
373 sphere.setRadius(radius);
374 sphere.orthoXform(mat);
375 if( frustum.contains( & sphere ) != SG_OUTSIDE ) {
376 float squareDist = dist[0]*dist[0] + dist[1]*dist[1] + dist[2]*dist[2];
378 tmp.aCloud = iCloud->aCloud;
379 sgCopyVec3( tmp.eyePos, eyePos );
380 // save distance for later sort, opposite distance because we want back to front
381 tmp.dist = - squareDist;
382 tmp.heading = -SG_PI/2.0 - atan2( dist[0], dist[2] ); // + SG_PI;
383 tmp.alt = iCloud->pos[1];
384 inViewClouds.push_back(tmp);
385 if( squareDist - radius*radius < 100*100 )
386 sgEnviro.set_view_in_cloud(true);
393 // Render a cloud field
394 // because no field can have an infinite size (and we don't want to reach his border)
395 // we draw this field and adjacent fields.
396 // adjacent fields are not real, its the same field displaced by some offset
397 void SGCloudField::Render(float *sun_color) {
398 // sun_color used to depend on an extern SGSky *thesky definition
399 // above. However, this is bad form for a library and it's much
400 // more clean to just pass in the needed value. For reference, here is
401 // the old way that sun_color was fetched ...
402 // float *sun_color = thesky->get_sun_color();
411 if( last_density != density ) {
412 last_density = density;
419 if( ! SGNewCloud::cldCache->isRttAvailable() )
422 inViewClouds.clear();
427 sgMat4 modelview, tmp, invtrans;
429 // try to find the sun position
430 sgTransposeNegateMat4( invtrans, transform );
432 ssgGetLight( 0 )->getPosition( lightVec );
433 sgXformVec3( lightVec, invtrans );
435 sgSetVec3( SGNewCloud::modelSunDir, lightVec[0], lightVec[2], lightVec[1]);
436 // try to find the lighting data (not accurate)
437 sgVec4 diffuse, ambient;
438 ssgGetLight( 0 )->getColour( GL_DIFFUSE, diffuse );
439 ssgGetLight( 0 )->getColour( GL_AMBIENT, ambient );
440 // sgScaleVec3 ( SGNewCloud::sunlight, diffuse , 1.0f);
441 sgScaleVec3 ( SGNewCloud::ambLight, ambient , 1.1f);
442 // trying something else : clouds are more yellow/red at dawn/dusk
443 // and added a bit of blue ambient
444 sgScaleVec3 ( SGNewCloud::sunlight, sun_color , 0.4f);
445 SGNewCloud::ambLight[2] += 0.1f;
448 sgSubVec3(delta_light, last_sunlight, SGNewCloud::sunlight);
449 if( (fabs(delta_light[0]) + fabs(delta_light[1]) + fabs(delta_light[2])) > 0.05f ) {
450 sgCopyVec3( last_sunlight, SGNewCloud::sunlight );
451 // force the redraw of all the impostors
452 SGNewCloud::cldCache->invalidateCache();
455 // voodoo things on the matrix stack
456 ssgGetModelviewMatrix( modelview );
457 sgCopyMat4( tmp, transform );
458 sgPostMultMat4( tmp, modelview );
460 // cloud fields are tiled on the flat earth
461 // compute the position in the tile
462 relx = fmod( deltax + relative_position[SG_X], fieldSize );
463 rely = fmod( deltay + relative_position[SG_Y], fieldSize );
465 relx = fmod( relx + fieldSize, fieldSize );
466 rely = fmod( rely + fieldSize, fieldSize );
467 sgSetVec3( eyePos, relx, alt, rely);
469 sgSetVec3( view_X, tmp[0][0], tmp[1][0], tmp[2][0] );
470 sgSetVec3( view_Y, tmp[0][1], tmp[1][1], tmp[2][1] );
471 sgSetVec3( view_vec, tmp[0][2], tmp[1][2], tmp[2][2] );
473 ssgLoadModelviewMatrix( tmp );
488 for(int x = -1 ; x <= 1 ; x++)
489 for(int y = -1 ; y <= 1 ; y++ ) {
491 // pretend we are not where we are
492 sgSetVec3(fieldPos, eyePos[0] + x*fieldSize, eyePos[1], eyePos[2] + y*fieldSize);
493 cullClouds(fieldPos, tmp);
495 // sort all visible clouds back to front (because of transparency)
496 std::sort( inViewClouds.begin(), inViewClouds.end() );
499 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
500 glEnable(GL_ALPHA_TEST);
501 glAlphaFunc(GL_GREATER, 0.0f);
502 glDisable(GL_CULL_FACE);
503 glEnable(GL_DEPTH_TEST);
504 glDepthMask( GL_FALSE );
507 glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
508 glEnable( GL_TEXTURE_2D );
510 glDisable(GL_LIGHTING);
512 // test data: field = 11x11 cloud, 9 fields
513 // depending on position and view direction, perhaps 40 to 60 clouds not culled
514 list_of_culledCloud::iterator iCloud;
515 for( iCloud = inViewClouds.begin() ; iCloud != inViewClouds.end() ; iCloud++ ) {
516 // iCloud->aCloud->drawContainers();
517 iCloud->aCloud->Render(iCloud->eyePos);
518 sgEnviro.callback_cloud(iCloud->heading, iCloud->alt,
519 iCloud->aCloud->getRadius(), iCloud->aCloud->getFamilly(), - iCloud->dist, iCloud->aCloud->getId());
522 glBindTexture(GL_TEXTURE_2D, 0);
523 glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
525 glEnable(GL_CULL_FACE);
526 glEnable(GL_DEPTH_TEST);
528 ssgLoadModelviewMatrix( modelview );