1 //------------------------------------------------------------------------------
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
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
22 * Implementation of class SkyCloud.
25 // warning for truncation of template name for browse info
26 #pragma warning( disable : 4786)
29 # include <simgear_config.h>
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"
48 //! The version used for cloud archive files.
49 #define CLOUD_ARCHIVE_VERSION 0.1f
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;
64 //------------------------------------------------------------------------------
65 // Function : SkyCloud::SkyCloud
67 //------------------------------------------------------------------------------
69 * @fn SkyCloud::SkyCloud()
74 _bUsePhaseFunction(true),
75 _vecLastSortViewDir(Vec3f::ZERO),
76 _vecLastSortCamPos(Vec3f::ZERO)
78 if (!s_pShadeMaterial)
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);
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
112 //------------------------------------------------------------------------------
113 // Function : SkyCloud::~SkyCloud
115 //------------------------------------------------------------------------------
117 * @fn SkyCloud::~SkyCloud()
120 SkyCloud::~SkyCloud()
125 //------------------------------------------------------------------------------
126 // Function : SkyCloud::Update
128 //------------------------------------------------------------------------------
130 * @fn SkyCloud::Update(const Camera &cam, SkyRenderableInstance* pInstance)
131 * @brief Currently does nothing.
133 SKYRESULT SkyCloud::Update(const Camera &cam, SkyRenderableInstance* pInstance)
140 //------------------------------------------------------------------------------
141 // Function : DrawQuad
143 //------------------------------------------------------------------------------
145 * @fn DrawQuad(Vec3f pos, Vec3f x, Vec3f y, Vec4f color)
146 * @brief Draw a quad.
148 inline void DrawQuad(Vec3f pos, Vec3f x, Vec3f y, Vec4f color)
150 glColor4fv(&(color.x));
151 Vec3f left = pos; left -= y;
152 Vec3f right = left; right += 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));
163 //------------------------------------------------------------------------------
164 // Function : SkyCloud::Display
166 //------------------------------------------------------------------------------
168 * @fn SkyCloud::Display(const Camera &camera, SkyRenderableInstance *pInstance)
169 * @brief Renders the cloud.
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.
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.
180 SKYRESULT SkyCloud::Display(const Camera &camera, SkyRenderableInstance *pInstance)
182 // copy the current camera
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
195 float rSquareDistanceSinceLastSort =
196 (cam.Orig - _vecLastSortCamPos).LengthSqr();
198 if (rCosAngleSinceLastSort < s_rSortAngleErrorTolerance ||
199 rSquareDistanceSinceLastSort > s_rSortSquareDistanceTolerance)
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();
209 // sort the particles from back to front wrt the camera position.
210 _SortParticles(cam.ViewDir(), _vecSortPos, SKY_CLOUD_SORT_TOWARD);
212 //_vecLastSortViewDir = GLVU::GetCurrent()->GetCurrentCam()->ViewDir();
213 //_vecLastSortCamPos = GLVU::GetCurrent()->GetCurrentCam()->Orig;
214 _vecLastSortViewDir = cam.ViewDir();
215 _vecLastSortCamPos = cam.Orig;
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();
228 // Draw the particles using immediate mode.
232 for (ParticleIterator iter = _particles.begin(); iter != _particles.end(); iter++)
235 SkyCloudParticle *p = *iter;
237 // Start with ambient light
238 color = p->GetBaseColor();
240 if (_bUsePhaseFunction) // use the phase function for anisotropic scattering.
243 eyeDir -= p->GetPosition();
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++)
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;
258 else // just use isotropic scattering instead.
260 for (int i = 0; i < (*iter)->GetNumLitColors(); ++i)
262 color += p->GetLitColor(i);
266 // Set the transparency independently of the colors
267 color.w = 1 - s_rTransparency;
269 // draw the particle as a textured billboard.
270 DrawQuad((*iter)->GetPosition(), cam.X * p->GetRadius(), cam.Y * p->GetRadius(), color);
278 //------------------------------------------------------------------------------
279 // Function : SkyCloud::DisplaySplit
281 //------------------------------------------------------------------------------
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.
286 * This is used to render clouds into two impostor images for displaying clouds that contain objects.
288 * @see SkyRenderableInstanceCloud
290 SKYRESULT SkyCloud::DisplaySplit(const Camera &camera,
291 const Vec3f &vecSplitPoint,
293 SkyRenderableInstance *pInstance /* = NULL */)
295 // copy the current camera
298 Vec3f vecCloudSpaceSplit = vecSplitPoint;
300 if (bBackHalf) // only sort when rendering the back half. Reuse sort for front half.
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();
310 // sort the particles from back to front wrt the camera position.
311 _SortParticles(cam.ViewDir(), _vecSortPos, SKY_CLOUD_SORT_TOWARD);
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;
319 // compute the split distance.
320 vecCloudSpaceSplit -= _vecSortPos;
321 _rSplitDistance = vecCloudSpaceSplit * cam.ViewDir();
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();
334 // Draw the particles using immediate mode.
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;
341 iter = _particles.begin();
343 // iterate over the particles and render them.
344 for (; iter != _particles.end(); ++iter)
346 SkyCloudParticle *p = *iter;
348 if (bBackHalf && (p->GetSquareSortDistance() < _rSplitDistance))
351 // Start with ambient light
352 color = p->GetBaseColor();
354 if (_bUsePhaseFunction) // use the phase function for anisotropic scattering.
357 eyeDir -= p->GetPosition();
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++)
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;
372 else // just use isotropic scattering instead.
374 for (int i = 0; i < p->GetNumLitColors(); ++i)
376 color += p->GetLitColor(i);
380 // set the transparency independently of the colors.
381 color.w = 1 - s_rTransparency;
383 // draw the particle as a textured billboard.
384 DrawQuad((*iter)->GetPosition(), cam.X * p->GetRadius(), cam.Y * p->GetRadius(), color);
391 //------------------------------------------------------------------------------
392 // Function : SkyCloud::Illuminate
394 //------------------------------------------------------------------------------
396 * @fn SkyCloud::Illuminate(SkyLight *pLight, SkyRenderableInstance* pInstance, bool bReset)
397 * @brief Compute the illumination of the cloud by the lightsource @a pLight
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.
407 SKYRESULT SkyCloud::Illuminate(SkyLight *pLight, SkyRenderableInstance* pInstance, bool bReset)
411 glGetIntegerv(GL_VIEWPORT, iOldVP);
412 glViewport(0, 0, s_iShadeResolution, s_iShadeResolution);
414 Vec3f vecDir(pLight->GetDirection());
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.
420 _lightDirections.clear();
421 _lightDirections.push_back(vecDir); // cache the (unit-length) light direction
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();
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.
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.
441 cam.LookAt(vecLightPos, _boundingBox.GetCenter(), vecUp);
443 // sort the particles away from the light source.
444 _SortParticles(cam.ViewDir(), vecLightPos, SKY_CLOUD_SORT_AWAY);
446 // projected dist to cntr along viewdir
447 float DistToCntr = (_boundingBox.GetCenter() - vecLightPos) * cam.ViewDir();
449 // calc tight-fitting near and far distances for the orthographic frustum
450 float rNearDist = DistToCntr - _boundingBox.GetRadius();
451 float rFarDist = DistToCntr + _boundingBox.GetRadius();
453 // set the modelview matrix from this camera.
454 glMatrixMode(GL_MODELVIEW);
459 cam.GetModelviewMatrix(M);
462 // switch to parallel projection
463 glMatrixMode(GL_PROJECTION);
466 glOrtho(-_boundingBox.GetRadius(), _boundingBox.GetRadius(),
467 -_boundingBox.GetRadius(), _boundingBox.GetRadius(),
468 rNearDist, rFarDist);
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();
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);
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);
488 float rPixelsPerLength = s_iShadeResolution / (2 * _boundingBox.GetRadius());
490 // the solid angle over which we will sample forward-scattered light.
491 float rSolidAngle = 0.09;
494 for (ParticleIterator iter = _particles.begin(); iter != _particles.end(); ++iter, ++i)
496 Vec3f vecParticlePos = (*iter)->GetPosition();
498 Vec3f vecOffset(vecLightPos);
499 vecOffset -= vecParticlePos;
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;
505 float rArea = rSolidAngle * rDistance * rDistance;
506 int iPixelDim = sqrt(rArea) * rPixelsPerLength;
507 int iNumPixels = iPixelDim * iPixelDim;
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);
517 unsigned char *c = new unsigned char[4 * iNumPixels];
521 // find the position in the buffer to which the particle position projects.
522 if (!gluProject(vecParticlePos.x, vecParticlePos.y, vecParticlePos.z,
524 &(vecWinPos.x), &(vecWinPos.y), &(vecWinPos.z)))
526 FAIL_RETURN_MSG(SKYRESULT_FAIL,
527 "Error: SkyCloud::Illuminate(): failed to project particle position.");
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;
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);
540 // scattering coefficient vector.
541 Vec4f vecScatter(s_rScatterFactor, s_rScatterFactor, s_rScatterFactor, 1);
543 // add up the read back pixels (only need one component -- its grayscale)
545 for (int k = 0; k < 4 * iNumPixels; k+=4)
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;
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;
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.
575 (*iter)->SetBaseColor(s_pMaterial->GetAmbient());
576 (*iter)->ClearLitColors();
577 (*iter)->AddLitColor(vecColor);
581 (*iter)->AddLitColor(vecColor);
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!
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;
594 vecScatteredAmount.x = 0.50; vecScatteredAmount.y = 0.60; vecScatteredAmount.z = 0.70;
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.
599 DrawQuad(vecParticlePos, cam.X * (*iter)->GetRadius(), cam.Y * (*iter)->GetRadius(), vecScatteredAmount);
602 //glutSwapBuffers(); // Uncomment this swap buffers to visualize cloud illumination computation.
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
609 // restore matrix stack and viewport.
610 glMatrixMode(GL_PROJECTION);
612 glMatrixMode(GL_MODELVIEW);
614 glViewport(iOldVP[0], iOldVP[1], iOldVP[2], iOldVP[3]);
620 //------------------------------------------------------------------------------
621 // Function : SkyCloud::CopyBoundingVolume
623 //------------------------------------------------------------------------------
625 * @fn SkyCloud::CopyBoundingVolume() const
626 * @brief Returns a new copy of the SkyMinMaxBox for this cloud.
628 SkyMinMaxBox* SkyCloud::CopyBoundingVolume() const
630 SkyMinMaxBox *pBox = new SkyMinMaxBox();
631 pBox->SetMax(_boundingBox.GetMax());
632 pBox->SetMin(_boundingBox.GetMin());
636 SKYRESULT SkyCloud::Load(const unsigned char *data, unsigned int size,
637 float rScale, /* = 1.0f */
638 double latitude, double longitude )
640 unsigned int iNumParticles;
641 Vec3f vecCenter = Vec3f::ZERO;
645 //_boundingBox.SetMin(vecCenter - Vec3f(rRadius, rRadius, rRadius));
646 //_boundingBox.SetMax(vecCenter + Vec3f(rRadius, rRadius, rRadius));
648 for (unsigned int i = 0; i < size*size*4; i += 4)
654 color.x = data[i]; // FIXME: Which unit?
659 radius = (color.w/255) * rScale;
660 //radius = (color.x * color.y * color.z * color.w * rScale) / 4096;
663 pos.x = (i / (size*4)) * 10; // FIXME: Which unit?
664 pos.y = (i % (size*4)) * 10;
669 SkyCloudParticle *pParticle = new SkyCloudParticle((pos + vecCenter) * rScale, radius * rScale, color);
670 _boundingBox.AddPoint(pParticle->GetPosition());
672 _particles.push_back(pParticle);
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
685 // clouds sit in the y-z plane and x-axis is the vertical cloud height
688 // rotate the cloud field about the fgfs z-axis based on initial longitude
692 float phi = longitude / 57.29578;
693 float one_min_cos = 1 - cos(phi);
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 );
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 );
707 phi = latitude / 57.29578;
708 one_min_cos = 1 - cos(phi);
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 );
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;
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 );
732 SKYRESULT SkyCloud::Load(const SkyArchive &archive,
733 float rScale, /* = 1.0f */
734 double latitude, double longitude )
736 unsigned int iNumParticles;
737 Vec3f vecCenter = Vec3f::ZERO;
740 //archive.FindVec3f("CldCenter", &vecCenter);
741 //archive.FindFloat32("CldRadius", &rRadius);
743 //_boundingBox.SetMin(vecCenter - Vec3f(rRadius, rRadius, rRadius));
744 //_boundingBox.SetMax(vecCenter + Vec3f(rRadius, rRadius, rRadius));
746 archive.FindUInt32("CldNumParticles", &iNumParticles);
747 iNumParticles = ulEndianLittle32(iNumParticles);
750 archive.FindVec3f("CldCenter", &vecCenter);
751 vecCenter.x = ulEndianLittleFloat(vecCenter.x);
752 vecCenter.y = ulEndianLittleFloat(vecCenter.y);
753 vecCenter.z = ulEndianLittleFloat(vecCenter.z);
755 Vec3f *pParticlePositions = new Vec3f[iNumParticles];
756 float *pParticleRadii = new float[iNumParticles];
757 Vec4f *pParticleColors = new Vec4f[iNumParticles];
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);
764 for (unsigned int i = 0; i < iNumParticles; ++i)
767 pParticlePositions[i].x = ulEndianLittleFloat(pParticlePositions[i].x);
768 pParticlePositions[i].y = ulEndianLittleFloat(pParticlePositions[i].y);
769 pParticlePositions[i].z = ulEndianLittleFloat(pParticlePositions[i].z);
771 pParticleRadii[i] = ulEndianLittleFloat(pParticleRadii[i]);
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);
778 SkyCloudParticle *pParticle = new SkyCloudParticle((pParticlePositions[i] + vecCenter) * rScale,
779 pParticleRadii[i] * rScale,
781 _boundingBox.AddPoint(pParticle->GetPosition());
783 _particles.push_back(pParticle);
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
794 // clouds sit in the y-z plane and x-axis is the vertical cloud height
797 // rotate the cloud field about the fgfs z-axis based on initial longitude
801 float phi = longitude / 57.29578;
802 float one_min_cos = 1 - cos(phi);
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 );
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 );
816 phi = latitude / 57.29578;
817 one_min_cos = 1 - cos(phi);
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 );
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;
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 );
841 //------------------------------------------------------------------------------
842 // Function : SkyCloud::Save
844 //------------------------------------------------------------------------------
846 * @fn SkyCloud::Save(SkyArchive &archive) const
847 * @brief Saves the cloud data to @a archive.
849 * @todo <WRITE EXTENDED SkyCloud::Save FUNCTION DOCUMENTATION>
851 SKYRESULT SkyCloud::Save(SkyArchive &archive) const
853 SkyArchive myArchive("Cloud");
854 //myArchive.AddVec3f("CldCenter", _center);
855 //myArchive.AddFloat32("CldRadius", _boundingBox.GetRadius());
856 myArchive.AddUInt32("CldNumParticles", _particles.size());
859 Vec3f *pParticlePositions = new Vec3f[_particles.size()];
860 float *pParticleRadii = new float[_particles.size()];
861 Vec4f *pParticleColors = new Vec4f[_particles.size()];
865 for (ParticleConstIterator iter = _particles.begin(); iter != _particles.end(); ++iter, ++i)
867 pParticlePositions[i] = (*iter)->GetPosition(); // position around origin
868 pParticleRadii[i] = (*iter)->GetRadius();
869 pParticleColors[i] = (*iter)->GetBaseColor();
872 myArchive.AddData("CldParticlePositions",
878 myArchive.AddData("CldParticleRadii",
884 myArchive.AddData("CldParticleColors",
890 archive.AddArchive(myArchive);
896 //------------------------------------------------------------------------------
897 // Function : SkyCloud::Rotate
899 //------------------------------------------------------------------------------
901 * @fn SkyCloud::Rotate(const Mat33f& rot)
902 * @brief @todo <WRITE BRIEF SkyCloud::Rotate DOCUMENTATION>
904 * @todo <WRITE EXTENDED SkyCloud::Rotate FUNCTION DOCUMENTATION>
906 void SkyCloud::Rotate(const Mat33f& rot)
908 _boundingBox.Clear();
909 for (int i = 0; i < _particles.size(); ++i)
911 _particles[i]->SetPosition(rot * _particles[i]->GetPosition());
912 _boundingBox.AddPoint(_particles[i]->GetPosition());
917 //------------------------------------------------------------------------------
918 // Function : SkyCloud::Translate
920 //------------------------------------------------------------------------------
922 * @fn SkyCloud::Translate(const Vec3f& trans)
923 * @brief @todo <WRITE BRIEF SkyCloud::Translate DOCUMENTATION>
925 * @todo <WRITE EXTENDED SkyCloud::Translate FUNCTION DOCUMENTATION>
927 void SkyCloud::Translate(const Vec3f& trans)
929 for (int i = 0; i < _particles.size(); ++i)
931 _particles[i]->SetPosition(_particles[i]->GetPosition() + trans);
933 _boundingBox.SetMax(_boundingBox.GetMax() + trans);
934 _boundingBox.SetMin(_boundingBox.GetMin() + trans);
938 //------------------------------------------------------------------------------
939 // Function : SkyCloud::Scale
941 //------------------------------------------------------------------------------
943 * @fn SkyCloud::Scale(const float scale)
944 * @brief @todo <WRITE BRIEF SkyCloud::Scale DOCUMENTATION>
946 * @todo <WRITE EXTENDED SkyCloud::Scale FUNCTION DOCUMENTATION>
948 void SkyCloud::Scale(const float scale)
950 _boundingBox.Clear();
951 for (int i = 0; i < _particles.size(); ++i)
953 _particles[i]->SetPosition(_particles[i]->GetPosition() * scale);
954 _boundingBox.AddPoint(_particles[i]->GetPosition());
959 //------------------------------------------------------------------------------
960 // Function : SkyCloud::_SortParticles
962 //------------------------------------------------------------------------------
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.
967 * @vecSortPoint is assumed to already be transformed into the basis space of the cloud.
969 void SkyCloud::_SortParticles(const Vec3f& vecViewDir,
970 const Vec3f& vecSortPoint,
974 for (int i = 0; i < _particles.size(); ++i)
976 partPos = _particles[i]->GetPosition();
977 partPos -= vecSortPoint;
978 _particles[i]->SetSquareSortDistance(partPos * vecViewDir);//partPos.LengthSqr());
983 case SKY_CLOUD_SORT_TOWARD:
984 std::sort(_particles.begin(), _particles.end(), _towardComparator);
986 case SKY_CLOUD_SORT_AWAY:
987 std::sort(_particles.begin(), _particles.end(), _awayComparator);
996 //------------------------------------------------------------------------------
997 // Function : EvalHermite
999 //------------------------------------------------------------------------------
1001 * EvalHermite(float pA, float pB, float vA, float vB, float u)
1002 * @brief Evaluates Hermite basis functions for the specified coefficients.
1004 inline float EvalHermite(float pA, float pB, float vA, float vB, float u)
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;
1011 return( B0*pA + B1*pB + B2*vA + B3*vB );
1014 // NORMALIZED GAUSSIAN INTENSITY MAP (N must be a power of 2)
1016 //------------------------------------------------------------------------------
1017 // Function : CreateGaussianMap
1019 //------------------------------------------------------------------------------
1021 * CreateGaussianMap(int N)
1023 * Creates a 2D gaussian image using a hermite surface.
1025 unsigned char* CreateGaussianMap(int N)
1027 float *M = new float[2*N*N];
1028 unsigned char *B = new unsigned char[4*N*N];
1030 float Incr = 2.0f/N;
1034 for (int y=0; y<N; y++, Y+=Incr)
1038 for (int x=0; x<N; x++, X+=Incr, i+=2, j+=4)
1040 Dist = (float)sqrt(X*X+Y2);
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);
1046 SAFE_DELETE_ARRAY(M);
1051 //------------------------------------------------------------------------------
1052 // Function : SkyCloud::_CreateSplatTexture
1054 //------------------------------------------------------------------------------
1056 * @fn SkyCloud::_CreateSplatTexture(unsigned int iResolution)
1057 * @brief Creates the texture map used for cloud particles.
1059 void SkyCloud::_CreateSplatTexture(unsigned int iResolution)
1061 unsigned char *splatTexture = CreateGaussianMap(iResolution);
1063 TextureManager::InstancePtr()->Create2DTextureObject(texture, iResolution, iResolution,
1064 GL_RGBA, splatTexture);
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);
1077 SAFE_DELETE_ARRAY(splatTexture);
1081 //------------------------------------------------------------------------------
1082 // Function : SkyCloud::_PhaseFunction
1084 //------------------------------------------------------------------------------
1086 * @fn SkyCloud::_PhaseFunction(const Vec3f& vecLightDir, const Vec3f& vecViewDir)
1087 * @brief Computes the phase (scattering) function of the given light and view directions.
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.
1100 float SkyCloud::_PhaseFunction(const Vec3f& vecLightDir, const Vec3f& vecViewDir)
1102 float rCosAlpha = vecLightDir * vecViewDir;
1103 return .75f * (1 + rCosAlpha * rCosAlpha); // rayleigh scattering = (3/4) * (1+cos^2(alpha))