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)
30 #include "SkyCloud.hpp"
31 #include "SkyRenderableInstance.hpp"
32 #include "SkyContext.hpp"
33 #include "SkyMaterial.hpp"
34 #include "SkyLight.hpp"
35 #include "SkyTextureManager.hpp"
36 #include "SkySceneManager.hpp"
39 //! The version used for cloud archive files.
40 #define CLOUD_ARCHIVE_VERSION 0.1f
42 //------------------------------------------------------------------------------
43 // Static initialization
44 //------------------------------------------------------------------------------
45 SkyMaterial* SkyCloud::s_pMaterial = NULL;
46 SkyMaterial* SkyCloud::s_pShadeMaterial = NULL;
47 unsigned int SkyCloud::s_iShadeResolution = 32;
48 float SkyCloud::s_rAlbedo = 0.9f;
49 float SkyCloud::s_rExtinction = 80.0f;
50 float SkyCloud::s_rTransparency = exp(-s_rExtinction);
51 float SkyCloud::s_rScatterFactor = s_rAlbedo * s_rExtinction * SKY_INV_4PI;
52 float SkyCloud::s_rSortAngleErrorTolerance = 0.8f;
53 float SkyCloud::s_rSortSquareDistanceTolerance = 100;
55 //------------------------------------------------------------------------------
56 // Function : SkyCloud::SkyCloud
58 //------------------------------------------------------------------------------
60 * @fn SkyCloud::SkyCloud()
65 _bUsePhaseFunction(true),
66 _vecLastSortViewDir(Vec3f::ZERO),
67 _vecLastSortCamPos(Vec3f::ZERO)
69 if (!s_pShadeMaterial)
71 s_pShadeMaterial = new SkyMaterial;
72 s_pShadeMaterial->SetAmbient(Vec4f(0.1f, 0.1f, 0.1f, 1));
73 s_pShadeMaterial->EnableDepthTest(false);
74 s_pShadeMaterial->SetBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
75 s_pShadeMaterial->EnableBlending(true);
76 s_pShadeMaterial->SetAlphaFunc(GL_GREATER);
77 s_pShadeMaterial->SetAlphaRef(0);
78 s_pShadeMaterial->EnableAlphaTest(true);
79 s_pShadeMaterial->SetColorMaterialMode(GL_DIFFUSE);
80 s_pShadeMaterial->EnableColorMaterial(true);
81 s_pShadeMaterial->EnableLighting(false);
82 s_pShadeMaterial->SetTextureApplicationMode(GL_MODULATE);
86 s_pMaterial = new SkyMaterial;
87 s_pMaterial->SetAmbient(Vec4f(0.3f, 0.3f, 0.3f, 1));
88 s_pMaterial->SetDepthMask(false);
89 s_pMaterial->SetBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
90 s_pMaterial->EnableBlending(true);
91 s_pMaterial->SetAlphaFunc(GL_GREATER);
92 s_pMaterial->SetAlphaRef(0);
93 s_pMaterial->EnableAlphaTest(true);
94 s_pMaterial->SetColorMaterialMode(GL_DIFFUSE);
95 s_pMaterial->EnableColorMaterial(true);
96 s_pMaterial->EnableLighting(false);
97 s_pMaterial->SetTextureApplicationMode(GL_MODULATE);
98 _CreateSplatTexture(32); // will assign the texture to both static materials
103 //------------------------------------------------------------------------------
104 // Function : SkyCloud::~SkyCloud
106 //------------------------------------------------------------------------------
108 * @fn SkyCloud::~SkyCloud()
111 SkyCloud::~SkyCloud()
116 //------------------------------------------------------------------------------
117 // Function : SkyCloud::Update
119 //------------------------------------------------------------------------------
121 * @fn SkyCloud::Update(const Camera &cam, SkyRenderableInstance* pInstance)
122 * @brief Currently does nothing.
124 SKYRESULT SkyCloud::Update(const Camera &cam, SkyRenderableInstance* pInstance)
131 //------------------------------------------------------------------------------
132 // Function : DrawQuad
134 //------------------------------------------------------------------------------
136 * @fn DrawQuad(Vec3f pos, Vec3f x, Vec3f y, Vec4f color)
137 * @brief Draw a quad.
139 inline void DrawQuad(Vec3f pos, Vec3f x, Vec3f y, Vec4f color)
141 glColor4fv(&(color.x));
142 Vec3f left = pos; left -= y;
143 Vec3f right = left; right += x;
145 glTexCoord2f(0, 0); glVertex3fv(&(left.x));
146 glTexCoord2f(1, 0); glVertex3fv(&(right.x));
147 left += y; left += y;
148 right += y; right += y;
149 glTexCoord2f(1, 1); glVertex3fv(&(right.x));
150 glTexCoord2f(0, 1); glVertex3fv(&(left.x));
154 //------------------------------------------------------------------------------
155 // Function : SkyCloud::Display
157 //------------------------------------------------------------------------------
159 * @fn SkyCloud::Display(const Camera &camera, SkyRenderableInstance *pInstance)
160 * @brief Renders the cloud.
162 * The cloud is rendered by splatting the particles from back to front with respect
163 * to @a camera. Since instances of clouds each have their own particles, which
164 * are pre-transformed into world space, @a pInstance is not used.
166 * An alternative method is to store the particles untransformed, and transform the
167 * camera and light into cloud space for rendering. This is more complicated,
168 * and not as straightforward. Since I have to store the particles with each instance
169 * anyway, I decided to pre-transform them instead.
171 SKYRESULT SkyCloud::Display(const Camera &camera, SkyRenderableInstance *pInstance)
173 // copy the current camera
176 // This cosine computation, along with the if() below, are an optimization. The goal
177 // is to avoid sorting when it will make no visual difference. This will be true when the
178 // cloud particles are almost sorted for the current viewpoint. This is the case most of the
179 // time, since the viewpoint does not move very far in a single frame. Each time we sort,
180 // we cache the current view direction. Then, each time the cloud is displayed, if the
181 // current view direction is very close to the current view direction (dot product is nearly 1)
182 // then we do not resort the particles.
183 float rCosAngleSinceLastSort =
184 _vecLastSortViewDir * cam.ViewDir(); // dot product
186 float rSquareDistanceSinceLastSort =
187 (cam.Orig - _vecLastSortCamPos).LengthSqr();
189 if (rCosAngleSinceLastSort < s_rSortAngleErrorTolerance ||
190 rSquareDistanceSinceLastSort > s_rSortSquareDistanceTolerance)
192 // compute the sort position for particles.
193 // don't just use the camera position -- if it is too far away from the cloud, then
194 // precision limitations may cause the STL sort to hang. Instead, put the sort position
195 // just outside the bounding sphere of the cloud in the direction of the camera.
196 _vecSortPos = -cam.ViewDir();
197 _vecSortPos *= (1.1 * _boundingBox.GetRadius());
198 _vecSortPos += _boundingBox.GetCenter();
200 // sort the particles from back to front wrt the camera position.
201 _SortParticles(cam.ViewDir(), _vecSortPos, SKY_CLOUD_SORT_TOWARD);
203 //_vecLastSortViewDir = GLVU::GetCurrent()->GetCurrentCam()->ViewDir();
204 //_vecLastSortCamPos = GLVU::GetCurrent()->GetCurrentCam()->Orig;
205 _vecLastSortViewDir = cam.ViewDir();
206 _vecLastSortCamPos = cam.Orig;
209 // set the material state / properties that clouds use for rendering:
210 // Enables blending, with blend func (ONE, ONE_MINUS_SRC_ALPHA).
211 // Enables alpha test to discard completely transparent fragments.
212 // Disables depth test.
213 // Enables texturing, with modulation, and the texture set to the shared splat texture.
214 s_pMaterial->Activate();
219 // Draw the particles using immediate mode.
223 for (ParticleIterator iter = _particles.begin(); iter != _particles.end(); iter++)
226 SkyCloudParticle *p = *iter;
228 // Start with ambient light
229 color = p->GetBaseColor();
231 if (_bUsePhaseFunction) // use the phase function for anisotropic scattering.
234 eyeDir -= p->GetPosition();
238 // add the color contribution to this particle from each light source, modulated by
239 // the phase function. See _PhaseFunction() documentation for details.
240 for (int i = 0; i < p->GetNumLitColors(); i++)
242 pf = _PhaseFunction(_lightDirections[i], eyeDir);
243 // expand this to avoid temporary vector creation in the inner loop
244 color.x += p->GetLitColor(i).x * pf;
245 color.y += p->GetLitColor(i).y * pf;
246 color.z += p->GetLitColor(i).z * pf;
249 else // just use isotropic scattering instead.
251 for (int i = 0; i < (*iter)->GetNumLitColors(); ++i)
253 color += p->GetLitColor(i);
257 // Set the transparency independently of the colors
258 color.w = 1 - s_rTransparency;
260 // draw the particle as a textured billboard.
261 DrawQuad((*iter)->GetPosition(), cam.X * p->GetRadius(), cam.Y * p->GetRadius(), color);
269 //------------------------------------------------------------------------------
270 // Function : SkyCloud::DisplaySplit
272 //------------------------------------------------------------------------------
274 * @fn SkyCloud::DisplaySplit(const Camera &camera, const Vec3f &vecSplitPoint, bool bBackHalf, SkyRenderableInstance *pInstance)
275 * @brief The same as Display(), except it displays only the particles in front of or behind the split point.
277 * This is used to render clouds into two impostor images for displaying clouds that contain objects.
279 * @see SkyRenderableInstanceCloud
281 SKYRESULT SkyCloud::DisplaySplit(const Camera &camera,
282 const Vec3f &vecSplitPoint,
284 SkyRenderableInstance *pInstance /* = NULL */)
286 // copy the current camera
289 Vec3f vecCloudSpaceSplit = vecSplitPoint;
291 if (bBackHalf) // only sort when rendering the back half. Reuse sort for front half.
293 // compute the sort position for particles.
294 // don't just use the camera position -- if it is too far away from the cloud, then
295 // precision limitations may cause the STL sort to hang. Instead, put the sort position
296 // just outside the bounding sphere of the cloud in the direction of the camera.
297 _vecSortPos = -cam.ViewDir();
298 _vecSortPos *= (1.1 * _boundingBox.GetRadius());
299 _vecSortPos += _boundingBox.GetCenter();
301 // sort the particles from back to front wrt the camera position.
302 _SortParticles(cam.ViewDir(), _vecSortPos, SKY_CLOUD_SORT_TOWARD);
304 // we can't use the view direction optimization when the cloud is split, or we get a lot
305 // of popping of objects in and out of cloud cover. For consistency, though, we need to update
306 // the cached sort direction, since we just sorted the particles.
307 // _vecLastSortViewDir = GLVU::GetCurrent()->GetCurrentCam()->ViewDir();
308 // _vecLastSortCamPos = GLVU::GetCurrent()->GetCurrentCam()->Orig;
310 // compute the split distance.
311 vecCloudSpaceSplit -= _vecSortPos;
312 _rSplitDistance = vecCloudSpaceSplit * cam.ViewDir();
315 // set the material state / properties that clouds use for rendering:
316 // Enables blending, with blend func (ONE, ONE_MINUS_SRC_ALPHA).
317 // Enables alpha test to discard completely transparent fragments.
318 // Disables depth test.
319 // Enables texturing, with modulation, and the texture set to the shared splat texture.
320 s_pMaterial->Activate();
325 // Draw the particles using immediate mode.
328 // if bBackHalf is false, then we just continue where we left off. If it is true, we
329 // reset the iterator to the beginning of the sorted list.
330 static ParticleIterator iter;
332 iter = _particles.begin();
334 // iterate over the particles and render them.
335 for (; iter != _particles.end(); ++iter)
337 SkyCloudParticle *p = *iter;
339 if (bBackHalf && (p->GetSquareSortDistance() < _rSplitDistance))
342 // Start with ambient light
343 color = p->GetBaseColor();
345 if (_bUsePhaseFunction) // use the phase function for anisotropic scattering.
348 eyeDir -= p->GetPosition();
352 // add the color contribution to this particle from each light source, modulated by
353 // the phase function. See _PhaseFunction() documentation for details.
354 for (int i = 0; i < p->GetNumLitColors(); i++)
356 pf = _PhaseFunction(_lightDirections[i], eyeDir);
357 // expand this to avoid temporary vector creation in the inner loop
358 color.x += p->GetLitColor(i).x * pf;
359 color.y += p->GetLitColor(i).y * pf;
360 color.z += p->GetLitColor(i).z * pf;
363 else // just use isotropic scattering instead.
365 for (int i = 0; i < p->GetNumLitColors(); ++i)
367 color += p->GetLitColor(i);
371 // set the transparency independently of the colors.
372 color.w = 1 - s_rTransparency;
374 // draw the particle as a textured billboard.
375 DrawQuad((*iter)->GetPosition(), cam.X * p->GetRadius(), cam.Y * p->GetRadius(), color);
382 //------------------------------------------------------------------------------
383 // Function : SkyCloud::Illuminate
385 //------------------------------------------------------------------------------
387 * @fn SkyCloud::Illuminate(SkyLight *pLight, SkyRenderableInstance* pInstance, bool bReset)
388 * @brief Compute the illumination of the cloud by the lightsource @a pLight
390 * This method uses graphics hardware to compute multiple forward scattering at each cloud
391 * in the cloud of light from the directional light source @a pLight. The algorithm works
392 * by successively subtracting "light" from an initially white (fully lit) frame buffer by
393 * using hardware blending and read back. The method stores the illumination from each light
394 * source passed to it separately at each particle, unless @a bReset is true, in which case
395 * the lists of illumination in the particles are reset before the lighting is computed.
398 SKYRESULT SkyCloud::Illuminate(SkyLight *pLight, SkyRenderableInstance* pInstance, bool bReset)
402 glGetIntegerv(GL_VIEWPORT, iOldVP);
403 glViewport(0, 0, s_iShadeResolution, s_iShadeResolution);
405 Vec3f vecDir(pLight->GetDirection());
407 // if this is the first pass through the lights, reset will be true, and the cached light
408 // directions should be updated. Light directions are cached in cloud space to accelerate
409 // computation of the phase function, which depends on light direction and view direction.
411 _lightDirections.clear();
412 _lightDirections.push_back(vecDir); // cache the (unit-length) light direction
414 // compute the light/sort position for particles from the light direction.
415 // don't just use the camera position -- if it is too far away from the cloud, then
416 // precision limitations may cause the STL sort to hang. Instead, put the sort position
417 // just outside the bounding sphere of the cloud in the direction of the camera.
418 Vec3f vecLightPos(vecDir);
419 vecLightPos *= (1.1*_boundingBox.GetRadius());
420 vecLightPos += _boundingBox.GetCenter();
422 // Set up a camera to look at the cloud from the light position. Since the sun is an infinite
423 // light source, this camera will use an orthographic projection tightly fit to the bounding
424 // sphere of the cloud.
427 // Avoid degenerate camera bases.
428 Vec3f vecUp(0, 1, 0);
429 if (fabs(vecDir * vecUp) - 1 < 1e-6) // check that the view and up directions are not parallel.
432 cam.LookAt(vecLightPos, _boundingBox.GetCenter(), vecUp);
434 // sort the particles away from the light source.
435 _SortParticles(cam.ViewDir(), vecLightPos, SKY_CLOUD_SORT_AWAY);
437 // projected dist to cntr along viewdir
438 float DistToCntr = (_boundingBox.GetCenter() - vecLightPos) * cam.ViewDir();
440 // calc tight-fitting near and far distances for the orthographic frustum
441 float rNearDist = DistToCntr - _boundingBox.GetRadius();
442 float rFarDist = DistToCntr + _boundingBox.GetRadius();
444 // set the modelview matrix from this camera.
445 glMatrixMode(GL_MODELVIEW);
450 cam.GetModelviewMatrix(M);
453 // switch to parallel projection
454 glMatrixMode(GL_PROJECTION);
457 glOrtho(-_boundingBox.GetRadius(), _boundingBox.GetRadius(),
458 -_boundingBox.GetRadius(), _boundingBox.GetRadius(),
459 rNearDist, rFarDist);
461 // set the material state / properties that clouds use for shading:
462 // Enables blending, with blend func (ONE, ONE_MINUS_SRC_ALPHA).
463 // Enables alpha test to discard completely transparent fragments.
464 // Disables depth test.
465 // Enables texturing, with modulation, and the texture set to the shared splat texture.
466 s_pShadeMaterial->Activate();
468 // these are used for projecting the particle position to determine where to read pixels.
469 double MM[16], PM[16];
470 int VP[4] = { 0, 0, s_iShadeResolution, s_iShadeResolution };
471 glGetDoublev(GL_MODELVIEW_MATRIX, MM);
472 glGetDoublev(GL_PROJECTION_MATRIX, PM);
474 // initialize back buffer to all white -- modulation darkens areas where cloud particles
475 // absorb light, and lightens it where they scatter light in the forward direction.
476 glClearColor(1, 1, 1, 1);
477 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
479 float rPixelsPerLength = s_iShadeResolution / (2 * _boundingBox.GetRadius());
481 // the solid angle over which we will sample forward-scattered light.
482 float rSolidAngle = 0.09;
485 for (ParticleIterator iter = _particles.begin(); iter != _particles.end(); ++iter, ++i)
487 Vec3f vecParticlePos = (*iter)->GetPosition();
489 Vec3f vecOffset(vecLightPos);
490 vecOffset -= vecParticlePos;
492 // compute the pixel area to read back in order to integrate the illumination of the particle
493 // over a constant solid angle.
494 float rDistance = fabs(cam.ViewDir() * vecOffset) - rNearDist;
496 float rArea = rSolidAngle * rDistance * rDistance;
497 int iPixelDim = sqrt(rArea) * rPixelsPerLength;
498 int iNumPixels = iPixelDim * iPixelDim;
505 // the scale factor to convert the read back pixel colors to an average illumination of the area.
506 float rColorScaleFactor = rSolidAngle / (iNumPixels * 255.0f);
508 unsigned char *c = new unsigned char[4 * iNumPixels];
512 // find the position in the buffer to which the particle position projects.
513 if (!gluProject(vecParticlePos.x, vecParticlePos.y, vecParticlePos.z,
515 &(vecWinPos.x), &(vecWinPos.y), &(vecWinPos.z)))
517 FAIL_RETURN_MSG(SKYRESULT_FAIL,
518 "Error: SkyCloud::Illuminate(): failed to project particle position.");
521 // offset the projected window position by half the size of the readback region.
522 vecWinPos.x -= 0.5 * iPixelDim;
523 if (vecWinPos.x < 0) vecWinPos.x = 0;
524 vecWinPos.y -= 0.5 * iPixelDim;
525 if (vecWinPos.y < 0) vecWinPos.y = 0;
527 // read back illumination of this particle from the buffer.
528 glReadBuffer(GL_BACK);
529 glReadPixels(vecWinPos.x, vecWinPos.y, iPixelDim, iPixelDim, GL_RGBA, GL_UNSIGNED_BYTE, c);
531 // scattering coefficient vector.
532 Vec4f vecScatter(s_rScatterFactor, s_rScatterFactor, s_rScatterFactor, 1);
534 // add up the read back pixels (only need one component -- its grayscale)
536 for (int k = 0; k < 4 * iNumPixels; k+=4)
540 // compute the amount of light scattered to this particle by particles closer to the light.
541 // this is the illumination over the solid angle that we measured (using glReadPixels) times
542 // the scattering coefficient (vecScatter);
543 Vec4f vecScatteredAmount(iSum * rColorScaleFactor,
544 iSum * rColorScaleFactor,
545 iSum * rColorScaleFactor,
546 1 - s_rTransparency);
547 vecScatteredAmount &= vecScatter;
549 // the color of th particle (iter) contributed by this light source (pLight) is the
550 // scattered light from the part of the cloud closer to the light, times the diffuse color
551 // of the light source. The alpha is 1 - the uniform transparency of all particles (modulated
552 // by the splat texture).
553 Vec4f vecColor = vecScatteredAmount;
554 vecColor &= pLight->GetDiffuse();
555 vecColor.w = 1 - s_rTransparency;
557 // add this color to the list of lit colors for the particle. The contribution from each light
558 // is kept separate because the phase function we apply at runtime depends on the light vector
559 // for each light source separately. This view-dependent effect is impossible without knowing
560 // the amount of light contributed for each light. This, of course, assumes the clouds will
561 // be lit by a reasonably small number of lights (The sun plus some simulation of light reflected
562 // from the sky and / or ground.) This technique works very well for simulating anisotropic
563 // illumination by skylight.
566 (*iter)->SetBaseColor(s_pMaterial->GetAmbient());
567 (*iter)->ClearLitColors();
568 (*iter)->AddLitColor(vecColor);
572 (*iter)->AddLitColor(vecColor);
575 // the following computation (scaling of the scattered amount by the phase function) is done
576 // after the lit color is stored so we don't add the scattering to this particle twice.
577 vecScatteredAmount *= 1.5; // rayleigh scattering phase function for angle of zero or 180 = 1.5!
580 if (vecScatteredAmount.x > 1) vecScatteredAmount.x = 1;
581 if (vecScatteredAmount.y > 1) vecScatteredAmount.y = 1;
582 if (vecScatteredAmount.z > 1) vecScatteredAmount.z = 1;
583 vecScatteredAmount.w = 1 - s_rTransparency;
585 vecScatteredAmount.x = 0.50; vecScatteredAmount.y = 0.60; vecScatteredAmount.z = 0.70;
587 // Draw the particle as a texture billboard. Use the scattered light amount as the color to
588 // simulate forward scattering of light by this particle.
590 DrawQuad(vecParticlePos, cam.X * (*iter)->GetRadius(), cam.Y * (*iter)->GetRadius(), vecScatteredAmount);
593 //glutSwapBuffers(); // Uncomment this swap buffers to visualize cloud illumination computation.
596 // Note: here we could optionally store the current back buffer as a shadow image
597 // to be projected from the light position onto the scene. This way we can have clouds shadow
600 // restore matrix stack and viewport.
601 glMatrixMode(GL_PROJECTION);
603 glMatrixMode(GL_MODELVIEW);
605 glViewport(iOldVP[0], iOldVP[1], iOldVP[2], iOldVP[3]);
611 //------------------------------------------------------------------------------
612 // Function : SkyCloud::CopyBoundingVolume
614 //------------------------------------------------------------------------------
616 * @fn SkyCloud::CopyBoundingVolume() const
617 * @brief Returns a new copy of the SkyMinMaxBox for this cloud.
619 SkyMinMaxBox* SkyCloud::CopyBoundingVolume() const
621 SkyMinMaxBox *pBox = new SkyMinMaxBox();
622 pBox->SetMax(_boundingBox.GetMax());
623 pBox->SetMin(_boundingBox.GetMin());
627 SKYRESULT SkyCloud::Load(const SkyArchive &archive,
628 float rScale, /* = 1.0f */
629 double latitude, double longitude )
631 unsigned int iNumParticles;
632 Vec3f vecCenter = Vec3f::ZERO;
635 //archive.FindVec3f("CldCenter", &vecCenter);
636 //archive.FindFloat32("CldRadius", &rRadius);
638 //_boundingBox.SetMin(vecCenter - Vec3f(rRadius, rRadius, rRadius));
639 //_boundingBox.SetMax(vecCenter + Vec3f(rRadius, rRadius, rRadius));
641 archive.FindUInt32("CldNumParticles", &iNumParticles);
642 _ulEndianSwap(&iNumParticles);
645 archive.FindVec3f("CldCenter", &vecCenter);
646 _ulEndianSwap((unsigned int*)&vecCenter.x);
647 _ulEndianSwap((unsigned int*)&vecCenter.y);
648 _ulEndianSwap((unsigned int*)&vecCenter.z);
650 Vec3f *pParticlePositions = new Vec3f[iNumParticles];
651 float *pParticleRadii = new float[iNumParticles];
652 Vec4f *pParticleColors = new Vec4f[iNumParticles];
654 unsigned int iNumBytes;
655 archive.FindData("CldParticlePositions", ANY_TYPE, (void**const)&pParticlePositions, &iNumBytes);
656 archive.FindData("CldParticleRadii", ANY_TYPE, (void**const)&pParticleRadii, &iNumBytes);
657 archive.FindData("CldParticleColors", ANY_TYPE, (void**const)&pParticleColors, &iNumBytes);
659 for (unsigned int i = 0; i < iNumParticles; ++i)
662 _ulEndianSwap((unsigned int*)&pParticlePositions[i].x);
663 _ulEndianSwap((unsigned int*)&pParticlePositions[i].y);
664 _ulEndianSwap((unsigned int*)&pParticlePositions[i].z);
666 _ulEndianSwap((unsigned int*)&pParticleRadii[i]);
668 _ulEndianSwap((unsigned int*)&pParticleColors[i].x);
669 _ulEndianSwap((unsigned int*)&pParticleColors[i].y);
670 _ulEndianSwap((unsigned int*)&pParticleColors[i].z);
671 _ulEndianSwap((unsigned int*)&pParticleColors[i].w);
674 SkyCloudParticle *pParticle = new SkyCloudParticle((pParticlePositions[i] + vecCenter) * rScale,
675 pParticleRadii[i] * rScale,
677 _boundingBox.AddPoint(pParticle->GetPosition());
679 _particles.push_back(pParticle);
681 // this is just a bad hack to align cloud field from skyworks with local horizon at KSFO
682 // this "almost" works not quite the right solution okay to get some up and running
683 // we need to develop our own scheme for loading and positioning clouds
690 // clouds sit in the y-z plane and x-axis is the vertical cloud height
693 // rotate the cloud field about the fgfs z-axis based on initial longitude
697 float phi = longitude / 57.29578;
698 float one_min_cos = 1 - cos(phi);
701 cos(phi) + one_min_cos*ex*ex, one_min_cos*ex*ey - ez*sin(phi), one_min_cos*ex*ez + ey*sin(phi),
702 one_min_cos*ex*ey + ez*sin(phi), cos(phi) + one_min_cos*ey*ey, one_min_cos*ey*ez - ex*sin(phi),
703 one_min_cos*ex*ez - ey*sin(phi), one_min_cos*ey*ez + ex*sin(phi), cos(phi) + one_min_cos*ez*ez );
707 // okay now that let's rotate about a vector for latitude where longitude forms the
708 // components of a unit vector in the x-y plane
709 ex = sin( longitude / 57.29578 );
710 ey = -cos( longitude / 57.29578 );
712 phi = latitude / 57.29578;
713 one_min_cos = 1 - cos(phi);
716 cos(phi) + one_min_cos*ex*ex, one_min_cos*ex*ey - ez*sin(phi), one_min_cos*ex*ez + ey*sin(phi),
717 one_min_cos*ex*ey + ez*sin(phi), cos(phi) + one_min_cos*ey*ey, one_min_cos*ey*ez - ex*sin(phi),
718 one_min_cos*ex*ez - ey*sin(phi), one_min_cos*ey*ez + ex*sin(phi), cos(phi) + one_min_cos*ez*ez );
721 // need to calculate an offset to place the clouds at ~3000 feet MSL ATM this is an approximation
722 // to move the clouds to some altitude above sea level. At some locations this could be underground
723 // will need a better scheme to position clouds per user preferences
724 float cloud_level_msl = 3000.0f;
726 float x_offset = ex * cloud_level_msl;
727 float y_offset = ey * cloud_level_msl;
728 float z_offset = cloud_level_msl * 0.5;
729 moveit.Set( x_offset, y_offset, z_offset );
737 //------------------------------------------------------------------------------
738 // Function : SkyCloud::Save
740 //------------------------------------------------------------------------------
742 * @fn SkyCloud::Save(SkyArchive &archive) const
743 * @brief Saves the cloud data to @a archive.
745 * @todo <WRITE EXTENDED SkyCloud::Save FUNCTION DOCUMENTATION>
747 SKYRESULT SkyCloud::Save(SkyArchive &archive) const
749 SkyArchive myArchive("Cloud");
750 //myArchive.AddVec3f("CldCenter", _center);
751 //myArchive.AddFloat32("CldRadius", _boundingBox.GetRadius());
752 myArchive.AddUInt32("CldNumParticles", _particles.size());
755 Vec3f *pParticlePositions = new Vec3f[_particles.size()];
756 float *pParticleRadii = new float[_particles.size()];
757 Vec4f *pParticleColors = new Vec4f[_particles.size()];
761 for (ParticleConstIterator iter = _particles.begin(); iter != _particles.end(); ++iter, ++i)
763 pParticlePositions[i] = (*iter)->GetPosition(); // position around origin
764 pParticleRadii[i] = (*iter)->GetRadius();
765 pParticleColors[i] = (*iter)->GetBaseColor();
768 myArchive.AddData("CldParticlePositions",
774 myArchive.AddData("CldParticleRadii",
780 myArchive.AddData("CldParticleColors",
786 archive.AddArchive(myArchive);
792 //------------------------------------------------------------------------------
793 // Function : SkyCloud::Rotate
795 //------------------------------------------------------------------------------
797 * @fn SkyCloud::Rotate(const Mat33f& rot)
798 * @brief @todo <WRITE BRIEF SkyCloud::Rotate DOCUMENTATION>
800 * @todo <WRITE EXTENDED SkyCloud::Rotate FUNCTION DOCUMENTATION>
802 void SkyCloud::Rotate(const Mat33f& rot)
804 _boundingBox.Clear();
805 for (int i = 0; i < _particles.size(); ++i)
807 _particles[i]->SetPosition(rot * _particles[i]->GetPosition());
808 _boundingBox.AddPoint(_particles[i]->GetPosition());
813 //------------------------------------------------------------------------------
814 // Function : SkyCloud::Translate
816 //------------------------------------------------------------------------------
818 * @fn SkyCloud::Translate(const Vec3f& trans)
819 * @brief @todo <WRITE BRIEF SkyCloud::Translate DOCUMENTATION>
821 * @todo <WRITE EXTENDED SkyCloud::Translate FUNCTION DOCUMENTATION>
823 void SkyCloud::Translate(const Vec3f& trans)
825 for (int i = 0; i < _particles.size(); ++i)
827 _particles[i]->SetPosition(_particles[i]->GetPosition() + trans);
829 _boundingBox.SetMax(_boundingBox.GetMax() + trans);
830 _boundingBox.SetMin(_boundingBox.GetMin() + trans);
834 //------------------------------------------------------------------------------
835 // Function : SkyCloud::Scale
837 //------------------------------------------------------------------------------
839 * @fn SkyCloud::Scale(const float scale)
840 * @brief @todo <WRITE BRIEF SkyCloud::Scale DOCUMENTATION>
842 * @todo <WRITE EXTENDED SkyCloud::Scale FUNCTION DOCUMENTATION>
844 void SkyCloud::Scale(const float scale)
846 _boundingBox.Clear();
847 for (int i = 0; i < _particles.size(); ++i)
849 _particles[i]->SetPosition(_particles[i]->GetPosition() * scale);
850 _boundingBox.AddPoint(_particles[i]->GetPosition());
855 //------------------------------------------------------------------------------
856 // Function : SkyCloud::_SortParticles
858 //------------------------------------------------------------------------------
860 * @fn SkyCloud::_SortParticles(const Vec3f& vecViewDir, const Vec3f& sortPoint, SortDirection dir)
861 * @brief Sorts the cloud particles in the direction specified by @a dir.
863 * @vecSortPoint is assumed to already be transformed into the basis space of the cloud.
865 void SkyCloud::_SortParticles(const Vec3f& vecViewDir,
866 const Vec3f& vecSortPoint,
870 for (int i = 0; i < _particles.size(); ++i)
872 partPos = _particles[i]->GetPosition();
873 partPos -= vecSortPoint;
874 _particles[i]->SetSquareSortDistance(partPos * vecViewDir);//partPos.LengthSqr());
879 case SKY_CLOUD_SORT_TOWARD:
880 std::sort(_particles.begin(), _particles.end(), _towardComparator);
882 case SKY_CLOUD_SORT_AWAY:
883 std::sort(_particles.begin(), _particles.end(), _awayComparator);
892 //------------------------------------------------------------------------------
893 // Function : EvalHermite
895 //------------------------------------------------------------------------------
897 * EvalHermite(float pA, float pB, float vA, float vB, float u)
898 * @brief Evaluates Hermite basis functions for the specified coefficients.
900 inline float EvalHermite(float pA, float pB, float vA, float vB, float u)
902 float u2=(u*u), u3=u2*u;
903 float B0 = 2*u3 - 3*u2 + 1;
904 float B1 = -2*u3 + 3*u2;
905 float B2 = u3 - 2*u2 + u;
907 return( B0*pA + B1*pB + B2*vA + B3*vB );
910 // NORMALIZED GAUSSIAN INTENSITY MAP (N must be a power of 2)
912 //------------------------------------------------------------------------------
913 // Function : CreateGaussianMap
915 //------------------------------------------------------------------------------
917 * CreateGaussianMap(int N)
919 * Creates a 2D gaussian image using a hermite surface.
921 unsigned char* CreateGaussianMap(int N)
923 float *M = new float[2*N*N];
924 unsigned char *B = new unsigned char[4*N*N];
930 for (int y=0; y<N; y++, Y+=Incr)
934 for (int x=0; x<N; x++, X+=Incr, i+=2, j+=4)
936 Dist = (float)sqrt(X*X+Y2);
938 M[i+1] = M[i] = EvalHermite(0.4f,0,0,0,Dist);// * (1 - noise);
939 B[j+3] = B[j+2] = B[j+1] = B[j] = (unsigned char)(M[i] * 255);
942 SAFE_DELETE_ARRAY(M);
947 //------------------------------------------------------------------------------
948 // Function : SkyCloud::_CreateSplatTexture
950 //------------------------------------------------------------------------------
952 * @fn SkyCloud::_CreateSplatTexture(unsigned int iResolution)
953 * @brief Creates the texture map used for cloud particles.
955 void SkyCloud::_CreateSplatTexture(unsigned int iResolution)
957 unsigned char *splatTexture = CreateGaussianMap(iResolution);
959 TextureManager::InstancePtr()->Create2DTextureObject(texture, iResolution, iResolution,
960 GL_RGBA, splatTexture);
962 s_pMaterial->SetTexture(0, GL_TEXTURE_2D, texture);
963 s_pShadeMaterial->SetTexture(0, GL_TEXTURE_2D, texture);
964 s_pMaterial->SetTextureParameter(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
965 s_pShadeMaterial->SetTextureParameter(0, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
966 s_pMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
967 s_pShadeMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
968 s_pMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
969 s_pShadeMaterial->SetTextureParameter(0, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
970 s_pMaterial->EnableTexture(0, true);
971 s_pShadeMaterial->EnableTexture(0, true);
973 SAFE_DELETE_ARRAY(splatTexture);
977 //------------------------------------------------------------------------------
978 // Function : SkyCloud::_PhaseFunction
980 //------------------------------------------------------------------------------
982 * @fn SkyCloud::_PhaseFunction(const Vec3f& vecLightDir, const Vec3f& vecViewDir)
983 * @brief Computes the phase (scattering) function of the given light and view directions.
985 * A phase function is a transfer function that determines, for any angle between incident
986 * and outgoing directions, how much of the incident light intensity will be
987 * scattered in the outgoing direction. For example, scattering by very small
988 * particles such as those found in clear air, can be approximated using <i>Rayleigh
989 * scattering</i>. The phase function for Rayleigh scattering is
990 * p(q) = 0.75*(1 + cos<sup>2</sup>(q)), where q is the angle between incident
991 * and scattered directions. Scattering by larger particles is more complicated.
992 * It is described by Mie scattering theory. Cloud particles are more in the regime
993 * of Mie scattering than Rayleigh scattering. However, we obtain good visual
994 * results by using the simpler Rayleigh scattering phase function as an approximation.
996 float SkyCloud::_PhaseFunction(const Vec3f& vecLightDir, const Vec3f& vecViewDir)
998 float rCosAlpha = vecLightDir * vecViewDir;
999 return .75f * (1 + rCosAlpha * rCosAlpha); // rayleigh scattering = (3/4) * (1+cos^2(alpha))