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