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, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
24 # include <simgear_config.h>
27 #include <simgear/compiler.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>
40 #include "newcloud.hxx"
41 #include "cloudfield.hxx"
43 static list_of_culledCloud inViewClouds;
45 // visibility distance for clouds in meters
46 float SGCloudField::CloudVis = 25000.0f;
47 bool SGCloudField::enable3D = true;
48 // fieldSize must be > CloudVis or we can destroy the impostor cache
49 // a cloud must only be seen once or the impostor will be generated for each of his positions
50 double SGCloudField::fieldSize = 27000.0;
51 float SGCloudField::density = 100.0;
52 static int last_cache_size = 1*1024;
53 static int cacheResolution = 64;
55 int SGCloudField::get_CacheResolution(void) {
56 return cacheResolution;
59 void SGCloudField::set_CacheResolution(int resolutionPixels) {
60 if(cacheResolution == resolutionPixels)
62 cacheResolution = resolutionPixels;
64 int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
67 SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
71 int SGCloudField::get_CacheSize(void) {
72 return SGNewCloud::cldCache->queryCacheSize();
75 void SGCloudField::set_CacheSize(int sizeKb) {
76 // apply in rendering option dialog
77 if(last_cache_size == sizeKb)
82 last_cache_size = sizeKb;
84 int count = last_cache_size * 1024 / (cacheResolution * cacheResolution * 4);
87 // SGNewCloud::cldCache->setCacheSize(sizeKb);
88 SGNewCloud::cldCache->setCacheSize(count, cacheResolution);
91 void SGCloudField::set_CloudVis(float distance) {
92 if( distance <= fieldSize )
93 SGCloudField::CloudVis = distance;
95 void SGCloudField::set_density(float density) {
96 SGCloudField::density = density;
98 void SGCloudField::set_enable3dClouds(bool enable) {
99 if(enable3D == enable)
103 SGNewCloud::cldCache->setCacheSize(last_cache_size);
105 SGNewCloud::cldCache->setCacheSize(0);
109 // reposition the cloud layer at the specified origin and orientation
110 void SGCloudField::reposition( sgVec3 p, sgVec3 up, double lon, double lat, double alt, double dt) {
114 sgMakeTransMat4( T1, p );
116 sgSetVec3( axis, 0.0, 0.0, 1.0 );
117 sgMakeRotMat4( LON, lon * SGD_RADIANS_TO_DEGREES, axis );
119 sgSetVec3( axis, 0.0, 1.0, 0.0 );
120 sgMakeRotMat4( LAT, 90.0 - lat * SGD_RADIANS_TO_DEGREES, axis );
124 sgCopyMat4( TRANSFORM, T1 );
125 sgPreMultMat4( TRANSFORM, LON );
126 sgPreMultMat4( TRANSFORM, LAT );
129 sgSetCoord( &layerpos, TRANSFORM );
131 sgMakeCoordMat4( transform, &layerpos );
136 // simulate clouds movement from wind
137 double speed = 10.0f;
138 double direction = 45.0;
139 double sp_dist = speed*dt;
141 double bx = cos((180.0-direction) * SGD_DEGREES_TO_RADIANS) * sp_dist;
142 double by = sin((180.0-direction) * SGD_DEGREES_TO_RADIANS) * sp_dist;
143 relative_position[SG_X] += bx;
144 relative_position[SG_Y] += by;
147 if ( lon != last_lon || lat != last_lat || sp_dist != 0 ) {
148 Point3D start( last_lon, last_lat, 0.0 );
149 Point3D dest( lon, lat, 0.0 );
150 double course = 0.0, dist = 0.0;
152 calc_gc_course_dist( dest, start, &course, &dist );
153 // if start and dest are too close together,
154 // calc_gc_course_dist() can return a course of "nan". If
155 // this happens, lets just use the last known good course.
156 // This is a hack, and it would probably be better to make
157 // calc_gc_course_dist() more robust.
158 if ( isnan(course) ) {
159 course = last_course;
161 last_course = course;
164 // calculate cloud movement due to external forces
165 double ax = 0.0, ay = 0.0;
168 ax = cos(course) * dist;
169 ay = sin(course) * dist;
180 // correct the frustum with the right far plane
181 ssgContext *context = ssgGetCurrentContext();
182 frustum = *context->getFrustum();
183 frustum.setNearFar(1.0, CloudVis);
186 SGCloudField::SGCloudField() :
192 sgSetVec3( relative_position, 0,0,0);
193 theField.reserve(200);
194 inViewClouds.reserve(200);
197 SGCloudField::~SGCloudField() {
198 list_of_Cloud::iterator iCloud;
199 for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
200 delete iCloud->aCloud;
206 // use a table or else we see poping when moving the slider...
207 static int densTable[][10] = {
208 {0,0,0,0,0,0,0,0,0,0},
209 {1,0,0,0,0,0,0,0,0,0},
210 {1,0,0,0,1,0,0,0,0,0},
211 {1,0,0,0,1,0,0,1,0,0}, // 30%
212 {1,0,1,0,1,0,0,1,0,0},
213 {1,0,1,0,1,0,1,1,0,0}, // 50%
214 {1,0,1,0,1,0,1,1,0,1},
215 {1,0,1,1,1,0,1,1,0,1}, // 70%
216 {1,1,1,1,1,0,1,1,0,1},
217 {1,1,1,1,1,0,1,1,1,1}, // 90%
218 {1,1,1,1,1,1,1,1,1,1}
221 // set the visible flag depending on density
222 void SGCloudField::applyDensity(void) {
223 int row = (int) (density / 10.0);
225 list_of_Cloud::iterator iCloud;
226 for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
229 if( densTable[row][col] ) {
230 iCloud->visible = true;
232 iCloud->visible = false;
234 last_density = density;
237 // add one cloud, data is not copied, ownership given
238 void SGCloudField::addCloud( sgVec3 pos, SGNewCloud *cloud) {
240 sgCopyVec3( cl.pos, pos );
243 cloud->SetPos( pos );
244 theField.push_back( cl );
248 static float Rnd(float n) {
249 return n * (-0.5f + rand() / (float) RAND_MAX);
253 // build a field of cloud of size 25x25 km, its a grid of 11x11 clouds
254 void SGCloudField::buildTestLayer(void) {
256 const float s = 2250.0f;
258 for( int z = -5 ; z <= 5 ; z++) {
259 for( int x = -5 ; x <= 5 ; x++ ) {
260 SGNewCloud *cloud = new SGNewCloud;
262 sgVec3 pos = {(x+Rnd(0.7)) * s, 750.0f, (z+Rnd(0.7)) * s};
263 addCloud(pos, cloud);
269 // cull all clouds of a tiled field
270 void SGCloudField::cullClouds(sgVec3 eyePos, sgMat4 mat) {
271 list_of_Cloud::iterator iCloud;
273 // TODO:cull the field before culling the clouds in the field (should eliminate 3 fields)
274 for( iCloud = theField.begin() ; iCloud != theField.end() ; iCloud++ ) {
277 if( ! iCloud->visible )
279 sgSubVec3( dist, iCloud->pos, eyePos );
280 sphere.setCenter(dist[0], dist[2], dist[1]);
281 float radius = iCloud->aCloud->getRadius();
282 sphere.setRadius(radius);
283 sphere.orthoXform(mat);
284 if( frustum.contains( & sphere ) != SG_OUTSIDE ) {
285 float squareDist = dist[0]*dist[0] + dist[1]*dist[1] + dist[2]*dist[2];
287 tmp.aCloud = iCloud->aCloud;
288 sgCopyVec3( tmp.eyePos, eyePos );
289 // save distance for later sort, opposite distance because we want back to front
290 tmp.dist = - squareDist;
291 inViewClouds.push_back(tmp);
292 if( squareDist - radius*radius < 100*100 )
293 sgEnviro.set_view_in_cloud(true);
300 // Render a cloud field
301 // because no field can have an infinite size (and we don't want to reach his border)
302 // we draw this field and adjacent fields.
303 // adjacent fields are not real, its the same field displaced by some offset
304 void SGCloudField::Render(void) {
311 if( last_density != density ) {
312 last_density = density;
316 // ask the impostor cache to do some cleanup
317 // TODO:don't do that for every field
318 SGNewCloud::cldCache->startNewFrame();
320 inViewClouds.clear();
325 sgMat4 modelview, tmp, invtrans;
327 // try to find the sun position
328 sgTransposeNegateMat4( invtrans, transform );
330 ssgGetLight( 0 )->getPosition( lightVec );
331 sgXformVec3( lightVec, invtrans );
332 sgCopyVec3( SGNewCloud::modelSunDir, lightVec );
333 sgSetVec3( SGNewCloud::modelSunDir, lightVec[0], lightVec[2], lightVec[1]);
334 // try to find the lighting data (buggy)
335 sgVec4 diffuse, ambient;
336 ssgGetLight( 0 )->getColour( GL_DIFFUSE, diffuse );
337 ssgGetLight( 0 )->getColour( GL_AMBIENT, ambient );
338 sgScaleVec3 ( SGNewCloud::sunlight, diffuse , 0.70f);
339 sgScaleVec3 ( SGNewCloud::ambLight, ambient , 0.60f);
341 // voodoo things on the matrix stack
342 ssgGetModelviewMatrix( modelview );
343 sgCopyMat4( tmp, transform );
344 sgPostMultMat4( tmp, modelview );
346 // cloud fields are tiled on the flat earth
347 // compute the position in the tile
348 relx = -fmod( deltax + relative_position[SG_X] + tmp[3][0], fieldSize );
349 rely = -fmod( deltay + relative_position[SG_Y] + tmp[3][1], fieldSize );
351 sgSetVec3( eyePos, -relx, alt, -rely);
356 ssgLoadModelviewMatrix( tmp );
371 for(int x = -1 ; x <= 1 ; x++)
372 for(int y = -1 ; y <= 1 ; y++ ) {
374 // pretend we are not where we are
375 sgSetVec3(fieldPos, eyePos[0] + x*fieldSize, eyePos[1], eyePos[2] + y*fieldSize);
376 cullClouds(fieldPos, tmp);
378 // sort all visible clouds back to front (because of transparency)
379 sort( inViewClouds.begin(), inViewClouds.end() );
382 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
383 glEnable(GL_ALPHA_TEST);
384 glAlphaFunc(GL_GREATER, 0.0f);
385 glDisable(GL_CULL_FACE);
386 glEnable(GL_DEPTH_TEST);
389 glBlendFunc( GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
390 glEnable( GL_TEXTURE_2D );
392 glDisable(GL_LIGHTING);
394 // test data: field = 11x11 cloud, 9 fields
395 // depending on position and view direction, perhaps 40 to 60 clouds not culled
396 list_of_culledCloud::iterator iCloud;
397 for( iCloud = inViewClouds.begin() ; iCloud != inViewClouds.end() ; iCloud++ ) {
398 // iCloud->aCloud->drawContainers();
399 iCloud->aCloud->Render(iCloud->eyePos);
402 glBindTexture(GL_TEXTURE_2D, 0);
403 glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ) ;
405 glEnable(GL_CULL_FACE);
406 glEnable(GL_DEPTH_TEST);
408 ssgLoadModelviewMatrix( modelview );