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_random.h>
32 #include <simgear/misc/sg_path.hxx>
34 #include STL_ALGORITHM
37 #include "cloudfield.hxx"
38 #include "newcloud.hxx"
44 static ssgTexture *cloudTextures[SGNewCloud::CLTexture_max];
47 bool SGNewCloud::useAnisotropic = true;
48 SGBbCache *SGNewCloud::cldCache = 0;
49 static bool texturesLoaded = false;
50 float SGNewCloud::nearRadius = 3500.0f;
51 bool SGNewCloud::lowQuality = false;
52 sgVec3 SGNewCloud::sunlight = {0.5f, 0.5f, 0.5f};
53 sgVec3 SGNewCloud::ambLight = {0.5f, 0.5f, 0.5f};
54 sgVec3 SGNewCloud::modelSunDir = {0,1,0};
58 SGNewCloud::SGNewCloud() :
61 minx(999), miny(999), minz(999), maxx(-999), maxy(-999), maxz(-999)
65 sgSetVec3(center, 0.0f, 0.0f, 0.0f);
66 sgSetVec3(cloudpos, 0.0f, 0.0f, 0.0f);
67 list_spriteContainer.reserve(8);
68 list_spriteDef.reserve(40);
69 // if( ! texturesLoaded ) {}
71 cldCache = new SGBbCache;
76 SGNewCloud::~SGNewCloud() {
77 list_spriteDef.clear();
78 list_spriteContainer.clear();
79 cldCache->free( bbId, cloudId );
83 // load all textures used to draw cloud sprites
84 void SGNewCloud::loadTextures(const string &tex_path) {
87 texturesLoaded = true;
91 cloud_path.set(tex_path);
92 cloud_path.append("cl_cumulus.rgb");
93 cloudTextures[ CLTexture_cumulus ] = new ssgTexture( cloud_path.str().c_str(), false, false, false );
94 cloudTextures[ CLTexture_cumulus ]->ref();
96 cloud_path.set(tex_path);
97 cloud_path.append("cl_stratus.rgb");
98 cloudTextures[ CLTexture_stratus ] = new ssgTexture( cloud_path.str().c_str(), false, false, false );
99 cloudTextures[ CLTexture_stratus ]->ref();
103 void SGNewCloud::startFade(bool direction, float duration, float pauseLength) {
105 void SGNewCloud::setFade(float howMuch) {
109 static float rayleighCoeffAngular(float cosAngle) {
110 return 3.0f / 4.0f * (1.0f + cosAngle * cosAngle);
113 // cp is normalized (len==1)
114 static void CartToPolar3d(sgVec3 cp, sgVec3 polar) {
115 polar[0] = atan2(cp[1], cp[0]);
116 polar[1] = SG_PI / 2.0f - atan2(sqrtf (cp[0] * cp[0] + cp[1] * cp[1]), cp[2]);
120 static void PolarToCart3d(sgVec3 p, sgVec3 cart) {
121 float tmp = cos(p[1]);
122 cart[0] = cos(p[0]) * tmp;
123 cart[1] = sin(p[0]) * tmp;
128 // compute the light for a cloud sprite corner
129 // from the normal and the sun, scaled by the Rayleigh factor
130 // and finaly added to the ambient light
131 static void lightFunction(sgVec3 normal, sgVec4 light, float pf) {
132 float cosAngle = sgScalarProductVec3( normal, SGNewCloud::modelSunDir);
133 float vl = (1.0f - 0.1f + cosAngle / 10.0f) * pf;
134 sgScaleVec3( light, SGNewCloud::sunlight, vl );
135 sgAddVec3( light, SGNewCloud::ambLight );
136 // we need to clamp or else the light will bug when adding transparency
137 if( light[0] > 1.0 ) light[0] = 1.0;
138 if( light[1] > 1.0 ) light[1] = 1.0;
139 if( light[2] > 1.0 ) light[2] = 1.0;
143 // compute the light for a cloud sprite
144 // we use ambient light and orientation versus sun position
145 // TODO:check sun pos and check code
146 void SGNewCloud::computeSimpleLight(sgVec3 FakeEyePos) {
147 // constant Rayleigh factor if we are not doing Anisotropic lighting
149 const float ang = 45.0f * SG_PI / 180.0f;
150 list_of_spriteDef::iterator iSprite;
151 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
152 if( useAnisotropic ) {
154 sgSubVec3(eyeDir, iSprite->pos, FakeEyePos);
155 sgNormaliseVec3(eyeDir);
156 float cosAngle = sgScalarProductVec3(eyeDir, modelSunDir);
157 pf = rayleighCoeffAngular(cosAngle);
159 // compute the vector going from the container box center to the sprite
160 // TODO : this is a constant except for cloudpos, compute the normal in setpos function
162 spriteContainer *thisSpriteContainer = &list_spriteContainer[iSprite->box];
163 sgSubVec3(normal, iSprite->pos, thisSpriteContainer->pos);
164 sgSubVec3(normal, thisSpriteContainer->center);
165 sgSubVec3(normal, cloudpos);
166 sgNormaliseVec3(normal);
168 // juste use the traditional normal to compute some lightning
170 lightFunction(normal, centerColor, pf);
171 sgCopyVec4(iSprite->l0, centerColor);
172 sgCopyVec4(iSprite->l1, centerColor);
173 sgCopyVec4(iSprite->l2, centerColor);
174 sgCopyVec4(iSprite->l3, centerColor);
177 // use exotic lightning function, this will give more 'relief' to the clouds
178 // compute a normal for each vextex this will simulate a smooth shading for a round shape
179 sgVec3 polar, cart, pt;
180 // I suspect this code to be bugged...
181 CartToPolar3d(normal, polar);
183 // offset the normal vector by some angle for each vertex
184 sgSetVec3(pt, polar[0] - ang, polar[1] - ang, polar[2]);
185 PolarToCart3d(pt, cart);
186 lightFunction(cart, iSprite->l0, pf);
187 sgSetVec3(pt, polar[0] + ang, polar[1] - ang, polar[2]);
188 PolarToCart3d(pt, cart);
189 lightFunction(cart, iSprite->l1, pf);
190 sgSetVec3(pt, polar[0] + ang, polar[1] + ang, polar[2]);
191 PolarToCart3d(pt, cart);
192 lightFunction(cart, iSprite->l2, pf);
193 sgSetVec3(pt, polar[0] - ang, polar[1] + ang, polar[2]);
194 PolarToCart3d(pt, cart);
195 lightFunction(cart, iSprite->l3, pf);
201 // add a new box to the cloud
202 void SGNewCloud::addContainer (float x, float y, float z, float r, CLbox_type type) {
203 spriteContainer cont;
204 sgSetVec3( cont.pos, x, y, z );
206 cont.cont_type = type;
207 sgSetVec3( cont.center, 0.0f, 0.0f, 0.0f);
208 list_spriteContainer.push_back( cont );
211 // add a sprite inside a box
212 void SGNewCloud::addSprite(float x, float y, float z, float r, CLbox_type type, int box) {
213 spriteDef newSpriteDef;
214 int rank = list_spriteDef.size();
215 sgSetVec3( newSpriteDef.pos, x, y, z);
216 newSpriteDef.box = box;
217 newSpriteDef.sprite_type = type;
218 newSpriteDef.rank = rank;
220 list_spriteDef.push_back( newSpriteDef );
221 spriteContainer *thisBox = &list_spriteContainer[box];
223 sgSubVec3( deltaPos, newSpriteDef.pos, thisBox->pos );
224 sgAddVec3( thisBox->center, deltaPos );
226 r = r * 0.6f; // 0.5 * 1.xxx
242 // return a random number between -n/2 and n/2
243 static float Rnd(float n) {
244 return n * (-0.5f + sg_random());
247 // generate all sprite with defined boxes
248 void SGNewCloud::genSprites( void ) {
251 N = list_spriteContainer.size();
252 for(int i = 0 ; i < N ; i++ ) {
253 spriteContainer *thisBox = & list_spriteContainer[i];
254 // the type defines how the sprites can be positioned inside the box, their size, etc
255 switch(thisBox->cont_type) {
257 for( sc = 0 ; sc <= 4 ; sc ++ ) {
258 r = thisBox->r + Rnd(0.2f);
259 x = thisBox->pos[SG_X] + Rnd(thisBox->r);
260 y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.2f);
261 z = thisBox->pos[SG_Z] + Rnd(thisBox->r);
262 addSprite(x, y, z, r, thisBox->cont_type, i);
268 x = thisBox->pos[SG_X];
269 y = thisBox->pos[SG_Y];
270 z = thisBox->pos[SG_Z];
271 addSprite(x, y, z, r, thisBox->cont_type, i);
274 for( sc = 0 ; sc <= 4 ; sc ++ ) {
275 r = thisBox->r + Rnd(0.2f);
276 x = thisBox->pos[SG_X] + Rnd(thisBox->r * 0.75f);
277 y = thisBox->pos[SG_Y] + Rnd(thisBox->r * 0.5f);
278 z = thisBox->pos[SG_Z] + Rnd(thisBox->r * 0.75f);
279 if ( y < thisBox->pos[SG_Y] - thisBox->r / 10.0f )
280 y = thisBox->pos[SG_Y] - thisBox->r / 10.0f;
281 addSprite(x, y, z, r, thisBox->cont_type, i);
285 for( sc = 0 ; sc <= 4 ; sc ++ ) {
286 r = thisBox->r + Rnd(0.2f);
287 x = thisBox->pos[SG_X] + Rnd(thisBox->r);
288 y = thisBox->pos[SG_Y] + Rnd(thisBox->r);
289 z = thisBox->pos[SG_Z] + Rnd(thisBox->r);
290 addSprite(x, y, z, r, thisBox->cont_type, i);
294 sgScaleVec3(thisBox->center, 1.0f / sc);
297 radius = maxx - minx;
298 if ( (maxy - miny) > radius )
299 radius = (maxy - miny);
300 if ( (maxz - minz) > radius )
301 radius = (maxz - minz);
303 sgSetVec3( center, (maxx + minx) / 2.0f, (maxy + miny) / 2.0f, (maxz + minz) / 2.0f );
306 ' fadingrank = UBound(tbSpriteDef()) * 10
308 // TODO : compute initial sprite normals for lighting function
312 // definition of a cu cloud, only for testing
313 void SGNewCloud::new_cu(void) {
315 float r = Rnd(1.0) + 0.5;
317 addContainer(0.0f, 0.0f, 0.0f, s, CLbox_cumulus);
318 addContainer(s, 0, 0, s, CLbox_cumulus);
319 addContainer(0, 0, 2 * s, s, CLbox_cumulus);
320 addContainer(s, 0, 2 * s, s, CLbox_cumulus);
322 addContainer(-1.2f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
323 addContainer(0.2f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
324 addContainer(1.6f * s, 0.2f * s, s, s * 1.4f, CLbox_cumulus);
325 } else if ( r < 0.90f ) {
326 addContainer(0, 0, 0, s * 1.2, CLbox_cumulus);
327 addContainer(s, 0, 0, s, CLbox_cumulus);
328 addContainer(0, 0, s, s, CLbox_cumulus);
329 addContainer(s * 1.1, 0, s, s * 1.2, CLbox_cumulus);
331 addContainer(-1.2 * s, 1 + 0.2 * s, s * 0.5, s * 1.4, CLbox_standard);
332 addContainer(0.2 * s, 1 + 0.25 * s, s * 0.5, s * 1.5, CLbox_standard);
333 addContainer(1.6 * s, 1 + 0.2 * s, s * 0.5, s * 1.4, CLbox_standard);
338 addContainer(0, 0, 0, s, CLbox_cumulus);
339 addContainer(0, 0, s, s, CLbox_cumulus);
340 addContainer(s, 0, s, s, CLbox_cumulus);
341 addContainer(s, 0, 0, s, CLbox_cumulus);
343 addContainer(s / 2, s, s / 2, s * 1.5, CLbox_standard);
345 addContainer(0, 2 * s, 0, s, CLbox_standard);
346 addContainer(0, 2 * s, s, s, CLbox_standard);
347 addContainer(s, 2 * s, s, s, CLbox_standard);
348 addContainer(s, 2 * s, 0, s, CLbox_standard);
355 // define the new position of the cloud (inside the cloud field, not on sphere)
356 void SGNewCloud::SetPos(sgVec3 newPos) {
357 int N = list_spriteDef.size();
359 sgSubVec3( deltaPos, newPos, cloudpos );
362 for(int i = 0 ; i < N ; i ++) {
363 sgAddVec3( list_spriteDef[i].pos, deltaPos );
365 sgAddVec3( center, deltaPos );
366 sgSetVec3( cloudpos, newPos[SG_X], newPos[SG_Y], newPos[SG_Z]);
367 // TODO : recompute sprite normal so we don't have to redo that each frame
373 void SGNewCloud::drawContainers() {
380 // sort on distance to eye because of transparency
381 void SGNewCloud::sortSprite( sgVec3 eye ) {
382 list_of_spriteDef::iterator iSprite;
384 // compute distance from sprite to eye
385 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
387 sgSubVec3( dist, iSprite->pos, eye );
388 iSprite->dist = -(dist[0]*dist[0] + dist[1]*dist[1] + dist[2]*dist[2]);
390 std::sort( list_spriteDef.begin(), list_spriteDef.end() );
393 // render the cloud on screen or on the RTT texture to build the impostor
394 void SGNewCloud::Render3Dcloud( bool drawBB, sgVec3 FakeEyePos, sgVec3 deltaPos, float dist_center ) {
396 /* int clrank = fadingrank / 10;
397 int clfadeinrank = fadingrank - clrank * 10;*/
398 float CloudVisFade = 1.0 / (1.5 * SGCloudField::get_CloudVis());
400 computeSimpleLight( FakeEyePos );
402 // view point sort, we sort because of transparency
403 sortSprite( FakeEyePos );
405 GLint previousTexture = -1, thisTexture;
406 list_of_spriteDef::iterator iSprite;
407 for( iSprite = list_spriteDef.begin() ; iSprite != list_spriteDef.end() ; iSprite++ ) {
408 // choose texture to use depending on sprite type
409 switch(iSprite->sprite_type) {
411 thisTexture = CLTexture_stratus;
414 thisTexture = CLTexture_cumulus;
417 // in practice there is no texture switch (atm)
418 if( previousTexture != thisTexture ) {
419 previousTexture = thisTexture;
420 glBindTexture(GL_TEXTURE_2D, cloudTextures[thisTexture]->getHandle());
425 sgCopyVec3( translate, iSprite->pos);
426 sgSubVec3( translate, iSprite->pos, deltaPos );
429 sgSubVec3( translate, iSprite->pos, deltaPos);
432 // flipx and flipy are random texture flip flags, this gives more random clouds
433 float flipx = (float) ( iSprite->rank & 1 );
434 float flipy = (float) ( (iSprite->rank >> 1) & 1 );
435 // cu texture have a flat bottom so we can't do a vertical flip
436 if( iSprite->sprite_type == CLbox_cumulus || iSprite->sprite_type == CLbox_stratus )
438 if( iSprite->sprite_type == CLbox_stratus )
440 // adjust colors depending on cloud type
441 // TODO : rewrite that later, still experimental
442 switch(iSprite->sprite_type) {
445 sgScaleVec3(iSprite->l0, 0.6f);
446 sgScaleVec3(iSprite->l1, 0.6f);
450 sgScaleVec3(iSprite->l0, 0.8f);
451 sgScaleVec3(iSprite->l1, 0.8f);
452 sgScaleVec3(iSprite->l2, 0.8f);
453 sgScaleVec3(iSprite->l3, 0.8f);
456 // darker bottom than top
457 sgScaleVec3(iSprite->l0, 0.8f);
458 sgScaleVec3(iSprite->l1, 0.8f);
461 float r = iSprite->r * 0.5f;
463 sgVec4 l0, l1, l2, l3;
464 sgCopyVec4 ( l0, iSprite->l0 );
465 sgCopyVec4 ( l1, iSprite->l1 );
466 sgCopyVec4 ( l2, iSprite->l2 );
467 sgCopyVec4 ( l3, iSprite->l3 );
469 // blend clouds with sky based on distance to limit the contrast of distant cloud
470 float t = 1.0f - dist_center * CloudVisFade;
472 t = 0.0f; // no, it should have been culled
473 // now clouds at the far plane are half blended
474 sgScaleVec4( l0, t );
475 sgScaleVec4( l1, t );
476 sgScaleVec4( l2, t );
477 sgScaleVec4( l3, t );
479 // compute the rotations so that the quad is facing the camera
481 sgSetVec3( pos, translate[SG_X], translate[SG_Z], translate[SG_Y] );
482 sgCopyVec3( translate, pos );
483 sgNormaliseVec3( translate );
484 sgVec3 x, y, up = {0.0f, 0.0f, 1.0f};
485 sgVectorProductVec3(x, translate, up);
488 sgVectorProductVec3(y, x, translate);
494 sgSetVec3( left, iSprite->pos[SG_X], iSprite->pos[SG_Z], iSprite->pos[SG_Y]);
496 sgCopyVec3( left, pos );
498 sgAddVec3 (right, left, x);
503 glTexCoord2f(flipx, 1.0f - flipy);
506 glTexCoord2f(1.0f - flipx, 1.0f - flipy);
508 sgScaleVec3( y, 2.0 );
510 sgAddVec3( right, y);
512 glTexCoord2f(1.0f - flipx, flipy);
515 glTexCoord2f(flipx, flipy);
524 // compute rotations so that a quad is facing the camera
525 // TODO:change obsolete code because we dont use glrotate anymore
526 void SGNewCloud::CalcAngles(sgVec3 refpos, sgVec3 FakeEyePos, float *angleY, float *angleX) {
527 sgVec3 upAux, lookAt, objToCamProj, objToCam;
530 sgSetVec3(objToCamProj, -FakeEyePos[SG_X] + refpos[SG_X], -FakeEyePos[SG_Z] + refpos[SG_Z], 0.0f);
531 sgNormaliseVec3(objToCamProj);
533 sgSetVec3(lookAt, 0.0f, 1.0f, 0.0f);
534 sgVectorProductVec3(upAux, lookAt, objToCamProj);
535 angle = sgScalarProductVec3(lookAt, objToCamProj);
536 if( (angle < 0.9999f) && (angle > -0.9999f) ) {
537 angle = acos(angle) * 180.0f / SG_PI;
538 if( upAux[2] < 0.0f )
543 sgSetVec3(objToCam, -FakeEyePos[SG_X] + refpos[SG_X], -FakeEyePos[SG_Z] + refpos[SG_Z], -FakeEyePos[SG_Y] + refpos[SG_Y]);
544 sgNormaliseVec3(objToCam);
546 angle2 = sgScalarProductVec3(objToCamProj, objToCam);
547 if( (angle2 < 0.9999f) && (angle2 > -0.9999f) ) {
548 angle2 = -acos(angle2) * 180.0f / SG_PI;
549 if( objToCam[2] > 0.0f )
560 // draw a cloud but this time we use the impostor texture
561 void SGNewCloud::RenderBB(sgVec3 deltaPos, float angleY, float angleX, float dist_center) {
562 // TODO:glrotate is not needed
564 glTranslatef(center[SG_X] - deltaPos[SG_X], center[SG_Z] - deltaPos[SG_Z], center[SG_Y] - deltaPos[SG_Y]);
565 glRotatef(angleY, 0.0f, 0.0f, 1.0f);
566 glRotatef(angleX, 1.0f, 0.0f, 0.0f);
568 // blend clouds with sky based on distance to limit the contrast of distant cloud
569 float CloudVisFade = (1.5 * SGCloudField::get_CloudVis());
571 float t = 1.0f - dist_center / CloudVisFade;
572 // err the alpha value is not good for impostor, debug that
577 glColor4f(t, t, t, t);
580 glTexCoord2f(0.0f, 1.0f);
582 glTexCoord2f(1.0f, 1.0f);
584 glTexCoord2f(1.0f, 0.0f);
586 glTexCoord2f(0.0f, 0.0f);
591 int age = cldCache->queryImpostorAge(bbId);
592 // draw a red border for the newly generated BBs else draw a white border
598 glBindTexture(GL_TEXTURE_2D, 0);
599 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
606 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
614 // determine if it is a good idea to use an impostor to render the cloud
615 bool SGNewCloud::isBillboardable(float dist) {
617 if( dist <= ( 2.1f * radius ) ) {
621 if( (dist-radius) <= nearRadius ) {
622 // near clouds we don't want to use BB
630 // render the cloud, fakepos is a relative position inside the cloud field
631 void SGNewCloud::Render(sgVec3 FakeEyePos) {
636 sgCopyVec3( deltaPos, FakeEyePos);
637 sgSubVec3( dist, center, FakeEyePos);
638 float dist_center = sgLengthVec3(dist);
642 if( !isBillboardable(dist_center) ) {
643 // not a good candidate for impostors, draw a real cloud
644 Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
647 // lets use our impostor
649 texID = cldCache->QueryTexID(cloudId, bbId);
651 // ok someone took our impostor, so allocate a new one
653 // allocate a new Impostor
654 bbId = cldCache->alloc(cloudId);
655 texID = cldCache->QueryTexID(cloudId, bbId);
658 // no more free texture in the pool
659 Render3Dcloud(false, FakeEyePos, deltaPos, dist_center);
661 float angleX, angleY;
662 CalcAngles(center, FakeEyePos, &angleY, &angleX);
663 if( ! cldCache->isBbValid( cloudId, bbId, angleY, angleX) ) {
664 // we must build or rebuild this billboard
665 // start render to texture
666 cldCache->beginCapture();
667 // set transformation matrices
668 cldCache->setRadius(radius, dist_center);
669 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);
671 Render3Dcloud(true, FakeEyePos, deltaPos, dist_center);
672 // save rotation angles for later use
673 // TODO:this is not ok
674 cldCache->setReference(cloudId, bbId, angleY, angleX);
675 // save the rendered cloud into the cache
676 cldCache->setTextureData( bbId );
677 // finish render to texture and go back into standard context
678 cldCache->endCapture();
680 // draw the newly built BB or an old one
681 glBindTexture(GL_TEXTURE_2D, texID);
682 RenderBB(deltaPos, angleY, angleX, dist_center);