Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members

render.cpp

Go to the documentation of this file.
00001 // render.cpp
00002 //
00003 // Copyright (C) 2001-2004, Chris Laurel <claurel@shatters.net>
00004 //
00005 // This program is free software; you can redistribute it and/or
00006 // modify it under the terms of the GNU General Public License
00007 // as published by the Free Software Foundation; either version 2
00008 // of the License, or (at your option) any later version.
00009 
00010 #include <algorithm>
00011 #include <cstdio>
00012 #include <cstring>
00013 #include <cassert>
00014 
00015 #ifndef _WIN32
00016 #ifndef MACOSX_PB
00017 #include <config.h>
00018 #endif
00019 #endif /* _WIN32 */
00020 
00021 #include <celutil/debug.h>
00022 #include <celmath/frustum.h>
00023 #include <celmath/distance.h>
00024 #include <celmath/intersect.h>
00025 #include <celutil/utf8.h>
00026 #include <celutil/util.h>
00027 #include "gl.h"
00028 #include "astro.h"
00029 #include "glext.h"
00030 #include "vecgl.h"
00031 #include "glshader.h"
00032 #include "shadermanager.h"
00033 #include "spheremesh.h"
00034 #include "lodspheremesh.h"
00035 #include "model.h"
00036 #include "regcombine.h"
00037 #include "vertexprog.h"
00038 #include "texmanager.h"
00039 #include "meshmanager.h"
00040 #include "render.h"
00041 
00042 using namespace std;
00043 
00044 #define FOV           45.0f
00045 #define NEAR_DIST      0.5f
00046 #define FAR_DIST       1.0e9f
00047 
00048 // This should be in the GL headers, but where?
00049 #ifndef GL_COLOR_SUM_EXT
00050 #define GL_COLOR_SUM_EXT 0x8458
00051 #endif
00052 
00053 static const float  STAR_DISTANCE_LIMIT  = 1.0e6f;
00054 static const double DSO_DISTANCE_LIMIT   = 1.8e9;
00055 static const int REF_DISTANCE_TO_SCREEN  = 400; //[mm]
00056 
00057 static const float  X0_SOL = astro::AUtoKilometers(5.0f);
00058 
00059 static const int StarVertexListSize = 1024;
00060 
00061 // Fractional pixel offset used when rendering text as texture mapped
00062 // quads to ensure consistent mapping of texels to pixels.
00063 static const float PixelOffset = 0.125f;
00064 
00065 // These two values constrain the near and far planes of the view frustum
00066 // when rendering planet and object meshes.  The near plane will never be
00067 // closer than MinNearPlaneDistance, and the far plane is set so that far/near
00068 // will not exceed MaxFarNearRatio.
00069 static const float MinNearPlaneDistance = 0.0001f; // km
00070 static const float MaxFarNearRatio      = 2000.0f;
00071 
00072 static const float RenderDistance       = 50.0f;
00073 
00074 static const float MaxScaledDiscStarSize = 8.0f;
00075 
00076 
00077 // The minimum apparent size of an objects orbit in pixels before we display
00078 // a label for it.  This minimizes label clutter.
00079 static const float MinOrbitSizeForLabel = 20.0f;
00080 
00081 // The minimum apparent size of a surface feature in pixels before we display
00082 // a label for it.
00083 static const float MinFeatureSizeForLabel = 20.0f;
00084 
00085 // Static meshes and textures used by all instances of Simulation
00086 
00087 static bool commonDataInitialized = false;
00088 
00089 static LODSphereMesh* lodSphere = NULL;
00090 
00091 static Texture* normalizationTex = NULL;
00092 
00093 static Texture* starTex = NULL;
00094 static Texture* glareTex = NULL;
00095 static Texture* shadowTex = NULL;
00096 
00097 // Shadow textures are scaled down slightly to leave some extra blank pixels
00098 // near the border.  This keeps axis aligned streaks from appearing on hardware
00099 // that doesn't support clamp to border color.
00100 static const float ShadowTextureScale = 15.0f / 16.0f;
00101 
00102 static Texture* eclipseShadowTextures[4];
00103 static Texture* shadowMaskTexture = NULL;
00104 static Texture* penumbraFunctionTexture = NULL;
00105 
00106 static const Color compassColor(0.4f, 0.4f, 1.0f);
00107 
00108 static const float CoronaHeight = 0.2f;
00109 
00110 static bool buggyVertexProgramEmulation = true;
00111 
00112 struct SphericalCoordLabel
00113 {
00114     string label;
00115     float ra;
00116     float dec;
00117 
00118     SphericalCoordLabel() : ra(0), dec(0) {};
00119     SphericalCoordLabel(float _ra, float _dec) : ra(_ra), dec(_dec)
00120     {
00121     }
00122 };
00123 
00124 static int nCoordLabels = 32;
00125 static SphericalCoordLabel* coordLabels = NULL;
00126 
00127 static const int MaxSkyRings = 32;
00128 static const int MaxSkySlices = 180;
00129 static const int MinSkySlices = 30;
00130 
00131 
00132 Renderer::Renderer() :
00133     context(0),
00134     windowWidth(0),
00135     windowHeight(0),
00136     fov(FOV),
00137     screenDpi(96),
00138     corrFac(1.12f),
00139     faintestAutoMag45deg(7.0f),
00140     renderMode(GL_FILL),
00141     labelMode(NoLabels),
00142     renderFlags(ShowStars | ShowPlanets),
00143     orbitMask(Body::Planet | Body::Moon),
00144     ambientLightLevel(0.1f),
00145     fragmentShaderEnabled(false),
00146     vertexShaderEnabled(false),
00147     brightnessBias(0.0f),
00148     saturationMagNight(1.0f),
00149     saturationMag(1.0f),
00150     starStyle(FuzzyPointStars),
00151     starVertexBuffer(NULL),
00152     useVertexPrograms(false),
00153     useRescaleNormal(false),
00154     usePointSprite(false),
00155     textureResolution(medres),
00156     minOrbitSize(MinOrbitSizeForLabel),
00157     distanceLimit(1.0e6f),
00158     minFeatureSize(MinFeatureSizeForLabel),
00159     locationFilter(~0),
00160     colorTemp(NULL)
00161 {
00162     starVertexBuffer = new StarVertexBuffer(2048);
00163     skyVertices = new SkyVertex[MaxSkySlices * (MaxSkyRings + 1)];
00164     skyIndices = new uint32[(MaxSkySlices + 1) * 2 * MaxSkyRings];
00165     skyContour = new SkyContourPoint[MaxSkySlices + 1];
00166     colorTemp = GetStarColorTable(ColorTable_Enhanced);
00167 }
00168 
00169 
00170 Renderer::~Renderer()
00171 {
00172     if (starVertexBuffer != NULL)
00173         delete starVertexBuffer;
00174     delete[] skyVertices;
00175     delete[] skyIndices;
00176     delete[] skyContour;
00177 }
00178 
00179 
00180 Renderer::DetailOptions::DetailOptions() :
00181     ringSystemSections(100),
00182     orbitPathSamplePoints(100),
00183     shadowTextureSize(256),
00184     eclipseTextureSize(128)
00185 {
00186 }
00187 
00188 
00189 static void StarTextureEval(float u, float v, float w,
00190                             unsigned char *pixel)
00191 {
00192     float r = 1 - (float) sqrt(u * u + v * v);
00193     if (r < 0)
00194         r = 0;
00195     else if (r < 0.5f)
00196         r = 2.0f * r;
00197     else 
00198         r = 1;
00199 
00200     int pixVal = (int) (r * 255.99f);
00201     pixel[0] = pixVal;
00202     pixel[1] = pixVal;
00203     pixel[2] = pixVal;
00204 }
00205 
00206 static void GlareTextureEval(float u, float v, float w,
00207                              unsigned char *pixel)
00208 {
00209     float r = 0.9f - (float) sqrt(u * u + v * v);
00210     if (r < 0)
00211         r = 0;
00212 
00213     int pixVal = (int) (r * 255.99f);
00214     pixel[0] = 65;
00215     pixel[1] = 64;
00216     pixel[2] = 65;
00217     pixel[3] = pixVal;
00218 }
00219 
00220 static void ShadowTextureEval(float u, float v, float w,
00221                               unsigned char *pixel)
00222 {
00223     float r = (float) sqrt(u * u + v * v);
00224 
00225     // Leave some white pixels around the edges to the shadow doesn't
00226     // 'leak'.  We'll also set the maximum mip map level for this texture to 3
00227     // so we don't have problems with the edge texels at high mip map levels.
00228     int pixVal = r < 15.0f / 16.0f ? 0 : 255;
00229     pixel[0] = pixVal;
00230     pixel[1] = pixVal;
00231     pixel[2] = pixVal;
00232 }
00233 
00234 
00236 //  between the occluder and sun disc, and the output is the fraction of
00237 //  full brightness.
00238 static void PenumbraFunctionEval(float u, float v, float w,
00239                                  unsigned char *pixel)
00240 {
00241     u = (u + 1.0f) * 0.5f;
00242 
00243     // Using the cube root produces a good visual result
00244     unsigned char pixVal = (unsigned char) (::pow((double) u, 0.33) * 255.99);
00245 
00246     pixel[0] = pixVal;
00247 }
00248 
00249 
00250 // ShadowTextureFunction is a function object for creating shadow textures
00251 // used for rendering eclipses.
00252 class ShadowTextureFunction : public TexelFunctionObject
00253 {
00254 public:
00255     ShadowTextureFunction(float _umbra) : umbra(_umbra) {};
00256     virtual void operator()(float u, float v, float w, unsigned char* pixel);
00257     float umbra;
00258 };
00259 
00260 void ShadowTextureFunction::operator()(float u, float v, float w,
00261                                        unsigned char* pixel)
00262 {
00263     float r = (float) sqrt(u * u + v * v);
00264     int pixVal = 255;
00265 
00266     // Leave some white pixels around the edges to the shadow doesn't
00267     // 'leak'.  We'll also set the maximum mip map level for this texture to 3
00268     // so we don't have problems with the edge texels at high mip map levels.
00269     r = r / (15.0f / 16.0f);
00270     if (r < 1)
00271     {
00272         // The pixel value should depend on the area of the sun which is
00273         // occluded.  We just fudge it here and use the square root of the
00274         // radius.
00275         if (r <= umbra)
00276             pixVal = 0;
00277         else
00278             pixVal = (int) (sqrt((r - umbra) / (1 - umbra)) * 255.99f);
00279     }
00280 
00281     pixel[0] = pixVal;
00282     pixel[1] = pixVal;
00283     pixel[2] = pixVal;
00284 };
00285 
00286 
00287 class ShadowMaskTextureFunction : public TexelFunctionObject
00288 {
00289 public:
00290     ShadowMaskTextureFunction() {};
00291     virtual void operator()(float u, float v, float w, unsigned char* pixel);
00292     float dummy;
00293 };
00294 
00295 void ShadowMaskTextureFunction::operator()(float u, float v, float w,
00296                                            unsigned char* pixel)
00297 {
00298     unsigned char a = u > 0.0f ? 255 : 0;
00299     pixel[0] = a;
00300     pixel[1] = a;
00301     pixel[2] = a;
00302     pixel[3] = a;
00303 }
00304 
00305 
00306 static void IllumMapEval(float x, float y, float z,
00307                          unsigned char* pixel)
00308 {
00309     Vec3f v(x, y, z);
00310     Vec3f u(0, 0, 1);
00311 
00312 #if 0
00313     Vec3f n(0, 0, 1);
00314     // Experimental illumination function
00315     float c = v * n;
00316     if (c < 0.0f)
00317     {
00318         u = v;
00319     }
00320     else
00321     {
00322         c = (1 - ((1 - c))) * 1.0f;
00323         u = v + (c * n);
00324         u.normalize();
00325     }
00326 #else
00327     u = v;
00328 #endif
00329 
00330     pixel[0] = 128 + (int) (127 * u.x);
00331     pixel[1] = 128 + (int) (127 * u.y);
00332     pixel[2] = 128 + (int) (127 * u.z);
00333 }
00334 
00335 bool operator<(const RenderListEntry& a, const RenderListEntry& b)
00336 {
00337     // This comparison functions tries to determine which of two objects is
00338     // closer to the viewer.  Looking just at the distances of the centers
00339     // is not enough, nor is comparing distances to the bounding spheres.
00340     // Here we trace a ray from the viewer to the center of the smaller
00341     // of the two objects and see which object it intersects first.  If the
00342     // ray doesn't intersect the larger object at all, it's safe to use
00343     // the distance to bounding sphere test.
00344     if (a.radius < b.radius)
00345     {
00346         Vec3f dir = a.position - Point3f(0.0f, 0.0f, 0.0f);
00347         float distance;
00348         dir.normalize();
00349         if (testIntersection(Ray3f(Point3f(0.0f, 0.0f, 0.0f), dir),
00350                              Spheref(b.position, b.radius),
00351                              distance))
00352         {
00353             return a.distance - a.radius < distance;
00354         }
00355     }
00356     else
00357     {
00358         Vec3f dir = b.position - Point3f(0.0f, 0.0f, 0.0f);
00359         float distance;
00360         dir.normalize();
00361         if (testIntersection(Ray3f(Point3f(0.0f, 0.0f, 0.0f), dir),
00362                              Spheref(a.position, a.radius),
00363                              distance))
00364         {
00365             return distance < b.distance - b.radius;
00366         }
00367     }
00368 
00369     return a.distance - a.radius < b.distance - b.radius;
00370 }
00371 
00372 
00373 bool operator<(const Renderer::Label& a, const Renderer::Label& b)
00374 {
00375     return a.position.z > b.position.z;
00376 }
00377 
00378 
00379 bool Renderer::init(GLContext* _context,
00380                     int winWidth, int winHeight,
00381                     DetailOptions& _detailOptions)
00382 {
00383     context = _context;
00384     detailOptions = _detailOptions;
00385 
00386     // Initialize static meshes and textures common to all instances of Renderer
00387     if (!commonDataInitialized)
00388     {
00389         lodSphere = new LODSphereMesh();
00390 
00391         starTex = CreateProceduralTexture(64, 64, GL_RGB, StarTextureEval);
00392 
00393         glareTex = LoadTextureFromFile("textures/flare.jpg");
00394         if (glareTex == NULL)
00395             glareTex = CreateProceduralTexture(64, 64, GL_RGB, GlareTextureEval);
00396 
00397         // Max mipmap level doesn't work reliably on all graphics
00398         // cards.  In particular, Rage 128 and TNT cards resort to software
00399         // rendering when this feature is enabled.  The only workaround is to
00400         // disable mipmapping completely unless texture border clamping is
00401         // supported, which solves the problem much more elegantly than all
00402         // the mipmap level nonsense.
00403         // shadowTex->setMaxMipMapLevel(3);
00404         Texture::AddressMode shadowTexAddress = Texture::EdgeClamp;
00405         Texture::MipMapMode shadowTexMip = Texture::NoMipMaps;
00406         useClampToBorder = context->extensionSupported("GL_ARB_texture_border_clamp");
00407         if (useClampToBorder)
00408         {
00409             shadowTexAddress = Texture::BorderClamp;
00410             shadowTexMip = Texture::DefaultMipMaps;
00411         }
00412         
00413         shadowTex = CreateProceduralTexture(detailOptions.shadowTextureSize,
00414                                             detailOptions.shadowTextureSize,
00415                                             GL_RGB,
00416                                             ShadowTextureEval,
00417                                             shadowTexAddress, shadowTexMip);
00418         shadowTex->setBorderColor(Color::White);
00419 
00420         // Create the eclipse shadow textures
00421         {
00422             for (int i = 0; i < 4; i++)
00423             {
00424                 ShadowTextureFunction func(i * 0.25f);
00425                 eclipseShadowTextures[i] =
00426                     CreateProceduralTexture(detailOptions.eclipseTextureSize,
00427                                             detailOptions.eclipseTextureSize,
00428                                             GL_RGB, func,
00429                                             shadowTexAddress, shadowTexMip);
00430                 if (eclipseShadowTextures[i] != NULL)
00431                 {
00432                     // eclipseShadowTextures[i]->setMaxMipMapLevel(2);
00433                     eclipseShadowTextures[i]->setBorderColor(Color::White);
00434                 }
00435             }
00436         }
00437 
00438         // Create the shadow mask texture
00439         {
00440             ShadowMaskTextureFunction func;
00441             shadowMaskTexture = CreateProceduralTexture(128, 2, GL_RGBA, func);
00442             //shadowMaskTexture->bindName();
00443         }
00444 
00445         // Create a function lookup table in a texture for use with
00446         // fragment program eclipse shadows.
00447         penumbraFunctionTexture = CreateProceduralTexture(512, 1, GL_LUMINANCE,
00448                                                           PenumbraFunctionEval,
00449                                                           Texture::EdgeClamp);
00450 
00451         if (context->extensionSupported("GL_EXT_texture_cube_map"))
00452         {
00453             // normalizationTex = CreateNormalizationCubeMap(64);
00454             normalizationTex = CreateProceduralCubeMap(64, GL_RGB, IllumMapEval);
00455         }
00456 
00457         // Create labels for celestial sphere
00458         {
00459             char buf[10];
00460             int i;
00461 
00462             coordLabels = new SphericalCoordLabel[nCoordLabels];
00463             for (i = 0; i < 12; i++)
00464             {
00465                 coordLabels[i].ra = float(i * 2);
00466                 coordLabels[i].dec = 0;
00467                 sprintf(buf, "%dh", i * 2);
00468                 coordLabels[i].label = string(buf);
00469             }
00470 
00471             coordLabels[12] = SphericalCoordLabel(0, -75);
00472             coordLabels[13] = SphericalCoordLabel(0, -60);
00473             coordLabels[14] = SphericalCoordLabel(0, -45);
00474             coordLabels[15] = SphericalCoordLabel(0, -30);
00475             coordLabels[16] = SphericalCoordLabel(0, -15);
00476             coordLabels[17] = SphericalCoordLabel(0,  15);
00477             coordLabels[18] = SphericalCoordLabel(0,  30);
00478             coordLabels[19] = SphericalCoordLabel(0,  45);
00479             coordLabels[20] = SphericalCoordLabel(0,  60);
00480             coordLabels[21] = SphericalCoordLabel(0,  75);
00481             for (i = 22; i < nCoordLabels; i++)
00482             {
00483                 coordLabels[i].ra = 12;
00484                 coordLabels[i].dec = coordLabels[i - 10].dec;
00485             }
00486 
00487             for (i = 12; i < nCoordLabels; i++)
00488             {
00489                 char buf[10];
00490                 sprintf(buf, "%d", (int) coordLabels[i].dec);
00491                 coordLabels[i].label = string(buf);
00492             }
00493         }
00494 
00495         commonDataInitialized = true;
00496     }
00497 
00498     if (context->extensionSupported("GL_EXT_rescale_normal"))
00499     {
00500         // We need this enabled because we use glScale, but only
00501         // with uniform scale factors.
00502         DPRINTF(1, "Renderer: EXT_rescale_normal supported.\n");
00503         useRescaleNormal = true;
00504         glEnable(GL_RESCALE_NORMAL_EXT);
00505     }
00506 
00507     if (context->extensionSupported("GL_ARB_point_sprite"))
00508     {
00509         DPRINTF(1, "Renderer: point sprites supported.\n");
00510         usePointSprite = true;
00511     }
00512 
00513     // Ugly renderer-specific bug workarounds follow . . .
00514     char* glRenderer = (char*) glGetString(GL_RENDERER);
00515     if (glRenderer != NULL)
00516     {
00517         // Fog is broken with vertex program emulation in most versions of
00518         // the GF 1 and 2 drivers; we need to detect this and disable
00519         // vertex programs which output fog coordinates
00520         if (strstr(glRenderer, "GeForce3") != NULL ||
00521             strstr(glRenderer, "GeForce4") != NULL)
00522         {
00523             buggyVertexProgramEmulation = false;
00524         }
00525 
00526         if (strstr(glRenderer, "Savage4") != NULL ||
00527             strstr(glRenderer, "ProSavage") != NULL)
00528         {
00529             // S3 Savage4 drivers appear to rescale normals without reporting
00530             // EXT_rescale_normal.  Lighting will be messed up unless
00531             // we set the useRescaleNormal flag.
00532             useRescaleNormal = true;
00533         }
00534     }
00535 
00536     // More ugly hacks; according to Matt Craighead at NVIDIA, an NVIDIA
00537     // OpenGL driver that reports version 1.3.1 or greater will have working
00538     // fog in emulated vertex programs.
00539     char* glVersion = (char*) glGetString(GL_VERSION);
00540     if (glVersion != NULL)
00541     {
00542         int major = 0, minor = 0, extra = 0;
00543         int nScanned = sscanf(glVersion, "%d.%d.%d", &major, &minor, &extra);
00544 
00545         if (nScanned >= 2)
00546         {
00547             if (major > 1 || minor > 3 || (minor == 3 && extra >= 1))
00548                 buggyVertexProgramEmulation = false;
00549         }
00550     }
00551 
00552     glLoadIdentity();
00553 
00554     glEnable(GL_CULL_FACE);
00555     glCullFace(GL_BACK);
00556 
00557     glEnable(GL_COLOR_MATERIAL);
00558     glEnable(GL_LIGHTING);
00559     glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
00560 
00561     // LEQUAL rather than LESS required for multipass rendering
00562     glDepthFunc(GL_LEQUAL);
00563 
00564     resize(winWidth, winHeight);
00565 
00566     return true;
00567 }
00568 
00569 
00570 void Renderer::resize(int width, int height)
00571 {
00572     windowWidth = width;
00573     windowHeight = height;
00574     // glViewport(windowWidth, windowHeight);
00575 }
00576 
00577 float Renderer::calcPixelSize(float fovY, float windowHeight)
00578 {
00579     return 2 * (float) tan(degToRad(fovY / 2.0)) / (float) windowHeight;
00580 }
00581 
00582 void Renderer::setFieldOfView(float _fov)
00583 {
00584     fov = _fov;
00585     corrFac = (0.12f * fov/FOV * fov/FOV + 1.0f);
00586 }
00587 
00588 int Renderer::getScreenDpi() const
00589 {
00590     return screenDpi;
00591 }
00592 
00593 void Renderer::setScreenDpi(int _dpi)
00594 {
00595     screenDpi = _dpi;
00596 }
00597 
00598 void Renderer::setFaintestAM45deg(float _faintestAutoMag45deg)
00599 {
00600     faintestAutoMag45deg = _faintestAutoMag45deg;
00601 }
00602 
00603 float Renderer::getFaintestAM45deg()
00604 {
00605     return faintestAutoMag45deg;
00606 }
00607 
00608 unsigned int Renderer::getResolution()
00609 {
00610     return textureResolution;
00611 }
00612 
00613 
00614 void Renderer::setResolution(unsigned int resolution)
00615 {
00616     if (resolution < TEXTURE_RESOLUTION)
00617         textureResolution = resolution;
00618 }
00619 
00620 
00621 TextureFont* Renderer::getFont() const
00622 {
00623     return font;
00624 }
00625 
00626 void Renderer::setFont(TextureFont* txf)
00627 {
00628     font = txf;
00629 }
00630 
00631 void Renderer::setRenderMode(int _renderMode)
00632 {
00633     renderMode = _renderMode;
00634 }
00635 
00636 int Renderer::getRenderFlags() const
00637 {
00638     return renderFlags;
00639 }
00640 
00641 void Renderer::setRenderFlags(int _renderFlags)
00642 {
00643     renderFlags = _renderFlags;
00644 }
00645 
00646 int Renderer::getLabelMode() const
00647 {
00648     return labelMode;
00649 }
00650 
00651 void Renderer::setLabelMode(int _labelMode)
00652 {
00653     labelMode = _labelMode;
00654 }
00655 
00656 int Renderer::getOrbitMask() const
00657 {
00658     return orbitMask;
00659 }
00660 
00661 void Renderer::setOrbitMask(int mask)
00662 {
00663     orbitMask = mask;
00664 }
00665 
00666 
00667 const ColorTemperatureTable*
00668 Renderer::getStarColorTable() const
00669 {
00670     return colorTemp;
00671 }
00672 
00673 
00674 void
00675 Renderer::setStarColorTable(const ColorTemperatureTable* ct)
00676 {
00677     colorTemp = ct;
00678 }
00679 
00680 
00681 void Renderer::addLabelledStar(Star* star, const string& label)
00682 {
00683     labelledStars.push_back(StarLabel(star, label));
00684 }
00685 
00686 
00687 void Renderer::clearLabelledStars()
00688 {
00689     labelledStars.clear();
00690 }
00691 
00692 
00693 float Renderer::getAmbientLightLevel() const
00694 {
00695     return ambientLightLevel;
00696 }
00697 
00698 
00699 void Renderer::setAmbientLightLevel(float level)
00700 {
00701     ambientLightLevel = level;
00702 }
00703 
00704 
00705 float Renderer::getMinimumFeatureSize() const
00706 {
00707     return minFeatureSize;
00708 }
00709 
00710 
00711 void Renderer::setMinimumFeatureSize(float pixels)
00712 {
00713     minFeatureSize = pixels;
00714 }
00715 
00716 
00717 float Renderer::getMinimumOrbitSize() const
00718 {
00719     return minOrbitSize;
00720 }
00721 
00722 // Orbits and labels are only rendered when the orbit of the object
00723 // occupies some minimum number of pixels on screen.
00724 void Renderer::setMinimumOrbitSize(float pixels)
00725 {
00726     minOrbitSize = pixels;
00727 }
00728 
00729 
00730 float Renderer::getDistanceLimit() const
00731 {
00732     return distanceLimit;
00733 }
00734 
00735 
00736 void Renderer::setDistanceLimit(float distanceLimit_)
00737 {
00738     distanceLimit = distanceLimit_;
00739 }
00740 
00741 
00742 bool Renderer::getFragmentShaderEnabled() const
00743 {
00744     return fragmentShaderEnabled;
00745 }
00746 
00747 void Renderer::setFragmentShaderEnabled(bool enable)
00748 {
00749     fragmentShaderEnabled = enable && fragmentShaderSupported();
00750 }
00751 
00752 bool Renderer::fragmentShaderSupported() const
00753 {
00754     return context->bumpMappingSupported();
00755 }
00756 
00757 bool Renderer::getVertexShaderEnabled() const
00758 {
00759     return vertexShaderEnabled;
00760 }
00761 
00762 void Renderer::setVertexShaderEnabled(bool enable)
00763 {
00764     vertexShaderEnabled = enable && vertexShaderSupported();
00765 }
00766 
00767 bool Renderer::vertexShaderSupported() const
00768 {
00769     return useVertexPrograms;
00770 }
00771 
00772 
00773 void Renderer::addLabel(string text,
00774                         Color color,
00775                         const Point3f& pos,
00776                         float depth)
00777 {
00778     double winX, winY, winZ;
00779     int view[4] = { 0, 0, 0, 0 };
00780     view[0] = -windowWidth / 2;
00781     view[1] = -windowHeight / 2;
00782     view[2] = windowWidth;
00783     view[3] = windowHeight;
00784     depth = (float) (pos.x * modelMatrix[2] +
00785                      pos.y * modelMatrix[6] +
00786                      pos.z * modelMatrix[10]);
00787     if (gluProject(pos.x, pos.y, pos.z,
00788                    modelMatrix,
00789                    projMatrix,
00790                    (const GLint*) view,
00791                    &winX, &winY, &winZ) != GL_FALSE)
00792     {
00793         Label l;
00794         l.text = ReplaceGreekLetterAbbr(text.c_str());
00795         l.color = color;
00796         l.position = Point3f((float) winX, (float) winY, -depth);
00797         labels.insert(labels.end(), l);
00798     }
00799 }
00800 
00801 
00802 void Renderer::addSortedLabel(string text, Color color, const Point3f& pos)
00803 {
00804     double winX, winY, winZ;
00805     int view[4] = { 0, 0, 0, 0 };
00806     view[0] = -windowWidth / 2;
00807     view[1] = -windowHeight / 2;
00808     view[2] = windowWidth;
00809     view[3] = windowHeight;
00810     float depth = (float) (pos.x * modelMatrix[2] +
00811                            pos.y * modelMatrix[6] +
00812                            pos.z * modelMatrix[10]);
00813     if (gluProject(pos.x, pos.y, pos.z,
00814                    modelMatrix,
00815                    projMatrix,
00816                    (const GLint*) view,
00817                    &winX, &winY, &winZ) != GL_FALSE)
00818     {
00819         Label l;
00820         l.text = ReplaceGreekLetterAbbr(_(text.c_str()));
00821         l.color = color;
00822         l.position = Point3f((float) winX, (float) winY, -depth);
00823         depthSortedLabels.insert(depthSortedLabels.end(), l);
00824     }
00825 }
00826 
00827 
00828 void Renderer::clearLabels()
00829 {
00830     labels.clear();
00831     depthSortedLabels.clear();
00832 }
00833 
00834 
00835 static void enableSmoothLines()
00836 {
00837     // glEnable(GL_BLEND);
00838     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
00839     glEnable(GL_LINE_SMOOTH);
00840     glLineWidth(1.5f);
00841 }
00842 
00843 static void disableSmoothLines()
00844 {
00845     // glDisable(GL_BLEND);
00846     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
00847     glDisable(GL_LINE_SMOOTH);
00848     glLineWidth(1.0f);
00849 }
00850 
00851 
00852 class OrbitRenderer : public OrbitSampleProc
00853 {
00854 public:
00855     OrbitRenderer() {};
00856     
00857     void sample(double, const Point3d& p)
00858     {
00859         glVertex3f(astro::kilometersToAU((float) p.x * 100),
00860                    astro::kilometersToAU((float) p.y * 100),
00861                    astro::kilometersToAU((float) p.z * 100));
00862         
00863     };
00864 
00865 private:
00866     int dummy;
00867 };
00868 
00869 
00870 class OrbitSampler : public OrbitSampleProc
00871 {
00872 public:
00873     vector<Renderer::OrbitSample>* samples;
00874 
00875     OrbitSampler(vector<Renderer::OrbitSample>* _samples) : samples(_samples) {};
00876     void sample(double t, const Point3d& p)
00877     {
00878         Renderer::OrbitSample samp;
00879         samp.pos = Point3f((float) p.x, (float) p.y, (float) p.z);
00880         samp.t = t;
00881         samples->insert(samples->end(), samp);
00882     };
00883 };
00884 
00885 
00886 void renderOrbitColor(const Body* body, bool selected)
00887 {
00888     if (selected)
00889     {
00890         // Highlight the orbit of the selected object in red
00891         glColor4f(1, 0, 0, 1);
00892     }
00893     else
00894     {
00895         switch (body->getClassification())
00896         {
00897         case Body::Moon:
00898             glColor4f(0.0f, 0.2f, 0.5f, 1.0f);
00899             break;
00900         case Body::Asteroid:
00901             glColor4f(0.35f, 0.2f, 0.0f, 1.0f);
00902             break;
00903         case Body::Comet:
00904             glColor4f(0.0f, 0.5f, 0.5f, 1.0f);
00905             break;
00906         case Body::Spacecraft:
00907             glColor4f(0.4f, 0.4f, 0.4f, 1.0f);
00908             break;
00909         case Body::Planet:
00910         default:
00911             glColor4f(0.0f, 0.4f, 1.0f, 1.0f);
00912             break;
00913         }
00914     }
00915 }
00916 
00917 
00918 void Renderer::renderOrbit(Body* body, double t)
00919 {
00920     vector<OrbitSample>* trajectory = NULL;
00921     for (vector<CachedOrbit*>::const_iterator iter = orbitCache.begin();
00922          iter != orbitCache.end(); iter++)
00923     {
00924         if ((*iter)->body == body)
00925         {
00926             (*iter)->keep = true;
00927             trajectory = &((*iter)->trajectory);
00928             break;
00929         }
00930     }
00931 
00932     // If it's not in the cache already
00933     if (trajectory == NULL)
00934     {
00935         CachedOrbit* orbit = NULL;
00936 
00937         // Search the cache an see if we can reuse an old orbit
00938         for (vector<CachedOrbit*>::const_iterator iter = orbitCache.begin();
00939              iter != orbitCache.end(); iter++)
00940         {
00941             if ((*iter)->body == NULL)
00942             {
00943                 orbit = *iter;
00944                 orbit->trajectory.clear();
00945                 break;
00946             }
00947         }
00948 
00949         // If we can't reuse an old orbit, allocate a new one.
00950         bool reuse = true;
00951         if (orbit == NULL)
00952         {
00953             orbit = new CachedOrbit();
00954             reuse = false;
00955         }
00956 
00957         double startTime = t;
00958         int nSamples = detailOptions.orbitPathSamplePoints;
00959 
00960         // Adjust the number of samples used for aperiodic orbits--these aren't
00961         // true orbits, but are sampled trajectories, generally of spacecraft.
00962         // Better control is really needed--some sort of adaptive sampling would
00963         // be ideal.
00964         if (!body->getOrbit()->isPeriodic())
00965         {
00966             double begin = 0.0, end = 0.0;
00967             body->getOrbit()->getValidRange(begin, end);
00968 
00969             if (begin != end)
00970             {
00971                 startTime = begin;
00972                 nSamples = (int) (body->getOrbit()->getPeriod() * 100.0);
00973                 nSamples = max(min(nSamples, 1000), 100);
00974             }
00975         }
00976 
00977         orbit->body = body;
00978         orbit->keep = true;
00979         OrbitSampler sampler(&orbit->trajectory);
00980         body->getOrbit()->sample(startTime,
00981                                  body->getOrbit()->getPeriod(),
00982                                  nSamples,
00983                                  sampler);
00984         trajectory = &orbit->trajectory;
00985 
00986         // If the orbit is new, put it back in the cache
00987         if (!reuse)
00988             orbitCache.insert(orbitCache.end(), orbit);
00989     }
00990 
00991     // Actually render the orbit
00992     if (body->getOrbit()->isPeriodic())
00993         glBegin(GL_LINE_LOOP);
00994     else
00995         glBegin(GL_LINE_STRIP);
00996 
00997     if ((renderFlags & ShowPartialTrajectories) == 0 ||
00998         body->getOrbit()->isPeriodic())
00999     {
01000         // Show the complete trajectory
01001         for (vector<OrbitSample>::const_iterator p = trajectory->begin();
01002              p != trajectory->end(); p++)
01003         {
01004             glVertex3f(astro::kilometersToAU(p->pos.x),
01005                        astro::kilometersToAU(p->pos.y),
01006                        astro::kilometersToAU(p->pos.z));
01007         }
01008     }
01009     else
01010     {
01011         vector<OrbitSample>::const_iterator p;
01012 
01013         // Show the portion of the trajectory travelled up to this point
01014         for (p = trajectory->begin(); p != trajectory->end() && p->t < t; p++)
01015         {
01016             glVertex3f(astro::kilometersToAU(p->pos.x),
01017                        astro::kilometersToAU(p->pos.y),
01018                        astro::kilometersToAU(p->pos.z));
01019         }
01020 
01021         // If we're midway through a non-periodic trajectory, we will need
01022         // to render a partial orbit segment.
01023         if (p != trajectory->end())
01024         {
01025             Point3d pos = body->getOrbit()->positionAtTime(t);
01026             glVertex3f((float) astro::kilometersToAU(pos.x),
01027                        (float) astro::kilometersToAU(pos.y),
01028                        (float) astro::kilometersToAU(pos.z));
01029         }
01030     }
01031 
01032 
01033     glEnd();
01034 }
01035 
01036 
01037 void Renderer::renderOrbits(PlanetarySystem* planets,
01038                             const Selection& sel,
01039                             double t,
01040                             const Point3d& observerPos,
01041                             const Point3d& center)
01042 {
01043     if (planets == NULL)
01044         return;
01045 
01046     double distance = (center - observerPos).length();
01047 
01048     int nBodies = planets->getSystemSize();
01049     for (int i = 0; i < nBodies; i++)
01050     {
01051         Body* body = planets->getBody(i);
01052             
01053         // Only show orbits for major bodies or selected objects
01054         if ((body->getClassification() & orbitMask) != 0 || body == sel.body())
01055         {
01056             renderOrbitColor(body, body == sel.body());
01057 
01058             float orbitRadiusInPixels =
01059                 (float) (body->getOrbit()->getBoundingRadius() /
01060                          (distance * pixelSize));
01061             if (orbitRadiusInPixels > minOrbitSize)
01062             {
01063                 float farDistance = 
01064                     (float) (body->getOrbit()->getBoundingRadius() + distance);
01065                 farDistance = astro::kilometersToAU(farDistance);
01066 
01067                 // Set up the projection matrix so that the far plane is
01068                 // distant enough that the orbit won't be clipped.
01069                 glMatrixMode(GL_PROJECTION);
01070                 glLoadIdentity();
01071                 gluPerspective(fov,
01072                                (float) windowWidth / (float) windowHeight,
01073                                max( farDistance * 1e-6f, (float)(1e-5/2./tan(fov*3.14159/360.0)) ),
01074                                farDistance * 1.1f );
01075                 glMatrixMode(GL_MODELVIEW);
01076                 renderOrbit(body, t);
01077 
01078                 if (body->getSatellites() != NULL)
01079                 {
01080                     Point3d localPos = body->getOrbit()->positionAtTime(t);
01081                     Quatd rotation =
01082                         Quatd::yrotation(body->getRotationElements().ascendingNode) *
01083                         Quatd::xrotation(body->getRotationElements().obliquity);
01084                     double scale = astro::kilometersToAU(1.0);
01085                     glPushMatrix();
01086                     glTranslated(localPos.x * scale,
01087                                  localPos.y * scale,
01088                                  localPos.z * scale);
01089                     glRotate(rotation);
01090                     renderOrbits(body->getSatellites(), sel, t,
01091                                  observerPos,
01092                                  body->getHeliocentricPosition(t));
01093                     glPopMatrix();
01094                 }
01095             }
01096         }
01097     }
01098 }
01099 
01100 
01101 void transformOrbits(const PlanetarySystem *system)
01102 {
01103     if (system->getPrimaryBody())
01104     {
01105         const Body *body = system->getPrimaryBody();
01106         transformOrbits(body->getSystem());
01107         Quatd rotation =
01108             Quatd::yrotation(body->getRotationElements().ascendingNode) *
01109             Quatd::xrotation(body->getRotationElements().obliquity);
01110         glRotate(rotation);      
01111     } 
01112 }
01113 
01114 
01115 void Renderer::renderForegroundOrbits(const PlanetarySystem* system,
01116                                       const Point3f &center, // km
01117                                       float distance, // km
01118                                       float discSizeInPixels,
01119                                       const Selection& sel,
01120                                       double t)
01121 {
01122     // Render orbit paths
01123   
01124     if ((renderFlags & ShowOrbits) == 0 || orbitMask == 0)
01125         return;
01126     if (system == NULL)
01127         return;
01128     if (discSizeInPixels < minOrbitSize)
01129         return;
01130 
01131     distance = astro::kilometersToAU(distance);
01132     double scale = astro::kilometersToAU(1.0);
01133     glPushMatrix();
01134     glTranslated(center.x * scale,
01135                  center.y * scale,
01136                  center.z * scale);
01137 
01138     glDisable(GL_LIGHTING);
01139     glDisable(GL_TEXTURE_2D);
01140     if ((renderFlags & ShowSmoothLines) != 0)
01141         enableSmoothLines();
01142     transformOrbits(system);
01143     
01144     glMatrixMode(GL_PROJECTION);
01145     glLoadIdentity();
01146     gluPerspective(fov,
01147                    (float) windowWidth / (float) windowHeight,
01148                    max( distance * 1e-6f, 
01149                         (float)(1e-5/2./tan(fov*3.14159/360.0)) ),
01150                    distance);
01151     
01152     glMatrixMode(GL_MODELVIEW);
01153 
01154     int nBodies = system->getSystemSize();
01155     for (int i = 0; i < nBodies; i++)
01156     {
01157         Body* body = system->getBody(i);
01158 
01159         // Only show orbits for major bodies or selected objects
01160         if ((body->getClassification() & orbitMask) == 0 && 
01161             body != sel.body())
01162             continue;
01163         renderOrbitColor(body, body == sel.body());
01164         renderOrbit(body, t);
01165     }
01166 
01167     if ((renderFlags & ShowSmoothLines) != 0)
01168         disableSmoothLines();
01169     glPopMatrix();
01170 }
01171 
01172 
01173 // Convert a position in the universal coordinate system to astrocentric
01174 // coordinates, taking into account possible orbital motion of the star.
01175 static Point3d astrocentricPosition(const UniversalCoord& pos,
01176                                     const Star& star,
01177                                     double t)
01178 {
01179     UniversalCoord starPos = star.getPosition(t);
01180 
01181     Vec3d v = pos - starPos;
01182     return Point3d(astro::microLightYearsToKilometers(v.x),
01183                    astro::microLightYearsToKilometers(v.y),
01184                    astro::microLightYearsToKilometers(v.z));
01185 }
01186 
01187 
01188 void Renderer::autoMag(float& faintestMag)
01189 {
01190       float fieldCorr = 2.0f * FOV/(fov + FOV);
01191       faintestMag = (float) (faintestAutoMag45deg * sqrt(fieldCorr));
01192       saturationMag = saturationMagNight * (1.0f + fieldCorr * fieldCorr);
01193 }
01194 
01195 
01196 // Set up the light sources for rendering a solar system.  The positions of
01197 // all nearby stars are converted from universal to solar system coordinates.
01198 static void
01199 setupLightSources(const vector<const Star*>& nearStars,
01200                   const Star& sun,
01201                   double t,
01202                   vector<Renderer::LightSource>& lightSources)
01203 {
01204     UniversalCoord center = sun.getPosition(t);
01205 
01206     lightSources.clear();
01207 
01208     for (vector<const Star*>::const_iterator iter = nearStars.begin();
01209          iter != nearStars.end(); iter++)
01210     {
01211         if ((*iter)->getVisibility())
01212         {
01213             Vec3d v = ((*iter)->getPosition(t) - center) *
01214                 astro::microLightYearsToKilometers(1.0);
01215 
01216             Renderer::LightSource ls;
01217             ls.position = Point3d(v.x, v.y, v.z);
01218             ls.luminosity = (*iter)->getLuminosity();
01219             ls.radius = (*iter)->getRadius();
01220 
01221             // If the star is sufficiently cool, change the light color
01222             // from white.  Though our sun appears yellow, we still make
01223             // it and all hotter stars emit white light, as this is the
01224             // 'natural' light to which our eyes are accustomed.  We also
01225             // assign a slight bluish tint to light from O and B type stars,
01226             // though these will almost never have planets for their light
01227             // to shine upon.
01228             float temp = (*iter)->getTemperature();
01229             if (temp > 30000.0f)
01230                 ls.color = Color(0.8f, 0.8f, 1.0f);
01231             else if (temp > 10000.0f)
01232                 ls.color = Color(0.9f, 0.9f, 1.0f);
01233             else if (temp > 5400.0f)
01234                 ls.color = Color(1.0f, 1.0f, 1.0f);
01235             else if (temp > 3900.0f)
01236                 ls.color = Color(1.0f, 0.9f, 0.8f);
01237             else if (temp > 2000.0f)
01238                 ls.color = Color(1.0f, 0.7f, 0.7f);
01239             else
01240                 ls.color = Color(1.0f, 0.4f, 0.4f);
01241 
01242             lightSources.push_back(ls);
01243         }
01244     }
01245 }
01246 
01247 
01248 void Renderer::render(const Observer& observer,
01249                       const Universe& universe,
01250                       float faintestMagNight,
01251                       const Selection& sel)
01252 {
01253     // Get the observer's time
01254     double now = observer.getTime();
01255 
01256     // Compute the size of a pixel
01257     setFieldOfView(radToDeg(observer.getFOV()));
01258     pixelSize = calcPixelSize(fov, (float) windowHeight);
01259     // Set up the projection we'll use for rendering stars.
01260     glMatrixMode(GL_PROJECTION);
01261     glLoadIdentity();
01262     gluPerspective(fov,
01263                    (float) windowWidth / (float) windowHeight,
01264                    NEAR_DIST, FAR_DIST);
01265     
01266     // Set the modelview matrix
01267     glMatrixMode(GL_MODELVIEW);
01268 
01269     // Get the displayed surface texture set to use from the observer
01270     displayedSurface = observer.getDisplayedSurface();
01271 
01272     locationFilter = observer.getLocationFilter();
01273 
01274     // Set up the camera
01275     Point3f observerPos = (Point3f) observer.getPosition();
01276     observerPos.x *= 1e-6f;
01277     observerPos.y *= 1e-6f;
01278     observerPos.z *= 1e-6f;
01279     glPushMatrix();
01280     glRotate(observer.getOrientation());
01281 
01282     // Get the model matrix *before* translation.  We'll use this for
01283     // positioning star and planet labels.
01284     glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
01285     glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
01286     
01287     clearLabels();
01288 
01289     // Put all solar system bodies into the render list.  Stars close and
01290     // large enough to have discernible surface detail are also placed in
01291     // renderList.
01292     renderList.clear();
01293 
01294     // See if we want to use AutoMag.
01295     if ((renderFlags & ShowAutoMag) != 0)
01296     {
01297         autoMag(faintestMag);
01298     }
01299     else 
01300     {
01301         faintestMag = faintestMagNight;
01302         saturationMag = saturationMagNight;
01303     }
01304 
01305     // Automatic FOV-based adjustment of limiting magnitude for solar system
01306     // objects.  Now that we have the automag feature, this hack should no
01307     // longer be required.
01308     // faintestPlanetMag = faintestMag + (2.5f * (float) log10((double) square(45.0f / fov)));
01309     faintestPlanetMag = faintestMag;
01310 
01311     if (renderFlags & ShowPlanets)
01312     {
01313         nearStars.clear();
01314         universe.getNearStars(observer.getPosition(), 1.0f, nearStars);
01315 
01316         unsigned int solarSysIndex = 0;
01317         for (vector<const Star*>::const_iterator iter = nearStars.begin();
01318              iter != nearStars.end(); iter++)
01319         {
01320             const Star* sun = *iter;
01321             SolarSystem* solarSystem = universe.getSolarSystem(sun);
01322             if (solarSystem != NULL)
01323             {
01324                 setupLightSources(nearStars, *sun, now,
01325                                   lightSourceLists[solarSysIndex]);
01326                 renderPlanetarySystem(*sun,
01327                                       *solarSystem->getPlanets(),
01328                                       observer,
01329                                       now,
01330                                       solarSysIndex,
01331                                       (labelMode & (BodyLabelMask)) != 0);
01332                 solarSysIndex++;
01333             }
01334         }
01335         starTex->bind();
01336     }
01337 
01338     Color skyColor(0.0f, 0.0f, 0.0f);
01339 
01340     // Scan through the render list to see if we're inside a planetary
01341     // atmosphere.  If so, we need to adjust the sky color as well as the
01342     // limiting magnitude of stars (so stars aren't visible in the daytime
01343     // on planets with thick atmospheres.)
01344     if ((renderFlags & ShowAtmospheres) != 0)
01345     {
01346         for (vector<RenderListEntry>::iterator iter = renderList.begin();
01347              iter != renderList.end(); iter++)
01348         {
01349             if (iter->body != NULL && iter->body->getAtmosphere() != NULL)
01350             {
01351                 // Compute the density of the atmosphere, and from that
01352                 // the amount light scattering.  It's complicated by the
01353                 // possibility that the planet is oblate and a simple distance
01354                 // to sphere calculation will not suffice.
01355                 const Atmosphere* atmosphere = iter->body->getAtmosphere();
01356                 float radius = iter->body->getRadius();
01357                 float oblateness = iter->body->getOblateness();
01358                 Vec3f recipSemiAxes(1.0f, 1.0f / (1.0f - oblateness), 1.0f);
01359                 Mat3f A = Mat3f::scaling(recipSemiAxes);
01360                 Vec3f eyeVec = iter->position - Point3f(0.0f, 0.0f, 0.0f);
01361                 eyeVec *= (1.0f / radius);
01362 
01363                 // Compute the orientation of the planet before axial rotation
01364                 Quatd qd = iter->body->getEclipticalToEquatorial(now);
01365                 Quatf q((float) qd.w, (float) qd.x, (float) qd.y, (float) qd.z);
01366                 eyeVec = eyeVec * conjugate(q).toMatrix3();
01367 
01368                 // ellipDist is not the true distance from the surface unless
01369                 // the planet is spherical.  The quantity that we do compute
01370                 // is the distance to the surface along a line from the eye
01371                 // position to the center of the ellipsoid.
01372                 float ellipDist = (float) sqrt((eyeVec * A) * (eyeVec * A)) - 1.0f;
01373                 if (ellipDist < atmosphere->height / radius &&
01374                     atmosphere->height > 0.0f)
01375                 {
01376                     float density = 1.0f - ellipDist /
01377                         (atmosphere->height / radius);
01378                     if (density > 1.0f)
01379                         density = 1.0f;
01380 
01381                     Vec3f sunDir = iter->sun;
01382                     Vec3f normal = Point3f(0.0f, 0.0f, 0.0f) - iter->position;
01383                     sunDir.normalize();
01384                     normal.normalize();
01385                     float illumination = Math<float>::clamp((sunDir * normal) + 0.2f);
01386 
01387                     float lightness = illumination * density;
01388                     faintestMag = faintestMag  - 15.0f * lightness;
01389                     saturationMag = saturationMag - 15.0f * lightness;
01390                 }
01391             }
01392         }
01393     }
01394 
01395     // Now we need to determine how to scale the brightness of stars.  The
01396     // brightness will be proportional to the apparent magnitude, i.e.
01397     // a logarithmic function of the stars apparent brightness.  This mimics
01398     // the response of the human eye.  We sort of fudge things here and
01399     // maintain a minimum range of four magnitudes between faintest visible
01400     // and saturation; this keeps stars from popping in or out as the sun
01401     // sets or rises.
01402     if (faintestMag - saturationMag >= 4.0f)
01403         brightnessScale = 1.0f / (faintestMag -  saturationMag);
01404     else
01405         brightnessScale = 0.25f;
01406 
01407     ambientColor = Color(ambientLightLevel, ambientLightLevel, ambientLightLevel);
01408 
01409     // Create the ambient light source.  For realistic scenes in space, this
01410     // should be black.
01411     glAmbientLightColor(ambientColor);
01412 
01413     glClearColor(skyColor.red(), skyColor.green(), skyColor.blue(), 1);
01414     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
01415 
01416     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
01417 
01418     glDisable(GL_LIGHTING);
01419     glDepthMask(GL_FALSE);
01420     glEnable(GL_BLEND);
01421     glEnable(GL_TEXTURE_2D);
01422 
01423     if ((renderFlags & ShowCelestialSphere) != 0)
01424     {
01425         glColor4f(0.3f, 0.7f, 0.7f, 0.55f);
01426         glDisable(GL_TEXTURE_2D);
01427         if ((renderFlags & ShowSmoothLines) != 0)
01428             enableSmoothLines();
01429         renderCelestialSphere(observer);
01430         if ((renderFlags & ShowSmoothLines) != 0)
01431             disableSmoothLines();
01432         glEnable(GL_BLEND);
01433         glEnable(GL_TEXTURE_2D);
01434     }
01435 
01436     if (universe.getDSOCatalog() != NULL)
01437         renderDeepSkyObjects(universe, observer, faintestMag);
01438 
01439     // Translate the camera before rendering the stars
01440     glPushMatrix();
01441     glTranslatef(-observerPos.x, -observerPos.y, -observerPos.z);
01442     // Render stars
01443     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
01444     if ((renderFlags & ShowStars) != 0 && universe.getStarCatalog() != NULL)
01445         renderStars(*universe.getStarCatalog(), faintestMag, observer);
01446 
01447     // Render asterisms
01448     if ((renderFlags & ShowDiagrams) != 0 && universe.getAsterisms() != NULL)
01449     {
01450         glColor4f(0.28f, 0.0f, 0.66f, 0.96f);
01451         glDisable(GL_TEXTURE_2D);
01452         if ((renderFlags & ShowSmoothLines) != 0)
01453             enableSmoothLines();
01454         AsterismList* asterisms = universe.getAsterisms();
01455         for (AsterismList::const_iterator iter = asterisms->begin();
01456              iter != asterisms->end(); iter++)
01457         {
01458             Asterism* ast = *iter;
01459 
01460             for (int i = 0; i < ast->getChainCount(); i++)
01461             {
01462                 const Asterism::Chain& chain = ast->getChain(i);
01463 
01464                 glBegin(GL_LINE_STRIP);
01465                 for (Asterism::Chain::const_iterator iter = chain.begin();
01466                      iter != chain.end(); iter++)
01467                     glVertex(*iter);
01468                 glEnd();
01469             }
01470         }
01471         
01472         if ((renderFlags & ShowSmoothLines) != 0)
01473             disableSmoothLines();
01474     }
01475 
01476     if ((renderFlags & ShowBoundaries) != 0)
01477     {
01478         glColor4f(0.8f, 0.33f, 0.63f, 0.35f);
01479         glDisable(GL_TEXTURE_2D);
01480         if ((renderFlags & ShowSmoothLines) != 0)
01481             enableSmoothLines();
01482         if (universe.getBoundaries() != NULL)
01483             universe.getBoundaries()->render();
01484         if ((renderFlags & ShowSmoothLines) != 0)
01485             disableSmoothLines();
01486     }
01487 
01488     if ((labelMode & StarLabels) != 0 && universe.getStarCatalog() != NULL)
01489         labelStars(labelledStars, *universe.getStarCatalog(), observer);
01490     if ((labelMode & ConstellationLabels) != 0 &&
01491         universe.getAsterisms() != NULL)
01492         labelConstellations(*universe.getAsterisms(), observer);
01493 
01494     glPopMatrix();
01495 
01496     // Render orbit paths
01497     if ((renderFlags & ShowOrbits) != 0 && orbitMask != 0)
01498     {
01499         // Clear the keep flag for all orbits in the cache; if they're not
01500         // used when rendering this frame, they'll get marked for
01501         // recycling.
01502         vector<CachedOrbit*>::const_iterator iter;
01503         for (iter = orbitCache.begin(); iter != orbitCache.end(); iter++)
01504             (*iter)->keep = false;
01505 
01506         glDisable(GL_LIGHTING);
01507         glDisable(GL_TEXTURE_2D);
01508         if ((renderFlags & ShowSmoothLines) != 0)
01509             enableSmoothLines();
01510         
01511         for (vector<const Star*>::const_iterator starIter = nearStars.begin();
01512              starIter != nearStars.end(); starIter++)
01513         {
01514             const Star* sun = *starIter;
01515             SolarSystem* solarSystem = universe.getSolarSystem(sun);
01516 
01517             if (solarSystem != NULL)
01518             {
01519                 Point3d obsPos = astrocentricPosition(observer.getPosition(),
01520                                                       *sun, observer.getTime());
01521                 glPushMatrix();
01522                 glTranslatef((float) astro::kilometersToAU(-obsPos.x),
01523                              (float) astro::kilometersToAU(-obsPos.y),
01524                              (float) astro::kilometersToAU(-obsPos.z));
01525                 renderOrbits(solarSystem->getPlanets(), sel, now,
01526                              obsPos, Point3d(0.0, 0.0, 0.0));
01527                 glPopMatrix();
01528             }
01529         }
01530 
01531         if ((renderFlags & ShowSmoothLines) != 0)
01532             disableSmoothLines();
01533 
01534         // Mark for recycling all unused orbits in the cache
01535         for (iter = orbitCache.begin(); iter != orbitCache.end(); iter++)
01536         {
01537             if (!(*iter)->keep)
01538                 (*iter)->body = NULL;
01539         }
01540     }
01541 
01542     renderLabels();
01543 
01544     glPolygonMode(GL_FRONT, (GLenum) renderMode);
01545     glPolygonMode(GL_BACK, (GLenum) renderMode);
01546 
01547     {
01548         Frustum frustum(degToRad(fov),
01549                         (float) windowWidth / (float) windowHeight,
01550                         MinNearPlaneDistance);
01551         Mat3f viewMat = conjugate(observer.getOrientation()).toMatrix3();
01552 
01553         // Remove objects from the render list that lie completely outside the
01554         // view frustum.
01555         vector<RenderListEntry>::iterator notCulled = renderList.begin();
01556         for (vector<RenderListEntry>::iterator iter = renderList.begin();
01557              iter != renderList.end(); iter++)
01558         {
01559             Point3f center = iter->position * viewMat;
01560 
01561             bool convex = true;
01562             float radius = 1.0f;
01563             float cullRadius = 1.0f;
01564             float cloudHeight = 0.0f;
01565             if (iter->body != NULL)
01566             {
01567                 radius = iter->body->getBoundingRadius();
01568                 if (iter->body->getRings() != NULL)
01569                 {
01570                     radius = iter->body->getRings()->outerRadius;
01571                     convex = false;
01572                 }
01573 
01574                 if (iter->body->getModel() != InvalidResource)
01575                     convex = false;
01576 
01577                 cullRadius = radius;
01578                 if (iter->body->getAtmosphere() != NULL)
01579                 {
01580                     cullRadius += iter->body->getAtmosphere()->height;
01581                     cloudHeight = iter->body->getAtmosphere()->cloudHeight;
01582                 }
01583             }
01584             else if (iter->star != NULL)
01585             {
01586                 radius = iter->star->getRadius();
01587                 cullRadius = radius * (1.0f + CoronaHeight);
01588             }
01589 
01590             // Test the object's bounding sphere against the view frustum
01591             if (frustum.testSphere(center, cullRadius) != Frustum::Outside)
01592             {
01593                 float nearZ = center.distanceFromOrigin() - radius;
01594                 float maxSpan = (float) sqrt(square((float) windowWidth) +
01595                                              square((float) windowHeight));
01596 
01597                 nearZ = -nearZ * (float) cos(degToRad(fov / 2)) *
01598                     ((float) windowHeight / maxSpan);
01599                 
01600                 if (nearZ > -MinNearPlaneDistance)
01601                     iter->nearZ = -MinNearPlaneDistance;
01602                 else
01603                     iter->nearZ = nearZ;
01604 
01605                 if (!convex)
01606                 {
01607                     iter->farZ = center.z - radius;
01608                     if (iter->farZ / iter->nearZ > MaxFarNearRatio)
01609                         iter->nearZ = iter->farZ / MaxFarNearRatio;
01610                 }
01611                 else
01612                 {
01613                     // Make the far plane as close as possible
01614                     float d = center.distanceFromOrigin();
01615 
01616                     // Account for the oblateness
01617                     float eradius = radius;
01618                     if (iter->body != NULL)
01619                         eradius *= 1.0f - iter->body->getOblateness();
01620 
01621                     if (d > eradius)
01622                     {
01623                         // Multiply by a factor to eliminate overaggressive
01624                         // clipping due to limited floating point precision
01625                         iter->farZ = (float) (sqrt(square(d) - 
01626                                                    square(eradius)) * -1.1);
01627                     }
01628                     else
01629                     {
01630                         // We're inside the bounding sphere (and, if the planet
01631                         // is spherical, inside the planet.)
01632                         iter->farZ = iter->nearZ * 2.0f;
01633                     }
01634 
01635                     if (cloudHeight > 0.0f && d < eradius + cloudHeight)
01636                     {
01637                         // If there's a cloud layer, we need to move the 
01638                         // far plane out so that the clouds aren't clipped
01639                         float cloudLayerRadius = eradius + cloudHeight;
01640                         iter->farZ -= (float) sqrt(square(cloudLayerRadius) -
01641                                                    square(eradius));
01642                     }
01643                 }
01644 
01645                 *notCulled = *iter;
01646                 notCulled++;
01647             }
01648         }
01649 
01650         renderList.resize(notCulled - renderList.begin());
01651 
01652         // The calls to renderSolarSystem/renderStars filled renderList
01653         // with visible planetary bodies.  Sort it by distance, then
01654         // render each entry.
01655         stable_sort(renderList.begin(), renderList.end());
01656 
01657         // Sort the labels
01658         sort(depthSortedLabels.begin(), depthSortedLabels.end());
01659 
01660         int nEntries = renderList.size();
01661 
01662         // Determine how to split up the depth buffer.  Typically, each body 
01663         // with an apparent size greater than one pixel is allocated its own
01664         // depth buffer range.  However, this will not correctly handle
01665         // overlapping objects.  If two objects overlap in depth, we attempt
01666         // to coalesce their depth buckets.  Coalescing will succeed as long
01667         // as the far / near plane ratio is not too large.  If it does exceed
01668         // the limit, coaslescing fails and the objects may not be rendered
01669         // correctly, though the result should be superior to throwing away
01670         // depth buffer precision by allowing the far / near ratio to get too
01671         // large.
01672         int nDepthBuckets = 1;
01673         int i;
01674         {
01675             float lastNear = 0.0f;
01676             float lastFar = 0.0f;
01677             int firstCoalesced = 0;
01678 
01679             for (i = 0; i < nEntries; i++)
01680             {
01681                 // Don't worry about objects that are smaller than a pixel,
01682                 // as they'll just be rendered as points.
01683                 if (renderList[i].discSizeInPixels > 1)
01684                 {
01685                     if (nDepthBuckets == 1 || renderList[i].nearZ <= lastFar)
01686                     {
01687                         // This object doesn't overlap any others in depth,
01688                         // no need to coaslesce.
01689                         nDepthBuckets++;
01690                         firstCoalesced = i;
01691                         lastNear = renderList[i].nearZ;
01692                         lastFar = renderList[i].farZ;
01693                     }
01694                     else
01695                     {
01696                         // Consider coalescing this object with the previous
01697                         // one.
01698                         float farthest = min(lastFar, renderList[i].farZ);
01699                         float nearest = max(lastNear, renderList[i].nearZ);
01700                         static int blah = 0;
01701 
01702                         // Need just a bit of slack in the farthest/nearest
01703                         // ratio test.
01704                         if (farthest / nearest < MaxFarNearRatio * 1.01f)
01705                         {
01706                             // Far/near ratio is acceptable, so coalesce
01707                             // this object's depth bucket with the previous
01708                             // one.
01709                             for (int j = firstCoalesced; j <= i; j++)
01710                             {
01711                                 renderList[j].farZ  = farthest;
01712                                 renderList[j].nearZ = nearest;
01713                             }
01714 #if DEBUG_COALESCE
01715                             clog << "Coalesce #" << i << ": " <<
01716                                 renderList[i].body->getName()  << ", " <<
01717                                 nearest << ", " << farthest << " -- " << blah++ << '\n';
01718 #endif
01719                             lastNear = nearest;
01720                             lastFar = farthest;
01721                         }
01722                         else
01723                         {
01724                             // Coalesce failed, so create a new depth bucket
01725                             // for this object.
01726                             nDepthBuckets++;
01727                             firstCoalesced = i;
01728                             lastNear = renderList[i].nearZ;
01729                             lastFar = renderList[i].farZ;
01730 #if DEBUG_COALESCE
01731                             clog << "Coalesce #" << i << ": " <<
01732                                 renderList[i].body->getName() << " failed! " <<
01733                                 nearest << ", " << farthest << " -- " << blah++ << '\n';
01734 #endif
01735                         }
01736                     }
01737                     renderList[i].depthBucket = nDepthBuckets - 1;
01738                 }
01739             }
01740         }
01741         
01742         float depthRange = 1.0f / (float) nDepthBuckets;
01743 
01744         int depthBucket = nDepthBuckets - 1;
01745         i = nEntries - 1;
01746 
01747         // Set up the depth bucket.
01748         glDepthRange(depthBucket * depthRange, (depthBucket + 1) * depthRange);
01749 
01750         // Set the initial near and far plane distance; any reasonable choice
01751         // for these will do, since different values will be chosen as soon
01752         // as we need to render a body as a mesh.
01753         float nearPlaneDistance = 1.0f;
01754         float farPlaneDistance = 10.0f;
01755         glMatrixMode(GL_PROJECTION);
01756         glLoadIdentity();
01757         gluPerspective(fov, (float) windowWidth / (float) windowHeight,
01758                        nearPlaneDistance, farPlaneDistance);
01759         glMatrixMode(GL_MODELVIEW);
01760 
01761         vector<Label>::iterator label = depthSortedLabels.begin();
01762 
01763         // Render all the bodies in the render list.
01764         for (i = nEntries - 1; i >= 0; i--)
01765         {
01766             label = renderSortedLabels(label, -renderList[i].farZ);
01767 
01768             if (renderList[i].discSizeInPixels > 1)
01769             {
01770                 depthBucket = renderList[i].depthBucket;
01771                 glDepthRange(depthBucket * depthRange, (depthBucket + 1) * depthRange);
01772                 
01773                 nearPlaneDistance = renderList[i].nearZ * -0.9f;
01774                 farPlaneDistance = renderList[i].farZ * -1.5f;
01775                 if (nearPlaneDistance < MinNearPlaneDistance)
01776                     nearPlaneDistance = MinNearPlaneDistance;
01777                 if (farPlaneDistance / nearPlaneDistance > MaxFarNearRatio)
01778                     farPlaneDistance = nearPlaneDistance * MaxFarNearRatio;
01779 
01780                 glMatrixMode(GL_PROJECTION);
01781                 glLoadIdentity();
01782                 gluPerspective(fov, (float) windowWidth / (float) windowHeight,
01783                                nearPlaneDistance, farPlaneDistance);
01784                 glMatrixMode(GL_MODELVIEW);
01785             }
01786 
01787             if (renderList[i].body != NULL)
01788             {
01789                 if (renderList[i].isCometTail)
01790                 {
01791                     renderCometTail(*renderList[i].body,
01792                                     renderList[i].position,
01793                                     renderList[i].distance,
01794                                     renderList[i].appMag,
01795                                     now,
01796                                     observer.getOrientation(),
01797                                     lightSourceLists[renderList[i].solarSysIndex],
01798                                     nearPlaneDistance, farPlaneDistance);
01799                 }
01800                 else
01801                 {
01802                     renderPlanet(*renderList[i].body,
01803                                  renderList[i].position,
01804                                  renderList[i].distance,
01805                                  renderList[i].appMag,
01806                                  now,
01807                                  observer.getOrientation(),
01808                                  lightSourceLists[renderList[i].solarSysIndex],
01809                                  nearPlaneDistance, farPlaneDistance);
01810 
01811                     renderForegroundOrbits(renderList[i].body->getSatellites(),
01812                                            renderList[i].position,
01813                                            renderList[i].distance,
01814                                            renderList[i].discSizeInPixels,
01815                                            sel,
01816                                            now);
01817                 }
01818             }
01819             else if (renderList[i].star != NULL)
01820             {
01821                 renderStar(*renderList[i].star,
01822                            renderList[i].position,
01823                            renderList[i].distance,
01824                            renderList[i].appMag,
01825                            observer.getOrientation(),
01826                            now,
01827                            nearPlaneDistance, farPlaneDistance);
01828             }
01829 
01830             // If this body is larger than a pixel, we rendered it as a mesh
01831             // instead of just a particle.  We move to the next depth buffer 
01832             // bucket before proceeding with further rendering.
01833             if (renderList[i].discSizeInPixels > 1)
01834             {
01835                 depthBucket--;
01836                 glDepthRange(depthBucket * depthRange, (depthBucket + 1) * depthRange);
01837             }
01838         }
01839 
01840         renderSortedLabels(label, 0.0f);
01841 
01842         // reset the depth range
01843         glDepthRange(0, 1);
01844     }
01845 
01846     glPopMatrix();
01847 
01848     glEnable(GL_TEXTURE_2D);
01849     glDisable(GL_LIGHTING);
01850     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
01851 
01852     glPolygonMode(GL_FRONT, GL_FILL);
01853     glPolygonMode(GL_BACK, GL_FILL);
01854 
01855     {
01856         Mat3f m = observer.getOrientation().toMatrix3();
01857         for (int i = 0; i < (int) labels.size(); i++)
01858         {
01859             
01860         }
01861     }
01862 
01863     if ((renderFlags & ShowMarkers) != 0)
01864     {
01865         renderMarkers(*universe.getMarkers(),
01866                       observer.getPosition(),
01867                       observer.getOrientation(),
01868                       now);
01869     }
01870 
01871     glDisable(GL_BLEND);
01872     glDepthMask(GL_TRUE);
01873     glEnable(GL_LIGHTING);
01874 
01875 #if 0
01876     int errCode = glGetError();
01877     if (errCode != GL_NO_ERROR)
01878     {
01879         cout << "glError: " << (char*) gluErrorString(errCode) << '\n';
01880     }
01881 #endif
01882 }
01883 
01884 
01885 static void renderRingSystem(float innerRadius,
01886                              float outerRadius,
01887                              float beginAngle,
01888                              float endAngle,
01889                              unsigned int nSections)
01890 {
01891     float angle = endAngle - beginAngle;
01892 
01893     glBegin(GL_QUAD_STRIP);
01894     for (unsigned int i = 0; i <= nSections; i++)
01895     {
01896         float t = (float) i / (float) nSections;
01897         float theta = beginAngle + t * angle;
01898         float s = (float) sin(theta);
01899         float c = (float) cos(theta);
01900         glTexCoord2f(0, 0.5f);
01901         glVertex3f(c * innerRadius, 0, s * innerRadius);
01902         glTexCoord2f(1, 0.5f);
01903         glVertex3f(c * outerRadius, 0, s * outerRadius);
01904     }
01905     glEnd();
01906 }
01907 
01908 
01909 // If the an object occupies a pixel or less of screen space, we don't
01910 // render its mesh at all and just display a starlike point instead.
01911 // Switching between the particle and mesh renderings of an object is
01912 // jarring, however . . . so we'll blend in the particle view of the
01913 // object to smooth things out, making it dimmer as the disc size exceeds the
01914 // max disc size.
01915 void Renderer::renderBodyAsParticle(Point3f position,
01916                                     float appMag,
01917                                     float _faintestMag,
01918                                     float discSizeInPixels,
01919                                     Color color,
01920                                     const Quatf& orientation,
01921                                     float renderZ,
01922                                     bool useHaloes)
01923 {
01924     float maxDiscSize = (starStyle == ScaledDiscStars) ? MaxScaledDiscStarSize : 1.0f;
01925     float maxBlendDiscSize = maxDiscSize + 3.0f;
01926     float discSize = 1.0f;
01927 
01928     if (discSizeInPixels < maxBlendDiscSize || useHaloes)
01929     {
01930         float a = 1;
01931         float fade = 1.0f;
01932 
01933         if (discSizeInPixels > maxDiscSize)
01934         {
01935             fade = (maxBlendDiscSize - discSizeInPixels) /
01936                 (maxBlendDiscSize - maxDiscSize - 1.0f);
01937             if (fade > 1)
01938                 fade = 1;
01939         }
01940 
01941         a = (_faintestMag - appMag) * brightnessScale + brightnessBias;
01942         if (starStyle == ScaledDiscStars && a > 1.0f)
01943             discSize = min(discSize * (2.0f * a - 1.0f), maxDiscSize);
01944         a = clamp(a) * fade;
01945 
01946         // We scale up the particle by a factor of 1.6 (at fov = 45deg) 
01947         // so that it's more
01948         // visible--the texture we use has fuzzy edges, and if we render it
01949         // in just one pixel, it's likely to disappear.  Also, the render
01950         // distance is scaled by a factor of 0.1 so that we're rendering in
01951         // front of any mesh that happens to be sharing this depth bucket.
01952         // What we really want is to render the particle with the frontmost
01953         // z value in this depth bucket, and scaling the render distance is
01954         // just hack to accomplish this.  There are cases where it will fail
01955         // and a more robust method should be implemented.
01956 
01957         float size = discSize * pixelSize * 1.6f * renderZ / corrFac;
01958         float posScale = abs(renderZ / (position * conjugate(orientation).toMatrix3()).z);
01959 
01960         Point3f center(position.x * posScale,
01961                        position.y * posScale,
01962                        position.z * posScale);
01963         Mat3f m = orientation.toMatrix3();
01964         Vec3f v0 = Vec3f(-1, -1, 0) * m;
01965         Vec3f v1 = Vec3f( 1, -1, 0) * m;
01966         Vec3f v2 = Vec3f( 1,  1, 0) * m;
01967         Vec3f v3 = Vec3f(-1,  1, 0) * m;
01968 
01969         starTex->bind();
01970         glColor(color, a);
01971         glBegin(GL_QUADS);
01972         glTexCoord2f(0, 0);
01973         glVertex(center + (v0 * size));
01974         glTexCoord2f(1, 0);
01975         glVertex(center + (v1 * size));
01976         glTexCoord2f(1, 1);
01977         glVertex(center + (v2 * size));
01978         glTexCoord2f(0, 1);
01979         glVertex(center + (v3 * size));
01980         glEnd();
01981 
01982         // If the object is brighter than magnitude 1, add a halo around it to
01983         // make it appear more brilliant.  This is a hack to compensate for the
01984         // limited dynamic range of monitors.
01985         //
01986         // TODO: Currently, this is extremely broken.  Stars look fine,
01987         // but planets look ridiculous with bright haloes.
01988         if (useHaloes && appMag < saturationMag)
01989         {
01990             a = 0.4f * clamp((appMag - saturationMag) * -0.8f);
01991             float s = renderZ * 0.001f * (3 - (appMag - saturationMag)) * 2;
01992             if (s > size * 3)
01993                 size = s * 2.0f/(1.0f +FOV/fov);
01994             else
01995                 size = size * 3;
01996             float realSize = discSizeInPixels * pixelSize * renderZ;
01997             if (size < realSize * 10)
01998                 size = realSize * 10;
01999             glareTex->bind();
02000             glColor(color, a);
02001             glBegin(GL_QUADS);
02002             glTexCoord2f(0, 0);
02003             glVertex(center + (v0 * size));
02004             glTexCoord2f(1, 0);
02005             glVertex(center + (v1 * size));
02006             glTexCoord2f(1, 1);
02007             glVertex(center + (v2 * size));
02008             glTexCoord2f(0, 1);
02009             glVertex(center + (v3 * size));
02010             glEnd();
02011         }
02012     }
02013 }
02014 
02015 
02016 static void renderBumpMappedMesh(const GLContext& context,
02017                                  Texture& baseTexture,
02018                                  Texture& bumpTexture,
02019                                  Vec3f lightDirection,
02020                                  Quatf orientation,
02021                                  Color ambientColor,
02022                                  const Frustum& frustum,
02023                                  float lod)
02024 {
02025     // We're doing our own per-pixel lighting, so disable GL's lighting
02026     glDisable(GL_LIGHTING);
02027 
02028     // Render the base texture on the first pass . . .  The color
02029     // should have already been set up by the caller.
02030     lodSphere->render(context,
02031                       LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
02032                       frustum, lod,
02033                       &baseTexture);
02034 
02035     // The 'default' light vector for the bump map is (0, 0, 1).  Determine
02036     // a rotation transformation that will move the sun direction to
02037     // this vector.
02038     Quatf lightOrientation;
02039     {
02040         Vec3f zeroLightDirection(0, 0, 1);
02041         Vec3f axis = lightDirection ^ zeroLightDirection;
02042         float cosAngle = zeroLightDirection * lightDirection;
02043         float angle = 0.0f;
02044         float epsilon = 1e-5f;
02045 
02046         if (cosAngle + 1 < epsilon)
02047         {
02048             axis = Vec3f(0, 1, 0);
02049             angle = (float) PI;
02050         }
02051         else if (cosAngle - 1 > -epsilon)
02052         {
02053             axis = Vec3f(0, 1, 0);
02054             angle = 0.0f;
02055         }
02056         else
02057         {
02058             axis.normalize();
02059             angle = (float) acos(cosAngle);
02060         }
02061         lightOrientation.setAxisAngle(axis, angle);
02062     }
02063 
02064     glEnable(GL_BLEND);
02065     glBlendFunc(GL_DST_COLOR, GL_ZERO);
02066 
02067     // Set up the bump map with one directional light source
02068     SetupCombinersBumpMap(bumpTexture, *normalizationTex, ambientColor);
02069 
02070     // The second set texture coordinates will contain the light
02071     // direction in tangent space.  We'll generate the texture coordinates
02072     // from the surface normals using GL_NORMAL_MAP_EXT and then
02073     // use the texture matrix to rotate them into tangent space.
02074     // This method of generating tangent space light direction vectors
02075     // isn't as general as transforming the light direction by an
02076     // orthonormal basis for each mesh vertex, but it works well enough
02077     // for spheres illuminated by directional light sources.
02078     glx::glActiveTextureARB(GL_TEXTURE1_ARB);
02079 
02080     // Set up GL_NORMAL_MAP_EXT texture coordinate generation.  This
02081     // mode is part of the cube map extension.
02082     glEnable(GL_TEXTURE_GEN_R);
02083     glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
02084     glEnable(GL_TEXTURE_GEN_S);
02085     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
02086     glEnable(GL_TEXTURE_GEN_T);
02087     glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
02088 
02089     // Set up the texture transformation--the light direction and the
02090     // viewer orientation both need to be considered.
02091     glMatrixMode(GL_TEXTURE);
02092     glScalef(-1.0f, 1.0f, 1.0f);
02093     glRotate(lightOrientation * ~orientation);
02094     glMatrixMode(GL_MODELVIEW);
02095     glx::glActiveTextureARB(GL_TEXTURE0_ARB);
02096 
02097     lodSphere->render(context,
02098                       LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
02099                       frustum, lod,
02100                       &bumpTexture);
02101 
02102     // Reset the second texture unit
02103     glx::glActiveTextureARB(GL_TEXTURE1_ARB);
02104     glMatrixMode(GL_TEXTURE);
02105     glLoadIdentity();
02106     glMatrixMode(GL_MODELVIEW);
02107     glDisable(GL_TEXTURE_GEN_R);
02108     glDisable(GL_TEXTURE_GEN_S);
02109     glDisable(GL_TEXTURE_GEN_T);
02110 
02111     DisableCombiners();
02112     glDisable(GL_BLEND);
02113 }
02114 
02115 
02116 static void renderSmoothMesh(const GLContext& context,
02117                              Texture& baseTexture,
02118                              Vec3f lightDirection,
02119                              Quatf orientation,
02120                              Color ambientColor,
02121                              float lod,
02122                              const Frustum& frustum,
02123                              bool invert = false)
02124 {
02125     Texture* textures[4];
02126 
02127     // We're doing our own per-pixel lighting, so disable GL's lighting
02128     glDisable(GL_LIGHTING);
02129 
02130     // The 'default' light vector for the bump map is (0, 0, 1).  Determine
02131     // a rotation transformation that will move the sun direction to
02132     // this vector.
02133     Quatf lightOrientation;
02134     {
02135         Vec3f zeroLightDirection(0, 0, 1);
02136         Vec3f axis = lightDirection ^ zeroLightDirection;
02137         float cosAngle = zeroLightDirection * lightDirection;
02138         float angle = 0.0f;
02139         float epsilon = 1e-5f;
02140 
02141         if (cosAngle + 1 < epsilon)
02142         {
02143             axis = Vec3f(0, 1, 0);
02144             angle = (float) PI;
02145         }
02146         else if (cosAngle - 1 > -epsilon)
02147         {
02148             axis = Vec3f(0, 1, 0);
02149             angle = 0.0f;
02150         }
02151         else
02152         {
02153             axis.normalize();
02154             angle = (float) acos(cosAngle);
02155         }
02156         lightOrientation.setAxisAngle(axis, angle);
02157     }
02158 
02159     SetupCombinersSmooth(baseTexture, *normalizationTex, ambientColor, invert);
02160 
02161     // The second set texture coordinates will contain the light
02162     // direction in tangent space.  We'll generate the texture coordinates
02163     // from the surface normals using GL_NORMAL_MAP_EXT and then
02164     // use the texture matrix to rotate them into tangent space.
02165     // This method of generating tangent space light direction vectors
02166     // isn't as general as transforming the light direction by an
02167     // orthonormal basis for each mesh vertex, but it works well enough
02168     // for spheres illuminated by directional light sources.
02169     glx::glActiveTextureARB(GL_TEXTURE1_ARB);
02170 
02171     // Set up GL_NORMAL_MAP_EXT texture coordinate generation.  This
02172     // mode is part of the cube map extension.
02173     glEnable(GL_TEXTURE_GEN_R);
02174     glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
02175     glEnable(GL_TEXTURE_GEN_S);
02176     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
02177     glEnable(GL_TEXTURE_GEN_T);
02178     glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
02179 
02180     // Set up the texture transformation--the light direction and the
02181     // viewer orientation both need to be considered.
02182     glMatrixMode(GL_TEXTURE);
02183     glRotate(lightOrientation * ~orientation);
02184     glMatrixMode(GL_MODELVIEW);
02185     glx::glActiveTextureARB(GL_TEXTURE0_ARB);
02186 
02187     textures[0] = &baseTexture;
02188     lodSphere->render(context,
02189                       LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
02190                       frustum, lod,
02191                       textures, 1);
02192 
02193     // Reset the second texture unit
02194     glx::glActiveTextureARB(GL_TEXTURE1_ARB);
02195     glMatrixMode(GL_TEXTURE);
02196     glLoadIdentity();
02197     glMatrixMode(GL_MODELVIEW);
02198     glDisable(GL_TEXTURE_GEN_R);
02199     glDisable(GL_TEXTURE_GEN_S);
02200     glDisable(GL_TEXTURE_GEN_T);
02201 
02202     DisableCombiners();
02203 }
02204 
02205 
02206 struct RenderInfo
02207 {
02208     Color color;
02209     Texture* baseTex;
02210     Texture* bumpTex;
02211     Texture* nightTex;
02212     Texture* glossTex;
02213     Texture* overlayTex;
02214     Color hazeColor;
02215     Color specularColor;
02216     float specularPower;
02217     Vec3f sunDir_eye;
02218     Vec3f sunDir_obj;
02219     Vec3f eyeDir_obj;
02220     Point3f eyePos_obj;
02221     Color sunColor;
02222     Color ambientColor;
02223     Quatf orientation;
02224     float pixWidth;
02225     bool useTexEnvCombine;
02226 
02227     RenderInfo() : color(1.0f, 1.0f, 1.0f),
02228                    baseTex(NULL),
02229                    bumpTex(NULL),
02230                    nightTex(NULL),
02231                    glossTex(NULL),
02232                    overlayTex(NULL),
02233                    hazeColor(0.0f, 0.0f, 0.0f),
02234                    specularColor(0.0f, 0.0f, 0.0f),
02235                    specularPower(0.0f),
02236                    sunDir_eye(0.0f, 0.0f, 1.0f),
02237                    sunDir_obj(0.0f, 0.0f, 1.0f),
02238                    eyeDir_obj(0.0f, 0.0f, 1.0f),
02239                    eyePos_obj(0.0f, 0.0f, 0.0f),
02240                    sunColor(1.0f, 1.0f, 1.0f),
02241                    ambientColor(0.0f, 0.0f, 0.0f),
02242                    orientation(1.0f, 0.0f, 0.0f, 0.0f),
02243                    pixWidth(1.0f),
02244                    useTexEnvCombine(false)
02245     {};
02246 };
02247 
02248 
02249 // Used to sort light sources in order of decreasing irradiance
02250 struct LightIrradiancePredicate
02251 {
02252     int unused;
02253 
02254     LightIrradiancePredicate() {};
02255 
02256     bool operator()(const DirectionalLight& l0,
02257                     const DirectionalLight& l1) const
02258     {
02259         return (l0.irradiance > l1.irradiance);
02260     }
02261 };
02262 
02263 
02264 void renderAtmosphere(const Atmosphere& atmosphere,
02265                       Point3f center,
02266                       float radius,
02267                       const Vec3f& sunDirection,
02268                       Color ambientColor,
02269                       float fade,
02270                       bool lit)
02271 {
02272     if (atmosphere.height == 0.0f)
02273         return;
02274 
02275     glDepthMask(GL_FALSE);
02276 
02277     Vec3f eyeVec = center - Point3f(0.0f, 0.0f, 0.0f);
02278     double centerDist = eyeVec.length();
02279     // double surfaceDist = (double) centerDist - (double) radius;
02280 
02281     Vec3f normal = eyeVec;
02282     normal = normal / (float) centerDist;
02283 
02284     float tangentLength = (float) sqrt(square(centerDist) - square(radius));
02285     float atmRadius = tangentLength * radius / (float) centerDist;
02286     float atmOffsetFromCenter = square(radius) / (float) centerDist;
02287     Point3f atmCenter = center - atmOffsetFromCenter * normal;
02288 
02289     Vec3f uAxis, vAxis;
02290     if (abs(normal.x) < abs(normal.y) && abs(normal.x) < abs(normal.z))
02291     {
02292         uAxis = Vec3f(1, 0, 0) ^ normal;
02293         uAxis.normalize();
02294     }
02295     else if (abs(eyeVec.y) < abs(normal.z))
02296     {
02297         uAxis = Vec3f(0, 1, 0) ^ normal;
02298         uAxis.normalize();
02299     }
02300     else
02301     {
02302         uAxis = Vec3f(0, 0, 1) ^ normal;
02303         uAxis.normalize();
02304     }
02305     vAxis = uAxis ^ normal;
02306 
02307     float height = atmosphere.height / radius;
02308 
02309     glBegin(GL_QUAD_STRIP);
02310     int divisions = 180;
02311     for (int i = 0; i <= divisions; i++)
02312     {
02313         float theta = (float) i / (float) divisions * 2 * (float) PI;
02314         Vec3f v = (float) cos(theta) * uAxis + (float) sin(theta) * vAxis;
02315         Point3f base = atmCenter + v * atmRadius;
02316         Vec3f toCenter = base - center;
02317 
02318         float cosSunAngle = (toCenter * sunDirection) / radius;
02319         float brightness = 1.0f;
02320         float botColor[3];
02321         float topColor[3];
02322         botColor[0] = atmosphere.lowerColor.red();
02323         botColor[1] = atmosphere.lowerColor.green();
02324         botColor[2] = atmosphere.lowerColor.blue();
02325         topColor[0] = atmosphere.upperColor.red();
02326         topColor[1] = atmosphere.upperColor.green();
02327         topColor[2] = atmosphere.upperColor.blue();
02328 
02329         if (cosSunAngle < 0.2f && lit)
02330         {
02331             if (cosSunAngle < -0.2f)
02332             {
02333                 brightness = 0;
02334             }
02335             else
02336             {
02337                 float t = (0.2f + cosSunAngle) * 2.5f;
02338                 brightness = t;
02339                 botColor[0] = Mathf::lerp(t, 1.0f, botColor[0]);
02340                 botColor[1] = Mathf::lerp(t, 0.3f, botColor[1]);
02341                 botColor[2] = Mathf::lerp(t, 0.0f, botColor[2]);
02342                 topColor[0] = Mathf::lerp(t, 1.0f, topColor[0]);
02343                 topColor[1] = Mathf::lerp(t, 0.3f, topColor[1]);
02344                 topColor[2] = Mathf::lerp(t, 0.0f, topColor[2]);
02345             }
02346         }
02347 
02348         glColor4f(botColor[0], botColor[1], botColor[2],
02349                   0.85f * fade * brightness + ambientColor.red());
02350         glVertex(base - toCenter * height * 0.05f);
02351         glColor4f(topColor[0], topColor[1], topColor[2], 0.0f);
02352         glVertex(base + toCenter * height);
02353     }
02354     glEnd();
02355 }
02356 
02357 
02358 static Vec3f ellipsoidTangent(const Vec3f& recipSemiAxes,
02359                               const Vec3f& w,
02360                               const Vec3f& e,
02361                               const Vec3f& e_,
02362                               float ee)
02363 {
02364     // We want to find t such that -E(1-t) + Wt is the direction of a ray
02365     // tangent to the ellipsoid.  A tangent ray will intersect the ellipsoid
02366     // at exactly one point.  Finding the intersection between a ray and an
02367     // ellipsoid ultimately requires using the quadratic formula, which has
02368     // one solution when the discriminant (b^2 - 4ac) is zero.  The code below
02369     // computes the value of t that results in a discriminant of zero.
02370     Vec3f w_(w.x * recipSemiAxes.x, w.y * recipSemiAxes.y, w.z * recipSemiAxes.z);
02371     float ww = w_ * w_;
02372     float ew = w_ * e_;
02373 
02374     // Before elimination of terms:
02375     // float a =  4 * square(ee + ew) - 4 * (ee + 2 * ew + ww) * (ee - 1.0f);
02376     // float b = -8 * ee * (ee + ew)  - 4 * (-2 * (ee + ew) * (ee - 1.0f));
02377     // float c =  4 * ee * ee         - 4 * (ee * (ee - 1.0f));
02378 
02379     float a =  4 * square(ee + ew) - 4 * (ee + 2 * ew + ww) * (ee - 1.0f);
02380     float b = -8 * (ee + ew);
02381     float c =  4 * ee;
02382 
02383     float t = 0.0f;
02384     float discriminant = b * b - 4 * a * c;
02385 
02386     if (discriminant < 0.0f)
02387         t = (-b + (float) sqrt(-discriminant)) / (2 * a); // Bad!
02388     else
02389         t = (-b + (float) sqrt(discriminant)) / (2 * a);
02390 
02391     // V is the direction vector.  We now need the point of intersection,
02392     // which we obtain by solving the quadratic equation for the ray-ellipse
02393     // intersection.  Since we already know that the discriminant is zero,
02394     // the solution is just -b/2a
02395     Vec3f v = -e * (1 - t) + w * t;
02396     Vec3f v_(v.x * recipSemiAxes.x, v.y * recipSemiAxes.y, v.z * recipSemiAxes.z);
02397     float a1 = v_ * v_;
02398     float b1 = 2.0f * v_ * e_;
02399     float t1 = -b1 / (2 * a1);
02400 
02401     return e + v * t1;
02402 }
02403 
02404 
02405 void Renderer::renderEllipsoidAtmosphere(const Atmosphere& atmosphere,
02406                                          Point3f center,
02407                                          const Quatf& orientation,
02408                                          Vec3f semiAxes,
02409                                          const Vec3f& sunDirection,
02410                                          Color ambientColor,
02411                                          float pixSize,
02412                                          bool lit)
02413 {
02414     if (atmosphere.height == 0.0f)
02415         return;
02416 
02417     glDepthMask(GL_FALSE);
02418 
02419     // Gradually fade in the atmosphere if it's thickness on screen is just
02420     // over one pixel.
02421     float fade = clamp(pixSize - 2);
02422 
02423     Mat3f rot = orientation.toMatrix3();
02424     Mat3f irot = conjugate(orientation).toMatrix3();
02425 
02426     Point3f eyePos(0.0f, 0.0f, 0.0f);
02427     float radius = max(semiAxes.x, max(semiAxes.y, semiAxes.z));
02428     Vec3f eyeVec = center - eyePos;
02429     eyeVec = eyeVec * irot;
02430     double centerDist = eyeVec.length();
02431 
02432     float height = atmosphere.height / radius;
02433     Vec3f recipSemiAxes(1.0f / semiAxes.x, 1.0f / semiAxes.y, 1.0f / semiAxes.z);
02434 
02435     Vec3f recipAtmSemiAxes = recipSemiAxes / (1.0f + height);
02436     Mat3f A = Mat3f::scaling(recipAtmSemiAxes);
02437     Mat3f A1 = Mat3f::scaling(recipSemiAxes);
02438 
02439     // ellipDist is not the true distance from the surface unless the
02440     // planet is spherical.  Computing the true distance requires finding
02441     // the roots of a sixth degree polynomial, and isn't actually what we
02442     // want anyhow since the atmosphere region is just the planet ellipsoid
02443     // multiplied by a uniform scale factor.  The value that we do compute
02444     // is the distance to the surface along a line from the eye position to
02445     // the center of the ellipsoid.
02446     float ellipDist = (float) sqrt((eyeVec * A1) * (eyeVec * A1)) - 1.0f;
02447     bool within = ellipDist < height;
02448 
02449     // Adjust the tesselation of the sky dome/ring based on distance from the
02450     // planet surface.
02451     int nSlices = MaxSkySlices;
02452     if (ellipDist < 0.25f)
02453     {
02454         nSlices = MinSkySlices + max(0, (int) ((ellipDist / 0.25f) * (MaxSkySlices - MinSkySlices)));
02455         nSlices &= ~1;
02456     }
02457 
02458     int nRings = min(1 + (int) pixSize / 5, 6);
02459     int nHorizonRings = nRings;
02460     if (within)
02461         nRings += 12;
02462 
02463     float horizonHeight = height;
02464     if (within)
02465     {
02466         if (ellipDist <= 0.0f)
02467             horizonHeight = 0.0f;
02468         else
02469             horizonHeight *= max((float) pow(ellipDist / height, 0.33f), 0.001f);
02470     }
02471 
02472     Vec3f e = -eyeVec;
02473     Vec3f e_(e.x * recipSemiAxes.x, e.y * recipSemiAxes.y, e.z * recipSemiAxes.z);
02474     float ee = e_ * e_;
02475 
02476     // Compute the cosine of the altitude of the sun.  This is used to compute
02477     // the degree of sunset/sunrise coloration.
02478     float cosSunAltitude = 0.0f;
02479     {
02480         // Check for a sun either directly behind or in front of the viewer
02481         float cosSunAngle = (float) ((sunDirection * e) / centerDist);
02482         if (cosSunAngle < -1.0f + 1.0e-6f)
02483         {
02484             cosSunAltitude = 0.0f;
02485         }
02486         else if (cosSunAngle > 1.0f - 1.0e-6f)
02487         {
02488             cosSunAltitude = 0.0f;
02489         }
02490         else
02491         {
02492             Point3f tangentPoint = center +
02493                 ellipsoidTangent(recipSemiAxes,
02494                                  (-sunDirection * irot) * (float) centerDist,
02495                                  e, e_, ee) * rot;
02496             Vec3f tangentDir = tangentPoint - eyePos;
02497             tangentDir.normalize();
02498             cosSunAltitude = sunDirection * tangentDir;
02499         }
02500     }
02501 
02502     Vec3f normal = eyeVec;
02503     normal = normal / (float) centerDist;
02504 
02505     Vec3f uAxis, vAxis;
02506     if (abs(normal.x) < abs(normal.y) && abs(normal.x) < abs(normal.z))
02507     {
02508         uAxis = Vec3f(1, 0, 0) ^ normal;
02509         uAxis.normalize();
02510     }
02511     else if (abs(eyeVec.y) < abs(normal.z))
02512     {
02513         uAxis = Vec3f(0, 1, 0) ^ normal;
02514         uAxis.normalize();
02515     }
02516     else
02517     {
02518         uAxis = Vec3f(0, 0, 1) ^ normal;
02519         uAxis.normalize();
02520     }
02521     vAxis = uAxis ^ normal;
02522 
02523     // Compute the contour of the ellipsoid
02524     int i;
02525     for (i = 0; i <= nSlices; i++)
02526     {
02527         // We want rays with an origin at the eye point and tangent to the the
02528         // ellipsoid.
02529         float theta = (float) i / (float) nSlices * 2 * (float) PI;
02530         Vec3f w = (float) cos(theta) * uAxis + (float) sin(theta) * vAxis;
02531         w = w * (float) centerDist;
02532 
02533         Vec3f toCenter = ellipsoidTangent(recipSemiAxes, w, e, e_, ee);
02534         skyContour[i].v = toCenter * rot;
02535         skyContour[i].centerDist = skyContour[i].v.length();
02536         skyContour[i].eyeDir = skyContour[i].v + (center - eyePos);
02537         skyContour[i].eyeDist = skyContour[i].eyeDir.length();
02538         skyContour[i].eyeDir.normalize();
02539         
02540         float skyCapDist = (float) sqrt(square(skyContour[i].eyeDist) +
02541                                         square(horizonHeight * radius));
02542         skyContour[i].cosSkyCapAltitude = skyContour[i].eyeDist /
02543             skyCapDist;
02544     }
02545 
02546 
02547     Vec3f botColor(atmosphere.lowerColor.red(),
02548                    atmosphere.lowerColor.green(),
02549                    atmosphere.lowerColor.blue());
02550     Vec3f topColor(atmosphere.upperColor.red(),
02551                    atmosphere.upperColor.green(),
02552                    atmosphere.upperColor.blue());
02553     Vec3f sunsetColor(atmosphere.sunsetColor.red(),
02554                       atmosphere.sunsetColor.green(),
02555                       atmosphere.sunsetColor.blue());
02556     if (within)
02557     {
02558         Vec3f skyColor(atmosphere.skyColor.red(),
02559                        atmosphere.skyColor.green(),
02560                        atmosphere.skyColor.blue());
02561         if (ellipDist < 0.0f)
02562             topColor = skyColor;
02563         else
02564             topColor = skyColor + (topColor - skyColor) * (ellipDist / height);
02565     }
02566 
02567     Vec3f zenith = (skyContour[0].v + skyContour[nSlices / 2].v);
02568     zenith.normalize();
02569     zenith *= skyContour[0].centerDist * (1.0f + horizonHeight * 2.0f);
02570 
02571     float minOpacity = within ? (1.0f - ellipDist / height) * 0.75f : 0.0f;
02572     float sunset = cosSunAltitude < 0.9f ? 0.0f : (cosSunAltitude - 0.9f) * 10.0f;
02573 
02574     // Build the list of vertices
02575     SkyVertex* vtx = skyVertices;
02576     for (i = 0; i <= nRings; i++)
02577     {
02578         float h = min(1.0f, (float) i / (float) nHorizonRings);
02579         float hh = (float) sqrt(h);
02580         float u = i <= nHorizonRings ? 0.0f :
02581             (float) (i - nHorizonRings) / (float) (nRings - nHorizonRings);
02582         float r = Mathf::lerp(h, 1.0f - (horizonHeight * 0.05f), 1.0f + horizonHeight);
02583         float atten = 1.0f - hh;
02584 
02585         for (int j = 0; j < nSlices; j++)
02586         {
02587             Vec3f v;
02588             if (i <= nHorizonRings)
02589                 v = skyContour[j].v * r;
02590             else
02591                 v = (skyContour[j].v * (1.0f - u) + zenith * u) * r;
02592             Point3f p = center + v;
02593 
02594 
02595             Vec3f viewDir(p.x, p.y, p.z);
02596             viewDir.normalize();
02597             float cosSunAngle = viewDir * sunDirection;
02598             float cosAltitude = viewDir * skyContour[j].eyeDir;
02599             float brightness = 1.0f;
02600             float coloration = 0.0f;
02601             if (lit)
02602             {
02603                 if (sunset > 0.0f && cosSunAngle > 0.7f && cosAltitude > 0.98f)
02604                 {
02605                     coloration =  (1.0f / 0.30f) * (cosSunAngle - 0.70f);
02606                     coloration *= 50.0f * (cosAltitude - 0.98f);
02607                     coloration *= sunset;
02608                 }   
02609             
02610                 cosSunAngle = (skyContour[j].v * sunDirection) / skyContour[j].centerDist;
02611                 if (cosSunAngle > -0.2f)
02612                 {
02613                     if (cosSunAngle < 0.3f)
02614                         brightness = (cosSunAngle + 0.2f) * 2.0f;
02615                     else
02616                         brightness = 1.0f;
02617                 }
02618                 else
02619                 {
02620                     brightness = 0.0f;
02621                 }
02622             }
02623 
02624             vtx->x = p.x;
02625             vtx->y = p.y;
02626             vtx->z = p.z;
02627 
02628 #if 0
02629             // Better way of generating sky color gradients--based on 
02630             // altitude angle.
02631             if (!within)
02632             {
02633                 hh = (1.0f - cosAltitude) / (1.0f - skyContour[j].cosSkyCapAltitude);
02634             }
02635             else
02636             {
02637                 float top = pow((ellipDist / height), 0.125f) * skyContour[j].cosSkyCapAltitude;
02638                 if (cosAltitude < top)
02639                     hh = 1.0f;
02640                 else
02641                     hh = (1.0f - cosAltitude) / (1.0f - top);
02642             }
02643             hh = sqrt(hh);
02644             //hh = (float) pow(hh, 0.25f);
02645 #endif
02646 
02647             atten = 1.0f - hh;
02648             Vec3f color = (1.0f - hh) * botColor + hh * topColor;
02649             brightness *= minOpacity + (1.0f - minOpacity) * fade * atten;
02650             if (coloration != 0.0f)
02651                 color = (1.0f - coloration) * color + coloration * sunsetColor;
02652 
02653             Color(brightness * color.x,
02654                   brightness * color.y,
02655                   brightness * color.z,
02656                   fade * (minOpacity + (1.0f - minOpacity)) * atten).get(vtx->color);
02657             vtx++;
02658         }
02659     }
02660 
02661     // Create the index list
02662     int index = 0;
02663     for (i = 0; i < nRings; i++)
02664     {
02665         int baseVertex = i * nSlices;
02666         for (int j = 0; j < nSlices; j++)
02667         {
02668             skyIndices[index++] = baseVertex + j;
02669             skyIndices[index++] = baseVertex + nSlices + j;
02670         }
02671         skyIndices[index++] = baseVertex;
02672         skyIndices[index++] = baseVertex + nSlices;
02673     }
02674 
02675     glEnableClientState(GL_VERTEX_ARRAY);
02676     glVertexPointer(3, GL_FLOAT, sizeof(SkyVertex), &skyVertices[0].x);
02677     glEnableClientState(GL_COLOR_ARRAY);
02678     glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(SkyVertex),
02679                    static_cast<void*>(&skyVertices[0].color));
02680 
02681     for (i = 0; i < nRings; i++)
02682     {
02683         glDrawElements(GL_QUAD_STRIP,
02684                        (nSlices + 1) * 2,
02685                        GL_UNSIGNED_INT,
02686                        &skyIndices[(nSlices + 1) * 2 * i]);
02687     }
02688 
02689     glDisableClientState(GL_COLOR_ARRAY);
02690 }
02691 
02692 
02693 void renderCompass(Point3f center,
02694                    const Quatf& orientation,
02695                    Vec3f semiAxes,
02696                    float pixelSize)
02697 {
02698     Mat3f rot = orientation.toMatrix3();
02699     Mat3f irot = conjugate(orientation).toMatrix3();
02700 
02701     Point3f eyePos(0.0f, 0.0f, 0.0f);
02702     float radius = max(semiAxes.x, max(semiAxes.y, semiAxes.z));
02703     Vec3f eyeVec = center - eyePos;
02704     eyeVec = eyeVec * irot;
02705     double centerDist = eyeVec.length();
02706 
02707     float height = 1.0f / radius;
02708     Vec3f recipSemiAxes(1.0f / semiAxes.x,
02709                         1.0f / semiAxes.y,
02710                         1.0f / semiAxes.z);
02711 
02712     Vec3f recipAtmSemiAxes = recipSemiAxes / (1.0f + height);
02713     Mat3f A = Mat3f::scaling(recipAtmSemiAxes);
02714     Mat3f A1 = Mat3f::scaling(recipSemiAxes);
02715 
02716     const int nCompassPoints = 16;
02717     Vec3f compassPoints[nCompassPoints];
02718     
02719 
02720     // ellipDist is not the true distance from the surface unless the
02721     // planet is spherical.  Computing the true distance requires finding
02722     // the roots of a sixth degree polynomial, and isn't actually what we
02723     // want anyhow since the atmosphere region is just the planet ellipsoid
02724     // multiplied by a uniform scale factor.  The value that we do compute
02725     // is the distance to the surface along a line from the eye position to
02726     // the center of the ellipsoid.
02727     float ellipDist = (float) sqrt((eyeVec * A1) * (eyeVec * A1)) - 1.0f;
02728 
02729     Vec3f e = -eyeVec;
02730     Vec3f e_(e.x * recipSemiAxes.x, e.y * recipSemiAxes.y, e.z * recipSemiAxes.z);
02731     float ee = e_ * e_;
02732 
02733     Vec3f normal = eyeVec;
02734     normal = normal / (float) centerDist;
02735 
02736     Vec3f uAxis, vAxis;
02737     Vec3f northPole(0.0f, 1.0f, 0.0f);
02738     vAxis = normal ^ northPole;
02739     vAxis.normalize();
02740     uAxis = vAxis ^ normal;
02741 
02742     // Compute the compass points
02743     int i;
02744     for (i = 0; i < nCompassPoints; i++)
02745     {
02746         // We want rays with an origin at the eye point and tangent to the the
02747         // ellipsoid.
02748         float theta = (float) i / (float) nCompassPoints * 2 * (float) PI;
02749         Vec3f w = (float) cos(theta) * uAxis + (float) sin(theta) * vAxis;
02750         w = w * (float) centerDist;
02751 
02752         Vec3f toCenter = ellipsoidTangent(recipSemiAxes, w, e, e_, ee);
02753         compassPoints[i] = toCenter * rot;
02754     }
02755 
02756     glColor(compassColor);
02757     glBegin(GL_LINES);
02758     glDisable(GL_LIGHTING);
02759     for (i = 0; i < nCompassPoints; i++)
02760     {
02761         float distance = (center + compassPoints[i]).distanceFromOrigin();
02762         
02763         float length = distance * pixelSize * 8.0f;
02764         if (i % 4 == 0)
02765             length *= 3.0f;
02766         else if (i % 2 == 0)
02767             length *= 2.0f;
02768         
02769         glVertex(center + compassPoints[i]);
02770         glVertex(center + compassPoints[i] * (1.0f + length));
02771     }
02772     glEnd();
02773 }
02774 
02775 
02776 static void setupNightTextureCombine()
02777 {
02778     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02779     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
02780     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_ONE_MINUS_SRC_COLOR);
02781     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
02782     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
02783     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
02784 }
02785 
02786 
02787 static void setupBumpTexenv()
02788 {
02789     // Set up the texenv_combine extension to do DOT3 bump mapping.
02790     // No support for ambient light yet.
02791     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02792 
02793     // The primary color contains the light direction in surface
02794     // space, and texture0 is a normal map.  The lighting is
02795     // calculated by computing the dot product.
02796     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_ARB);
02797     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
02798     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
02799     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
02800     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
02801 
02802     // In the final stage, modulate the lighting value by the
02803     // base texture color.
02804     glx::glActiveTextureARB(GL_TEXTURE1_ARB);
02805     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
02806     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
02807     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
02808     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PREVIOUS_EXT);
02809     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
02810     glEnable(GL_TEXTURE_2D);
02811 
02812     glx::glActiveTextureARB(GL_TEXTURE0_ARB);
02813 }
02814 
02815 
02816 static void setupBumpTexenvAmbient(Color ambientColor)
02817 {
02818     float texenvConst[4];
02819     texenvConst[0] = ambientColor.red();
02820     texenvConst[1] = ambientColor.green();
02821     texenvConst[2] = ambientColor.blue();
02822     texenvConst[3] = ambientColor.alpha();
02823 
02824     // Set up the texenv_combine extension to do DOT3 bump mapping.
02825     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02826 
02827     // The primary color contains the light direction in surface
02828     // space, and texture0 is a normal map.  The lighting is
02829     // calculated by computing the dot product.
02830     glx::glActiveTextureARB(GL_TEXTURE0_ARB);
02831     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_ARB);
02832     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
02833     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
02834     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
02835     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
02836 
02837     // Add in the ambient color
02838     glx::glActiveTextureARB(GL_TEXTURE1_ARB);
02839     glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, texenvConst);
02840     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02841     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
02842     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
02843     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
02844     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_CONSTANT_EXT);
02845     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
02846     glEnable(GL_TEXTURE_2D);
02847 
02848     // In the final stage, modulate the lighting value by the
02849     // base texture color.
02850     glx::glActiveTextureARB(GL_TEXTURE2_ARB);
02851     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02852     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
02853     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
02854     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
02855     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
02856     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
02857     glEnable(GL_TEXTURE_2D);
02858 
02859     glx::glActiveTextureARB(GL_TEXTURE0_ARB);
02860 }
02861 
02862 
02863 static void setupTexenvAmbient(Color ambientColor)
02864 {
02865     float texenvConst[4];
02866     texenvConst[0] = ambientColor.red();
02867     texenvConst[1] = ambientColor.green();
02868     texenvConst[2] = ambientColor.blue();
02869     texenvConst[3] = ambientColor.alpha();
02870 
02871     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02872 
02873     // The primary color contains the light direction in surface
02874     // space, and texture0 is a normal map.  The lighting is
02875     // calculated by computing the dot product.
02876     glx::glActiveTextureARB(GL_TEXTURE0_ARB);
02877     glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, texenvConst);
02878     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02879     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
02880     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
02881     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
02882     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_CONSTANT_EXT);
02883     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
02884     glEnable(GL_TEXTURE_2D);
02885 }
02886 
02887 
02888 static void setupTexenvGlossMapAlpha()
02889 {
02890     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
02891     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
02892     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
02893     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
02894     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
02895     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_ALPHA);
02896 
02897 }
02898 
02899 
02900 static void
02901 setEclipseShadowShaderConstants(const LightingState& ls,
02902                                 float planetRadius,
02903                                 const Mat4f& planetMat,
02904                                 CelestiaGLProgram& prog)
02905 {
02906     for (unsigned int li = 0;
02907          li < min(ls.nLights, MaxShaderLights);
02908          li++)
02909     {
02910         vector<EclipseShadow>* shadows = ls.shadows[li];
02911 
02912         if (shadows != NULL)
02913         {
02914             unsigned int nShadows = min((size_t) MaxShaderShadows, 
02915                                         shadows->size());
02916 
02917             for (unsigned int i = 0; i < nShadows; i++)
02918             {
02919                 EclipseShadow& shadow = shadows->at(i);
02920                 CelestiaGLProgramShadow& shadowParams = prog.shadows[li][i];
02921 
02922                 float R2 = 0.25f;
02923                 float umbra = shadow.umbraRadius / shadow.penumbraRadius;
02924                 umbra = umbra * umbra;
02925                 if (umbra < 0.0001f)
02926                     umbra = 0.0001f;
02927                 else if (umbra > 0.99f)
02928                     umbra = 0.99f;
02929 
02930                 float umbraRadius = R2 * umbra;
02931                 float penumbraRadius = R2;
02932                 float shadowBias = 1.0f / (1.0f - penumbraRadius / umbraRadius);
02933                 shadowParams.bias = shadowBias;
02934                 shadowParams.scale = -shadowBias / umbraRadius;
02935 
02936                 // Compute the transformation to use for generating texture
02937                 // coordinates from the object vertices.
02938                 Point3f origin = shadow.origin * planetMat;
02939                 Vec3f dir = shadow.direction * planetMat;
02940                 float scale = planetRadius / shadow.penumbraRadius;
02941                 Vec3f axis = Vec3f(0, 1, 0) ^ dir;
02942                 float angle = (float) acos(Vec3f(0, 1, 0) * dir);
02943                 axis.normalize();
02944                 Mat4f mat = Mat4f::rotation(axis, -angle);
02945                 Vec3f sAxis = Vec3f(0.5f * scale, 0, 0) * mat;
02946                 Vec3f tAxis = Vec3f(0, 0, 0.5f * scale) * mat;
02947 
02948                 float sw = (Point3f(0, 0, 0) - origin) * sAxis / planetRadius + 0.5f;
02949                 float tw = (Point3f(0, 0, 0) - origin) * tAxis / planetRadius + 0.5f;
02950                 shadowParams.texGenS = Vec4f(sAxis.x, sAxis.y, sAxis.z, sw);
02951                 shadowParams.texGenT = Vec4f(tAxis.x, tAxis.y, tAxis.z, tw);
02952             }
02953         }
02954     }
02955 }
02956 
02957 
02958 static void setLightParameters_VP(VertexProcessor& vproc,
02959                                   const LightingState& ls,
02960                                   Color materialDiffuse,
02961                                   Color materialSpecular)
02962 {
02963     Vec3f diffuseColor(materialDiffuse.red(),
02964                        materialDiffuse.green(),
02965                        materialDiffuse.blue());
02966     Vec3f specularColor(materialSpecular.red(),
02967                         materialSpecular.green(),
02968                         materialSpecular.blue());
02969 
02970     for (unsigned int i = 0; i < ls.nLights; i++)
02971     {
02972         const DirectionalLight& light = ls.lights[i];
02973 
02974         Vec3f lightColor = Vec3f(light.color.red(),
02975                                  light.color.green(),
02976                                  light.color.blue()) * light.irradiance;
02977         Vec3f diffuse(diffuseColor.x * lightColor.x,
02978                       diffuseColor.y * lightColor.y,
02979                       diffuseColor.z * lightColor.z);
02980         Vec3f specular(specularColor.x * lightColor.x,
02981                        specularColor.y * lightColor.y,
02982                        specularColor.z * lightColor.z);
02983 
02984         // Just handle two light sources for now
02985         if (i == 0)
02986         {
02987             vproc.parameter(vp::LightDirection0, ls.lights[0].direction_obj);
02988             vproc.parameter(vp::DiffuseColor0, diffuse);
02989             vproc.parameter(vp::SpecularColor0, specular);
02990         }
02991         else if (i == 1)
02992         {
02993             vproc.parameter(vp::LightDirection1, ls.lights[1].direction_obj);
02994             vproc.parameter(vp::DiffuseColor1, diffuse);
02995             vproc.parameter(vp::SpecularColor1, specular);
02996         }
02997     }
02998 }
02999 
03000 
03001 static void setLightParameters_GLSL(CelestiaGLProgram& prog,
03002                                     const ShaderProperties& shadprop,
03003                                     const LightingState& ls,
03004                                     Color materialDiffuse,
03005                                     Color materialSpecular)
03006 {
03007     unsigned int nLights = min(MaxShaderLights, ls.nLights);
03008 
03009     Vec3f diffuseColor(materialDiffuse.red(),
03010                        materialDiffuse.green(),
03011                        materialDiffuse.blue());
03012     Vec3f specularColor(materialSpecular.red(),
03013                         materialSpecular.green(),
03014                         materialSpecular.blue());
03015     
03016     for (unsigned int i = 0; i < nLights; i++)
03017     {
03018         const DirectionalLight& light = ls.lights[i];
03019 
03020         Vec3f lightColor = Vec3f(light.color.red(),
03021                                  light.color.green(),
03022                                  light.color.blue()) * light.irradiance;
03023         prog.lights[i].direction = light.direction_obj;
03024 
03025         if (shadprop.usesShadows() ||
03026             shadprop.usesFragmentLighting() ||
03027             shadprop.lightModel == ShaderProperties::RingIllumModel)
03028         {
03029             prog.fragLightColor[i] = Vec3f(lightColor.x * diffuseColor.x,
03030                                            lightColor.y * diffuseColor.y,
03031                                            lightColor.z * diffuseColor.z);
03032             if (shadprop.lightModel == ShaderProperties::SpecularModel)
03033             {
03034                 prog.fragLightSpecColor[i] = Vec3f(lightColor.x * specularColor.x,
03035                                                    lightColor.y * specularColor.y,
03036                                                    lightColor.z * specularColor.z);
03037             }
03038         }
03039         else
03040         {
03041             prog.lights[i].diffuse = Vec3f(lightColor.x * diffuseColor.x,
03042                                            lightColor.y * diffuseColor.y,
03043                                            lightColor.z * diffuseColor.z);
03044         }
03045         prog.lights[i].specular = Vec3f(lightColor.x * specularColor.x,
03046                                         lightColor.y * specularColor.y,
03047                                         lightColor.z * specularColor.z);
03048 
03049         Vec3f halfAngle_obj = ls.eyeDir_obj + light.direction_obj;
03050         if (halfAngle_obj.length() != 0.0f)
03051             halfAngle_obj.normalize();
03052         prog.lights[i].halfVector = halfAngle_obj;
03053     }
03054 }
03055 
03056 
03057 static void renderModelDefault(Model* model,
03058                                const RenderInfo& ri,
03059                                bool lit)
03060 {
03061     FixedFunctionRenderContext rc;
03062     //rc.makeCurrent();
03063 
03064     if (lit)
03065         glEnable(GL_LIGHTING);
03066     else
03067         glDisable(GL_LIGHTING);
03068 
03069     if (ri.baseTex == NULL)
03070     {
03071         glDisable(GL_TEXTURE_2D);
03072     }
03073     else
03074     {
03075         glEnable(GL_TEXTURE_2D);
03076         ri.baseTex->bind();
03077     }
03078 
03079     glColor(ri.color);
03080 
03081     if (ri.baseTex != NULL)
03082         rc.lock();
03083 
03084     model->render(rc);
03085     if (model->usesTextureType(Mesh::EmissiveMap))
03086     {
03087         glDisable(GL_LIGHTING);
03088         glEnable(GL_BLEND);
03089         glBlendFunc(GL_ONE, GL_ONE);
03090         rc.setRenderPass(RenderContext::EmissivePass);
03091         rc.setMaterial(NULL);
03092 
03093         model->render(rc);
03094     }
03095 
03096     // Reset the material
03097     float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
03098     float zero = 0.0f;
03099     glColor4fv(black);
03100     glMaterialfv(GL_FRONT, GL_EMISSION, black);
03101     glMaterialfv(GL_FRONT, GL_SPECULAR, black);
03102     glMaterialfv(GL_FRONT, GL_SHININESS, &zero);
03103 }
03104 
03105 
03106 static void renderModel_GLSL(Model* Model,
03107                              const LightingState& ls,
03108                              float radius,
03109                              const Mat4f& planetMat)
03110 {
03111     glDisable(GL_LIGHTING);
03112 }
03113 
03114 
03115 static void renderSphereDefault(const RenderInfo& ri,
03116                                 const Frustum& frustum,
03117                                 bool lit,
03118                                 const GLContext& context)
03119 {
03120     if (lit)
03121         glEnable(GL_LIGHTING);
03122     else
03123         glDisable(GL_LIGHTING);
03124 
03125     if (ri.baseTex == NULL)
03126     {
03127         glDisable(GL_TEXTURE_2D);
03128     }
03129     else
03130     {
03131         glEnable(GL_TEXTURE_2D);
03132         ri.baseTex->bind();
03133     }
03134 
03135     glColor(ri.color);
03136 
03137     lodSphere->render(context,
03138                       LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03139                       frustum, ri.pixWidth,
03140                       ri.baseTex);
03141     if (ri.nightTex != NULL && ri.useTexEnvCombine)
03142     {
03143         ri.nightTex->bind();
03144         setupNightTextureCombine();
03145         glEnable(GL_BLEND);
03146         glBlendFunc(GL_ONE, GL_ONE);
03147         glAmbientLightColor(Color::Black); // Disable ambient light
03148         lodSphere->render(context,
03149                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03150                           frustum, ri.pixWidth,
03151                           ri.nightTex);
03152         glAmbientLightColor(ri.ambientColor);
03153         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03154     }
03155 
03156     if (ri.overlayTex != NULL)
03157     {
03158         ri.overlayTex->bind();
03159         glEnable(GL_BLEND);
03160         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
03161         lodSphere->render(context,
03162                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03163                           frustum, ri.pixWidth,
03164                           ri.overlayTex);
03165         glBlendFunc(GL_ONE, GL_ONE);
03166     }
03167 }
03168 
03169 
03170 // DEPRECATED -- renderSphere_Combiners_VP should be used instead; only
03171 // very old drivers don't support vertex programs.
03172 static void renderSphere_Combiners(const RenderInfo& ri,
03173                                    const Frustum& frustum,
03174                                    const GLContext& context)
03175 {
03176     glDisable(GL_LIGHTING);
03177 
03178     if (ri.baseTex == NULL)
03179     {
03180         glDisable(GL_TEXTURE_2D);
03181     }
03182     else
03183     {
03184         glEnable(GL_TEXTURE_2D);
03185         ri.baseTex->bind();
03186     }
03187 
03188     glColor(ri.color * ri.sunColor);
03189 
03190     if (ri.bumpTex != NULL)
03191     {
03192         renderBumpMappedMesh(context,
03193                              *(ri.baseTex),
03194                              *(ri.bumpTex),
03195                              ri.sunDir_eye,
03196                              ri.orientation,
03197                              ri.ambientColor,
03198                              frustum,
03199                              ri.pixWidth);
03200     }
03201     else if (ri.baseTex != NULL)
03202     {
03203         renderSmoothMesh(context,
03204                          *(ri.baseTex),
03205                          ri.sunDir_eye,
03206                          ri.orientation,
03207                          ri.ambientColor,
03208                          ri.pixWidth,
03209                          frustum);
03210     }
03211     else
03212     {
03213         glEnable(GL_LIGHTING);
03214         lodSphere->render(context, frustum, ri.pixWidth, NULL, 0);
03215     }
03216 
03217     if (ri.nightTex != NULL)
03218     {
03219         ri.nightTex->bind();
03220         glEnable(GL_BLEND);
03221         glBlendFunc(GL_ONE, GL_ONE);
03222         renderSmoothMesh(context,
03223                          *(ri.nightTex),
03224                          ri.sunDir_eye, 
03225                          ri.orientation,
03226                          Color::Black,
03227                          ri.pixWidth,
03228                          frustum,
03229                          true);
03230     }
03231 
03232     if (ri.overlayTex != NULL)
03233     {
03234         glEnable(GL_LIGHTING);
03235         ri.overlayTex->bind();
03236         glEnable(GL_BLEND);
03237         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
03238         lodSphere->render(context,
03239                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0, 
03240                           frustum, ri.pixWidth,
03241                           ri.overlayTex);
03242 #if 0
03243         renderSmoothMesh(context,
03244                          *(ri.overlayTex),
03245                          ri.sunDir_eye,
03246                          ri.orientation,
03247                          ri.ambientColor,
03248                          ri.pixWidth,
03249                          frustum);
03250 #endif
03251         glBlendFunc(GL_ONE, GL_ONE);
03252     }
03253 
03254     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
03255 }
03256 
03257 
03258 static void renderSphere_DOT3_VP(const RenderInfo& ri,
03259                                  const LightingState& ls,
03260                                  const Frustum& frustum,
03261                                  const GLContext& context)
03262 {
03263     VertexProcessor* vproc = context.getVertexProcessor();
03264     assert(vproc != NULL);
03265 
03266     if (ri.baseTex == NULL)
03267     {
03268         glDisable(GL_TEXTURE_2D);
03269     }
03270     else
03271     {
03272         glEnable(GL_TEXTURE_2D);
03273         ri.baseTex->bind();
03274     }
03275 
03276     vproc->enable();
03277     vproc->parameter(vp::EyePosition, ri.eyePos_obj);
03278     setLightParameters_VP(*vproc, ls, ri.color, ri.specularColor);
03279 
03280     vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
03281     vproc->parameter(vp::SpecularExponent, 0.0f, 1.0f, 0.5f, ri.specularPower);
03282 
03283     if (ri.bumpTex != NULL && ri.baseTex != NULL)
03284     {
03285         // We don't yet handle the case where there's a bump map but no
03286         // base texture.
03287         vproc->use(vp::diffuseBump);
03288         if (ri.ambientColor != Color::Black)
03289         {
03290             // If there's ambient light, we'll need to render in two passes:
03291             // one for the ambient light, and the second for light from the star.
03292             // We could do this in a single pass using three texture stages, but
03293             // this isn't won't work with hardware that only supported two
03294             // texture stages.
03295 
03296             // Render the base texture modulated by the ambient color
03297             setupTexenvAmbient(ri.ambientColor);
03298             lodSphere->render(context,
03299                               LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
03300                               frustum, ri.pixWidth,
03301                               ri.baseTex);
03302 
03303             // Add the light from the sun
03304             glEnable(GL_BLEND);
03305             glBlendFunc(GL_ONE, GL_ONE);
03306             setupBumpTexenv();
03307             lodSphere->render(context,
03308                               LODSphereMesh::Normals | LODSphereMesh::Tangents |
03309                               LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
03310                               frustum, ri.pixWidth,
03311                               ri.bumpTex, ri.baseTex);
03312             glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03313             glDisable(GL_BLEND);
03314         }
03315         else
03316         {
03317             glx::glActiveTextureARB(GL_TEXTURE1_ARB);
03318             ri.baseTex->bind();
03319             glx::glActiveTextureARB(GL_TEXTURE0_ARB);
03320             ri.bumpTex->bind();
03321             setupBumpTexenv();
03322             lodSphere->render(context,
03323                               LODSphereMesh::Normals | LODSphereMesh::Tangents |
03324                               LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
03325                               frustum, ri.pixWidth,
03326                               ri.bumpTex, ri.baseTex);
03327             glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03328         }
03329     }
03330     else
03331     {
03332         if (ls.nLights > 1)
03333             vproc->use(vp::diffuse_2light);
03334         else
03335             vproc->use(vp::diffuse);
03336         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03337         lodSphere->render(context,
03338                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
03339                           LODSphereMesh::VertexProgParams,
03340                           frustum, ri.pixWidth,
03341                           ri.baseTex);
03342     }
03343 
03344     // Render a specular pass; can't be done in one pass because
03345     // specular needs to be modulated with a gloss map.
03346     if (ri.specularColor != Color::Black)
03347     {
03348         glEnable(GL_BLEND);
03349         glBlendFunc(GL_ONE, GL_ONE);
03350         vproc->use(vp::glossMap);
03351 
03352         if (ri.glossTex != NULL)
03353             glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03354         else
03355             setupTexenvGlossMapAlpha();
03356 
03357         lodSphere->render(context,
03358                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03359                           frustum, ri.pixWidth,
03360                           ri.glossTex != NULL ? ri.glossTex : ri.baseTex);
03361 
03362         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03363         glDisable(GL_BLEND);
03364     }
03365 
03366     if (ri.nightTex != NULL)
03367     {
03368         ri.nightTex->bind();
03369         if (ls.nLights > 1)
03370             vproc->use(vp::nightLights_2light);
03371         else
03372             vproc->use(vp::nightLights);
03373         setupNightTextureCombine();
03374         glEnable(GL_BLEND);
03375         glBlendFunc(GL_ONE, GL_ONE);
03376         lodSphere->render(context,
03377                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03378                           frustum, ri.pixWidth,
03379                           ri.nightTex);
03380         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03381     }
03382 
03383     if (ri.overlayTex != NULL)
03384     {
03385         ri.overlayTex->bind();
03386         vproc->use(vp::diffuse);
03387         glEnable(GL_BLEND);
03388         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
03389         lodSphere->render(context,
03390                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03391                           frustum, ri.pixWidth,
03392                           ri.overlayTex);
03393         glBlendFunc(GL_ONE, GL_ONE);
03394     }
03395 
03396     vproc->disable();
03397 }
03398 
03399 
03400 static void renderSphere_Combiners_VP(const RenderInfo& ri,
03401                                       const LightingState& ls,
03402                                       const Frustum& frustum,
03403                                       const GLContext& context)
03404 {
03405     Texture* textures[4];
03406     VertexProcessor* vproc = context.getVertexProcessor();
03407     assert(vproc != NULL);
03408 
03409     if (ri.baseTex == NULL)
03410     {
03411         glDisable(GL_TEXTURE_2D);
03412     }
03413     else
03414     {
03415         glEnable(GL_TEXTURE_2D);
03416         ri.baseTex->bind();
03417     }
03418 
03419     // Set up the fog parameters if the haze density is non-zero
03420     float hazeDensity = ri.hazeColor.alpha();
03421 
03422     if (hazeDensity > 0.0f && !buggyVertexProgramEmulation)
03423     {
03424         glEnable(GL_FOG);
03425         float fogColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
03426         fogColor[0] = ri.hazeColor.red();
03427         fogColor[1] = ri.hazeColor.green();
03428         fogColor[2] = ri.hazeColor.blue();
03429         glFogfv(GL_FOG_COLOR, fogColor);
03430         glFogi(GL_FOG_MODE, GL_LINEAR);
03431         glFogf(GL_FOG_START, 0.0);
03432         glFogf(GL_FOG_END, 1.0f / hazeDensity);
03433     }
03434 
03435     vproc->enable();
03436 
03437     vproc->parameter(vp::EyePosition, ri.eyePos_obj);
03438     setLightParameters_VP(*vproc, ls, ri.color, ri.specularColor);
03439 
03440     vproc->parameter(vp::SpecularExponent, 0.0f, 1.0f, 0.5f, ri.specularPower);
03441     vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
03442     vproc->parameter(vp::HazeColor, ri.hazeColor);
03443 
03444     if (ri.bumpTex != NULL)
03445     {
03446         if (hazeDensity > 0.0f)
03447             vproc->use(vp::diffuseBumpHaze);
03448         else
03449             vproc->use(vp::diffuseBump);
03450         SetupCombinersDecalAndBumpMap(*(ri.bumpTex),
03451                                       ri.ambientColor * ri.color,
03452                                       ri.sunColor * ri.color);
03453         lodSphere->render(context,
03454                           LODSphereMesh::Normals | LODSphereMesh::Tangents |
03455                           LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
03456                           frustum, ri.pixWidth,
03457                           ri.baseTex, ri.bumpTex);
03458         DisableCombiners();
03459 
03460         // Render a specular pass
03461         if (ri.specularColor != Color::Black)
03462         {
03463             glEnable(GL_BLEND);
03464             glBlendFunc(GL_ONE, GL_ONE);
03465             glEnable(GL_COLOR_SUM_EXT);
03466             vproc->use(vp::specular);
03467 
03468             // Disable ambient and diffuse
03469             vproc->parameter(vp::AmbientColor, Color::Black);
03470             vproc->parameter(vp::DiffuseColor0, Color::Black);
03471             SetupCombinersGlossMap(ri.glossTex != NULL ? GL_TEXTURE0_ARB : 0);
03472 
03473             textures[0] = ri.glossTex != NULL ? ri.glossTex : ri.baseTex;
03474             lodSphere->render(context,
03475                               LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03476                               frustum, ri.pixWidth,
03477                               textures, 1);
03478 
03479             // re-enable diffuse
03480             vproc->parameter(vp::DiffuseColor0, ri.sunColor * ri.color);
03481 
03482             DisableCombiners();
03483             glDisable(GL_COLOR_SUM_EXT);
03484             glDisable(GL_BLEND);
03485         }
03486     }
03487     else if (ri.specularColor != Color::Black)
03488     {
03489         glEnable(GL_COLOR_SUM_EXT);
03490         if (ls.nLights > 1)
03491             vproc->use(vp::specular_2light);
03492         else
03493             vproc->use(vp::specular);
03494         SetupCombinersGlossMapWithFog(ri.glossTex != NULL ? GL_TEXTURE1_ARB : 0);
03495         unsigned int attributes = LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
03496             LODSphereMesh::VertexProgParams;
03497         lodSphere->render(context,
03498                           attributes, frustum, ri.pixWidth,
03499                           ri.baseTex, ri.glossTex);
03500         DisableCombiners();
03501         glDisable(GL_COLOR_SUM_EXT);
03502     }
03503     else
03504     {
03505         if (ls.nLights > 1)
03506         {
03507             if (hazeDensity > 0.0f)
03508                 vproc->use(vp::diffuseHaze_2light);
03509             else
03510                 vproc->use(vp::diffuse_2light);
03511         }
03512         else
03513         {
03514             if (hazeDensity > 0.0f)
03515                 vproc->use(vp::diffuseHaze);
03516             else
03517                 vproc->use(vp::diffuse);
03518         }
03519 
03520         lodSphere->render(context,
03521                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
03522                           LODSphereMesh::VertexProgParams,
03523                           frustum, ri.pixWidth,
03524                           ri.baseTex);
03525     }
03526 
03527     if (hazeDensity > 0.0f)
03528         glDisable(GL_FOG);
03529 
03530     if (ri.nightTex != NULL)
03531     {
03532         ri.nightTex->bind();
03533         if (ls.nLights > 1)
03534             vproc->use(vp::nightLights_2light);
03535         else
03536             vproc->use(vp::nightLights);
03537         setupNightTextureCombine();
03538         glEnable(GL_BLEND);
03539         glBlendFunc(GL_ONE, GL_ONE);
03540         lodSphere->render(context,
03541                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03542                           frustum, ri.pixWidth,
03543                           ri.nightTex);
03544         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03545     }
03546 
03547     if (ri.overlayTex != NULL)
03548     {
03549         ri.overlayTex->bind();
03550         vproc->use(vp::diffuse);
03551         glEnable(GL_BLEND);
03552         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
03553         lodSphere->render(context,
03554                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03555                           frustum, ri.pixWidth,
03556                           ri.overlayTex);
03557         glBlendFunc(GL_ONE, GL_ONE);
03558     }
03559 
03560     vproc->disable();
03561 }
03562 
03563 
03564 // Render a planet sphere using both fragment and vertex programs
03565 static void renderSphere_FP_VP(const RenderInfo& ri,
03566                                const Frustum& frustum,
03567                                const GLContext& context)
03568 {
03569     Texture* textures[4];
03570     VertexProcessor* vproc = context.getVertexProcessor();
03571     FragmentProcessor* fproc = context.getFragmentProcessor();
03572     assert(vproc != NULL && fproc != NULL);
03573 
03574     if (ri.baseTex == NULL)
03575     {
03576         glDisable(GL_TEXTURE_2D);
03577     }
03578     else
03579     {
03580         glEnable(GL_TEXTURE_2D);
03581         ri.baseTex->bind();
03582     }
03583 
03584     // Compute the half angle vector required for specular lighting
03585     Vec3f halfAngle_obj = ri.eyeDir_obj + ri.sunDir_obj;
03586     if (halfAngle_obj.length() != 0.0f)
03587         halfAngle_obj.normalize();
03588 
03589     // Set up the fog parameters if the haze density is non-zero
03590     float hazeDensity = ri.hazeColor.alpha();
03591 
03592     if (hazeDensity > 0.0f)
03593     {
03594         glEnable(GL_FOG);
03595         float fogColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
03596         fogColor[0] = ri.hazeColor.red();
03597         fogColor[1] = ri.hazeColor.green();
03598         fogColor[2] = ri.hazeColor.blue();
03599         glFogfv(GL_FOG_COLOR, fogColor);
03600         glFogi(GL_FOG_MODE, GL_LINEAR);
03601         glFogf(GL_FOG_START, 0.0);
03602         glFogf(GL_FOG_END, 1.0f / hazeDensity);
03603     }
03604 
03605     vproc->enable();
03606 
03607     vproc->parameter(vp::EyePosition, ri.eyePos_obj);
03608     vproc->parameter(vp::LightDirection0, ri.sunDir_obj);
03609     vproc->parameter(vp::DiffuseColor0, ri.sunColor * ri.color);
03610     vproc->parameter(vp::SpecularExponent, 0.0f, 1.0f, 0.5f, ri.specularPower);
03611     vproc->parameter(vp::SpecularColor0, ri.sunColor * ri.specularColor);
03612     vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
03613     vproc->parameter(vp::HazeColor, ri.hazeColor);
03614 
03615     if (ri.bumpTex != NULL)
03616     {
03617         fproc->enable();
03618         
03619         if (hazeDensity > 0.0f)
03620             vproc->use(vp::diffuseBumpHaze);
03621         else
03622             vproc->use(vp::diffuseBump);
03623         fproc->use(fp::texDiffuseBump);
03624         lodSphere->render(context,
03625                           LODSphereMesh::Normals | LODSphereMesh::Tangents |
03626                           LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
03627                           frustum, ri.pixWidth,
03628                           ri.baseTex, ri.bumpTex);
03629         fproc->disable();
03630 
03631         // Render a specular pass
03632         if (ri.specularColor != Color::Black)
03633         {
03634             glEnable(GL_BLEND);
03635             glBlendFunc(GL_ONE, GL_ONE);
03636             glEnable(GL_COLOR_SUM_EXT);
03637             vproc->use(vp::specular);
03638 
03639             // Disable ambient and diffuse
03640             vproc->parameter(vp::AmbientColor, Color::Black);
03641             vproc->parameter(vp::DiffuseColor0, Color::Black);
03642             SetupCombinersGlossMap(ri.glossTex != NULL ? GL_TEXTURE0_ARB : 0);
03643 
03644             textures[0] = ri.glossTex != NULL ? ri.glossTex : ri.baseTex;
03645             lodSphere->render(context,
03646                               LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03647                               frustum, ri.pixWidth,
03648                               textures, 1);
03649 
03650             // re-enable diffuse
03651             vproc->parameter(vp::DiffuseColor0, ri.sunColor * ri.color);
03652 
03653             DisableCombiners();
03654             glDisable(GL_COLOR_SUM_EXT);
03655             glDisable(GL_BLEND);
03656         }
03657     }
03658     else if (ri.specularColor != Color::Black)
03659     {
03660         fproc->enable();
03661         if (ri.glossTex == NULL)
03662         {
03663             vproc->use(vp::perFragmentSpecularAlpha);
03664             fproc->use(fp::texSpecularAlpha);
03665         }
03666         else
03667         {
03668             vproc->use(vp::perFragmentSpecular);
03669             fproc->use(fp::texSpecular);
03670         }
03671         fproc->parameter(fp::DiffuseColor, ri.sunColor * ri.color);
03672         fproc->parameter(fp::SunDirection, ri.sunDir_obj);
03673         fproc->parameter(fp::SpecularColor, ri.specularColor);
03674         fproc->parameter(fp::SpecularExponent, ri.specularPower, 0.0f, 0.0f, 0.0f);
03675         fproc->parameter(fp::AmbientColor, ri.ambientColor);
03676 
03677         unsigned int attributes = LODSphereMesh::Normals |
03678                                   LODSphereMesh::TexCoords0 |
03679                                   LODSphereMesh::VertexProgParams;
03680         lodSphere->render(context,
03681                           attributes, frustum, ri.pixWidth,
03682                           ri.baseTex, ri.glossTex);
03683         fproc->disable();
03684     }
03685     else
03686     {
03687         fproc->enable();
03688         if (hazeDensity > 0.0f)
03689             vproc->use(vp::diffuseHaze);
03690         else
03691             vproc->use(vp::diffuse);
03692         fproc->use(fp::texDiffuse);
03693         lodSphere->render(context,
03694                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
03695                           LODSphereMesh::VertexProgParams,
03696                           frustum, ri.pixWidth,
03697                           ri.baseTex);
03698         fproc->disable();
03699     }
03700 
03701     if (hazeDensity > 0.0f)
03702         glDisable(GL_FOG);
03703 
03704     if (ri.nightTex != NULL)
03705     {
03706         ri.nightTex->bind();
03707         vproc->use(vp::nightLights);
03708         setupNightTextureCombine();
03709         glEnable(GL_BLEND);
03710         glBlendFunc(GL_ONE, GL_ONE);
03711         lodSphere->render(context,
03712                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03713                           frustum, ri.pixWidth,
03714                           ri.nightTex);
03715         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
03716     }
03717 
03718     if (ri.overlayTex != NULL)
03719     {
03720         ri.overlayTex->bind();
03721         vproc->use(vp::diffuse);
03722         glEnable(GL_BLEND);
03723         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
03724         lodSphere->render(context,
03725                           LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
03726                           frustum, ri.pixWidth,
03727                           ri.overlayTex);
03728         glBlendFunc(GL_ONE, GL_ONE);
03729     }
03730 
03731     vproc->disable();
03732 }
03733 
03734 
03735 static void renderSphere_GLSL(const RenderInfo& ri,
03736                               const LightingState& ls,
03737                               RingSystem* rings,
03738                               float radius,
03739                               const Mat4f& planetMat,
03740                               const Frustum& frustum,
03741                               const GLContext& context)
03742 {
03743     Texture* textures[4] = { NULL, NULL, NULL, NULL };
03744     unsigned int nTextures = 0;
03745     //VertexProcessor* vproc = context.getVertexProcessor();
03746     //assert(vproc != NULL);
03747 
03748     //vproc->disable();
03749     glDisable(GL_LIGHTING);
03750 
03751     ShaderProperties shadprop;
03752     shadprop.nLights = min(ls.nLights, MaxShaderLights);
03753 
03754     // Set up the textures used by this object
03755     if (ri.baseTex != NULL)
03756     {
03757         shadprop.texUsage = ShaderProperties::DiffuseTexture;
03758         textures[nTextures++] = ri.baseTex;
03759     }
03760 
03761     if (ri.bumpTex != NULL)
03762     {
03763         shadprop.texUsage |= ShaderProperties::NormalTexture;
03764         textures[nTextures++] = ri.bumpTex;
03765     }
03766 
03767     if (ri.specularColor != Color::Black)
03768     {
03769         shadprop.lightModel = ShaderProperties::SpecularModel;
03770         if (ri.glossTex == NULL)
03771         {
03772             shadprop.texUsage |= ShaderProperties::SpecularInDiffuseAlpha;
03773         }
03774         else
03775         {
03776             shadprop.texUsage |= ShaderProperties::SpecularTexture;
03777             textures[nTextures++] = ri.glossTex;
03778         }
03779     }
03780 
03781     if (ri.nightTex != NULL)
03782     {
03783         shadprop.texUsage |= ShaderProperties::NightTexture;
03784         textures[nTextures++] = ri.nightTex;
03785     }
03786     
03787     if (rings != NULL)
03788 #if 0
03789         (renderFlags & ShowRingShadows) != 0)
03790 #endif
03791     {
03792         Texture* ringsTex = rings->texture.find(medres);
03793         if (ringsTex != NULL)
03794         {
03795             glx::glActiveTextureARB(GL_TEXTURE0_ARB + nTextures);
03796             ringsTex->bind();
03797             nTextures++;
03798 
03799             // Tweak the texture--set clamp to border and a border color with
03800             // a zero alpha.
03801             float bc[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
03802             glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bc);
03803             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
03804                             GL_CLAMP_TO_BORDER_ARB);
03805             glx::glActiveTextureARB(GL_TEXTURE0_ARB);
03806 
03807             shadprop.texUsage |= ShaderProperties::RingShadowTexture;
03808         }
03809     }
03810 
03811     // Set the shadow information.
03812     // Track the total number of shadows; if there are too many, we'll have
03813     // to fall back to multipass.
03814     unsigned int totalShadows = 0;
03815     for (unsigned int li = 0; li < ls.nLights; li++)
03816     {
03817         if (ls.shadows[li] && !ls.shadows[li]->empty())
03818         {
03819             unsigned int nShadows = (unsigned int) min((size_t) MaxShaderShadows, ls.shadows[li]->size());
03820             shadprop.setShadowCountForLight(li, nShadows);
03821             totalShadows += nShadows;
03822         }
03823     }
03824 
03825     // Get a shader for the current rendering configuration
03826     CelestiaGLProgram* prog = GetShaderManager().getShader(shadprop);
03827     if (prog == NULL)
03828         return;
03829 
03830     prog->use();
03831 
03832     setLightParameters_GLSL(*prog, shadprop, ls,
03833                             ri.color, ri.specularColor);
03834 
03835     prog->shininess = ri.specularPower;
03836     prog->ambientColor = Vec3f(ri.ambientColor.red(), ri.ambientColor.green(),
03837                                ri.ambientColor.blue());
03838     
03839     if (shadprop.texUsage & ShaderProperties::RingShadowTexture)
03840     {
03841         float ringWidth = rings->outerRadius - rings->innerRadius;
03842         prog->ringRadius = rings->innerRadius / radius;
03843         prog->ringWidth = 1.0f / (ringWidth / radius);
03844     }
03845 
03846     if (shadprop.shadowCounts != 0)    
03847         setEclipseShadowShaderConstants(ls, radius, planetMat, *prog);
03848 
03849     glColor(ri.color);
03850 
03851     unsigned int attributes = LODSphereMesh::Normals;
03852     if (ri.bumpTex != NULL)
03853         attributes |= LODSphereMesh::Tangents;
03854     lodSphere->render(context,
03855                       attributes,
03856                       frustum, ri.pixWidth,
03857                       textures[0], textures[1], textures[2], textures[3]);
03858 
03859     glx::glUseProgramObjectARB(0);
03860 }
03861 
03862 
03863 static void renderClouds_GLSL(const RenderInfo& ri,
03864                               const LightingState& ls,
03865                               Texture* cloudTex,
03866                               float texOffset,
03867                               RingSystem* rings,
03868                               float radius,
03869                               const Mat4f& planetMat,
03870                               const Frustum& frustum,
03871                               const GLContext& context)
03872 {
03873     unsigned int nTextures = 0;
03874 
03875     glDisable(GL_LIGHTING);
03876 
03877     ShaderProperties shadprop;
03878     shadprop.nLights = ls.nLights;
03879 
03880     // Set up the textures used by this object
03881     if (cloudTex != NULL)
03882     {
03883         shadprop.texUsage = ShaderProperties::DiffuseTexture;
03884         nTextures++;
03885     }
03886 
03887     if (rings != NULL)
03888         //(renderFlags & ShowRingShadows) != 0)
03889     {
03890         Texture* ringsTex = rings->texture.find(medres);
03891         if (ringsTex != NULL)
03892         {
03893             glx::glActiveTextureARB(GL_TEXTURE0_ARB + nTextures);
03894             ringsTex->bind();
03895             nTextures++;
03896 
03897             // Tweak the texture--set clamp to border and a border color with
03898             // a zero alpha.
03899             float bc[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
03900             glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bc);
03901             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
03902                             GL_CLAMP_TO_BORDER_ARB);
03903             glx::glActiveTextureARB(GL_TEXTURE0_ARB);
03904 
03905             shadprop.texUsage |= ShaderProperties::RingShadowTexture;
03906         }
03907     }
03908 
03909     // Set the shadow information.
03910     // Track the total number of shadows; if there are too many, we'll have
03911     // to fall back to multipass.
03912     unsigned int totalShadows = 0;
03913     for (unsigned int li = 0; li < ls.nLights; li++)
03914     {
03915         if (ls.shadows[li] && !ls.shadows[li]->empty())
03916         {
03917             unsigned int nShadows = (unsigned int) min((size_t) MaxShaderShadows, ls.shadows[li]->size());
03918             shadprop.setShadowCountForLight(li, nShadows);
03919             totalShadows += nShadows;
03920         }
03921     }
03922 
03923     // Get a shader for the current rendering configuration
03924     CelestiaGLProgram* prog = GetShaderManager().getShader(shadprop);
03925     if (prog == NULL)
03926         return;
03927 
03928     prog->use();
03929 
03930     setLightParameters_GLSL(*prog, shadprop, ls,
03931                             ri.color, ri.specularColor);
03932 
03933     prog->ambientColor = Vec3f(ri.ambientColor.red(), ri.ambientColor.green(),
03934                                ri.ambientColor.blue());
03935     prog->textureOffset = texOffset;
03936     
03937     if (shadprop.texUsage & ShaderProperties::RingShadowTexture)
03938     {
03939         float ringWidth = rings->outerRadius - rings->innerRadius;
03940         prog->ringRadius = rings->innerRadius / radius;
03941         prog->ringWidth = 1.0f / (ringWidth / radius);
03942     }
03943 
03944     if (shadprop.shadowCounts != 0)    
03945         setEclipseShadowShaderConstants(ls, radius, planetMat, *prog);
03946 
03947     unsigned int attributes = LODSphereMesh::Normals;
03948     lodSphere->render(context,
03949                       LODSphereMesh::Normals,
03950                       frustum, ri.pixWidth,
03951                       cloudTex);
03952 
03953     prog->textureOffset = 0.0f;
03954 
03955     glx::glUseProgramObjectARB(0);
03956 }
03957 
03958 
03959 static void texGenPlane(GLenum coord, GLenum mode, const Vec4f& plane)
03960 {
03961     float f[4];
03962     f[0] = plane.x; f[1] = plane.y; f[2] = plane.z; f[3] = plane.w;
03963     glTexGenfv(coord, mode, f);
03964 }
03965 
03966 static void renderShadowedModelDefault(Model* model,
03967                                        const RenderInfo& ri,
03968                                        const Frustum& frustum,
03969                                        float *sPlane,
03970                                        float *tPlane,
03971                                        const Vec3f& lightDir,
03972                                        bool useShadowMask,
03973                                        const GLContext& context)
03974 {
03975     glEnable(GL_TEXTURE_GEN_S);
03976     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
03977     glTexGenfv(GL_S, GL_OBJECT_PLANE, sPlane);
03978     //texGenPlane(GL_S, GL_OBJECT_PLANE, sPlane);
03979     glEnable(GL_TEXTURE_GEN_T);
03980     glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
03981     glTexGenfv(GL_T, GL_OBJECT_PLANE, tPlane);
03982 
03983     if (useShadowMask)
03984     {
03985         glx::glActiveTextureARB(GL_TEXTURE1_ARB);
03986         glEnable(GL_TEXTURE_GEN_S);
03987         glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
03988         texGenPlane(GL_S, GL_OBJECT_PLANE,
03989                     Vec4f(lightDir.x, lightDir.y, lightDir.z, 0.5f));
03990         glx::glActiveTextureARB(GL_TEXTURE0_ARB);
03991     }
03992 
03993     glColor4f(1, 1, 1, 1);
03994     glDisable(GL_LIGHTING);
03995 
03996     if (model == NULL)
03997     {
03998         lodSphere->render(context,
03999                           LODSphereMesh::Normals | LODSphereMesh::Multipass,
04000                           frustum, ri.pixWidth, NULL);
04001     }
04002     else
04003     {
04004         FixedFunctionRenderContext rc;
04005         model->render(rc);
04006     }
04007     glEnable(GL_LIGHTING);
04008 
04009     if (useShadowMask)
04010     {
04011         glx::glActiveTextureARB(GL_TEXTURE1_ARB);
04012         glDisable(GL_TEXTURE_GEN_S);
04013         glx::glActiveTextureARB(GL_TEXTURE0_ARB);
04014     }
04015     glDisable(GL_TEXTURE_GEN_S);
04016     glDisable(GL_TEXTURE_GEN_T);
04017 }
04018 
04019 
04020 static void renderShadowedModelVertexShader(const RenderInfo& ri,
04021                                             const Frustum& frustum,
04022                                             float* sPlane, float* tPlane,
04023                                             Vec3f& lightDir,
04024                                             const GLContext& context)
04025 {
04026     VertexProcessor* vproc = context.getVertexProcessor();
04027     assert(vproc != NULL);
04028 
04029     vproc->enable();
04030     vproc->parameter(vp::LightDirection0, lightDir);
04031     vproc->parameter(vp::TexGen_S, sPlane);
04032     vproc->parameter(vp::TexGen_T, tPlane);
04033     vproc->use(vp::shadowTexture);
04034 
04035     lodSphere->render(context,
04036                       LODSphereMesh::Normals | LODSphereMesh::Multipass, frustum,
04037                       ri.pixWidth, NULL);
04038 
04039     vproc->disable();
04040 }
04041 
04042 
04043 static void renderRings(RingSystem& rings,
04044                         RenderInfo& ri,
04045                         float planetRadius,
04046                         float planetOblateness,
04047                         unsigned int textureResolution,
04048                         bool renderShadow,
04049                         const GLContext& context,
04050                         unsigned int nSections)
04051 {
04052     float inner = rings.innerRadius / planetRadius;
04053     float outer = rings.outerRadius / planetRadius;
04054 
04055     // Ring Illumination:
04056     // Since a ring system is composed of millions of individual
04057     // particles, it's not at all realistic to model it as a flat
04058     // Lambertian surface.  We'll approximate the llumination
04059     // function by assuming that the ring system contains Lambertian
04060     // particles, and that the brightness at some point in the ring
04061     // system is proportional to the illuminated fraction of a
04062     // particle there.  In fact, we'll simplify things further and
04063     // set the illumination of the entire ring system to the same
04064     // value, computing the illuminated fraction of a hypothetical
04065     // particle located at the center of the planet.  This
04066     // approximation breaks down when you get close to the planet.
04067     float ringIllumination = 0.0f;
04068     {
04069         float illumFraction = (1.0f + ri.eyeDir_obj * ri.sunDir_obj) / 2.0f;
04070         // Just use the illuminated fraction for now . . .
04071         ringIllumination = illumFraction;
04072     }
04073 
04074     GLContext::VertexPath vpath = context.getVertexPath();
04075     VertexProcessor* vproc = context.getVertexProcessor();
04076     FragmentProcessor* fproc = context.getFragmentProcessor();
04077 
04078     if (vproc != NULL)
04079     {
04080         vproc->enable();
04081         vproc->use(vp::ringIllum);
04082         vproc->parameter(vp::LightDirection0, ri.sunDir_obj);
04083         vproc->parameter(vp::DiffuseColor0, ri.sunColor * rings.color);
04084         vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
04085         vproc->parameter(vp::Constant0, Vec3f(0, 0.5, 1.0));
04086     }
04087 
04088     // If we have multi-texture support, we'll use the second texture unit
04089     // to render the shadow of the planet on the rings.  This is a bit of
04090     // a hack, and assumes that the planet is ellipsoidal in shape,
04091     // and only works for a planet illuminated by a single sun where the
04092     // distance to the sun is very large relative to its diameter.
04093     if (renderShadow)
04094     {
04095         glx::glActiveTextureARB(GL_TEXTURE1_ARB);
04096         glEnable(GL_TEXTURE_2D);
04097         shadowTex->bind();
04098 
04099         float sPlane[4] = { 0, 0, 0, 0.5f };
04100         float tPlane[4] = { 0, 0, 0, 0.5f };
04101 
04102         // Compute the projection vectors based on the sun direction.
04103         // I'm being a little careless here--if the sun direction lies
04104         // along the y-axis, this will fail.  It's unlikely that a
04105         // planet would ever orbit underneath its sun (an orbital
04106         // inclination of 90 degrees), but this should be made
04107         // more robust anyway.
04108         Vec3f axis = Vec3f(0, 1, 0) ^ ri.sunDir_obj;
04109         float cosAngle = Vec3f(0.0f, 1.0f, 0.0f) * ri.sunDir_obj;
04110         float angle = (float) acos(cosAngle);
04111         axis.normalize();
04112 
04113         float sScale = 1.0f;
04114         float tScale = 1.0f;
04115         if (fproc == NULL)
04116         {
04117             // When fragment programs aren't used, we render shadows with circular
04118             // textures.  We scale up the texture slightly to account for the
04119             // padding pixels near the texture borders.
04120             sScale *= ShadowTextureScale;
04121             tScale *= ShadowTextureScale;
04122         }
04123 
04124         if (planetOblateness != 0.0f)
04125         {
04126             // For oblate planets, the size of the shadow volume will vary based
04127             // on the light direction.
04128 
04129             // A vertical slice of the planet is an ellipse
04130             float a = 1.0f;                          // semimajor axis
04131             float b = a * (1.0f - planetOblateness); // semiminor axis
04132             float ecc2 = 1.0f - (b * b) / (a * a);   // square of eccentricity
04133 
04134             // Calculate the radius of the ellipse at the incident angle of the
04135             // light on the ring plane + 90 degrees.
04136             float r = a * (float) sqrt((1.0f - ecc2) /
04137                                        (1.0f - ecc2 * square(cosAngle)));
04138             
04139             tScale *= a / r;
04140         }
04141 
04142         // The s axis is perpendicular to the shadow axis in the plane of the
04143         // of the rings, and the t axis completes the orthonormal basis.
04144         Vec3f sAxis = axis * 0.5f;
04145         Vec3f tAxis = (axis ^ ri.sunDir_obj) * 0.5f * tScale;
04146 
04147         sPlane[0] = sAxis.x; sPlane[1] = sAxis.y; sPlane[2] = sAxis.z;
04148         tPlane[0] = tAxis.x; tPlane[1] = tAxis.y; tPlane[2] = tAxis.z;
04149 
04150         if (vproc != NULL)
04151         {
04152             vproc->parameter(vp::TexGen_S, sPlane);
04153             vproc->parameter(vp::TexGen_T, tPlane);
04154         }
04155         else
04156         {
04157             glEnable(GL_TEXTURE_GEN_S);
04158             glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
04159             glTexGenfv(GL_S, GL_EYE_PLANE, sPlane);
04160             glEnable(GL_TEXTURE_GEN_T);
04161             glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
04162             glTexGenfv(GL_T, GL_EYE_PLANE, tPlane);
04163         }
04164 
04165         glx::glActiveTextureARB(GL_TEXTURE0_ARB);
04166 
04167         if (fproc != NULL)
04168         {
04169             float r0 = 0.24f;
04170             float r1 = 0.25f;
04171             float bias = 1.0f / (1.0f - r1 / r0);
04172             float scale = -bias / r0;
04173 
04174             fproc->enable();
04175             fproc->use(fp::sphereShadowOnRings);
04176             fproc->parameter(fp::ShadowParams0, scale, bias, 0.0f, 0.0f);
04177             fproc->parameter(fp::AmbientColor, ri.ambientColor * ri.color);
04178         }
04179     }
04180 
04181     glEnable(GL_BLEND);
04182     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
04183 
04184     Texture* ringsTex = rings.texture.find(textureResolution);
04185 
04186     if (ringsTex != NULL)
04187         ringsTex->bind();
04188     else
04189         glDisable(GL_TEXTURE_2D);
04190         
04191     // Perform our own lighting for the rings.
04192     // TODO: Don't forget about light source color (required when we
04193     // paying attention to star color.)
04194     if (vpath == GLContext::VPath_Basic)
04195     {
04196         glDisable(GL_LIGHTING);
04197         Vec3f litColor(rings.color.red(), rings.color.green(), rings.color.blue());
04198         litColor = litColor * ringIllumination +
04199             Vec3f(ri.ambientColor.red(), ri.ambientColor.green(),
04200                   ri.ambientColor.blue());
04201         glColor4f(litColor.x, litColor.y, litColor.z, 1.0f);
04202     }
04203 
04204     // This gets tricky . . .  we render the rings in two parts.  One
04205     // part is potentially shadowed by the planet, and we need to
04206     // render that part with the projected shadow texture enabled.
04207     // The other part isn't shadowed, but will appear so if we don't
04208     // first disable the shadow texture.  The problem is that the
04209     // shadow texture will affect anything along the line between the
04210     // sun and the planet, regardless of whether it's in front or
04211     // behind the planet.
04212 
04213     // Compute the angle of the sun projected on the ring plane
04214     float sunAngle = (float) atan2(ri.sunDir_obj.z, ri.sunDir_obj.x);
04215 
04216     // If there's a fragment program, it will handle the ambient term--make
04217     // sure that we don't add it both in the fragment and vertex programs.
04218     if (vproc != NULL && fproc != NULL)
04219         glAmbientLightColor(Color::Black);
04220 
04221     renderRingSystem(inner, outer,
04222                      (float) (sunAngle + PI / 2),
04223                      (float) (sunAngle + 3 * PI / 2),
04224                      nSections / 2);
04225     renderRingSystem(inner, outer,
04226                      (float) (sunAngle +  3 * PI / 2),
04227                      (float) (sunAngle + PI / 2),
04228                      nSections / 2);
04229 
04230     if (vproc != NULL && fproc != NULL)
04231         glAmbientLightColor(ri.ambientColor * ri.color);
04232 
04233     // Disable the second texture unit if it was used
04234     if (renderShadow)
04235     {
04236         glx::glActiveTextureARB(GL_TEXTURE1_ARB);
04237         glDisable(GL_TEXTURE_2D);
04238         glDisable(GL_TEXTURE_GEN_S);
04239         glDisable(GL_TEXTURE_GEN_T);
04240         glx::glActiveTextureARB(GL_TEXTURE0_ARB);
04241 
04242         if (fproc != NULL)
04243             fproc->disable();
04244     }
04245 
04246     // Render the unshadowed side
04247     renderRingSystem(inner, outer,
04248                      (float) (sunAngle - PI / 2),
04249                      (float) (sunAngle + PI / 2),
04250                      nSections / 2);
04251     renderRingSystem(inner, outer,
04252                      (float) (sunAngle + PI / 2),
04253                      (float) (sunAngle - PI / 2),
04254                      nSections / 2);
04255     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
04256 
04257     if (vproc != NULL)
04258         vproc->disable();
04259 }
04260 
04261 
04262 static void renderRings_GLSL(RingSystem& rings,
04263                              RenderInfo& ri,
04264                              const LightingState& ls,
04265                              float planetRadius,
04266                              float planetOblateness,
04267                              unsigned int textureResolution,
04268                              bool renderShadow,
04269                              unsigned int nSections)
04270 {
04271     float inner = rings.innerRadius / planetRadius;
04272     float outer = rings.outerRadius / planetRadius;
04273     Texture* ringsTex = rings.texture.find(textureResolution);
04274 
04275     ShaderProperties shadprop;
04276     // Set up the shader properties for ring rendering
04277     {
04278         shadprop.lightModel = ShaderProperties::RingIllumModel;
04279         shadprop.nLights = min(ls.nLights, MaxShaderLights);
04280 
04281         if (renderShadow)
04282         {
04283             // Set one shadow (the planet's) per light
04284             for (unsigned int li = 0; li < ls.nLights; li++)
04285                 shadprop.setShadowCountForLight(li, 1);
04286         }
04287 
04288         if (ringsTex)
04289             shadprop.texUsage = ShaderProperties::DiffuseTexture;
04290     }
04291             
04292 
04293     // Get a shader for the current rendering configuration
04294     CelestiaGLProgram* prog = GetShaderManager().getShader(shadprop);
04295     if (prog == NULL)
04296         return;
04297 
04298     prog->use();
04299 
04300     prog->eyePosition = ls.eyePos_obj;
04301     prog->ambientColor = Vec3f(ri.ambientColor.red(), ri.ambientColor.green(),
04302                                ri.ambientColor.blue());
04303     setLightParameters_GLSL(*prog, shadprop, ls,
04304                             ri.color, ri.specularColor);
04305         
04306     for (unsigned int li = 0; li < ls.nLights; li++)
04307     {
04308         const DirectionalLight& light = ls.lights[li];
04309 
04310         // Compute the projection vectors based on the sun direction.
04311         // I'm being a little careless here--if the sun direction lies
04312         // along the y-axis, this will fail.  It's unlikely that a
04313         // planet would ever orbit underneath its sun (an orbital
04314         // inclination of 90 degrees), but this should be made
04315         // more robust anyway.
04316         Vec3f axis = Vec3f(0, 1, 0) ^ light.direction_obj;
04317         float cosAngle = Vec3f(0.0f, 1.0f, 0.0f) * light.direction_obj;
04318         float angle = (float) acos(cosAngle);
04319         axis.normalize();
04320 
04321         float tScale = 1.0f;
04322         if (planetOblateness != 0.0f)
04323         {
04324             // For oblate planets, the size of the shadow volume will vary
04325             // based on the light direction.
04326 
04327             // A vertical slice of the planet is an ellipse
04328             float a = 1.0f;                          // semimajor axis
04329             float b = a * (1.0f - planetOblateness); // semiminor axis
04330             float ecc2 = 1.0f - (b * b) / (a * a);   // square of eccentricity
04331 
04332             // Calculate the radius of the ellipse at the incident angle of the
04333             // light on the ring plane + 90 degrees.
04334             float r = a * (float) sqrt((1.0f - ecc2) /
04335                                        (1.0f - ecc2 * square(cosAngle)));
04336             
04337             tScale *= a / r;
04338         }
04339 
04340         // The s axis is perpendicular to the shadow axis in the plane of the
04341         // of the rings, and the t axis completes the orthonormal basis.
04342         Vec3f sAxis = axis * 0.5f;
04343         Vec3f tAxis = (axis ^ light.direction_obj) * 0.5f * tScale;
04344         Vec4f texGenS(sAxis.x, sAxis.y, sAxis.z, 0.5f);
04345         Vec4f texGenT(tAxis.x, tAxis.y, tAxis.z, 0.5f);
04346 
04347         float r0 = 0.24f;
04348         float r1 = 0.25f;
04349         float bias = 1.0f / (1.0f - r1 / r0);
04350         float scale = -bias / r0;
04351 
04352         prog->shadows[li][0].texGenS = texGenS;
04353         prog->shadows[li][0].texGenT = texGenT;
04354         prog->shadows[li][0].bias = bias;
04355         prog->shadows[li][0].scale = -bias / r0;
04356     }
04357 
04358     glEnable(GL_BLEND);
04359     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
04360 
04361     if (ringsTex != NULL)
04362         ringsTex->bind();
04363     else
04364         glDisable(GL_TEXTURE_2D);
04365         
04366     renderRingSystem(inner, outer, 0, (float) PI * 2.0f, nSections);
04367     renderRingSystem(inner, outer, (float) PI * 2.0f, 0, nSections);
04368 
04369     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
04370 
04371     glx::glUseProgramObjectARB(0);
04372 }
04373 
04374 
04375 static void
04376 renderEclipseShadows(Model* model,
04377                      vector<EclipseShadow>& eclipseShadows,
04378                      RenderInfo& ri,
04379                      float planetRadius,
04380                      Mat4f& planetMat,
04381                      Frustum& viewFrustum,
04382                      const GLContext& context)
04383 {
04384     // Eclipse shadows on mesh objects aren't working yet.
04385     if (model != NULL)
04386         return;
04387 
04388     for (vector<EclipseShadow>::iterator iter = eclipseShadows.begin();
04389          iter != eclipseShadows.end(); iter++)
04390     {
04391         EclipseShadow shadow = *iter;
04392 
04393 #ifdef DEBUG_ECLIPSE_SHADOWS
04394         // Eclipse debugging: render the central axis of the eclipse
04395         // shadow volume.
04396         glDisable(GL_TEXTURE_2D);
04397         glColor4f(1, 0, 0, 1);
04398         Point3f blorp = shadow.origin * planetMat;
04399         Vec3f blah = shadow.direction * planetMat;
04400         blorp.x /= planetRadius; blorp.y /= planetRadius; blorp.z /= planetRadius;
04401         float foo = blorp.distanceFromOrigin();
04402         glBegin(GL_LINES);
04403         glVertex(blorp);
04404         glVertex(blorp + foo * blah);
04405         glEnd();
04406         glEnable(GL_TEXTURE_2D);
04407 #endif
04408 
04409         // Determine which eclipse shadow texture to use.  This is only
04410         // a very rough approximation to reality.  Since there are an
04411         // infinite number of possible eclipse volumes, what we should be
04412         // doing is generating the eclipse textures on the fly using
04413         // render-to-texture.  But for now, we'll just choose from a fixed
04414         // set of eclipse shadow textures based on the relative size of
04415         // the umbra and penumbra.
04416         Texture* eclipseTex = NULL;
04417         float umbra = shadow.umbraRadius / shadow.penumbraRadius;
04418         if (umbra < 0.1f)
04419             eclipseTex = eclipseShadowTextures[0];
04420         else if (umbra < 0.35f)
04421             eclipseTex = eclipseShadowTextures[1];
04422         else if (umbra < 0.6f)
04423             eclipseTex = eclipseShadowTextures[2];
04424         else if (umbra < 0.9f)
04425             eclipseTex = eclipseShadowTextures[3];
04426         else
04427             eclipseTex = shadowTex;
04428 
04429         // Compute the transformation to use for generating texture
04430         // coordinates from the object vertices.
04431         Point3f origin = shadow.origin * planetMat;
04432         Vec3f dir = shadow.direction * planetMat;
04433         float scale = planetRadius / shadow.penumbraRadius;
04434         Vec3f axis = Vec3f(0, 1, 0) ^ dir;
04435         float angle = (float) acos(Vec3f(0, 1, 0) * dir);
04436         axis.normalize();
04437         Mat4f mat = Mat4f::rotation(axis, -angle);
04438         Vec3f sAxis = Vec3f(0.5f * scale, 0, 0) * mat;
04439         Vec3f tAxis = Vec3f(0, 0, 0.5f * scale) * mat;
04440 
04441         float sPlane[4] = { 0, 0, 0, 0 };
04442         float tPlane[4] = { 0, 0, 0, 0 };
04443         sPlane[0] = sAxis.x; sPlane[1] = sAxis.y; sPlane[2] = sAxis.z;
04444         tPlane[0] = tAxis.x; tPlane[1] = tAxis.y; tPlane[2] = tAxis.z;
04445         sPlane[3] = (Point3f(0, 0, 0) - origin) * sAxis / planetRadius + 0.5f;
04446         tPlane[3] = (Point3f(0, 0, 0) - origin) * tAxis / planetRadius + 0.5f;
04447 
04448         // TODO: Multiple eclipse shadows should be rendered in a single
04449         // pass using multitexture.
04450         if (eclipseTex != NULL)
04451             eclipseTex->bind();
04452         // shadowMaskTexture->bind();
04453         glEnable(GL_BLEND);
04454         glBlendFunc(GL_ZERO, GL_SRC_COLOR);
04455 
04456         // If the ambient light level is greater than zero, reduce the
04457         // darkness of the shadows.
04458         if (ri.useTexEnvCombine)
04459         {
04460             float color[4] = { ri.ambientColor.red(), ri.ambientColor.green(),
04461                                ri.ambientColor.blue(), 1.0f };
04462             glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
04463             glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
04464             glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_CONSTANT_EXT);
04465             glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
04466             glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
04467             glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
04468             glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
04469 
04470             // The second texture unit has the shadow 'mask'
04471             glx::glActiveTextureARB(GL_TEXTURE1_ARB);
04472             glEnable(GL_TEXTURE_2D);
04473             shadowMaskTexture->bind();
04474             glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
04475             glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
04476             glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
04477             glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
04478             glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
04479             glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
04480             glx::glActiveTextureARB(GL_TEXTURE0_ARB);
04481         }
04482 
04483         // Since invariance between nVidia's vertex programs and the
04484         // standard transformation pipeline isn't guaranteed, we have to
04485         // make sure to use the same transformation engine on subsequent
04486         // rendering passes as we did on the initial one.
04487         if (context.getVertexPath() != GLContext::VPath_Basic && model == NULL)
04488         {
04489             renderShadowedModelVertexShader(ri, viewFrustum,
04490                                            sPlane, tPlane,
04491                                            dir,
04492                                            context);
04493         }
04494         else
04495         {
04496             renderShadowedModelDefault(model, ri, viewFrustum,
04497                                        sPlane, tPlane,
04498                                        dir,
04499                                        ri.useTexEnvCombine,
04500                                        context);
04501         }
04502 
04503         if (ri.useTexEnvCombine)
04504         {
04505             // Disable second texture unit
04506             glx::glActiveTextureARB(GL_TEXTURE1_ARB);
04507             glDisable(GL_TEXTURE_2D);
04508             glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
04509             glx::glActiveTextureARB(GL_TEXTURE0_ARB);
04510 
04511             float color[4] = { 0, 0, 0, 0 };
04512             glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
04513             glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
04514         }
04515 
04516         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
04517         glDisable(GL_BLEND);
04518     }
04519 }
04520 
04521 
04522 static void
04523 renderEclipseShadows_Shaders(Model* model,
04524                              vector<EclipseShadow>& eclipseShadows,
04525                              RenderInfo& ri,
04526                              float planetRadius,
04527                              Mat4f& planetMat,
04528                              Frustum& viewFrustum,
04529                              const GLContext& context)
04530 {
04531     // Eclipse shadows on mesh objects aren't working yet.
04532     if (model != NULL)
04533         return;
04534 
04535     glEnable(GL_TEXTURE_2D);
04536     penumbraFunctionTexture->bind();
04537 
04538     glEnable(GL_BLEND);
04539     glBlendFunc(GL_ZERO, GL_SRC_COLOR);
04540 
04541     float sPlanes[4][4];
04542     float tPlanes[4][4];
04543     float shadowParams[4][4];
04544 
04545     int n = 0;
04546     for (vector<EclipseShadow>::iterator iter = eclipseShadows.begin();
04547          iter != eclipseShadows.end() && n < 4; iter++, n++)
04548     {
04549         EclipseShadow shadow = *iter;
04550 
04551         float R2 = 0.25f;
04552         float umbra = shadow.umbraRadius / shadow.penumbraRadius;
04553         umbra = umbra * umbra;
04554         if (umbra < 0.0001f)
04555             umbra = 0.0001f;
04556         else if (umbra > 0.99f)
04557             umbra = 0.99f;
04558 
04559         float umbraRadius = R2 * umbra;
04560         float penumbraRadius = R2;
04561         float shadowBias = 1.0f / (1.0f - penumbraRadius / umbraRadius);
04562         float shadowScale = -shadowBias / umbraRadius;
04563 
04564         shadowParams[n][0] = shadowScale;
04565         shadowParams[n][1] = shadowBias;
04566         shadowParams[n][2] = 0.0f;
04567         shadowParams[n][3] = 0.0f;
04568 
04569         // Compute the transformation to use for generating texture
04570         // coordinates from the object vertices.
04571         Point3f origin = shadow.origin * planetMat;
04572         Vec3f dir = shadow.direction * planetMat;
04573         float scale = planetRadius / shadow.penumbraRadius;
04574         Vec3f axis = Vec3f(0, 1, 0) ^ dir;
04575         float angle = (float) acos(Vec3f(0, 1, 0) * dir);
04576         axis.normalize();
04577         Mat4f mat = Mat4f::rotation(axis, -angle);
04578         Vec3f sAxis = Vec3f(0.5f * scale, 0, 0) * mat;
04579         Vec3f tAxis = Vec3f(0, 0, 0.5f * scale) * mat;
04580 
04581         sPlanes[n][0] = sAxis.x;
04582         sPlanes[n][1] = sAxis.y;
04583         sPlanes[n][2] = sAxis.z;
04584         sPlanes[n][3] = (Point3f(0, 0, 0) - origin) * sAxis / planetRadius + 0.5f;
04585         tPlanes[n][0] = tAxis.x;
04586         tPlanes[n][1] = tAxis.y;
04587         tPlanes[n][2] = tAxis.z;
04588         tPlanes[n][3] = (Point3f(0, 0, 0) - origin) * tAxis / planetRadius + 0.5f;
04589     }
04590 
04591 
04592     VertexProcessor* vproc = context.getVertexProcessor();
04593     FragmentProcessor* fproc = context.getFragmentProcessor();
04594 
04595     vproc->enable();
04596     vproc->use(vp::multiShadow);
04597 
04598     fproc->enable();
04599     if (n == 1)
04600         fproc->use(fp::eclipseShadow1);
04601     else
04602         fproc->use(fp::eclipseShadow2);
04603 
04604     fproc->parameter(fp::ShadowParams0, shadowParams[0]);
04605     vproc->parameter(vp::TexGen_S, sPlanes[0]);
04606     vproc->parameter(vp::TexGen_T, tPlanes[0]);
04607     if (n >= 2)
04608     {
04609         fproc->parameter(fp::ShadowParams1, shadowParams[1]);
04610         vproc->parameter(vp::TexGen_S2, sPlanes[1]);
04611         vproc->parameter(vp::TexGen_T2, tPlanes[1]);
04612     }
04613     if (n >= 3)
04614     {
04615         //fproc->parameter(fp::ShadowParams2, shadowParams[2]);
04616         vproc->parameter(vp::TexGen_S3, sPlanes[2]);
04617         vproc->parameter(vp::TexGen_T3, tPlanes[2]);
04618     }
04619     if (n >= 4)
04620     {
04621         //fproc->parameter(fp::ShadowParams3, shadowParams[3]);
04622         vproc->parameter(vp::TexGen_S4, sPlanes[3]);
04623         vproc->parameter(vp::TexGen_T4, tPlanes[3]);
04624     }
04625 
04626     //vproc->parameter(vp::LightDirection0, lightDir);
04627 
04628     lodSphere->render(context,
04629                       LODSphereMesh::Normals | LODSphereMesh::Multipass,
04630                       viewFrustum,
04631                       ri.pixWidth, NULL);
04632 
04633     vproc->disable();
04634     fproc->disable();
04635 
04636     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
04637     glDisable(GL_BLEND);
04638 }
04639 
04640 
04641 static void
04642 renderRingShadowsVS(Model* model,
04643                     const RingSystem& rings,
04644                     const Vec3f& sunDir,
04645                     RenderInfo& ri,
04646                     float planetRadius,
04647                     float oblateness,
04648                     Mat4f& planetMat,
04649                     Frustum& viewFrustum,
04650                     const GLContext& context)
04651 {
04652     // Compute the transformation to use for generating texture
04653     // coordinates from the object vertices.
04654     float ringWidth = rings.outerRadius - rings.innerRadius;
04655     float s = ri.sunDir_obj.y;
04656     float scale = (abs(s) < 0.001f) ? 1000.0f : 1.0f / s;
04657 
04658     if (abs(s) > 1.0f - 1.0e-4f)
04659     {
04660         // Planet is illuminated almost directly from above, so
04661         // no ring shadow will be cast on the planet.  Conveniently
04662         // avoids some potential division by zero when ray-casting.
04663         return;
04664     }
04665 
04666     glEnable(GL_BLEND);
04667     glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
04668 
04669     // If the ambient light level is greater than zero, reduce the
04670     // darkness of the shadows.
04671     float color[4] = { ri.ambientColor.red(), ri.ambientColor.green(),
04672                        ri.ambientColor.blue(), 1.0f };
04673     glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
04674     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
04675     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_CONSTANT_EXT);
04676     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
04677     glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
04678     glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
04679     glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
04680 
04681     // Tweak the texture--set clamp to border and a border color with
04682     // a zero alpha.  If a graphics card doesn't support clamp to border,
04683     // it doesn't get to play.  It's possible to get reasonable behavior
04684     // by turning off mipmaps and assuming transparent rows of pixels for
04685     // the top and bottom of the ring textures . . . maybe later.
04686     float bc[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
04687     glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bc);
04688     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB);
04689 
04690     // Ring shadows look strange if they're always completely black.  Vary
04691     // the darkness of the shadow based on the angle between the sun and the
04692     // ring plane.  There's some justification for this--the larger the angle
04693     // between the sun and the ring plane (normal), the more ring material
04694     // there is to travel through.
04695     //float alpha = (1.0f - abs(ri.sunDir_obj.y)) * 1.0f;
04696     // ...but, images from Cassini are showing very dark ring shadows, so we'll
04697     // go with that.
04698     float alpha = 1.0f;
04699 
04700     VertexProcessor* vproc = context.getVertexProcessor();
04701     assert(vproc != NULL);
04702 
04703     vproc->enable();
04704     vproc->use(vp::ringShadow);
04705     vproc->parameter(vp::LightDirection0, ri.sunDir_obj);
04706     vproc->parameter(vp::DiffuseColor0, 1, 1, 1, alpha); // color = white
04707     vproc->parameter(vp::TexGen_S,
04708                      rings.innerRadius / planetRadius,
04709                      1.0f / (ringWidth / planetRadius),
04710                      0.0f, 0.5f);
04711     vproc->parameter(vp::TexGen_T, scale, 0, 0, 0);
04712     lodSphere->render(context, LODSphereMesh::Multipass,
04713                       viewFrustum, ri.pixWidth, NULL);
04714     vproc->disable();
04715 
04716     // Restore the texture combiners
04717     if (ri.useTexEnvCombine)
04718     {
04719         float color[4] = { 0, 0, 0, 0 };
04720         glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
04721         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
04722     }
04723 
04724     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
04725     glDisable(GL_BLEND);
04726 }
04727 
04728 
04729 void Renderer::renderLocations(const vector<Location*>& locations,
04730                                const Quatf& cameraOrientation,
04731                                const Point3f& positionf,
04732                                const Quatf& orientation,
04733                                float scale)
04734 {
04735     if (font == NULL)
04736         return;
04737 
04738     double winX, winY, winZ;
04739     int view[4] = { 0, 0, 0, 0 };
04740     view[0] = -windowWidth / 2;
04741     view[1] = -windowHeight / 2;
04742     view[2] = windowWidth;
04743     view[3] = windowHeight;
04744 
04745     Vec3f viewNormal = Vec3f(0.0f, 0.0f, -1.0f) *
04746         cameraOrientation.toMatrix3();
04747     Vec3d viewNormald = Vec3d(viewNormal.x, viewNormal.y, viewNormal.z);
04748 
04749     double modelview[16];
04750     double projection[16];
04751     glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
04752     glGetDoublev(GL_PROJECTION_MATRIX, projection);
04753 
04754     glEnable(GL_DEPTH_TEST);
04755     glEnable(GL_TEXTURE_2D);
04756     font->bind();
04757     glEnable(GL_BLEND);
04758     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
04759     glDisable(GL_LIGHTING);
04760 
04761     glMatrixMode(GL_PROJECTION);
04762     glPushMatrix();
04763     glLoadIdentity();
04764     glOrtho(0, windowWidth, 0, windowHeight, 1.0f, -1.0f);
04765     glMatrixMode(GL_MODELVIEW);
04766     glPushMatrix();
04767     glLoadIdentity();
04768 
04769     // Render the labels very close to the near plane with z=-0.999f.  In fact,
04770     // z=-1.0f should work, but I'm concerned that some OpenGL implementations
04771     // might clip things placed right on the near plane.
04772     glTranslatef(GLfloat((int) (windowWidth / 2)),
04773                  GLfloat((int) (windowHeight / 2)), -0.999f);
04774 
04775     Point3d position(positionf.x, positionf.y, positionf.z);
04776     Point3d origin(0.0, 0.0, 0.0);
04777 
04778     Ellipsoidd ellipsoid(position, Vec3d(scale, scale, scale));
04779 
04780     float iScale = 1.0f / scale;
04781     Mat3f mat = orientation.toMatrix3();
04782 
04783     for (vector<Location*>::const_iterator iter = locations.begin();
04784          iter != locations.end(); iter++)
04785     {
04786         if ((*iter)->getFeatureType() & locationFilter)
04787         {
04788             // Get the position of the label with respect to the planet center
04789             Vec3f ppos = (*iter)->getPosition();
04790             // Get the rotated position
04791             Vec3f pposRotated = ppos * mat;
04792             // Double precision required for stable intersection calculations
04793             Vec3d pposd(pposRotated.x, pposRotated.y, pposRotated.z);
04794             // Get the position in camera space.  Add a slight scale factor
04795             // to keep the point from being exactly on the surface.
04796             Point3d cpos(position + pposd * 1.0001);
04797 
04798             float effSize = (*iter)->getImportance();
04799             if (effSize < 0.0f)
04800                 effSize = (*iter)->getSize();
04801             float pixSize = effSize / (float) (cpos.distanceFromOrigin() * pixelSize);
04802             
04803             if (pixSize > minFeatureSize &&
04804                 (cpos - origin) * viewNormald > 0.0)
04805             {
04806                 double r = pposd.length();
04807                 if (r < scale * 0.99)
04808                     cpos = position + pposd * (scale * 1.01 / r);
04809                 
04810                 double t = 0.0f;
04811 
04812                 // Test for a intersection of the eye-to-location ray with
04813                 // the planet ellipsoid.  If we hit the planet first, then
04814                 // the label is obscured by the planet.  An exact calculation
04815                 // for irregular objects would be too expensive, and the
04816                 // ellipsoid approximation works reasonably well for them.
04817                 bool hit = testIntersection(Ray3d(origin, cpos - origin),
04818                                             ellipsoid, t);
04819                 if (!hit || t >= 1.0)
04820                 {
04821                     if (gluProject(ppos.x * iScale, ppos.y * iScale, ppos.z * iScale,
04822                                    modelview,
04823                                    projection,
04824                                    (const GLint*) view,
04825                                    &winX, &winY, &winZ) != GL_FALSE)
04826                     {
04827                         glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
04828                         glPushMatrix();
04829                         glTranslatef((int) winX + PixelOffset,
04830                                      (int) winY + PixelOffset,
04831                                      0.0f);
04832                         font->render((*iter)->getName(true));
04833                         glPopMatrix();
04834                     }
04835                 }
04836             }
04837         }
04838     }
04839 
04840     glPopMatrix();
04841     glMatrixMode(GL_PROJECTION);
04842     glPopMatrix();
04843     glMatrixMode(GL_MODELVIEW);
04844 }
04845 
04846 
04847 static void
04848 setupObjectLighting(const vector<Renderer::LightSource>& suns,
04849                     const Point3d& objPosition,
04850                     const Quatf& objOrientation,
04851                     const Vec3f& objScale,
04852                     const Point3f& objPosition_eye,
04853                     LightingState& ls)
04854 {
04855     unsigned int nLights = min(MaxLights, (unsigned int) suns.size());
04856     if (nLights == 0)
04857         return;
04858 
04859     unsigned int i;
04860     for (i = 0; i < nLights; i++)
04861     {
04862         Vec3d dir = suns[i].position - objPosition;
04863         ls.lights[i].direction_eye =
04864             Vec3f((float) dir.x, (float) dir.y, (float) dir.z);
04865         float distance = ls.lights[i].direction_eye.length();
04866         ls.lights[i].direction_eye *= 1.0f / distance;
04867         distance = astro::kilometersToAU((float) dir.length());
04868         ls.lights[i].irradiance = suns[i].luminosity / (distance * distance);
04869         ls.lights[i].color = suns[i].color;
04870 
04871         // Store the position and apparent size because we'll need them for
04872         // testing for eclipses.
04873         ls.lights[i].position = suns[i].position;
04874         ls.lights[i].apparentSize = (float) (suns[i].radius / dir.length());
04875     }
04876 
04877     // Sort light sources by brightness.  Light zero should always be the
04878     // brightest.  Optimize common cases of one and two lights.
04879     if (nLights == 2)
04880     {
04881         if (ls.lights[0].irradiance < ls.lights[1].irradiance)
04882             swap(ls.lights[0], ls.lights[1]);
04883     }
04884     else if (nLights > 2)
04885     {
04886         sort(ls.lights, ls.lights + nLights, LightIrradiancePredicate());
04887     }
04888 
04889     // Compute the total irradiance
04890     float totalIrradiance = 0.0f;
04891     for (i = 0; i < nLights; i++)
04892         totalIrradiance += ls.lights[i].irradiance;
04893 
04894     // Compute a gamma factor to make dim light sources visible.  This is
04895     // intended to approximate what we see with our eyes--for example,
04896     // Earth-shine is visible on the night side of the Moon, even though
04897     // the amount of reflected light from the Earth is 1/10000 of what
04898     // the Moon receives directly from the Sun.
04899     //
04900     // TODO: Skip this step when high dynamic range rendering to floating point
04901     //   buffers is enabled.
04902     float minVisibleFraction = 1.0f / 10000.0f;
04903     float minDisplayableValue = 1.0f / 255.0f;
04904     float gamma = (float) (log(minDisplayableValue) / log(minVisibleFraction));
04905     float minVisibleIrradiance = minVisibleFraction * totalIrradiance;
04906 
04907     Mat3f m = (~objOrientation).toMatrix3();
04908 
04909     // Gamma scale and normalize the light sources; cull light sources that
04910     // aren't bright enough to contribute the final pixels rendered into the
04911     // frame buffer.
04912     ls.nLights = 0;
04913     for (i = 0; i < nLights && ls.lights[i].irradiance > minVisibleIrradiance; i++)
04914     {
04915         ls.lights[i].irradiance =
04916             (float) pow(ls.lights[i].irradiance / totalIrradiance, gamma);
04917 
04918         // Compute the direction of the light in object space
04919         ls.lights[i].direction_obj = ls.lights[i].direction_eye * m;
04920 
04921         ls.nLights++;
04922     }
04923 
04924     Point3f pos((float) objPosition.x,
04925                 (float) objPosition.y,
04926                 (float) objPosition.z);
04927     ls.eyePos_obj = Point3f(-objPosition_eye.x / objScale.x,
04928                             -objPosition_eye.y / objScale.y,
04929                             -objPosition_eye.z / objScale.z) * m;
04930     ls.eyeDir_obj = (Point3f(0.0f, 0.0f, 0.0f) - objPosition_eye) * m;
04931     ls.eyeDir_obj.normalize();
04932 
04933 #if 0
04934     // Old code: linear scaling approach
04935 
04936     // After sorting, the first light is always the brightest
04937     float maxIrradiance = ls.lights[0].irradiance;
04938 
04939     // Normalize the brightnesses of the light sources.
04940     // TODO: Investigate logarithmic functions for scaling light brightness, to
04941     //   better simulate what the human eye would see.
04942     ls.nLights = 0;
04943     for (i = 0; i < nLights; i++)
04944     {
04945         ls.lights[i].irradiance /= maxIrradiance;
04946         
04947         // Cull light sources that don't contribute significantly (less than
04948         // the resolution of an 8-bit color channel.)
04949         if (ls.lights[i].irradiance < 1.0f / 255.0f)
04950             break;
04951 
04952         // Compute the direction of the light in object space
04953         ls.lights[i].direction_obj = ls.lights[i].direction_eye * m;
04954 
04955         ls.nLights++;
04956     }
04957 #endif
04958 }
04959               
04960 
04961 void Renderer::renderObject(Point3f pos,
04962                             float distance,
04963                             double now,
04964                             Quatf cameraOrientation,
04965                             float nearPlaneDistance,
04966                             float farPlaneDistance,
04967                             RenderProperties& obj,
04968                             const LightingState& ls)
04969 {
04970     RenderInfo ri;
04971 
04972     float altitude = distance - obj.radius;
04973     float discSizeInPixels = obj.radius /
04974         (max(nearPlaneDistance, altitude) * pixelSize);
04975 
04976     ri.sunDir_eye = Vec3f(0.0f, 1.0f, 0.0f);
04977     ri.sunDir_obj = Vec3f(0.0f, 1.0f, 0.0f);
04978     ri.sunColor = Color(0.0f, 0.0f, 0.0f);
04979     if (ls.nLights > 0)
04980     {
04981         ri.sunDir_eye = ls.lights[0].direction_eye;
04982         ri.sunDir_obj = ls.lights[0].direction_obj;
04983         ri.sunColor   = ls.lights[0].color;// * ls.lights[0].intensity;
04984     }
04985 
04986     // Enable depth buffering
04987     glEnable(GL_DEPTH_TEST);
04988     glDepthMask(GL_TRUE);
04989 
04990     glDisable(GL_BLEND);
04991 
04992     // Get the textures . . .
04993     if (obj.surface->baseTexture.tex[textureResolution] != InvalidResource)
04994         ri.baseTex = obj.surface->baseTexture.find(textureResolution);
04995     if ((obj.surface->appearanceFlags & Surface::ApplyBumpMap) != 0 &&
04996         context->bumpMappingSupported() &&
04997         obj.surface->bumpTexture.tex[textureResolution] != InvalidResource)
04998         ri.bumpTex = obj.surface->bumpTexture.find(textureResolution);
04999     if ((obj.surface->appearanceFlags & Surface::ApplyNightMap) != 0 &&
05000         (renderFlags & ShowNightMaps) != 0)
05001         ri.nightTex = obj.surface->nightTexture.find(textureResolution);
05002     if ((obj.surface->appearanceFlags & Surface::SeparateSpecularMap) != 0)
05003         ri.glossTex = obj.surface->specularTexture.find(textureResolution);
05004     if ((obj.surface->appearanceFlags & Surface::ApplyOverlay) != 0)
05005         ri.overlayTex = obj.surface->overlayTexture.find(textureResolution);
05006 
05007     // Apply the modelview transform for the object
05008     glPushMatrix();
05009     glTranslate(pos);
05010     glRotate(~obj.orientation);
05011 
05012     // Apply a scale factor which depends on the size of the planet and
05013     // its oblateness.  Since the oblateness is usually quite
05014     // small, the potentially nonuniform scale factor shouldn't mess up
05015     // the lighting calculations enough to be noticeable.
05016     // TODO:  Figure out a better way to render ellipsoids than applying
05017     // a nonunifom scale factor to a sphere.
05018     float radius = obj.radius;
05019     Vec3f semiAxes = obj.radius * obj.semiAxes;
05020     glScale(semiAxes);
05021 
05022     Mat4f planetMat = (~obj.orientation).toMatrix4();
05023     ri.eyeDir_obj = (Point3f(0, 0, 0) - pos) * planetMat;
05024     ri.eyeDir_obj.normalize();
05025     ri.eyePos_obj = Point3f(-pos.x / semiAxes.x,
05026                             -pos.y / semiAxes.y,
05027                             -pos.z / semiAxes.z) * planetMat;
05028     
05029     ri.orientation = cameraOrientation;
05030 
05031     ri.pixWidth = discSizeInPixels;
05032 
05033     // Set up the colors
05034     if (ri.baseTex == NULL ||
05035         (obj.surface->appearanceFlags & Surface::BlendTexture) != 0)
05036     {
05037         ri.color = obj.surface->color;
05038     }
05039 
05040     ri.ambientColor = ambientColor;
05041     ri.hazeColor = obj.surface->hazeColor;
05042     ri.specularColor = obj.surface->specularColor;
05043     ri.specularPower = obj.surface->specularPower;
05044     ri.useTexEnvCombine = context->getRenderPath() != GLContext::GLPath_Basic;
05045 
05046     // See if the surface should be lit
05047     bool lit = (obj.surface->appearanceFlags & Surface::Emissive) == 0;
05048 
05049     // Set the OpenGL light state
05050     unsigned int i;
05051     for (i = 0; i < ls.nLights; i++)
05052     {
05053         const DirectionalLight& light = ls.lights[i];
05054 
05055         glLightDirection(GL_LIGHT0 + i, ls.lights[i].direction_obj);
05056 
05057         // RANT ALERT!
05058         // This sucks, but it's necessary.  glScale is used to scale a unit
05059         // sphere up to planet size.  Since normals are transformed by the
05060         // inverse transpose of the model matrix, this means they end up
05061         // getting scaled by a factor of 1.0 / planet radius (in km).  This
05062         // has terrible effects on lighting: the planet appears almost
05063         // completely dark.  To get around this, the GL_rescale_normal
05064         // extension was introduced and eventually incorporated into into the
05065         // OpenGL 1.2 standard.  Of course, not everyone implemented this
05066         // incredibly simple and essential little extension.  Microsoft is
05067         // notorious for half-assed support of OpenGL, but 3dfx should have
05068         // known better: no Voodoo 1/2/3 drivers seem to support this
05069         // extension.  The following is an attempt to get around the problem by
05070         // scaling the light brightness by the planet radius.  According to the
05071         // OpenGL spec, this should work fine, as clamping of colors to [0, 1]
05072         // occurs *after* lighting.  It works fine on my GeForce3 when I
05073         // disable EXT_rescale_normal, but I'm not certain whether other
05074         // drivers are as well behaved as nVidia's.
05075         //
05076         // Addendum: Unsurprisingly, using color values outside [0, 1] produces
05077         // problems on Savage4 cards.
05078 
05079         Vec3f lightColor = Vec3f(light.color.red(),
05080                                  light.color.green(),
05081                                  light.color.blue()) * light.irradiance;
05082         if (useRescaleNormal)
05083         {
05084             glLightColor(GL_LIGHT0 + i, GL_DIFFUSE, lightColor);
05085             glLightColor(GL_LIGHT0 + i, GL_SPECULAR, lightColor);
05086         }
05087         else
05088         {
05089             glLightColor(GL_LIGHT0 + i, GL_DIFFUSE, lightColor * radius);
05090         }
05091         glEnable(GL_LIGHT0 + i);
05092     }
05093 
05094     // Compute the inverse model/view matrix
05095     Mat4f invMV = (cameraOrientation.toMatrix4() *
05096                    Mat4f::translation(Point3f(-pos.x, -pos.y, -pos.z)) *
05097                    planetMat *
05098                    Mat4f::scaling(1.0f / radius));
05099 
05100     // Transform the frustum into object coordinates using the
05101     // inverse model/view matrix.
05102     Frustum viewFrustum(degToRad(fov),
05103                         (float) windowWidth / (float) windowHeight,
05104                         nearPlaneDistance, farPlaneDistance);
05105     viewFrustum.transform(invMV);
05106 
05107     // Temporary hack until we fix culling for ringed planets
05108     if (obj.rings != NULL)
05109     {
05110         if (ri.pixWidth > 5000)
05111             ri.pixWidth = 5000;
05112     }
05113 
05114     Model* model = NULL;
05115     if (obj.model == InvalidResource)
05116     {
05117         // A null model indicates that this body is a sphere
05118         if (lit)
05119         {
05120             switch (context->getRenderPath())
05121             {
05122             case GLContext::GLPath_GLSL:
05123                 renderSphere_GLSL(ri, ls, obj.rings, obj.radius,
05124                                   planetMat, viewFrustum, *context);
05125                 break;
05126 
05127             case GLContext::GLPath_NV30:
05128                 renderSphere_FP_VP(ri, viewFrustum, *context);
05129                 break;
05130 
05131             case GLContext::GLPath_NvCombiner_ARBVP:
05132             case GLContext::GLPath_NvCombiner_NvVP:
05133                 renderSphere_Combiners_VP(ri, ls, viewFrustum, *context);
05134                 break;
05135 
05136             case GLContext::GLPath_NvCombiner:
05137                 renderSphere_Combiners(ri, viewFrustum, *context);
05138                 break;
05139 
05140             case GLContext::GLPath_DOT3_ARBVP:
05141                 renderSphere_DOT3_VP(ri, ls, viewFrustum, *context);
05142                 break;
05143 
05144             default:
05145                 renderSphereDefault(ri, viewFrustum, true, *context);
05146             }
05147         }
05148         else
05149         {
05150             renderSphereDefault(ri, viewFrustum, false, *context);
05151         }
05152     }
05153     else
05154     {
05155         // This is a model loaded from a file
05156         model = GetModelManager()->find(obj.model);
05157         if (model != NULL)
05158             renderModelDefault(model, ri, lit);
05159     }
05160 
05161     if (obj.rings != NULL && distance <= obj.rings->innerRadius)
05162     {
05163         if (context->getRenderPath() == GLContext::GLPath_GLSL)
05164         {
05165             renderRings_GLSL(*obj.rings, ri, ls,
05166                              radius, 1.0f - obj.semiAxes.y,
05167                              textureResolution,
05168                              (renderFlags & ShowRingShadows) != 0 && lit,
05169                              detailOptions.ringSystemSections);
05170         }
05171         else
05172         {
05173             renderRings(*obj.rings, ri, radius, 1.0f - obj.semiAxes.y,
05174                         textureResolution,
05175                         context->getMaxTextures() > 1 &&
05176                         (renderFlags & ShowRingShadows) != 0 && lit,
05177                         *context,
05178                         detailOptions.ringSystemSections);
05179         }
05180     }
05181 
05182     if (obj.atmosphere != NULL)
05183     {
05184         Atmosphere* atmosphere = const_cast<Atmosphere *>(obj.atmosphere);
05185 
05186         // Compute the apparent thickness in pixels of the atmosphere.
05187         // If it's only one pixel thick, it can look quite unsightly
05188         // due to aliasing.  To avoid popping, we gradually fade in the
05189         // atmosphere as it grows from two to three pixels thick.
05190         float fade;
05191         float thicknessInPixels = 0.0f;
05192         if (distance - radius > 0.0f)
05193         {
05194             thicknessInPixels = atmosphere->height /
05195                 ((distance - radius) * pixelSize);
05196             fade = clamp(thicknessInPixels - 2);
05197         }
05198         else
05199         {
05200             fade = 1.0f;
05201         }
05202 
05203         if (fade > 0 && (renderFlags & ShowAtmospheres) != 0)
05204         {
05205             glPushMatrix();
05206             glLoadIdentity();
05207             glDisable(GL_LIGHTING);
05208             glDisable(GL_TEXTURE_2D);
05209             glEnable(GL_BLEND);
05210             glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
05211 
05212 #ifdef OLD_ATMOSPHERE
05213             renderAtmosphere(*atmosphere,
05214                              pos * (~cameraOrientation).toMatrix3(),
05215                              radius,
05216                              ri.sunDir_eye * (~cameraOrientation).toMatrix3(),
05217                              ri.ambientColor,
05218                              fade,
05219                              lit);
05220 #else
05221             glRotate(cameraOrientation);
05222             renderEllipsoidAtmosphere(*atmosphere,
05223                                       pos,
05224                                       obj.orientation,
05225                                       semiAxes,
05226                                       ri.sunDir_eye,
05227                                       ri.ambientColor,
05228                                       thicknessInPixels,
05229                                       lit);
05230 #endif // OLD_ATMOSPHERE
05231             glEnable(GL_TEXTURE_2D);
05232             glPopMatrix();
05233         }
05234 
05235         // If there's a cloud layer, we'll render it now.
05236         Texture* cloudTex = NULL;
05237         if ((renderFlags & ShowCloudMaps) != 0 &&
05238             atmosphere->cloudTexture.tex[textureResolution] != InvalidResource)
05239             cloudTex = atmosphere->cloudTexture.find(textureResolution);
05240 
05241         if (cloudTex != NULL)
05242         {
05243             glPushMatrix();
05244 
05245             float cloudScale = 1.0f + atmosphere->cloudHeight / radius;
05246             glScalef(cloudScale, cloudScale, cloudScale);
05247 
05248             // If we're beneath the cloud level, render the interior of
05249             // the cloud sphere.
05250             if (distance - radius < atmosphere->cloudHeight)
05251                 glFrontFace(GL_CW);
05252 
05253             float texOffset = (float) (-pfmod(now * atmosphere->cloudSpeed / (2 * PI), 1.0));
05254             if (atmosphere->cloudSpeed != 0.0f)
05255             {
05256                 // Make the clouds appear to rotate above the surface of
05257                 // the planet.  This is easier to do with the texture
05258                 // matrix than the model matrix because changing the
05259                 // texture matrix doesn't require us to compute a second
05260                 // set of model space rendering parameters.
05261                 glMatrixMode(GL_TEXTURE);
05262                 glTranslatef(texOffset, 0.0f, 0.0f);
05263                 glMatrixMode(GL_MODELVIEW);
05264             }
05265 
05266             glEnable(GL_LIGHTING);
05267             glDepthMask(GL_FALSE);
05268             cloudTex->bind();
05269             glEnable(GL_BLEND);
05270             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
05271             glColor4f(1, 1, 1, 1);
05272 
05273             if (lit)
05274             {
05275                 if (context->getRenderPath() == GLContext::GLPath_GLSL)
05276                 {
05277                     renderClouds_GLSL(ri, ls,
05278                                       cloudTex,
05279                                       texOffset,
05280                                       obj.rings,
05281                                       radius * cloudScale,
05282                                       planetMat,
05283                                       viewFrustum,
05284                                       *context);
05285                 }
05286                 else
05287                 {
05288                     VertexProcessor* vproc = context->getVertexProcessor();
05289                     if (vproc != NULL)
05290                     {
05291                         vproc->enable();
05292                         vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
05293                         vproc->parameter(vp::TextureTranslation,
05294                                          texOffset, 0.0f, 0.0f, 0.0f);
05295                         if (ls.nLights > 1)
05296                             vproc->use(vp::diffuseTexOffset_2light);
05297                         else
05298                             vproc->use(vp::diffuseTexOffset);
05299                         setLightParameters_VP(*vproc, ls, ri.color, Color::Black);
05300                     }
05301 
05302                     lodSphere->render(*context,
05303                                       LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
05304                                       viewFrustum,
05305                                       ri.pixWidth,
05306                                       cloudTex);
05307 
05308                     if (vproc != NULL)
05309                         vproc->disable();
05310                 }
05311             }
05312             else
05313             {
05314                 glDisable(GL_LIGHTING);
05315                 lodSphere->render(*context,
05316                                   LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
05317                                   viewFrustum,
05318                                   ri.pixWidth,
05319                                   cloudTex);
05320                 glEnable(GL_LIGHTING);
05321             }
05322 
05323             // Reset the texture matrix
05324             glMatrixMode(GL_TEXTURE);
05325             glLoadIdentity();
05326             glMatrixMode(GL_MODELVIEW);
05327 
05328             glDepthMask(GL_TRUE);
05329             glFrontFace(GL_CCW);
05330 
05331             glPopMatrix();
05332         }
05333     }
05334 
05335     // No separate shadow rendering pass required for GLSL path
05336     if (ls.shadows[0] != NULL &&
05337         ls.shadows[0]->size() != 0 &&
05338         (obj.surface->appearanceFlags & Surface::Emissive) == 0 &&
05339         context->getRenderPath() != GLContext::GLPath_GLSL)
05340     {
05341 #if 1
05342         // renderEclipseShadows_Shaders() still needs some more work.
05343         if (context->getVertexProcessor() != NULL &&
05344             context->getFragmentProcessor() != NULL)
05345         {
05346             renderEclipseShadows_Shaders(model,
05347                                          *ls.shadows[0],
05348                                          ri,
05349                                          radius, planetMat, viewFrustum,
05350                                          *context);
05351         }
05352         else
05353 #endif
05354         {
05355             renderEclipseShadows(model,
05356                                  *ls.shadows[0],
05357                                  ri,
05358                                  radius, planetMat, viewFrustum,
05359                                  *context);
05360         }
05361     }
05362 
05363     if (obj.rings != NULL &&
05364         (obj.surface->appearanceFlags & Surface::Emissive) == 0 &&
05365         (renderFlags & ShowRingShadows) != 0)
05366     {
05367         Texture* ringsTex = obj.rings->texture.find(textureResolution);
05368         if (ringsTex != NULL)
05369         {
05370             Vec3f sunDir = pos - Point3f(0, 0, 0);
05371             sunDir.normalize();
05372 
05373             ringsTex->bind();
05374 
05375             if (useClampToBorder &&
05376                 context->getVertexPath() != GLContext::VPath_Basic &&
05377                 context->getRenderPath() != GLContext::GLPath_GLSL)
05378             {
05379                 renderRingShadowsVS(model,
05380                                     *obj.rings,
05381                                     sunDir,
05382                                     ri,
05383                                     radius, 1.0f - obj.semiAxes.y,
05384                                     planetMat, viewFrustum,
05385                                     *context);
05386             }
05387         }
05388     }
05389 
05390     if (obj.rings != NULL && distance > obj.rings->innerRadius)
05391     {
05392         glDepthMask(GL_FALSE);
05393         if (context->getRenderPath() == GLContext::GLPath_GLSL)
05394         {
05395             renderRings_GLSL(*obj.rings, ri, ls,
05396                              radius, 1.0f - obj.semiAxes.y,
05397                              textureResolution,
05398                              (renderFlags & ShowRingShadows) != 0 && lit,
05399                              detailOptions.ringSystemSections);
05400         }
05401         else
05402         {
05403             renderRings(*obj.rings, ri, radius, 1.0f - obj.semiAxes.y,
05404                         textureResolution,
05405                         (context->hasMultitexture() &&
05406                          (renderFlags & ShowRingShadows) != 0 && lit),
05407                         *context,
05408                         detailOptions.ringSystemSections);
05409         }
05410     }
05411 
05412     // Disable all light sources other than the first
05413     for (i = 0; i < ls.nLights; i++)
05414         glDisable(GL_LIGHT0 + i);
05415 
05416 
05417     if (obj.locations != NULL && (labelMode & LocationLabels) != 0)
05418         renderLocations(*obj.locations,
05419                         cameraOrientation,
05420                         pos, obj.orientation, radius);
05421 
05422     glPopMatrix();
05423     glDisable(GL_DEPTH_TEST);
05424     glDepthMask(GL_FALSE);
05425     glDisable(GL_LIGHTING);
05426     glEnable(GL_BLEND);
05427 }
05428 
05429 
05430 bool Renderer::testEclipse(const Body& receiver,
05431                            const Body& caster,
05432                            const DirectionalLight& light,
05433                            double now,
05434                            vector<EclipseShadow>& shadows)
05435 {
05436 
05437     // Ignore situations where the shadow casting body is much smaller than
05438     // the receiver, as these shadows aren't likely to be relevant.  Also,
05439     // ignore eclipses where the caster is not an ellipsoid, since we can't
05440     // generate correct shadows in this case.
05441     if (caster.getRadius() * 100 >= receiver.getRadius() &&
05442         caster.getClassification() != Body::Invisible &&
05443         caster.extant(now) &&
05444         caster.getModel() == InvalidResource)
05445     {
05446         // All of the eclipse related code assumes that both the caster
05447         // and receiver are spherical.  Irregular receivers will work more
05448         // or less correctly, but casters that are sufficiently non-spherical
05449         // will produce obviously incorrect shadows.  Another assumption we
05450         // make is that the distance between the caster and receiver is much
05451         // less than the distance between the sun and the receiver.  This
05452         // approximation works everywhere in the solar system, and likely
05453         // works for any orbitally stable pair of objects orbiting a star.
05454         Point3d posReceiver = receiver.getHeliocentricPosition(now);
05455         Point3d posCaster = caster.getHeliocentricPosition(now);
05456 
05457         //const Star* sun = receiver.getSystem()->getStar();
05458         //assert(sun != NULL);
05459         //double distToSun = posReceiver.distanceFromOrigin();
05460         //float appSunRadius = (float) (sun->getRadius() / distToSun);
05461         float appSunRadius = light.apparentSize;
05462 
05463         Vec3d dir = posCaster - posReceiver;
05464         double distToCaster = dir.length() - receiver.getRadius();
05465         float appOccluderRadius = (float) (caster.getRadius() / distToCaster);
05466 
05467         // The shadow radius is the radius of the occluder plus some additional
05468         // amount that depends upon the apparent radius of the sun.  For
05469         // a sun that's distant/small and effectively a point, the shadow
05470         // radius will be the same as the radius of the occluder.
05471         float shadowRadius = (1 + appSunRadius / appOccluderRadius) *
05472             caster.getRadius();
05473 
05474         // Test whether a shadow is cast on the receiver.  We want to know
05475         // if the receiver lies within the shadow volume of the caster.  Since
05476         // we're assuming that everything is a sphere and the sun is far
05477         // away relative to the caster, the shadow volume is a
05478         // cylinder capped at one end.  Testing for the intersection of a
05479         // singly capped cylinder is as simple as checking the distance
05480         // from the center of the receiver to the axis of the shadow cylinder.
05481         // If the distance is less than the sum of the caster's and receiver's
05482         // radii, then we have an eclipse.
05483         float R = receiver.getRadius() + shadowRadius;
05484         double dist = distance(posReceiver,
05485                                Ray3d(posCaster, posCaster - light.position));
05486         if (dist < R)
05487         {
05488             Vec3d sunDir = posCaster - light.position;
05489             sunDir.normalize();
05490 
05491             EclipseShadow shadow;
05492             shadow.origin = Point3f((float) dir.x,
05493                                     (float) dir.y,
05494                                     (float) dir.z);
05495             shadow.direction = Vec3f((float) sunDir.x,
05496                                      (float) sunDir.y,
05497                                      (float) sunDir.z);
05498             shadow.penumbraRadius = shadowRadius;
05499             shadow.umbraRadius = caster.getRadius() *
05500                 (appOccluderRadius - appSunRadius) / appOccluderRadius;
05501             shadows.push_back(shadow);
05502 
05503             return true;
05504         }
05505     }
05506 
05507     return false;
05508 }
05509 
05510 
05511 void Renderer::renderPlanet(Body& body,
05512                             Point3f pos,
05513                             float distance,
05514                             float appMag,
05515                             double now,
05516                             Quatf orientation,
05517                             const vector<LightSource>& lightSources,
05518                             float nearPlaneDistance,
05519                             float farPlaneDistance)
05520 {
05521     float altitude = distance - body.getRadius();
05522     float discSizeInPixels = body.getRadius() /
05523         (max(nearPlaneDistance, altitude) * pixelSize);
05524 
05525     if (discSizeInPixels > 1)
05526     {
05527         RenderProperties rp;
05528 
05529         if (displayedSurface.empty())
05530         {
05531             rp.surface = const_cast<Surface*>(&body.getSurface());
05532         }
05533         else
05534         {
05535             rp.surface = body.getAlternateSurface(displayedSurface);
05536             if (rp.surface == NULL)
05537                 rp.surface = const_cast<Surface*>(&body.getSurface());
05538         }
05539         rp.atmosphere = body.getAtmosphere();
05540         rp.rings = body.getRings();
05541         rp.radius = body.getRadius();
05542         rp.semiAxes = Vec3f(1.0f, 1.0f - body.getOblateness(), 1.0f);
05543         rp.model = body.getModel();
05544 
05545         // Compute the orientation of the planet before axial rotation
05546         Quatd q = body.getEclipticalToEquatorial(now);
05547 
05548         double rotation = 0.0;
05549         // Watch out for the precision limits of floats when computing
05550         // rotation . . .
05551         {
05552             RotationElements re = body.getRotationElements();
05553             double rotations = (now - re.epoch) / (double) re.period;
05554             double wholeRotations = floor(rotations);
05555             double remainder = rotations - wholeRotations;
05556 
05557             // Add an extra half rotation because of the convention in all
05558             // planet texture maps where zero deg long. is in the middle of
05559             // the texture.
05560             remainder += 0.5;
05561 
05562             rotation = remainder * 2 * PI + re.offset;
05563         }
05564         q.yrotate(-rotation);
05565         rp.orientation = body.getOrientation() *
05566             Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z);
05567 
05568         rp.locations = body.getLocations();
05569         if (rp.locations != NULL && (labelMode & LocationLabels) != 0)
05570             body.computeLocations();
05571 
05572         LightingState lights;
05573         setupObjectLighting(lightSources,
05574                             body.getHeliocentricPosition(now),
05575                             rp.orientation,
05576                             rp.semiAxes,
05577                             pos,
05578                             lights);
05579         assert(lights.nLights < MaxLights);
05580 
05581         {
05582             // Clear out the list of eclipse shadows
05583             for (unsigned int li = 0; li < lights.nLights; li++)
05584             {
05585                 eclipseShadows[li].clear();
05586                 lights.shadows[li] = &eclipseShadows[li];
05587             }
05588         }
05589 
05590 
05591         // Calculate eclipse circumstances
05592         if ((renderFlags & ShowEclipseShadows) != 0 &&
05593             body.getClassification() != Body::Invisible &&
05594             body.getSystem() != NULL)
05595         {
05596             PlanetarySystem* system = body.getSystem();
05597 
05598             if (system->getPrimaryBody() == NULL &&
05599                 body.getSatellites() != NULL)
05600             {
05601                 // The body is a planet.  Check for eclipse shadows
05602                 // from all of its satellites.
05603                 PlanetarySystem* satellites = body.getSatellites();
05604                 if (satellites != NULL)
05605                 {
05606                     int nSatellites = satellites->getSystemSize();
05607                     for (unsigned int li = 0; li < lights.nLights; li++)
05608                     {
05609                         for (int i = 0; i < nSatellites; i++)
05610                             testEclipse(body, *satellites->getBody(i),
05611                                         lights.lights[li],
05612                                         now, *lights.shadows[li]);
05613                     }
05614                 }
05615             }
05616             else if (system->getPrimaryBody() != NULL)
05617             {
05618                 for (unsigned int li = 0; li < lights.nLights; li++)
05619                 {
05620                     // The body is a moon.  Check for eclipse shadows from
05621                     // the parent planet and all satellites in the system.
05622                     Body* planet = system->getPrimaryBody();
05623                     testEclipse(body, *planet, lights.lights[li],
05624                                 now, *lights.shadows[li]);
05625                     
05626                     int nSatellites = system->getSystemSize();
05627                     for (int i = 0; i < nSatellites; i++)
05628                     {
05629                         if (system->getBody(i) != &body)
05630                         {
05631                             testEclipse(body, *system->getBody(i),
05632                                         lights.lights[li],
05633                                         now, *lights.shadows[li]);
05634                         }
05635                     }
05636                 }
05637             }
05638         }
05639 
05640         renderObject(pos, distance, now,
05641                      orientation, nearPlaneDistance, farPlaneDistance,
05642                      rp, lights);
05643 
05644         // Render the horizon compass for spherical and ellipsoidal bodies
05645         if ((renderFlags & ShowCelestialSphere) != 0 &&
05646             rp.model == InvalidResource &&
05647             discSizeInPixels > 100.0f)
05648         {
05649             glPushMatrix();
05650             glLoadIdentity();
05651             glDisable(GL_TEXTURE_2D);
05652             glRotate(orientation);
05653             
05654             Vec3f semiAxes = rp.semiAxes * rp.radius;
05655 
05656             // Compute the orientation of the planet before axial rotation
05657             Quatd q = body.getEclipticalToEquatorial(now);
05658             Quatf qf = Quatf((float) q.w, (float) q.x, (float) q.y,
05659                              (float) q.z);
05660 
05661             if ((renderFlags & ShowSmoothLines) != 0)
05662                 enableSmoothLines();
05663             renderCompass(pos, qf, semiAxes, pixelSize / rp.radius);
05664             if ((renderFlags & ShowSmoothLines) != 0)
05665                 disableSmoothLines();
05666 
05667             glEnable(GL_TEXTURE_2D);
05668             glPopMatrix();
05669         }
05670     }
05671 
05672     glEnable(GL_TEXTURE_2D);
05673     glEnable(GL_BLEND);
05674     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
05675     renderBodyAsParticle(pos,
05676                          appMag,
05677                          faintestPlanetMag,
05678                          discSizeInPixels,
05679                          body.getSurface().color,
05680                          orientation,
05681                          (nearPlaneDistance + farPlaneDistance) / 2.0f,
05682                          false);
05683 }
05684 
05685 
05686 void Renderer::renderStar(const Star& star,
05687                           Point3f pos,
05688                           float distance,
05689                           float appMag,
05690                           Quatf orientation,
05691                           double now,
05692                           float nearPlaneDistance,
05693                           float farPlaneDistance)
05694 {
05695     if (!star.getVisibility())
05696         return;
05697 
05698     //Color color = star.getStellarClass().getApparentColor();
05699     Color color = colorTemp->lookupColor(star.getTemperature());
05700     float radius = star.getRadius();
05701     float discSizeInPixels = radius / (distance * pixelSize);
05702 
05703     if (discSizeInPixels > 1)
05704     {
05705         Surface surface;
05706         RenderProperties rp;
05707 
05708         surface.color = color;
05709 
05710         MultiResTexture mtex = star.getTexture();
05711         if (mtex.tex[textureResolution] != InvalidResource)
05712         {
05713             surface.baseTexture = mtex;
05714         }
05715         else
05716         {
05717             surface.baseTexture = InvalidResource;
05718         }
05719         surface.appearanceFlags |= Surface::ApplyBaseTexture;
05720         surface.appearanceFlags |= Surface::Emissive;
05721 
05722         rp.surface = &surface;
05723         rp.rings = NULL;
05724         rp.radius = star.getRadius();
05725         rp.semiAxes = star.getEllipsoidSemiAxes();
05726         rp.model = star.getModel();
05727 
05728         Atmosphere atmosphere;
05729         atmosphere.height = radius * CoronaHeight;
05730         atmosphere.lowerColor = color;
05731         atmosphere.upperColor = color;
05732         atmosphere.skyColor = color;
05733         if (rp.model == InvalidResource)
05734             rp.atmosphere = &atmosphere;
05735         else
05736             rp.atmosphere = NULL;
05737 
05738         const RotationElements& re = star.getRotationElements();
05739         Quatd q = re.eclipticalToEquatorial(now);
05740 
05741         double rotation = 0.0;
05742         // Watch out for the precision limits of floats when computing
05743         // rotation . . .
05744         {
05745             const RotationElements& re = star.getRotationElements();
05746             double rotations = (now - re.epoch) / (double) re.period;
05747             double wholeRotations = floor(rotations);
05748             double remainder = rotations - wholeRotations;
05749 
05750             // Add an extra half rotation because of the convention in all
05751             // planet texture maps where zero deg long. is in the middle of
05752             // the texture.
05753             remainder += 0.5;
05754 
05755             rotation = remainder * 2 * PI + re.offset;
05756         }
05757         q.yrotate(-rotation);
05758         rp.orientation = Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z);
05759 
05760         renderObject(pos, distance, now,
05761                      orientation, nearPlaneDistance, farPlaneDistance,
05762                      rp, LightingState());
05763 
05764         glEnable(GL_TEXTURE_2D);
05765     }
05766 
05767     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
05768     renderBodyAsParticle(pos,
05769                          appMag,
05770                          faintestMag,
05771                          discSizeInPixels,
05772                          color,
05773                          orientation,
05774                          (nearPlaneDistance + farPlaneDistance) / 2.0f,
05775                          true);
05776 }
05777 
05778 
05779 static const int MaxCometTailPoints = 20;
05780 static const int CometTailSlices = 16;
05781 struct CometTailVertex
05782 {
05783     Point3f point;
05784     Vec3f normal;
05785     Point3f paraboloidPoint;
05786     float brightness;
05787 };
05788 
05789 static CometTailVertex cometTailVertices[CometTailSlices * MaxCometTailPoints];
05790 
05791 static void ProcessCometTailVertex(const CometTailVertex& v,
05792                                    const Vec3f& viewDir,
05793                                    float fadeDistFromSun)
05794 {
05795     // If fadeDistFromSun = x/x0 >= 1.0, comet tail starts fading,
05796     // i.e. fadeFactor quickly transits from 1 to 0.
05797     
05798     float fadeFactor = 0.5f - 0.5f * tanh(fadeDistFromSun - 1.0f / fadeDistFromSun);
05799     float shade = abs(viewDir * v.normal * v.brightness * fadeFactor);
05800     glColor4f(0.5f, 0.5f, 0.75f, shade);
05801     glVertex(v.point);
05802 }
05803 
05804 static void ProcessCometTailVertex(const CometTailVertex& v,
05805                                    const Point3f& cameraPos)
05806 {
05807     Vec3f viewDir = v.point - cameraPos;
05808     viewDir.normalize();
05809     float shade = abs(viewDir * v.normal * v.brightness);
05810     glColor4f(0.0f, 0.5f, 1.0f, shade);
05811     glVertex(v.point);
05812 }
05813 
05814 
05815 static void ProcessCometTailVertex(const CometTailVertex& v,
05816                                    const Point3f& eyePos_obj,
05817                                    float b,
05818                                    float h)
05819 {
05820     float shade = 0.0f;
05821     Vec3f R = v.paraboloidPoint - eyePos_obj;
05822     float c0 = b * (square(eyePos_obj.x) + square(eyePos_obj.y)) + eyePos_obj.z;
05823     float c1 = 2 * b * (R.x * eyePos_obj.x + R.y * eyePos_obj.y) - R.z;
05824     float c2 = b * (square(R.x) + square(R.y));
05825 
05826     float disc = square(c1) - 4 * c0 * c2;
05827 
05828     if (disc < 0.0f)
05829     {
05830         shade = 0.0f;
05831     }
05832     else
05833     {
05834         disc = (float) sqrt(disc);
05835         float s = 1.0f / (2 * c2);
05836         float t0 = (h - eyePos_obj.z) / R.z;
05837         float t1 = (c1 - disc) * s;
05838         float t2 = (c1 + disc) * s;
05839         float u0 = max(t0, 0.0f);
05840         float u1, u2;
05841 
05842         if (R.z < 0.0f)
05843         {
05844             u1 = max(t1, t0);
05845             u2 = max(t2, t0);
05846         }
05847         else
05848         {
05849             u1 = min(t1, t0);
05850             u2 = min(t2, t0);
05851         }
05852         u1 = max(0.0f, u1);
05853         u2 = max(0.0f, u2);
05854 
05855         shade = u2 - u1;
05856     }
05857 
05858     glColor4f(0.0f, 0.5f, 1.0f, shade);
05859     glVertex(v.point);
05860 }
05861 
05862 
05863 void Renderer::renderCometTail(const Body& body,
05864                                Point3f pos,
05865                                float distance,
05866                                float appMag,
05867                                double now,
05868                                Quatf orientation,
05869                                const vector<LightSource>& lightSources,
05870                                float nearPlaneDistance,
05871                                float farPlaneDistance)
05872 {
05873     Point3f cometPoints[MaxCometTailPoints];
05874     Point3d pos0 = body.getOrbit()->positionAtTime(now);
05875     Point3d pos1 = body.getOrbit()->positionAtTime(now - 0.01);
05876     Vec3d vd = pos1 - pos0;
05877     double t = now;
05878     float f = 1.0e15f;
05879     int nSteps = MaxCometTailPoints;
05880     float dt = 10000000.0f / (nSteps * (float) vd.length() * 100.0f);
05881     float distanceFromSun, irradiance_max = 0.0f;
05882     unsigned int li_eff;
05883 
05884     // Find the sun with the largest irrradiance of light onto the comet
05885     // as function of the comet's position;
05886     // irradiance = sun's luminosity / square(distanceFromSun);
05887     
05888     for (unsigned int li = 0; li < lightSources.size(); li++)
05889     {
05890         distanceFromSun = (float)(body.getHeliocentricPosition(now) -
05891                            lightSources[li].position).length();
05892         float irradiance = lightSources[li].luminosity / square(distanceFromSun);
05893         if (irradiance > irradiance_max )
05894         {
05895             li_eff = li;
05896             irradiance_max = irradiance;
05897         }
05898     }
05899     float fadeDistance = 1.0f / (X0_SOL * sqrt(irradiance_max));
05900     
05901     // direction to sun with dominant light irradiance:
05902     
05903     Vec3d sd = body.getHeliocentricPosition(now) - lightSources[li_eff].position;
05904     Vec3f sunDir = Vec3f((float) sd.x, (float) sd.y, (float) sd.z);
05905     sunDir.normalize();
05906     
05907     // cout<<astro::kilometersToAU(X0_SOL*sqrt(lightSources[li_eff].luminosity))<<"   "<<fadeDistance<<"   "<<astro::kilometersToAU(sd.length())<<endl;
05908     
05909     int i;
05910 #if 0
05911     for (i = 0; i < MaxCometTailPoints; i++)
05912     {
05913         Vec3d pd = body.getOrbit()->positionAtTime(t) - pos0;
05914         Point3f p = Point3f((float) pd.x, (float) pd.y, (float) pd.z);
05915         Vec3f r(p.x + (float) pos0.x,
05916                 p.y + (float) pos0.y,
05917                 p.z + (float) pos0.z);
05918         float distance = r.length();
05919             
05920         Vec3f a = r * ((1 / square(distance)) * f * dt);
05921         Vec3f dx = a * (square(i * dt) * 0.5f);
05922 
05923         cometPoints[i] = p + dx;
05924 
05925         t -= dt;
05926     }
05927 #endif
05928     // Compute a rough estimate of the visible length of the dust tail
05929     
05930     float dustTailLength = (1.0e8f / (float) pos0.distanceFromOrigin()) *
05931         (body.getRadius() / 5.0f) * 1.0e7f;
05932     float dustTailRadius = dustTailLength * 0.1f;
05933     float comaRadius = dustTailRadius * 0.5f;
05934 
05935     Point3f origin(0, 0, 0);
05936     origin -= sunDir * (body.getRadius() * 100);
05937     for (i = 0; i < MaxCometTailPoints; i++)
05938     {
05939         float alpha = (float) i / (float) MaxCometTailPoints;
05940         alpha = alpha * alpha;
05941         cometPoints[i] = origin + sunDir * (dustTailLength * alpha);
05942     }
05943 
05944     // We need three axes to define the coordinate system for rendering the
05945     // comet.  The first axis is the velocity.  We choose the second one
05946     // based on the orientation of the comet.  And the third is just the cross
05947     // product of the first and second axes.
05948     Quatd qd = body.getEclipticalToEquatorial(t);
05949     Quatf q((float) qd.w, (float) qd.x, (float) qd.y, (float) qd.z);
05950     Vec3f v = cometPoints[1] - cometPoints[0];
05951     v.normalize();
05952     Vec3f u0 = Vec3f(0, 1, 0) * q.toMatrix3();
05953     Vec3f u1 = Vec3f(1, 0, 0) * q.toMatrix3();
05954     Vec3f u;
05955     if (abs(u0 * v) < abs(u1 * v))
05956         u = v ^ u0;
05957     else
05958         u = v ^ u1;
05959     u.normalize();
05960     Vec3f w = u ^ v;
05961 
05962     glColor4f(0.0f, 1.0f, 1.0f, 0.5f);
05963     glPushMatrix();
05964     glTranslate(pos);
05965 
05966     // glx::glActiveTextureARB(GL_TEXTURE0_ARB);
05967     glDisable(GL_TEXTURE_2D);
05968     glDisable(GL_LIGHTING);
05969     glDepthMask(GL_FALSE);
05970     glEnable(GL_BLEND);
05971     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
05972 
05973     for (i = 0; i < MaxCometTailPoints; i++)
05974     {
05975         float brightness = 1.0f - (float) i / (float) (MaxCometTailPoints - 1);
05976         Vec3f v0, v1;
05977         float sectionLength;
05978         if (i != 0 && i != MaxCometTailPoints - 1)
05979         {
05980             v0 = cometPoints[i] - cometPoints[i - 1];
05981             v1 = cometPoints[i + 1] - cometPoints[i];
05982             sectionLength = v0.length();
05983             v0.normalize();
05984             v1.normalize();
05985             Vec3f axis = v1 ^ v0;
05986             float axisLength = axis.length();
05987             if (axisLength > 1e-5f)
05988             {
05989                 axis *= 1.0f / axisLength;
05990                 q.setAxisAngle(axis, (float) asin(axisLength));
05991                 Mat3f m = q.toMatrix3();
05992 
05993                 u = u * m;
05994                 v = v * m;
05995                 w = w * m;
05996             }
05997         }
05998         else if (i == 0)
05999         {
06000             v0 = cometPoints[i + 1] - cometPoints[i];
06001             sectionLength = v0.length();
06002             v0.normalize();
06003             v1 = v0;
06004         }
06005         else
06006         {
06007             v0 = v1 = cometPoints[i] - cometPoints[i - 1];
06008             sectionLength = v0.length();
06009             v0.normalize();
06010             v1 = v0;
06011         }
06012 
06013         float radius = (float) i / (float) MaxCometTailPoints *
06014             dustTailRadius;
06015         float dr = (dustTailRadius / (float) MaxCometTailPoints) /
06016             sectionLength;
06017         float w0 = (float) atan(dr);
06018         float w1 = (float) sqrt(1.0f - square(w0));
06019 
06020         for (int j = 0; j < CometTailSlices; j++)
06021         {
06022             float theta = (float) (2 * PI * (float) j / CometTailSlices);
06023             float s = (float) sin(theta);
06024             float c = (float) cos(theta);
06025             CometTailVertex* vtx = &cometTailVertices[i * CometTailSlices + j];
06026             vtx->normal = u * (s * w1) + w * (c * w1) + v * w0;
06027             s *= radius;
06028             c *= radius;
06029 
06030             vtx->point = Point3f(cometPoints[i].x + u.x * s + w.x * c,
06031                                  cometPoints[i].y + u.y * s + w.y * c,
06032                                  cometPoints[i].z + u.z * s + w.z * c);
06033             vtx->brightness = brightness;
06034             vtx->paraboloidPoint =
06035                 Point3f(c, s, square((float) i / (float) MaxCometTailPoints));
06036         }
06037     }
06038 
06039     Vec3f viewDir = pos - Point3f(0.0f, 0.0f, 0.0f);
06040     viewDir.normalize();
06041 
06042     glDisable(GL_CULL_FACE);
06043     for (i = 0; i < MaxCometTailPoints - 1; i++)
06044     {
06045         glBegin(GL_QUAD_STRIP);
06046         int n = i * CometTailSlices;
06047         for (int j = 0; j < CometTailSlices; j++)
06048         {
06049             ProcessCometTailVertex(cometTailVertices[n + j], viewDir, fadeDistance);
06050             ProcessCometTailVertex(cometTailVertices[n + j + CometTailSlices],
06051                                    viewDir, fadeDistance);
06052         }
06053         ProcessCometTailVertex(cometTailVertices[n], viewDir, fadeDistance);
06054         ProcessCometTailVertex(cometTailVertices[n + CometTailSlices],
06055                                viewDir, fadeDistance);
06056         glEnd();
06057     }
06058     glEnable(GL_CULL_FACE);
06059 
06060     glBegin(GL_LINE_STRIP);
06061     for (i = 0; i < MaxCometTailPoints; i++)
06062     {
06063         glVertex(cometPoints[i]);
06064     }
06065     glEnd();
06066 
06067     glDepthMask(GL_TRUE);
06068     glEnable(GL_TEXTURE_2D);
06069     glEnable(GL_BLEND);
06070 
06071     glPopMatrix();
06072 }
06073 
06074 
06075 void Renderer::renderPlanetarySystem(const Star& sun,
06076                                      const PlanetarySystem& solSystem,
06077                                      const Observer& observer,
06078                                      double now,
06079                                      unsigned int solarSysIndex,
06080                                      bool showLabels)
06081 {
06082     Point3f starPos = sun.getPosition();
06083     Point3d observerPos = astrocentricPosition(observer.getPosition(),
06084                                                sun, now);
06085 
06086     int nBodies = solSystem.getSystemSize();
06087     for (int i = 0; i < nBodies; i++)
06088     {
06089         Body* body = solSystem.getBody(i);
06090         if (!body->extant(now))
06091             continue;
06092 
06093         Point3d localPos = body->getOrbit()->positionAtTime(now);
06094         Point3d bodyPos = body->getHeliocentricPosition(now);
06095         
06096         // We now have the positions of the observer and the planet relative
06097         // to the sun.  From these, compute the position of the planet
06098         // relative to the observer.
06099         Vec3d posd = bodyPos - observerPos;
06100 
06101         vector<LightSource>& lightSources = lightSourceLists[solarSysIndex];
06102 
06103         // Compute the apparent magnitude; instead of summing the reflected
06104         // light from all nearby stars, we just consider the one with the
06105         // highest apparent brightness.
06106         float appMag = 100.0f;
06107         for (unsigned int li = 0; li < lightSources.size(); li++)
06108         {
06109             Vec3d sunPos = bodyPos - lightSources[li].position;
06110             appMag = min(appMag, body->getApparentMagnitude(lightSources[li].luminosity, sunPos, posd));
06111         }
06112 
06113         Vec3f pos((float) posd.x, (float) posd.y, (float) posd.z);
06114 
06115         // Compute the size of the planet/moon disc in pixels
06116         double distanceFromObserver = posd.length();
06117         float discSize = (body->getRadius() / (float) distanceFromObserver) / pixelSize;
06118 
06119         // if (discSize > 1 || appMag < 1.0f / brightnessScale)
06120         if ((discSize > 1 || appMag < faintestPlanetMag) &&
06121             body->getClassification() != Body::Invisible)
06122         {
06123             RenderListEntry rle;
06124             rle.body = body;
06125             rle.star = NULL;
06126             rle.isCometTail = false;
06127             rle.position = Point3f(pos.x, pos.y, pos.z);
06128             rle.sun = Vec3f((float) -bodyPos.x, (float) -bodyPos.y, (float) -bodyPos.z);
06129             rle.distance = (float) distanceFromObserver;
06130             rle.radius = body->getRadius();
06131             rle.discSizeInPixels = discSize;
06132             rle.appMag = appMag;
06133             rle.solarSysIndex = solarSysIndex;
06134             renderList.insert(renderList.end(), rle);
06135         }
06136 
06137         if (body->getClassification() == Body::Comet &&
06138             (renderFlags & ShowCometTails) != 0)
06139         {
06140             float radius = 10000000.0f; // body->getRadius() * 1000000.0f;
06141             discSize = (radius / (float) distanceFromObserver) / pixelSize;
06142             if (discSize > 1)
06143             {
06144                 RenderListEntry rle;
06145                 rle.body = body;
06146                 rle.star = NULL;
06147                 rle.isCometTail = true;
06148                 rle.position = Point3f(pos.x, pos.y, pos.z);
06149                 rle.radius = radius;
06150                 rle.sun = Vec3f((float) -bodyPos.x, (float) -bodyPos.y, (float) -bodyPos.z);
06151                 rle.distance = (float) distanceFromObserver;
06152                 rle.radius = radius;
06153                 rle.discSizeInPixels = discSize;
06154                 rle.appMag = appMag;
06155                 rle.solarSysIndex = solarSysIndex;
06156                 renderList.insert(renderList.end(), rle);
06157             }
06158         }
06159 
06160         if (showLabels && (pos * conjugate(observer.getOrientation()).toMatrix3()).z < 0)
06161         {
06162             float boundingRadiusSize = (float) (body->getOrbit()->getBoundingRadius() / distanceFromObserver) / pixelSize;
06163             if (boundingRadiusSize > minOrbitSize)
06164             {
06165                 Color labelColor;
06166                 bool showLabel = false;
06167 
06168                 switch (body->getClassification())
06169                 {
06170                 case Body::Planet:
06171                     if ((labelMode & PlanetLabels) != 0)
06172                     {
06173                         labelColor = Color(0.0f, 1.0f, 0.0f);
06174                         showLabel = true;
06175                     }
06176                     break;
06177                 case Body::Moon:
06178                     if ((labelMode & MoonLabels) != 0)
06179                     {
06180                         labelColor = Color(0.0f, 0.65f, 0.0f);
06181                         showLabel = true;
06182                     }
06183                     break;
06184                 case Body::Asteroid:
06185                     if ((labelMode & AsteroidLabels) != 0)
06186                     {
06187                         labelColor = Color(0.7f, 0.4f, 0.0f);
06188                         showLabel = true;
06189                     }
06190                     break;
06191                 case Body::Comet:
06192                     if ((labelMode & CometLabels) != 0)
06193                     {
06194                         labelColor = Color(0.0f, 1.0f, 1.0f);
06195                         showLabel = true;
06196                     }
06197                     break;
06198                 case Body::Spacecraft:
06199                     if ((labelMode & SpacecraftLabels) != 0)
06200                     {
06201                         labelColor = Color(0.6f, 0.6f, 0.6f);
06202                         showLabel = true;
06203                     }
06204                     break;
06205                 }
06206 
06207                 if (showLabel)
06208                 {
06209                     addSortedLabel(body->getName(), labelColor,
06210                                    Point3f(pos.x, pos.y, pos.z));
06211                 }
06212             }
06213         }
06214 
06215         if (appMag < faintestPlanetMag)
06216         {
06217             const PlanetarySystem* satellites = body->getSatellites();
06218             if (satellites != NULL)
06219             {
06220                 renderPlanetarySystem(sun, *satellites, observer,
06221                                       now, solarSysIndex, showLabels);
06222             }
06223         }
06224     }
06225 }
06226 
06227 
06228 
06229 
06230 template <class OBJ, class PREC> class ObjectRenderer : public OctreeProcessor<OBJ, PREC>
06231 {
06232  public:
06233     ObjectRenderer(const PREC distanceLimit);
06234 
06235     void process(const OBJ&, PREC, float) {};
06236 
06237  public:
06238     const Observer* observer;
06239 
06240     GLContext* context;
06241     Renderer*  renderer;
06242 
06243     Vec3f viewNormal;
06244 
06245     float fov;
06246     float size;
06247     float pixelSize;
06248     float faintestMag;
06249     float faintestMagNight;
06250     float saturationMag;
06251     float brightnessScale;
06252     float brightnessBias;
06253     float distanceLimit;
06254 
06255     // These are not fully used by this template's descendants
06256     // but we place them here just in case a more sophisticated
06257     // rendering scheme is implemented:
06258     int nRendered;
06259     int nClose;
06260     int nBright;
06261     int nProcessed;
06262     int nLabelled;
06263 
06264     int renderFlags;
06265     int labelMode;
06266 };
06267 
06268 
06269 template <class OBJ, class PREC>
06270 ObjectRenderer<OBJ, PREC>::ObjectRenderer(const PREC _distanceLimit) :
06271     distanceLimit((float) _distanceLimit),
06272     nRendered    (0),
06273     nClose       (0),
06274     nBright      (0),
06275     nProcessed   (0),
06276     nLabelled    (0)
06277 {
06278 }
06279 
06280 
06281 
06282 
06283 class StarRenderer : public ObjectRenderer<Star, float>
06284 {
06285  public:
06286     StarRenderer();
06287 
06288     void process(const Star& star, float distance, float appMag);
06289 
06290  public:
06291     Point3f obsPos;
06292 
06293     vector<Renderer::Particle>* glareParticles;
06294     vector<RenderListEntry>*    renderList;
06295     Renderer::StarVertexBuffer* starVertexBuffer;
06296 
06297     bool  useDiscs;
06298     float maxDiscSize;
06299 
06300     const ColorTemperatureTable* colorTemp;
06301 };
06302 
06303 
06304 StarRenderer::StarRenderer() :
06305     ObjectRenderer<Star, float>(STAR_DISTANCE_LIMIT),
06306     starVertexBuffer(0),
06307     useDiscs        (false),
06308     maxDiscSize     (1.0f),
06309     colorTemp       (NULL)
06310 {
06311 }
06312 
06313 
06314 void StarRenderer::process(const Star& star, float distance, float appMag)
06315 {
06316     nProcessed++;
06317 
06318     Point3f starPos = star.getPosition();
06319     Vec3f   relPos = starPos - obsPos;
06320     float   orbitalRadius = star.getOrbitalRadius();
06321     bool    hasOrbit = orbitalRadius > 0.0f;
06322 
06323     if (distance > distanceLimit)
06324         return;
06325 
06326     if (relPos * viewNormal > 0 || relPos.x * relPos.x < 0.1f || hasOrbit)
06327     {
06328         Color starColor = colorTemp->lookupColor(star.getTemperature());
06329         float renderDistance = distance;
06330         float s = renderDistance * size;
06331         float discSizeInPixels = 0.0f;
06332         float orbitSizeInPixels = 0.0f;
06333 
06334         if (hasOrbit)
06335             orbitSizeInPixels = orbitalRadius / (distance * pixelSize);
06336 
06337         // Special handling for stars less than one light year away . . .
06338         // We can't just go ahead and render a nearby star in the usual way
06339         // for two reasons:
06340         //   * It may be clipped by the near plane
06341         //   * It may be large enough that we should render it as a mesh
06342         //     instead of a particle
06343         // It's possible that the second condition might apply for stars
06344         // further than one light year away if the star is huge, the fov is
06345         // very small and the resolution is high.  We'll ignore this for now
06346         // and use the most inexpensive test possible . . .
06347         if (distance < 1.0f || orbitSizeInPixels > 1.0f)
06348         {
06349             // Compute the position of the observer relative to the star.
06350             // This is a much more accurate (and expensive) distance
06351             // calculation than the previous one which used the observer's
06352             // position rounded off to floats.
06353             Point3d hPos = astrocentricPosition(observer->getPosition(),
06354                                                 star,
06355                                                 observer->getTime());
06356             relPos = Vec3f((float) hPos.x, (float) hPos.y, (float) hPos.z) *
06357                 -astro::kilometersToLightYears(1.0f),
06358             distance = relPos.length();
06359 
06360             // Recompute apparent magnitude using new distance computation
06361             appMag = astro::absToAppMag(star.getAbsoluteMagnitude(), distance);
06362 
06363             float f        = RenderDistance / distance;
06364             renderDistance = RenderDistance;
06365             starPos        = obsPos + relPos * f;
06366 
06367             float radius = star.getRadius();
06368             discSizeInPixels = radius / astro::lightYearsToKilometers(distance) / pixelSize;
06369             ++nClose;
06370         }
06371 
06372         if (discSizeInPixels <= 1)
06373         {
06374             float alpha = (faintestMag - appMag) * brightnessScale + brightnessBias;
06375 
06376             if (useDiscs)
06377             {
06378                 float discSize = size;
06379                 if (alpha < 0.0f)
06380                 {
06381                     alpha = 0.0f;
06382                 }
06383                 else if (alpha > 1.0f)
06384                 {
06385                     discSize = min(discSize * (2.0f * alpha - 1.0f), maxDiscSize);
06386                     alpha = 1.0f;
06387                 }
06388                 starVertexBuffer->addStar(starPos,
06389                                           Color(starColor, alpha),
06390                                           renderDistance * discSize);
06391             }
06392             else
06393             {
06394                 alpha = clamp(alpha);
06395                 starVertexBuffer->addStar(starPos,
06396                                           Color(starColor, alpha),
06397                                           renderDistance * size);
06398             }
06399 
06400             ++nRendered;
06401 
06402             // If the star is brighter than the saturation magnitude, add a
06403             // halo around it to make it appear more brilliant.  This is a
06404             // hack to compensate for the limited dynamic range of monitors.
06405             if (appMag < saturationMag)
06406             {
06407                 Renderer::Particle p;
06408                 p.center = starPos;
06409                 p.size = renderDistance * size;
06410                 p.color = Color(starColor, alpha);
06411 
06412                 alpha = 0.4f * clamp((appMag - saturationMag) * -0.8f);
06413                 s = renderDistance * 0.001f * (3 - (appMag - saturationMag)) * 2;
06414                 if (s > p.size * 3 )
06415                                 p.size = s * 2.0f/(1.0f + FOV/fov);     
06416                 else
06417                     p.size = p.size * 3;
06418                 p.color = Color(starColor, alpha);
06419                 glareParticles->insert(glareParticles->end(), p);
06420                 ++nBright;
06421             }
06422         }
06423         else
06424         {
06425             RenderListEntry rle;
06426             rle.star = &star;
06427             rle.body = NULL;
06428             rle.isCometTail = false;
06429             
06430             // Objects in the render list are always rendered relative to
06431             // a viewer at the origin--this is different than for distant
06432             // stars.
06433             float scale = astro::lightYearsToKilometers(1.0f);
06434             rle.position = Point3f(relPos.x * scale, relPos.y * scale, relPos.z * scale);
06435             rle.distance = rle.position.distanceFromOrigin();
06436             rle.radius = star.getRadius();
06437             rle.discSizeInPixels = discSizeInPixels;
06438             rle.appMag = appMag;
06439             renderList->insert(renderList->end(), rle);
06440         }
06441     }
06442 }
06443 
06444 
06445 void Renderer::renderStars(const StarDatabase& starDB,
06446                            float faintestMagNight,
06447                            const Observer& observer)
06448 {
06449     StarRenderer starRenderer;
06450     Point3f obsPos = (Point3f) observer.getPosition();
06451     obsPos.x *= 1e-6f;
06452     obsPos.y *= 1e-6f;
06453     obsPos.z *= 1e-6f;
06454 
06455     starRenderer.context          = context;
06456     starRenderer.observer         = &observer;
06457     starRenderer.obsPos           = obsPos;
06458     starRenderer.viewNormal       = Vec3f(0, 0, -1) * observer.getOrientation().toMatrix3();
06459     starRenderer.glareParticles   = &glareParticles;
06460     starRenderer.renderList       = &renderList;
06461     starRenderer.starVertexBuffer = starVertexBuffer;
06462     starRenderer.fov              = fov;
06463 
06464     // size/pixelSize =0.86 at 120deg, 1.43 at 45deg and 1.6 at 0deg.
06465     starRenderer.size             = pixelSize * 1.6f / corrFac;
06466     starRenderer.pixelSize        = pixelSize;
06467     starRenderer.brightnessScale  = brightnessScale * corrFac;
06468     starRenderer.brightnessBias   = brightnessBias;
06469     starRenderer.faintestMag      = faintestMag;
06470     starRenderer.faintestMagNight = faintestMagNight;
06471     starRenderer.saturationMag    = saturationMag;
06472     starRenderer.distanceLimit    = distanceLimit;
06473 
06474     if (starStyle == ScaledDiscStars)
06475     {
06476         starRenderer.useDiscs = true;
06477         starRenderer.brightnessScale *= 2.0f;
06478         starRenderer.maxDiscSize = starRenderer.size * MaxScaledDiscStarSize;
06479     }
06480 
06481     starRenderer.colorTemp = colorTemp;
06482 
06483     glareParticles.clear();
06484 
06485     starVertexBuffer->setBillboardOrientation(observer.getOrientation());
06486 
06487     glEnable(GL_TEXTURE_2D);
06488 
06489     starTex->bind();
06490     starRenderer.starVertexBuffer->start(starStyle == PointStars);
06491     starDB.findVisibleStars(starRenderer,
06492                             obsPos,
06493                             observer.getOrientation(),
06494