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/misc/sg_path.hxx>
33 #include STL_ALGORITHM
36 #include "cloudfield.hxx"
37 #include "newcloud.hxx"
43 static ssgTexture *cloudTextures[SGNewCloud::CLTexture_max];
46 bool SGNewCloud::useAnisotropic = true;
47 SGBbCache *SGNewCloud::cldCache = 0;
48 static bool texturesLoaded = false;
49 float SGNewCloud::nearRadius = 3500.0f;
50 bool SGNewCloud::lowQuality = false;
51 sgVec3 SGNewCloud::sunlight = {0.5f, 0.5f, 0.5f};
52 sgVec3 SGNewCloud::ambLight = {0.5f, 0.5f, 0.5f};
53 sgVec3 SGNewCloud::modelSunDir = {0,1,0};
57 SGNewCloud::SGNewCloud() :
60 minx(999), miny(999), minz(999), maxx(-999), maxy(-999), maxz(-999)
64 sgSetVec3(center, 0.0f, 0.0f, 0.0f);
65 sgSetVec3(cloudpos, 0.0f, 0.0f, 0.0f);
66 list_spriteContainer.reserve(8);
67 list_spriteDef.reserve(40);
68 // if( ! texturesLoaded ) {}
70 cldCache = new SGBbCache;
75 SGNewCloud::~SGNewCloud() {
76 list_spriteDef.clear();
77 list_spriteContainer.clear();
78 cldCache->free( bbId, cloudId );
82 // load all textures used to draw cloud sprites
83 void SGNewCloud::loadTextures(const string &tex_path) {
86 texturesLoaded = true;
90 cloud_path.set(tex_path);
91 cloud_path.append("cl_cumulus.rgb");
92 cloudTextures[ CLTexture_cumulus ] = new ssgTexture( cloud_path.str().c_str(), false, false, false );
93 cloudTextures[ CLTexture_cumulus ]->ref();
95 cloud_path.set(tex_path);
96 cloud_path.append("cl_stratus.rgb");
97 cloudTextures[ CLTexture_stratus ] = new ssgTexture( cloud_path.str().c_str(), false, false, false );
98 cloudTextures[ CLTexture_stratus ]->ref();
102 void SGNewCloud::startFade(bool direction, float duration, float pauseLength) {
104 void SGNewCloud::setFade(float howMuch) {
108 static float rayleighCoeffAngular(float cosAngle) {
109 return 3.0f / 4.0f * (1.0f + cosAngle * cosAngle);
112 // cp is normalized (len==1)
113 static void CartToPolar3d(sgVec3 cp, sgVec3 polar) {
114 polar[0] = atan2(cp[1], cp[0]);
115 polar[1] = SG_PI / 2.0f - atan2(sqrtf (cp[0] * cp[0] + cp[1] * cp[1]), cp[2]);
119 static void PolarToCart3d(sgVec3 p, sgVec3 cart) {
120 float tmp = cos(p[1]);
121 cart[0] = cos(p[0]) * tmp;
122 cart[1] = sin(p[0]) * tmp;
127 // compute the light for a cloud sprite corner
128 // from the normal and the sun, scaled by the Rayleigh factor
129 // and finaly added to the ambient light
130 static void lightFunction(sgVec3 normal, sgVec4 light, float pf) {
131 float cosAngle = sgScalarProductVec3( normal, SGNewCloud::modelSunDir);
132 float vl = (1.0f - 0.1f + cosAngle / 10.0f) * pf;
133 sgScaleVec3( light, SGNewCloud::sunlight, vl );
134 sgAddVec3( light, SGNewCloud::ambLight );
135 // we need to clamp or else the light will bug when adding transparency
136 if( light[0] > 1.0 ) light[0] = 1.0;
137 if( light[1] > 1.0 ) light[1] = 1.0;
138 if( light[2] > 1.0 ) light[2] = 1.0;
142 // compute the light for a cloud sprite
143 // we use ambient light and orientation versus sun position
144 // TODO:check sun pos and check code
145 void SGNewCloud::computeSimpleLight(sgVec3 FakeEyePos) {
146 // constant Rayleigh factor if we are not doing Anisotropic lighting
148 const float ang = 45.0f * SG_PI / 180.0f;
149 list_of_spriteDef::iterator iSprite;
150 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
151 if( useAnisotropic ) {
153 sgSubVec3(eyeDir, iSprite->pos, FakeEyePos);
154 sgNormaliseVec3(eyeDir);
155 float cosAngle = sgScalarProductVec3(eyeDir, modelSunDir);
156 pf = rayleighCoeffAngular(cosAngle);
158 // compute the vector going from the container box center to the sprite
159 // TODO : this is a constant except for cloudpos, compute the normal in setpos function
161 spriteContainer *thisSpriteContainer = &list_spriteContainer[iSprite->box];
162 sgSubVec3(normal, iSprite->pos, thisSpriteContainer->pos);
163 sgSubVec3(normal, thisSpriteContainer->center);
164 sgSubVec3(normal, cloudpos);
165 sgNormaliseVec3(normal);
167 // juste use the traditional normal to compute some lightning
169 lightFunction(normal, centerColor, pf);
170 sgCopyVec4(iSprite->l0, centerColor);
171 sgCopyVec4(iSprite->l1, centerColor);
172 sgCopyVec4(iSprite->l2, centerColor);
173 sgCopyVec4(iSprite->l3, centerColor);
176 // use exotic lightning function, this will give more 'relief' to the clouds
177 // compute a normal for each vextex this will simulate a smooth shading for a round shape
178 sgVec3 polar, cart, pt;
179 // I suspect this code to be bugged...
180 CartToPolar3d(normal, polar);
182 // offset the normal vector by some angle for each vertex
183 sgSetVec3(pt, polar[0] - ang, polar[1] - ang, polar[2]);
184 PolarToCart3d(pt, cart);
185 lightFunction(cart, iSprite->l0, pf);
186 sgSetVec3(pt, polar[0] + ang, polar[1] - ang, polar[2]);
187 PolarToCart3d(pt, cart);
188 lightFunction(cart, iSprite->l1, pf);
189 sgSetVec3(pt, polar[0] + ang, polar[1] + ang, polar[2]);
190 PolarToCart3d(pt, cart);
191 lightFunction(cart, iSprite->l2, pf);
192 sgSetVec3(pt, polar[0] - ang, polar[1] + ang, polar[2]);
193 PolarToCart3d(pt, cart);
194 lightFunction(cart, iSprite->l3, pf);
200 // add a new box to the cloud
201 void SGNewCloud::addContainer (float x, float y, float z, float r, CLbox_type type) {
202 spriteContainer cont;
203 sgSetVec3( cont.pos, x, y, z );
205 cont.cont_type = type;
206 sgSetVec3( cont.center, 0.0f, 0.0f, 0.0f);
207 list_spriteContainer.push_back( cont );
210 // add a sprite inside a box
211 void SGNewCloud::addSprite(float x, float y, float z, float r, CLbox_type type, int box) {
212 spriteDef newSpriteDef;
213 int rank = list_spriteDef.size();
214 sgSetVec3( newSpriteDef.pos, x, y, z);
215 newSpriteDef.box = box;
216 newSpriteDef.sprite_type = type;
217 newSpriteDef.rank = rank;
219 list_spriteDef.push_back( newSpriteDef );
220 spriteContainer *thisBox = &list_spriteContainer[box];
222 sgSubVec3( deltaPos, newSpriteDef.pos, thisBox->pos );
223 sgAddVec3( thisBox->center, deltaPos );
225 r = r * 0.6f; // 0.5 * 1.xxx
241 // return a random number between -n/2 and n/2
242 static float Rnd(float n) {
243 return n * (-0.5f + rand() / (float) RAND_MAX);
246 // generate all sprite with defined boxes
247 void SGNewCloud::genSprites( void ) {
250 N = list_spriteContainer.size();
251 for(int i = 0 ; i < N ; i++ ) {
252 spriteContainer *thisBox = & list_spriteContainer[i];
253 // the type defines how the sprites can be positioned inside the box, their size, etc
254 switch(thisBox->cont_type) {
256 for( sc = 0 ; sc <= 4 ; sc ++ ) {
257 r = thisBox->r + Rnd(0.2f);
258 x = thisBox->pos[SG_X] + Rnd(thisBox->r);
259 y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.2f);
260 z = thisBox->pos[SG_Z] + Rnd(thisBox->r);
261 addSprite(x, y, z, r, thisBox->cont_type, i);
267 x = thisBox->pos[SG_X];
268 y = thisBox->pos[SG_Y];
269 z = thisBox->pos[SG_Z];
270 addSprite(x, y, z, r, thisBox->cont_type, i);
273 for( sc = 0 ; sc <= 4 ; sc ++ ) {
274 r = thisBox->r + Rnd(0.2f);
275 x = thisBox->pos[SG_X] + Rnd(thisBox->r * 0.75f);
276 y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.5f);
277 z = thisBox->pos[SG_Z] + Rnd(thisBox->r * 0.75f);
278 if ( y < thisBox->pos[SG_Y] - thisBox->r / 10.0f )
279 y = thisBox->pos[SG_Y] - thisBox->r / 10.0f;
280 addSprite(x, y, z, r, thisBox->cont_type, i);
284 for( sc = 0 ; sc <= 4 ; sc ++ ) {
285 r = thisBox->r + Rnd(0.2f);
286 x = thisBox->pos[SG_X] + Rnd(thisBox->r);
287 y = thisBox->pos[SG_Y] + Rnd(thisBox->r);
288 z = thisBox->pos[SG_Z] + Rnd(thisBox->r);
289 addSprite(x, y, z, r, thisBox->cont_type, i);
293 sgScaleVec3(thisBox->center, 1.0f / sc);
296 radius = maxx - minx;
297 if ( (maxy - miny) > radius )
298 radius = (maxy - miny);
299 if ( (maxz - minz) > radius )
300 radius = (maxz - minz);
302 sgSetVec3( center, (maxx + minx) / 2.0f, (maxy + miny) / 2.0f, (maxz + minz) / 2.0f );
305 ' fadingrank = UBound(tbSpriteDef()) * 10
307 // TODO : compute initial sprite normals for lighting function
311 // definition of a cu cloud, only for testing
312 void SGNewCloud::new_cu(void) {
314 float r = Rnd(1.0) + 0.5;
316 addContainer(0.0f, 0.0f, 0.0f, s, CLbox_cumulus);
317 addContainer(s, 0, 0, s, CLbox_cumulus);
318 addContainer(0, 0, 2 * s, s, CLbox_cumulus);
319 addContainer(s, 0, 2 * s, s, CLbox_cumulus);
321 addContainer(-1.2f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
322 addContainer(0.2f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
323 addContainer(1.6f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
324 } else if ( r < 0.90f ) {
325 addContainer(0, 0, 0, s * 1.2, CLbox_cumulus);
326 addContainer(s, 0, 0, s, CLbox_cumulus);
327 addContainer(0, 0, s, s, CLbox_cumulus);
328 addContainer(s * 1.1, 0, s, s * 1.2, CLbox_cumulus);
330 addContainer(-1.2 * s, 1 + 0.2 * s, s * 0.5, s * 1.4, CLbox_standard);
331 addContainer(0.2 * s, 1 + 0.25 * s, s * 0.5, s * 1.5, CLbox_standard);
332 addContainer(1.6 * s, 1 + 0.2 * s, s * 0.5, s * 1.4, CLbox_standard);
337 addContainer(0, 0, 0, s, CLbox_cumulus);
338 addContainer(0, 0, s, s, CLbox_cumulus);
339 addContainer(s, 0, s, s, CLbox_cumulus);
340 addContainer(s, 0, 0, s, CLbox_cumulus);
342 addContainer(s / 2, s, s / 2, s * 1.5, CLbox_standard);
344 addContainer(0, 2 * s, 0, s, CLbox_standard);
345 addContainer(0, 2 * s, s, s, CLbox_standard);
346 addContainer(s, 2 * s, s, s, CLbox_standard);
347 addContainer(s, 2 * s, 0, s, CLbox_standard);
354 // define the new position of the cloud (inside the cloud field, not on sphere)
355 void SGNewCloud::SetPos(sgVec3 newPos) {
356 int N = list_spriteDef.size();
358 sgSubVec3( deltaPos, newPos, cloudpos );
361 for(int i = 0 ; i < N ; i ++) {
362 sgAddVec3( list_spriteDef[i].pos, deltaPos );
364 sgAddVec3( center, deltaPos );
365 sgSetVec3( cloudpos, newPos[SG_X], newPos[SG_Y], newPos[SG_Z]);
366 // TODO : recompute sprite normal so we don't have to redo that each frame
372 void SGNewCloud::drawContainers() {
379 // sort on distance to eye because of transparency
380 void SGNewCloud::sortSprite( sgVec3 eye ) {
381 list_of_spriteDef::iterator iSprite;
383 // compute distance from sprite to eye
384 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
386 sgSubVec3( dist, iSprite->pos, eye );
387 iSprite->dist = -(dist[0]*dist[0] + dist[1]*dist[1] + dist[2]*dist[2]);
389 std::sort( list_spriteDef.begin(), list_spriteDef.end() );
392 // render the cloud on screen or on the RTT texture to build the impostor
393 void SGNewCloud::Render3Dcloud( bool drawBB, sgVec3 FakeEyePos, sgVec3 deltaPos, float dist_center ) {
395 /* int clrank = fadingrank / 10;
396 int clfadeinrank = fadingrank - clrank * 10;*/
397 float CloudVisFade = 1.0 / (1.5 * SGCloudField::get_CloudVis());
399 computeSimpleLight( FakeEyePos );
401 // view point sort, we sort because of transparency
402 sortSprite( FakeEyePos );
404 GLint previousTexture = -1, thisTexture;
405 list_of_spriteDef::iterator iSprite;
406 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
407 // choose texture to use depending on sprite type
408 switch(iSprite->sprite_type) {
410 thisTexture = CLTexture_stratus;
413 thisTexture = CLTexture_cumulus;
416 // in practice there is no texture switch (atm)
417 if( previousTexture != thisTexture ) {
418 previousTexture = thisTexture;
419 glBindTexture(GL_TEXTURE_2D, cloudTextures[thisTexture]->getHandle());
424 sgCopyVec3( translate, iSprite->pos);
425 sgSubVec3( translate, iSprite->pos, deltaPos );
428 sgSubVec3( translate, iSprite->pos, deltaPos);
431 // flipx and flipy are random texture flip flags, this gives more random clouds
432 float flipx = (float) ( iSprite->rank & 1 );
433 float flipy = (float) ( (iSprite->rank >> 1) & 1 );
434 // cu texture have a flat bottom so we can't do a vertical flip
435 if( iSprite->sprite_type == CLbox_cumulus || iSprite->sprite_type == CLbox_stratus )
437 if( iSprite->sprite_type == CLbox_stratus )
439 // adjust colors depending on cloud type
440 // TODO : rewrite that later, still experimental
441 switch(iSprite->sprite_type) {
444 sgScaleVec3(iSprite->l0, 0.6f);
445 sgScaleVec3(iSprite->l1, 0.6f);
449 sgScaleVec3(iSprite->l0, 0.8f);
450 sgScaleVec3(iSprite->l1, 0.8f);
451 sgScaleVec3(iSprite->l2, 0.8f);
452 sgScaleVec3(iSprite->l3, 0.8f);
455 // darker bottom than top
456 sgScaleVec3(iSprite->l0, 0.8f);
457 sgScaleVec3(iSprite->l1, 0.8f);
460 float r = iSprite->r * 0.5f;
462 sgVec4 l0, l1, l2, l3;
463 sgCopyVec4 ( l0, iSprite->l0 );
464 sgCopyVec4 ( l1, iSprite->l1 );
465 sgCopyVec4 ( l2, iSprite->l2 );
466 sgCopyVec4 ( l3, iSprite->l3 );
468 // blend clouds with sky based on distance to limit the contrast of distant cloud
469 float t = 1.0f - dist_center * CloudVisFade;
471 t = 0.0f; // no, it should have been culled
472 // now clouds at the far plane are half blended
473 sgScaleVec4( l0, t );
474 sgScaleVec4( l1, t );
475 sgScaleVec4( l2, t );
476 sgScaleVec4( l3, t );
478 // compute the rotations so that the quad is facing the camera
480 sgSetVec3( pos, translate[SG_X], translate[SG_Z], translate[SG_Y] );
481 sgCopyVec3( translate, pos );
482 sgNormaliseVec3( translate );
483 sgVec3 x, y, up = {0.0f, 0.0f, 1.0f};
484 sgVectorProductVec3(x, translate, up);
487 sgVectorProductVec3(y, x, translate);
493 sgSetVec3( left, iSprite->pos[SG_X], iSprite->pos[SG_Z], iSprite->pos[SG_Y]);
495 sgCopyVec3( left, pos );
497 sgAddVec3 (right, left, x);
502 glTexCoord2f(flipx, 1.0f - flipy);
505 glTexCoord2f(1.0f - flipx, 1.0f - flipy);
507 sgScaleVec3( y, 2.0 );
509 sgAddVec3( right, y);
511 glTexCoord2f(1.0f - flipx, flipy);
514 glTexCoord2f(flipx, flipy);
523 // compute rotations so that a quad is facing the camera
524 // TODO:change obsolete code because we dont use glrotate anymore
525 void SGNewCloud::CalcAngles(sgVec3 refpos, sgVec3 FakeEyePos, float *angleY, float *angleX) {
526 sgVec3 upAux, lookAt, objToCamProj, objToCam;
529 sgSetVec3(objToCamProj, -FakeEyePos[SG_X] + refpos[SG_X], -FakeEyePos[SG_Z] + refpos[SG_Z], 0.0f);
530 sgNormaliseVec3(objToCamProj);
532 sgSetVec3(lookAt, 0.0f, 1.0f, 0.0f);
533 sgVectorProductVec3(upAux, lookAt, objToCamProj);
534 angle = sgScalarProductVec3(lookAt, objToCamProj);
535 if( (angle < 0.9999f) && (angle > -0.9999f) ) {
536 angle = acos(angle) * 180.0f / SG_PI;
537 if( upAux[2] < 0.0f )
542 sgSetVec3(objToCam, -FakeEyePos[SG_X] + refpos[SG_X], -FakeEyePos[SG_Z] + refpos[SG_Z], -FakeEyePos[SG_Y] + refpos[SG_Y]);
543 sgNormaliseVec3(objToCam);
545 angle2 = sgScalarProductVec3(objToCamProj, objToCam);
546 if( (angle2 < 0.9999f) && (angle2 > -0.9999f) ) {
547 angle2 = -acos(angle2) * 180.0f / SG_PI;
548 if( objToCam[2] > 0.0f )
559 // draw a cloud but this time we use the impostor texture
560 void SGNewCloud::RenderBB(sgVec3 deltaPos, float angleY, float angleX, float dist_center) {
561 // TODO:glrotate is not needed
563 glTranslatef(center[SG_X] - deltaPos[SG_X], center[SG_Z] - deltaPos[SG_Z], center[SG_Y] - deltaPos[SG_Y]);
564 glRotatef(angleY, 0.0f, 0.0f, 1.0f);
565 glRotatef(angleX, 1.0f, 0.0f, 0.0f);
567 // blend clouds with sky based on distance to limit the contrast of distant cloud
568 float CloudVisFade = (1.5 * SGCloudField::get_CloudVis());
570 float t = 1.0f - dist_center / CloudVisFade;
571 // err the alpha value is not good for impostor, debug that
576 glColor4f(t, t, t, t);
579 glTexCoord2f(0.0f, 1.0f);
581 glTexCoord2f(1.0f, 1.0f);
583 glTexCoord2f(1.0f, 0.0f);
585 glTexCoord2f(0.0f, 0.0f);
590 int age = cldCache->queryImpostorAge(bbId);
591 // draw a red border for the newly generated BBs else draw a white border
597 glBindTexture(GL_TEXTURE_2D, 0);
598 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
605 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
613 // determine if it is a good idea to use an impostor to render the cloud
614 bool SGNewCloud::isBillboardable(float dist) {
616 if( dist <= ( 2.1f * radius ) ) {
620 if( (dist-radius) <= nearRadius ) {
621 // near clouds we don't want to use BB
629 // render the cloud, fakepos is a relative position inside the cloud field
630 void SGNewCloud::Render(sgVec3 FakeEyePos) {
635 sgCopyVec3( deltaPos, FakeEyePos);
636 sgSubVec3( dist, center, FakeEyePos);
637 float dist_center = sgLengthVec3(dist);
641 if( !isBillboardable(dist_center) ) {
642 // not a good candidate for impostors, draw a real cloud
643 Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
646 // lets use our impostor
648 texID = cldCache->QueryTexID(cloudId, bbId);
650 // ok someone took our impostor, so allocate a new one
652 // allocate a new Impostor
653 bbId = cldCache->alloc(cloudId);
654 texID = cldCache->QueryTexID(cloudId, bbId);
657 // no more free texture in the pool
658 Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
660 float angleX, angleY;
661 CalcAngles(center, FakeEyePos, &angleY, &angleX);
662 if( ! cldCache->isBbValid( cloudId, bbId, angleY, angleX) ) {
663 // we must build or rebuild this billboard
664 // start render to texture
665 cldCache->beginCapture();
666 // set transformation matrices
667 cldCache->setRadius(radius, dist_center);
668 gluLookAt(FakeEyePos[SG_X], FakeEyePos[SG_Z], FakeEyePos[SG_Y], center[SG_X], center[SG_Z], center[SG_Y], 0.0, 0.0, 1.0);
670 Render3Dcloud(true, FakeEyePos, deltaPos, dist_center);
671 // save rotation angles for later use
672 // TODO:this is not ok
673 cldCache->setReference(cloudId, bbId, angleY, angleX);
674 // save the rendered cloud into the cache
675 cldCache->setTextureData( bbId );
676 // finish render to texture and go back into standard context
677 cldCache->endCapture();
679 // draw the newly built BB or an old one
680 glBindTexture(GL_TEXTURE_2D, texID);
681 RenderBB(deltaPos, angleY, angleX, dist_center);