]> git.mxchange.org Git - simgear.git/blob - simgear/scene/sky/clouds3d/SkyCloud.cpp
Initial revision.
[simgear.git] / simgear / scene / sky / clouds3d / SkyCloud.cpp
1 //------------------------------------------------------------------------------
2 // File : SkyCloud.cpp
3 //------------------------------------------------------------------------------
4 // SkyWorks : Copyright 2002 Mark J. Harris and
5 //                                              The University of North Carolina at Chapel Hill
6 //------------------------------------------------------------------------------
7 // Permission to use, copy, modify, distribute and sell this software and its 
8 // documentation for any purpose is hereby granted without fee, provided that 
9 // the above copyright notice appear in all copies and that both that copyright 
10 // notice and this permission notice appear in supporting documentation. 
11 // Binaries may be compiled with this software without any royalties or 
12 // restrictions. 
13 //
14 // The author(s) and The University of North Carolina at Chapel Hill make no 
15 // representations about the suitability of this software for any purpose. 
16 // It is provided "as is" without express or 
17 // implied warranty.
18 /**
19  * @file SkyCloud.cpp
20  * 
21  * Implementation of class SkyCloud.
22  */
23
24 // warning for truncation of template name for browse info
25 #pragma warning( disable : 4786)
26
27 //#include "glvu.hpp"
28 #include "SkyCloud.hpp"
29 #include "SkyRenderableInstance.hpp" 
30 #include "SkyContext.hpp"
31 #include "SkyMaterial.hpp"
32 #include "SkyLight.hpp"
33 #include "SkyTextureManager.hpp"
34 #include "SkySceneManager.hpp"
35 #include <algorithm>
36
37 //! The version used for cloud archive files.
38 #define CLOUD_ARCHIVE_VERSION 0.1f
39
40 //------------------------------------------------------------------------------
41 // Static initialization
42 //------------------------------------------------------------------------------
43 SkyMaterial*  SkyCloud::s_pMaterial         = NULL;
44 SkyMaterial*  SkyCloud::s_pShadeMaterial    = NULL;
45 unsigned int  SkyCloud::s_iShadeResolution  = 32;
46 float         SkyCloud::s_rAlbedo           = 0.9f;
47 float         SkyCloud::s_rExtinction       = 80.0f;
48 float         SkyCloud::s_rTransparency     = exp(-s_rExtinction);
49 float         SkyCloud::s_rScatterFactor    = s_rAlbedo * s_rExtinction * SKY_INV_4PI;
50 float         SkyCloud::s_rSortAngleErrorTolerance = 0.8f;
51 float         SkyCloud::s_rSortSquareDistanceTolerance = 100;
52
53 //------------------------------------------------------------------------------
54 // Function               : SkyCloud::SkyCloud
55 // Description      : 
56 //------------------------------------------------------------------------------
57 /**
58  * @fn SkyCloud::SkyCloud()
59  * @brief Constructor.
60  */ 
61 SkyCloud::SkyCloud()
62 : SkyRenderable(),
63   _bUsePhaseFunction(true),
64   _vecLastSortViewDir(Vec3f::ZERO),
65   _vecLastSortCamPos(Vec3f::ZERO)
66 {
67   if (!s_pShadeMaterial)
68   {
69     s_pShadeMaterial = new SkyMaterial;
70     s_pShadeMaterial->SetAmbient(Vec4f(0.1f, 0.1f, 0.1f, 1));
71     s_pShadeMaterial->EnableDepthTest(false);
72     s_pShadeMaterial->SetBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
73     s_pShadeMaterial->EnableBlending(true);
74     s_pShadeMaterial->SetAlphaFunc(GL_GREATER);
75     s_pShadeMaterial->SetAlphaRef(0);
76     s_pShadeMaterial->EnableAlphaTest(true);
77     s_pShadeMaterial->SetColorMaterialMode(GL_DIFFUSE);
78     s_pShadeMaterial->EnableColorMaterial(true);
79     s_pShadeMaterial->EnableLighting(false);
80     s_pShadeMaterial->SetTextureApplicationMode(GL_MODULATE);
81   }
82   if (!s_pMaterial)
83   {
84     s_pMaterial = new SkyMaterial;
85     s_pMaterial->SetAmbient(Vec4f(0.3f, 0.3f, 0.3f, 1));
86     s_pMaterial->SetDepthMask(false);
87     s_pMaterial->SetBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
88     s_pMaterial->EnableBlending(true);
89     s_pMaterial->SetAlphaFunc(GL_GREATER);
90     s_pMaterial->SetAlphaRef(0);
91     s_pMaterial->EnableAlphaTest(true);
92     s_pMaterial->SetColorMaterialMode(GL_DIFFUSE);
93     s_pMaterial->EnableColorMaterial(true);
94     s_pMaterial->EnableLighting(false);
95     s_pMaterial->SetTextureApplicationMode(GL_MODULATE);
96     _CreateSplatTexture(32); // will assign the texture to both static materials
97   }
98 }
99
100
101 //------------------------------------------------------------------------------
102 // Function               : SkyCloud::~SkyCloud
103 // Description      : 
104 //------------------------------------------------------------------------------
105 /**
106  * @fn SkyCloud::~SkyCloud()
107  * @brief Destructor.
108  */ 
109 SkyCloud::~SkyCloud()
110 {
111 }
112
113
114 //------------------------------------------------------------------------------
115 // Function               : SkyCloud::Update
116 // Description      : 
117 //------------------------------------------------------------------------------
118 /**
119 * @fn SkyCloud::Update(const Camera &cam, SkyRenderableInstance* pInstance)
120 * @brief Currently does nothing.
121 */ 
122 SKYRESULT SkyCloud::Update(const Camera &cam, SkyRenderableInstance* pInstance)
123 {
124   return SKYRESULT_OK;
125 }
126
127
128
129 //------------------------------------------------------------------------------
130 // Function               : DrawQuad
131 // Description      : 
132 //------------------------------------------------------------------------------
133 /**
134  * @fn DrawQuad(Vec3f pos, Vec3f x, Vec3f y, Vec4f color)
135  * @brief Draw a quad.
136  */ 
137 inline void DrawQuad(Vec3f pos, Vec3f x, Vec3f y, Vec4f color)
138 {
139   glColor4fv(&(color.x));
140   Vec3f left  = pos;  left   -= y; 
141   Vec3f right = left; right  += x; 
142   left  -= x;
143   glTexCoord2f(0, 0); glVertex3fv(&(left.x));
144   glTexCoord2f(1, 0); glVertex3fv(&(right.x));
145   left  += y;  left  += y;
146   right += y;  right += y;
147   glTexCoord2f(1, 1); glVertex3fv(&(right.x));
148   glTexCoord2f(0, 1); glVertex3fv(&(left.x));
149 }
150
151
152 //------------------------------------------------------------------------------
153 // Function               : SkyCloud::Display
154 // Description      : 
155 //------------------------------------------------------------------------------
156 /**
157  * @fn SkyCloud::Display(const Camera &camera, SkyRenderableInstance *pInstance)
158  * @brief Renders the cloud.
159  * 
160  * The cloud is rendered by splatting the particles from back to front with respect
161  * to @a camera.  Since instances of clouds each have their own particles, which 
162  * are pre-transformed into world space, @a pInstance is not used.
163  *
164  * An alternative method is to store the particles untransformed, and transform the 
165  * camera and light into cloud space for rendering.  This is more complicated, 
166  * and not as straightforward.  Since I have to store the particles with each instance
167  * anyway, I decided to pre-transform them instead.
168  */ 
169 SKYRESULT SkyCloud::Display(const Camera &camera, SkyRenderableInstance *pInstance)
170 {
171   // copy the current camera
172   Camera cam(camera);
173   
174   // This cosine computation, along with the if() below, are an optimization.  The goal
175   // is to avoid sorting when it will make no visual difference.  This will be true when the 
176   // cloud particles are almost sorted for the current viewpoint.  This is the case most of the 
177   // time, since the viewpoint does not move very far in a single frame.  Each time we sort,
178   // we cache the current view direction.  Then, each time the cloud is displayed, if the 
179   // current view direction is very close to the current view direction (dot product is nearly 1)
180   // then we do not resort the particles.
181   float rCosAngleSinceLastSort = 
182       _vecLastSortViewDir * cam.ViewDir(); // dot product
183
184   float rSquareDistanceSinceLastSort = 
185     (cam.Orig - _vecLastSortCamPos).LengthSqr();
186
187   if (rCosAngleSinceLastSort < s_rSortAngleErrorTolerance || 
188       rSquareDistanceSinceLastSort > s_rSortSquareDistanceTolerance)
189   {
190     // compute the sort position for particles.
191     // don't just use the camera position -- if it is too far away from the cloud, then
192     // precision limitations may cause the STL sort to hang.  Instead, put the sort position
193     // just outside the bounding sphere of the cloud in the direction of the camera.
194     Vec3f vecSortPos = -cam.ViewDir();
195     vecSortPos *= (1.1 * _boundingBox.GetRadius());
196     
197     // sort the particles from back to front wrt the camera position.
198     _SortParticles(cam.ViewDir(), vecSortPos, SKY_CLOUD_SORT_TOWARD);
199
200     //_vecLastSortViewDir = GLVU::GetCurrent()->GetCurrentCam()->ViewDir();
201     //_vecLastSortCamPos = GLVU::GetCurrent()->GetCurrentCam()->Orig;
202     _vecLastSortViewDir = cam.ViewDir();
203     _vecLastSortCamPos = cam.Orig;
204   } 
205
206   // set the material state / properties that clouds use for rendering:
207   // Enables blending, with blend func (ONE, ONE_MINUS_SRC_ALPHA).
208   // Enables alpha test to discard completely transparent fragments.
209   // Disables depth test.
210   // Enables texturing, with modulation, and the texture set to the shared splat texture.
211   s_pMaterial->Activate();
212   
213   Vec4f color;
214   Vec3f eyeDir;
215
216   // Draw the particles using immediate mode.
217   glBegin(GL_QUADS);
218
219   int i = 0;
220   for (ParticleIterator iter = _particles.begin(); iter != _particles.end(); iter++)
221   {
222     i++;
223     SkyCloudParticle *p = *iter;
224
225     // Start with ambient light
226     color = p->GetBaseColor();
227     
228     if (_bUsePhaseFunction) // use the phase function for anisotropic scattering.
229     {\r 
230       eyeDir = cam.Orig;
231       eyeDir -= p->GetPosition();     
232       eyeDir.Normalize();
233       float pf;
234           
235       // add the color contribution to this particle from each light source, modulated by
236       // the phase function.  See _PhaseFunction() documentation for details.
237       for (int i = 0; i < p->GetNumLitColors(); i++)
238       {
239         pf = _PhaseFunction(_lightDirections[i], eyeDir);
240         // expand this to avoid temporary vector creation in the inner loop
241         color.x += p->GetLitColor(i).x * pf;
242         color.y += p->GetLitColor(i).y * pf;
243         color.z += p->GetLitColor(i).z * pf;
244       }
245     }
246     else  // just use isotropic scattering instead.
247     {   
248       for (int i = 0; i < (*iter)->GetNumLitColors(); ++i)
249       {
250         color += p->GetLitColor(i);
251       }
252     }
253     
254     // Set the transparency independently of the colors
255     color.w = 1 - s_rTransparency;
256     
257     // draw the particle as a textured billboard.
258     DrawQuad((*iter)->GetPosition(), cam.X * p->GetRadius(), cam.Y * p->GetRadius(), color);
259   }
260   glEnd();
261  
262   return SKYRESULT_OK;
263 }
264
265
266 //------------------------------------------------------------------------------
267 // Function               : SkyCloud::DisplaySplit
268 // Description      : 
269 //------------------------------------------------------------------------------
270 /**
271  * @fn SkyCloud::DisplaySplit(const Camera &camera, const Vec3f &vecSplitPoint, bool bBackHalf, SkyRenderableInstance *pInstance)
272  * @brief The same as Display(), except it displays only the particles in front of or behind the split point.
273  * 
274  * This is used to render clouds into two impostor images for displaying clouds that contain objects.
275  *
276  * @see SkyRenderableInstanceCloud
277  */ 
278 SKYRESULT SkyCloud::DisplaySplit(const Camera           &camera, 
279                                  const Vec3f            &vecSplitPoint,
280                                  bool                   bBackHalf,
281                                  SkyRenderableInstance  *pInstance /* = NULL */)
282 {
283   // copy the current camera
284   Camera cam(camera);
285
286   Vec3f vecCloudSpaceSplit = vecSplitPoint;
287
288   if (bBackHalf) // only sort when rendering the back half.  Reuse sort for front half.
289   {
290     // compute the sort position for particles.
291     // don't just use the camera position -- if it is too far away from the cloud, then
292     // precision limitations may cause the STL sort to hang.  Instead, put the sort position
293     // just outside the bounding sphere of the cloud in the direction of the camera.
294     _vecSortPos = -cam.ViewDir();
295     _vecSortPos *= (1.1 * _boundingBox.GetRadius());
296     
297     // sort the particles from back to front wrt the camera position.
298     _SortParticles(cam.ViewDir(), _vecSortPos, SKY_CLOUD_SORT_TOWARD);
299
300     // we can't use the view direction optimization when the cloud is split, or we get a lot
301     // of popping of objects in and out of cloud cover.  For consistency, though, we need to update
302     // the cached sort direction, since we just sorted the particles.
303     ///_vecLastSortViewDir = GLVU::GetCurrent()->GetCurrentCam()->ViewDir(); 
304
305     // compute the split distance.
306     vecCloudSpaceSplit  -= _vecSortPos;
307     _rSplitDistance = vecCloudSpaceSplit * cam.ViewDir();
308   }
309
310   // set the material state / properties that clouds use for rendering:
311   // Enables blending, with blend func (ONE, ONE_MINUS_SRC_ALPHA).
312   // Enables alpha test to discard completely transparent fragments.
313   // Disables depth test.
314   // Enables texturing, with modulation, and the texture set to the shared splat texture.
315   s_pMaterial->Activate();
316   
317   Vec4f color;
318   Vec3f eyeDir;
319
320   // Draw the particles using immediate mode.
321   glBegin(GL_QUADS);
322
323   // if bBackHalf is false, then we just continue where we left off.  If it is true, we
324   // reset the iterator to the beginning of the sorted list.
325   static ParticleIterator iter;
326   if (bBackHalf) 
327     iter = _particles.begin();
328   
329   // iterate over the particles and render them.
330   for (; iter != _particles.end(); ++iter)
331   {
332     SkyCloudParticle *p = *iter;
333
334     if (bBackHalf && (p->GetSquareSortDistance() < _rSplitDistance))
335       break;
336
337     // Start with ambient light
338     color = p->GetBaseColor();
339     
340     if (_bUsePhaseFunction) // use the phase function for anisotropic scattering.
341     {
342       eyeDir = cam.Orig;
343       eyeDir -= p->GetPosition();
344       eyeDir.Normalize();
345       float pf;
346           
347       // add the color contribution to this particle from each light source, modulated by
348       // the phase function.  See _PhaseFunction() documentation for details.
349       for (int i = 0; i < p->GetNumLitColors(); i++)
350       {
351         pf = _PhaseFunction(_lightDirections[i], eyeDir);
352         // expand this to avoid temporary vector creation in the inner loop
353         color.x += p->GetLitColor(i).x * pf;
354         color.y += p->GetLitColor(i).y * pf;
355         color.z += p->GetLitColor(i).z * pf;
356       }
357     }
358     else  // just use isotropic scattering instead.
359     {      
360       for (int i = 0; i < p->GetNumLitColors(); ++i)
361       {
362         color += p->GetLitColor(i);
363       }
364     }
365
366     // set the transparency independently of the colors.
367     color.w = 1 - s_rTransparency;
368     
369     // draw the particle as a textured billboard.
370     DrawQuad((*iter)->GetPosition(), cam.X * p->GetRadius(), cam.Y * p->GetRadius(), color);
371   }
372   glEnd();
373   
374   return SKYRESULT_OK;
375 }
376
377 //------------------------------------------------------------------------------
378 // Function               : SkyCloud::Illuminate
379 // Description      : 
380 //------------------------------------------------------------------------------
381 /**
382  * @fn SkyCloud::Illuminate(SkyLight *pLight, SkyRenderableInstance* pInstance, bool bReset)
383  * @brief Compute the illumination of the cloud by the lightsource @a pLight
384  * 
385  * This method uses graphics hardware to compute multiple forward scattering at each cloud
386  * in the cloud of light from the directional light source @a pLight.  The algorithm works
387  * by successively subtracting "light" from an initially white (fully lit) frame buffer by
388  * using hardware blending and read back.  The method stores the illumination from each light
389  * source passed to it separately at each particle, unless @a bReset is true, in which case
390  * the lists of illumination in the particles are reset before the lighting is computed.
391  * 
392  */ 
393 SKYRESULT SkyCloud::Illuminate(SkyLight *pLight, SkyRenderableInstance* pInstance, bool bReset)
394 {
395   int iOldVP[4];        
396
397   glGetIntegerv(GL_VIEWPORT, iOldVP);
398   glViewport(0, 0, s_iShadeResolution, s_iShadeResolution);
399
400   Vec3f vecDir(pLight->GetDirection());
401
402   // if this is the first pass through the lights, reset will be true, and the cached light 
403   // directions should be updated.  Light directions are cached in cloud space to accelerate 
404   // computation of the phase function, which depends on light direction and view direction.
405   if (bReset)
406     _lightDirections.clear();
407   _lightDirections.push_back(vecDir); // cache the (unit-length) light direction
408
409   // compute the light/sort position for particles from the light direction.
410   // don't just use the camera position -- if it is too far away from the cloud, then
411   // precision limitations may cause the STL sort to hang.  Instead, put the sort position
412   // just outside the bounding sphere of the cloud in the direction of the camera.
413   Vec3f vecLightPos(vecDir);
414   vecLightPos *= (1.1*_boundingBox.GetRadius());
415   vecLightPos += _boundingBox.GetCenter();
416
417   // Set up a camera to look at the cloud from the light position.  Since the sun is an infinite
418   // light source, this camera will use an orthographic projection tightly fit to the bounding
419   // sphere of the cloud.  
420   Camera cam;
421   
422   // Avoid degenerate camera bases.
423   Vec3f vecUp(0, 1, 0);
424   if (fabs(vecDir * vecUp) - 1 < 1e-6) // check that the view and up directions are not parallel.
425     vecUp.Set(1, 0, 0);
426   
427   cam.LookAt(vecLightPos, _boundingBox.GetCenter(), vecUp);
428
429   // sort the particles away from the light source.
430   _SortParticles(cam.ViewDir(), vecLightPos, SKY_CLOUD_SORT_AWAY);
431
432   // projected dist to cntr along viewdir
433   float DistToCntr = (_boundingBox.GetCenter() - vecLightPos) * cam.ViewDir();
434   
435   // calc tight-fitting near and far distances for the orthographic frustum
436   float rNearDist = DistToCntr - _boundingBox.GetRadius();
437   float rFarDist  = DistToCntr + _boundingBox.GetRadius();
438
439   // set the modelview matrix from this camera.
440   glMatrixMode(GL_MODELVIEW);
441   glPushMatrix();
442   float M[16];
443  
444  
445   cam.GetModelviewMatrix(M);
446   glLoadMatrixf(M);
447   
448   // switch to parallel projection
449   glMatrixMode(GL_PROJECTION);
450   glPushMatrix();
451   glLoadIdentity();
452   glOrtho(-_boundingBox.GetRadius(), _boundingBox.GetRadius(),
453           -_boundingBox.GetRadius(), _boundingBox.GetRadius(),
454           rNearDist, rFarDist);
455   
456   // set the material state / properties that clouds use for shading:
457   // Enables blending, with blend func (ONE, ONE_MINUS_SRC_ALPHA).
458   // Enables alpha test to discard completely transparent fragments.
459   // Disables depth test.
460   // Enables texturing, with modulation, and the texture set to the shared splat texture.
461   s_pShadeMaterial->Activate();
462
463   // these are used for projecting the particle position to determine where to read pixels.
464   double MM[16], PM[16];
465   int VP[4] = { 0, 0, s_iShadeResolution, s_iShadeResolution };
466   glGetDoublev(GL_MODELVIEW_MATRIX, MM);
467   glGetDoublev(GL_PROJECTION_MATRIX, PM);
468     
469   // initialize back buffer to all white -- modulation darkens areas where cloud particles
470   // absorb light, and lightens it where they scatter light in the forward direction.
471   glClearColor(1, 1, 1, 1);
472   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
473
474   float rPixelsPerLength = s_iShadeResolution / (2 * _boundingBox.GetRadius());
475
476   // the solid angle over which we will sample forward-scattered light.
477   float rSolidAngle = 0.09;
478   int i = 0;
479   int iNumFailed = 0;
480   for (ParticleIterator iter = _particles.begin(); iter != _particles.end(); ++iter, ++i)
481   {
482     Vec3f vecParticlePos = (*iter)->GetPosition();
483
484     Vec3f vecOffset(vecLightPos);
485     vecOffset -= vecParticlePos;
486    
487     // compute the pixel area to read back in order to integrate the illumination of the particle
488     // over a constant solid angle.
489     float rDistance  = fabs(cam.ViewDir() * vecOffset) - rNearDist;
490
491     float rArea      = rSolidAngle * rDistance * rDistance;
492     int   iPixelDim  = sqrt(rArea) * rPixelsPerLength;
493     int   iNumPixels = iPixelDim * iPixelDim;
494     if (iNumPixels < 1) 
495     {
496       iNumPixels = 1;
497       iPixelDim = 1;
498     }
499
500     // the scale factor to convert the read back pixel colors to an average illumination of the area.
501     float rColorScaleFactor = rSolidAngle / (iNumPixels * 255.0f);
502
503     unsigned char *c = new unsigned char[4 * iNumPixels];
504     
505     Vec3d vecWinPos;
506     
507     // find the position in the buffer to which the particle position projects.
508     if (!gluProject(vecParticlePos.x, vecParticlePos.y, vecParticlePos.z, 
509                     MM, PM, VP, 
510                     &(vecWinPos.x), &(vecWinPos.y), &(vecWinPos.z)))
511     {
512       FAIL_RETURN_MSG(SKYRESULT_FAIL, 
513                       "Error: SkyCloud::Illuminate(): failed to project particle position.");
514     }
515    
516     // offset the projected window position by half the size of the readback region.
517     vecWinPos.x -= 0.5 * iPixelDim;
518     if (vecWinPos.x < 0) vecWinPos.x = 0;
519     vecWinPos.y -= 0.5 * iPixelDim;
520     if (vecWinPos.y < 0) vecWinPos.y = 0;
521     
522     // read back illumination of this particle from the buffer.
523     glReadBuffer(GL_BACK);
524     glReadPixels(vecWinPos.x, vecWinPos.y, iPixelDim, iPixelDim, GL_RGBA, GL_UNSIGNED_BYTE, c);
525     
526     // scattering coefficient vector.
527     Vec4f vecScatter(s_rScatterFactor, s_rScatterFactor, s_rScatterFactor, 1);
528
529     // add up the read back pixels (only need one component -- its grayscale)
530     int iSum = 0;
531     for (int k = 0; k < 4 * iNumPixels; k+=4)
532       iSum += c[k];
533     delete [] c;
534     
535     // compute the amount of light scattered to this particle by particles closer to the light.
536     // this is the illumination over the solid angle that we measured (using glReadPixels) times
537     // the scattering coefficient (vecScatter);
538     Vec4f vecScatteredAmount(iSum * rColorScaleFactor, 
539                              iSum * rColorScaleFactor, 
540                              iSum * rColorScaleFactor, 
541                              1 - s_rTransparency);
542     vecScatteredAmount &= vecScatter;
543
544     // the color of th particle (iter) contributed by this light source (pLight) is the 
545     // scattered light from the part of the cloud closer to the light, times the diffuse color
546     // of the light source.  The alpha is 1 - the uniform transparency of all particles (modulated
547     // by the splat texture).
548     Vec4f vecColor = vecScatteredAmount;
549     vecColor      &= pLight->GetDiffuse();  
550     vecColor.w     = 1 - s_rTransparency;
551     
552     // add this color to the list of lit colors for the particle.  The contribution from each light
553     // is kept separate because the phase function we apply at runtime depends on the light vector
554     // for each light source separately.  This view-dependent effect is impossible without knowing 
555     // the amount of light contributed for each light.  This, of course, assumes the clouds will
556     // be lit by a reasonably small number of lights (The sun plus some simulation of light reflected
557     // from the sky and / or ground.) This technique works very well for simulating anisotropic 
558     // illumination by skylight.
559     if (bReset)
560     {
561       (*iter)->SetBaseColor(s_pMaterial->GetAmbient());  
562       (*iter)->ClearLitColors();
563       (*iter)->AddLitColor(vecColor);
564     }
565     else
566     {
567       (*iter)->AddLitColor(vecColor);
568     }
569     
570     // the following computation (scaling of the scattered amount by the phase function) is done
571     // after the lit color is stored so we don't add the scattering to this particle twice.
572     vecScatteredAmount *= 1.5; // rayleigh scattering phase function for angle of zero or 180 = 1.5!
573     
574     // clamp the color
575     if (vecScatteredAmount.x > 1) vecScatteredAmount.x = 1;
576     if (vecScatteredAmount.y > 1) vecScatteredAmount.y = 1;
577     if (vecScatteredAmount.z > 1) vecScatteredAmount.z = 1;
578     vecScatteredAmount.w = 1 - s_rTransparency;
579     
580     vecScatteredAmount.x = 0.50;  vecScatteredAmount.y = 0.60;  vecScatteredAmount.z = 0.70; 
581
582     // Draw the particle as a texture billboard.  Use the scattered light amount as the color to 
583     // simulate forward scattering of light by this particle.
584     glBegin(GL_QUADS);
585     DrawQuad(vecParticlePos, cam.X * (*iter)->GetRadius(), cam.Y * (*iter)->GetRadius(), vecScatteredAmount);
586     glEnd();
587
588     //glutSwapBuffers(); // Uncomment this swap buffers to visualize cloud illumination computation.
589   }
590   
591   // Note: here we could optionally store the current back buffer as a shadow image
592   // to be projected from the light position onto the scene.  This way we can have clouds shadow
593   // the environment.
594   
595   // restore matrix stack and viewport.
596   glMatrixMode(GL_PROJECTION);
597   glPopMatrix();
598   glMatrixMode(GL_MODELVIEW);
599   glPopMatrix();
600   glViewport(iOldVP[0], iOldVP[1], iOldVP[2], iOldVP[3]);
601
602   return SKYRESULT_OK; 
603 }
604
605
606 //------------------------------------------------------------------------------
607 // Function               : SkyCloud::CopyBoundingVolume
608 // Description      : 
609 //------------------------------------------------------------------------------
610 /**
611  * @fn SkyCloud::CopyBoundingVolume() const
612  * @brief Returns a new copy of the SkyMinMaxBox for this cloud.
613  */ 
614 SkyMinMaxBox* SkyCloud::CopyBoundingVolume() const
615 {
616   SkyMinMaxBox *pBox = new SkyMinMaxBox();
617   pBox->SetMax(_boundingBox.GetMax());
618   pBox->SetMin(_boundingBox.GetMin());
619   return pBox; 
620 }
621
622
623 //------------------------------------------------------------------------------
624 // Function               : SkyCloud::Load
625 // Description      : 
626 //------------------------------------------------------------------------------
627 /**
628  * @fn SkyCloud::Load(const SkyArchive &archive, float rScale, bool bLocal)
629  * @brief Loads the cloud data from @a archive.
630  * 
631  * If @a rScale does not equal 1.0, then the cloud is scaled by an amount rScale.
632  */ 
633 SKYRESULT SkyCloud::Load(const SkyArchive &archive, 
634                          float rScale /* = 1.0f */, 
635                          bool bLocal /* = false */)
636 {
637   unsigned int iNumParticles;
638   Vec3f vecCenter = Vec3f::ZERO;
639   //Vec3f vecCenter;
640   //float rRadius;
641   //archive.FindVec3f("CldCenter", &vecCenter);
642   //archive.FindFloat32("CldRadius", &rRadius);
643
644   //_boundingBox.SetMin(vecCenter - Vec3f(rRadius, rRadius, rRadius));
645   //_boundingBox.SetMax(vecCenter + Vec3f(rRadius, rRadius, rRadius));
646
647   archive.FindUInt32("CldNumParticles", &iNumParticles);
648   if (!bLocal)
649     archive.FindVec3f("CldCenter", &vecCenter);
650   
651   Vec3f *pParticlePositions = new Vec3f[iNumParticles];
652   float *pParticleRadii     = new float[iNumParticles];
653   Vec4f *pParticleColors    = new Vec4f[iNumParticles];
654
655   unsigned int iNumBytes;
656   archive.FindData("CldParticlePositions", ANY_TYPE, (void**const)&pParticlePositions, &iNumBytes);
657   archive.FindData("CldParticleRadii",     ANY_TYPE, (void**const)&pParticleRadii,     &iNumBytes);
658   archive.FindData("CldParticleColors",    ANY_TYPE, (void**const)&pParticleColors,    &iNumBytes);
659   
660   for (unsigned int i = 0; i < iNumParticles; ++i)
661   {
662     SkyCloudParticle *pParticle = new SkyCloudParticle((pParticlePositions[i] + vecCenter) * rScale,
663                                                        pParticleRadii[i] * rScale,
664                                                        pParticleColors[i]);
665     _boundingBox.AddPoint(pParticle->GetPosition());
666     
667     _particles.push_back(pParticle);
668   }
669   // this is just a bad hack to align cloud field from skyworks with local horizon at KSFO
670   // we need to develop out own scheme for loading and positioning clouds
671   Mat33f rot_mat;
672   Vec3f  moveit;
673   
674   //moveit.Set( -10000.0, 1500.0, 1500.0  );
675   moveit.Set( 0.0, 0.0, 1050.0  );
676   
677   rot_mat.Set( 1, 0, 0,
678                                                  0, 0, -1,
679                                                  0, 1, 0);
680   // flip the y and z axis
681   Rotate( rot_mat ); 
682     
683    // adjust for lon af KSFO            -122.357                                 
684   rot_mat.Set( -0.5352f, 0.8447f, 0.0f,
685                                                  -0.8447f, -0.5352f, 0.0f,
686                                                  -0.0f, 0.0f, 1.0f);
687                                                  
688   Rotate( rot_mat );
689
690    // and about x for latitude 37.6135
691   rot_mat.Set( 1.0f, 0.0, 0.0f,
692                                                  0.0f, 0.7921f, -0.6103f,
693                                                  0.0f, 0.6103f, 0.7921f);               
694    
695   Rotate( rot_mat ); 
696   
697   Translate( moveit );
698   
699   return SKYRESULT_OK;
700 }
701
702
703 //------------------------------------------------------------------------------
704 // Function               : SkyCloud::Save
705 // Description      : 
706 //------------------------------------------------------------------------------
707 /**
708 * @fn SkyCloud::Save(SkyArchive &archive) const
709 * @brief Saves the cloud data to @a archive.
710
711 * @todo <WRITE EXTENDED SkyCloud::Save FUNCTION DOCUMENTATION>
712 */ 
713 SKYRESULT SkyCloud::Save(SkyArchive &archive) const
714 {
715   SkyArchive myArchive("Cloud");
716   //myArchive.AddVec3f("CldCenter", _center);
717   //myArchive.AddFloat32("CldRadius", _boundingBox.GetRadius());
718   myArchive.AddUInt32("CldNumParticles", _particles.size());
719   
720   // make temp arrays
721   Vec3f *pParticlePositions = new Vec3f[_particles.size()];
722   float *pParticleRadii     = new float[_particles.size()];
723   Vec4f *pParticleColors    = new Vec4f[_particles.size()];
724   
725   unsigned int i = 0;
726
727   for (ParticleConstIterator iter = _particles.begin(); iter != _particles.end(); ++iter, ++i)
728   {
729     pParticlePositions[i] = (*iter)->GetPosition(); // position around origin
730     pParticleRadii[i]     = (*iter)->GetRadius();
731     pParticleColors[i]    = (*iter)->GetBaseColor();
732   }
733   
734   myArchive.AddData("CldParticlePositions", 
735                     ANY_TYPE, 
736                     pParticlePositions, 
737                     sizeof(Vec3f), 
738                     _particles.size());
739   
740   myArchive.AddData("CldParticleRadii", 
741                     ANY_TYPE, 
742                     pParticleRadii, 
743                     sizeof(float), 
744                     _particles.size());
745   
746   myArchive.AddData("CldParticleColors", 
747                     ANY_TYPE, 
748                     pParticleColors, 
749                     sizeof(Vec3f), 
750                     _particles.size());
751   
752   archive.AddArchive(myArchive);
753
754   return SKYRESULT_OK;
755 }
756
757
758 //------------------------------------------------------------------------------
759 // Function               : SkyCloud::Rotate
760 // Description      : 
761 //------------------------------------------------------------------------------
762 /**
763  * @fn SkyCloud::Rotate(const Mat33f& rot)
764  * @brief @todo <WRITE BRIEF SkyCloud::Rotate DOCUMENTATION>
765  * 
766  * @todo <WRITE EXTENDED SkyCloud::Rotate FUNCTION DOCUMENTATION>
767  */ 
768 void SkyCloud::Rotate(const Mat33f& rot)
769 {
770   _boundingBox.Clear();
771   for (int i = 0; i < _particles.size(); ++i)
772   {
773     _particles[i]->SetPosition(rot * _particles[i]->GetPosition());
774     _boundingBox.AddPoint(_particles[i]->GetPosition());
775   }
776 }
777
778
779 //------------------------------------------------------------------------------
780 // Function               : SkyCloud::Translate
781 // Description      : 
782 //------------------------------------------------------------------------------
783 /**
784  * @fn SkyCloud::Translate(const Vec3f& trans)
785  * @brief @todo <WRITE BRIEF SkyCloud::Translate DOCUMENTATION>
786  * 
787  * @todo <WRITE EXTENDED SkyCloud::Translate FUNCTION DOCUMENTATION>
788  */ 
789 void SkyCloud::Translate(const Vec3f& trans)
790 {
791   for (int i = 0; i < _particles.size(); ++i)
792   {
793     _particles[i]->SetPosition(_particles[i]->GetPosition() + trans);
794   }
795   _boundingBox.SetMax(_boundingBox.GetMax() + trans);
796   _boundingBox.SetMin(_boundingBox.GetMin() + trans);
797 }
798
799
800 //------------------------------------------------------------------------------
801 // Function               : SkyCloud::Scale
802 // Description      : 
803 //------------------------------------------------------------------------------
804 /**
805  * @fn SkyCloud::Scale(const float scale)
806  * @brief @todo <WRITE BRIEF SkyCloud::Scale DOCUMENTATION>
807  * 
808  * @todo <WRITE EXTENDED SkyCloud::Scale FUNCTION DOCUMENTATION>
809  */ 
810 void SkyCloud::Scale(const float scale)
811 {
812   _boundingBox.Clear();
813   for (int i = 0; i < _particles.size(); ++i)
814   {
815     _particles[i]->SetPosition(_particles[i]->GetPosition() * scale);
816     _boundingBox.AddPoint(_particles[i]->GetPosition());
817   }
818 }
819
820
821 //------------------------------------------------------------------------------
822 // Function               : SkyCloud::_SortParticles
823 // Description      : 
824 //------------------------------------------------------------------------------
825 /**
826  * @fn SkyCloud::_SortParticles(const Vec3f& vecViewDir, const Vec3f& sortPoint, SortDirection dir)
827  * @brief Sorts the cloud particles in the direction specified by @a dir.
828  * 
829  * @vecSortPoint is assumed to already be transformed into the basis space of the cloud.
830  */ 
831 void SkyCloud::_SortParticles(const Vec3f& vecViewDir,
832                               const Vec3f& vecSortPoint, 
833                               SortDirection dir)
834 {
835   Vec3f partPos;
836         for (int i = 0; i < _particles.size(); ++i)
837         {
838                 partPos = _particles[i]->GetPosition();
839                 partPos -= vecSortPoint;
840                 _particles[i]->SetSquareSortDistance(partPos * vecViewDir);//partPos.LengthSqr());
841         }
842
843   switch (dir)
844   {
845   case SKY_CLOUD_SORT_TOWARD:
846     std::sort(_particles.begin(), _particles.end(), _towardComparator);
847     break;
848   case SKY_CLOUD_SORT_AWAY:
849     std::sort(_particles.begin(), _particles.end(), _awayComparator);
850     break;
851   default:
852     break;
853   }
854 }
855
856
857
858 //------------------------------------------------------------------------------
859 // Function               : EvalHermite
860 // Description      : 
861 //------------------------------------------------------------------------------
862 /**
863  * EvalHermite(float pA, float pB, float vA, float vB, float u)
864  * @brief Evaluates Hermite basis functions for the specified coefficients.
865  */ 
866 inline float EvalHermite(float pA, float pB, float vA, float vB, float u)
867 {
868   float u2=(u*u), u3=u2*u;
869   float B0 = 2*u3 - 3*u2 + 1;
870   float B1 = -2*u3 + 3*u2;
871   float B2 = u3 - 2*u2 + u;
872   float B3 = u3 - u;
873   return( B0*pA + B1*pB + B2*vA + B3*vB );
874 }
875
876 // NORMALIZED GAUSSIAN INTENSITY MAP (N must be a power of 2)
877
878 //------------------------------------------------------------------------------
879 // Function               : CreateGaussianMap
880 // Description      : 
881 //------------------------------------------------------------------------------
882 /**
883  * CreateGaussianMap(int N)
884  * 
885  * Creates a 2D gaussian image using a hermite surface.
886  */ 
887 unsigned char* CreateGaussianMap(int N)
888 {
889   float *M = new float[2*N*N];
890   unsigned char *B = new unsigned char[4*N*N];
891   float X,Y,Y2,Dist;
892   float Incr = 2.0f/N;
893   int i=0;  
894   int j = 0;
895   Y = -1.0f;
896   for (int y=0; y<N; y++, Y+=Incr)
897   {
898     Y2=Y*Y;
899     X = -1.0f;
900     for (int x=0; x<N; x++, X+=Incr, i+=2, j+=4)
901     {
902       Dist = (float)sqrt(X*X+Y2);
903       if (Dist>1) Dist=1;
904       M[i+1] = M[i] = EvalHermite(0.4f,0,0,0,Dist);// * (1 - noise);
905       B[j+3] = B[j+2] = B[j+1] = B[j] = (unsigned char)(M[i] * 255);
906     }
907   }
908   SAFE_DELETE_ARRAY(M);
909   return(B);
910 }              
911
912
913 //------------------------------------------------------------------------------
914 // Function               : SkyCloud::_CreateSplatTexture
915 // Description      : 
916 //------------------------------------------------------------------------------
917 /**
918  * @fn SkyCloud::_CreateSplatTexture(unsigned int iResolution)
919  * @brief Creates the texture map used for cloud particles.
920  */ 
921 void SkyCloud::_CreateSplatTexture(unsigned int iResolution)
922 {
923   unsigned char *splatTexture = CreateGaussianMap(iResolution);
924   SkyTexture texture;
925   TextureManager::InstancePtr()->Create2DTextureObject(texture, iResolution, iResolution, 
926                                                        GL_RGBA, splatTexture);
927
928   s_pMaterial->SetTexture(0, GL_TEXTURE_2D, texture);
929   s_pShadeMaterial->SetTexture(0, GL_TEXTURE_2D, texture);
930   s_pMaterial->SetTextureParameter(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
931   s_pShadeMaterial->SetTextureParameter(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
932   s_pMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
933   s_pShadeMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
934   s_pMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
935   s_pShadeMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
936   s_pMaterial->EnableTexture(0, true);
937   s_pShadeMaterial->EnableTexture(0, true);
938
939   SAFE_DELETE_ARRAY(splatTexture);
940 }     
941
942
943 //------------------------------------------------------------------------------
944 // Function               : SkyCloud::_PhaseFunction
945 // Description      : 
946 //------------------------------------------------------------------------------
947 /**
948  * @fn SkyCloud::_PhaseFunction(const Vec3f& vecLightDir, const Vec3f& vecViewDir)
949  * @brief Computes the phase (scattering) function of the given light and view directions.
950  * 
951  * A phase function is a transfer function that determines, for any angle between incident 
952  * and outgoing directions, how much of the incident light intensity will be 
953  * scattered in the outgoing direction.  For example, scattering by very small 
954  * particles such as those found in clear air, can be approximated using <i>Rayleigh 
955  * scattering</i>.  The phase function for Rayleigh scattering is 
956  * p(q) = 0.75*(1 + cos<sup>2</sup>(q)), where q  is the angle between incident 
957  * and scattered directions.  Scattering by larger particles is more complicated.  
958  * It is described by Mie scattering theory.  Cloud particles are more in the regime 
959  * of Mie scattering than Rayleigh scattering.  However, we obtain good visual 
960  * results by using the simpler Rayleigh scattering phase function as an approximation.
961  */ 
962 float SkyCloud::_PhaseFunction(const Vec3f& vecLightDir, const Vec3f& vecViewDir)
963 {
964   float rCosAlpha = vecLightDir * vecViewDir;
965   return .75f * (1 + rCosAlpha * rCosAlpha); // rayleigh scattering = (3/4) * (1+cos^2(alpha))
966 }