1 // render.cpp
2 //
3 // Copyright (C) 2001-2008, the Celestia Development Team
4 // Original version by Chris Laurel <claurel@gmail.com>
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10
11 #include <algorithm>
12 #include <cstdio>
13 #include <cstring>
14 #include <cassert>
15 #include <sstream>
16 #include <iomanip>
17
18
19 #define DEBUG_COALESCE 0
20
21 //#define DEBUG_HDR
22 #ifdef DEBUG_HDR
23 //#define DEBUG_HDR_FILE
24 //#define DEBUG_HDR_ADAPT
25 //#define DEBUG_HDR_TONEMAP
26 #endif
27 #ifdef DEBUG_HDR_FILE
28 #include <fstream>
29 std::ofstream hdrlog;
30 #define HDR_LOG hdrlog
31 #else
32 #define HDR_LOG cout
33 #endif
34
35 #ifdef USE_HDR
36 #define BLUR_PASS_COUNT 2
37 #define BLUR_SIZE 128
38 #define DEFAULT_EXPOSURE -23.35f
39 #define EXPOSURE_HALFLIFE 0.4f
40 //#define USE_BLOOM_LISTS
41 #endif
42
43 #ifndef _WIN32
44 #ifndef TARGET_OS_MAC
45 #include <config.h>
46 #endif
47 #endif /* _WIN32 */
48
49 #include <celutil/debug.h>
50 #include <celmath/frustum.h>
51 #include <celmath/distance.h>
52 #include <celmath/intersect.h>
53 #include <celutil/utf8.h>
54 #include <celutil/util.h>
55 #include <celutil/timer.h>
56 #include "gl.h"
57 #include "astro.h"
58 #include "glext.h"
59 #include "vecgl.h"
60 #include "glshader.h"
61 #include "shadermanager.h"
62 #include "spheremesh.h"
63 #include "lodspheremesh.h"
64 #include "model.h"
65 #include "regcombine.h"
66 #include "vertexprog.h"
67 #include "texmanager.h"
68 #include "meshmanager.h"
69 #include "render.h"
70 #include "renderinfo.h"
71 #include "renderglsl.h"
72 #include "axisarrow.h"
73 #include "frametree.h"
74 #include "timelinephase.h"
75 #include "skygrid.h"
76
77 using namespace std;
78
79 #define FOV 45.0f
80 #define NEAR_DIST 0.5f
81 #define FAR_DIST 1.0e9f
82
83 // This should be in the GL headers, but where?
84 #ifndef GL_COLOR_SUM_EXT
85 #define GL_COLOR_SUM_EXT 0x8458
86 #endif
87
88 static const float STAR_DISTANCE_LIMIT = 1.0e6f;
89 static const int REF_DISTANCE_TO_SCREEN = 400; //[mm]
90
91 // Contribution from planetshine beyond this distance (in units of object radius)
92 // is considered insignificant.
93 static const float PLANETSHINE_DISTANCE_LIMIT_FACTOR = 100.0f;
94
95 // Planetshine from objects less than this pixel size is treated as insignificant
96 // and will be ignored.
97 static const float PLANETSHINE_PIXEL_SIZE_LIMIT = 0.1f;
98
99 // Distance from the Sun at which comet tails will start to fade out
100 static const float COMET_TAIL_ATTEN_DIST_SOL = astro::AUtoKilometers(5.0f);
101
102 static const int StarVertexListSize = 1024;
103
104 // Fractional pixel offset used when rendering text as texture mapped
105 // quads to ensure consistent mapping of texels to pixels.
106 static const float PixelOffset = 0.125f;
107
108 // These two values constrain the near and far planes of the view frustum
109 // when rendering planet and object meshes. The near plane will never be
110 // closer than MinNearPlaneDistance, and the far plane is set so that far/near
111 // will not exceed MaxFarNearRatio.
112 static const float MinNearPlaneDistance = 0.0001f; // km
113 static const float MaxFarNearRatio = 2000000.0f;
114
115 static const float RenderDistance = 50.0f;
116
117 // Star disc size in pixels
118 static const float BaseStarDiscSize = 5.0f;
119 static const float MaxScaledDiscStarSize = 8.0f;
120 static const float GlareOpacity = 0.65f;
121
122 static const float MinRelativeOccluderRadius = 0.005f;
123
124 static const float CubeCornerToCenterDistance = (float) sqrt(3.0);
125
126
127 // The minimum apparent size of an objects orbit in pixels before we display
128 // a label for it. This minimizes label clutter.
129 static const float MinOrbitSizeForLabel = 20.0f;
130
131 // The minimum apparent size of a surface feature in pixels before we display
132 // a label for it.
133 static const float MinFeatureSizeForLabel = 20.0f;
134
135 /* The maximum distance of the observer to the origin of coordinates before
136 asterism lines and labels start to linearly fade out (in uLY) */
137 static const float MaxAsterismLabelsConstDist = 6.0e6f;
138 static const float MaxAsterismLinesConstDist = 6.0e8f;
139
140 /* The maximum distance of the observer to the origin of coordinates before
141 asterisms labels and lines fade out completely (in uLY) */
142 static const float MaxAsterismLabelsDist = 2.0e7f;
143 static const float MaxAsterismLinesDist = 6.52e10f;
144
145 // Maximum size of a solar system in light years. Features beyond this distance
146 // will not necessarily be rendered correctly. This limit is used for
147 // visibility culling of solar systems.
148 static const float MaxSolarSystemSize = 1.0f;
149
150
151 // Static meshes and textures used by all instances of Simulation
152
153 static bool commonDataInitialized = false;
154
155 static const string EMPTY_STRING("");
156
157
158 LODSphereMesh* g_lodSphere = NULL;
159
160 static Texture* normalizationTex = NULL;
161
162 static Texture* starTex = NULL;
163 static Texture* glareTex = NULL;
164 static Texture* shadowTex = NULL;
165 static Texture* gaussianDiscTex = NULL;
166 static Texture* gaussianGlareTex = NULL;
167
168 // Shadow textures are scaled down slightly to leave some extra blank pixels
169 // near the border. This keeps axis aligned streaks from appearing on hardware
170 // that doesn't support clamp to border color.
171 static const float ShadowTextureScale = 15.0f / 16.0f;
172
173 static Texture* eclipseShadowTextures[4];
174 static Texture* shadowMaskTexture = NULL;
175 static Texture* penumbraFunctionTexture = NULL;
176
177 Texture* rectToSphericalTexture = NULL;
178
179 static const Color compassColor(0.4f, 0.4f, 1.0f);
180
181 static const float CoronaHeight = 0.2f;
182
183 static bool buggyVertexProgramEmulation = true;
184
185 static const int MaxSkyRings = 32;
186 static const int MaxSkySlices = 180;
187 static const int MinSkySlices = 30;
188
189 // Size at which the orbit cache will be flushed of old orbit paths
190 static const unsigned int OrbitCacheCullThreshold = 200;
191 // Age in frames at which unused orbit paths may be eliminated from the cache
192 static const uint32 OrbitCacheRetireAge = 16;
193
194 Color Renderer::StarLabelColor (0.471f, 0.356f, 0.682f);
195 Color Renderer::PlanetLabelColor (0.407f, 0.333f, 0.964f);
196 Color Renderer::DwarfPlanetLabelColor (0.407f, 0.333f, 0.964f);
197 Color Renderer::MoonLabelColor (0.231f, 0.733f, 0.792f);
198 Color Renderer::MinorMoonLabelColor (0.231f, 0.733f, 0.792f);
199 Color Renderer::AsteroidLabelColor (0.596f, 0.305f, 0.164f);
200 Color Renderer::CometLabelColor (0.768f, 0.607f, 0.227f);
201 Color Renderer::SpacecraftLabelColor (0.93f, 0.93f, 0.93f);
202 Color Renderer::LocationLabelColor (0.24f, 0.89f, 0.43f);
203 Color Renderer::GalaxyLabelColor (0.0f, 0.45f, 0.5f);
204 Color Renderer::GlobularLabelColor (0.8f, 0.45f, 0.5f);
205 Color Renderer::NebulaLabelColor (0.541f, 0.764f, 0.278f);
206 Color Renderer::OpenClusterLabelColor (0.239f, 0.572f, 0.396f);
207 Color Renderer::ConstellationLabelColor (0.225f, 0.301f, 0.36f);
208 Color Renderer::EquatorialGridLabelColor(0.64f, 0.72f, 0.88f);
209 Color Renderer::PlanetographicGridLabelColor(0.8f, 0.8f, 0.8f);
210 Color Renderer::GalacticGridLabelColor (0.88f, 0.72f, 0.64f);
211 Color Renderer::EclipticGridLabelColor (0.72f, 0.64f, 0.88f);
212 Color Renderer::HorizonGridLabelColor (0.72f, 0.72f, 0.72f);
213
214
215 Color Renderer::StarOrbitColor (0.5f, 0.5f, 0.8f);
216 Color Renderer::PlanetOrbitColor (0.3f, 0.323f, 0.833f);
217 Color Renderer::DwarfPlanetOrbitColor (0.3f, 0.323f, 0.833f);
218 Color Renderer::MoonOrbitColor (0.08f, 0.407f, 0.392f);
219 Color Renderer::MinorMoonOrbitColor (0.08f, 0.407f, 0.392f);
220 Color Renderer::AsteroidOrbitColor (0.58f, 0.152f, 0.08f);
221 Color Renderer::CometOrbitColor (0.639f, 0.487f, 0.168f);
222 Color Renderer::SpacecraftOrbitColor (0.4f, 0.4f, 0.4f);
223 Color Renderer::SelectionOrbitColor (1.0f, 0.0f, 0.0f);
224
225 Color Renderer::ConstellationColor (0.0f, 0.24f, 0.36f);
226 Color Renderer::BoundaryColor (0.24f, 0.10f, 0.12f);
227 Color Renderer::EquatorialGridColor (0.28f, 0.28f, 0.38f);
228 Color Renderer::PlanetographicGridColor (0.8f, 0.8f, 0.8f);
229 Color Renderer::PlanetEquatorColor (0.5f, 1.0f, 1.0f);
230 Color Renderer::GalacticGridColor (0.38f, 0.38f, 0.28f);
231 Color Renderer::EclipticGridColor (0.38f, 0.28f, 0.38f);
232 Color Renderer::HorizonGridColor (0.38f, 0.38f, 0.38f);
233 Color Renderer::EclipticColor (0.5f, 0.1f, 0.1f);
234
235 Color Renderer::SelectionCursorColor (1.0f, 0.0f, 0.0f);
236
237
238 // Some useful unit conversions
mmToInches(float mm)239 inline float mmToInches(float mm)
240 {
241 return mm * (1.0f / 25.4f);
242 }
243
inchesToMm(float in)244 inline float inchesToMm(float in)
245 {
246 return in * 25.4f;
247 }
248
249
250 // Fade function for objects that shouldn't be shown when they're too small
251 // on screen such as orbit paths and some object labels. The will fade linearly
252 // from invisible at minSize pixels to full visibility at opaqueScale*minSize.
sizeFade(float screenSize,float minScreenSize,float opaqueScale)253 inline float sizeFade(float screenSize, float minScreenSize, float opaqueScale)
254 {
255 return min(1.0f, (screenSize - minScreenSize) / (minScreenSize * (opaqueScale - 1)));
256 }
257
258
259 // Calculate the cosine of half the maximum field of view. We'll use this for
260 // fast testing of object visibility. The function takes the vertical FOV (in
261 // degrees) as an argument. When computing the view cone, we want the field of
262 // view as measured on the diagonal between viewport corners.
computeCosViewConeAngle(double verticalFOV,double width,double height)263 double computeCosViewConeAngle(double verticalFOV, double width, double height)
264 {
265 double h = tan(degToRad(verticalFOV / 2));
266 double diag = sqrt(1.0 + square(h) + square(h * width / height));
267 return 1.0 / diag;
268 }
269
270
Renderer()271 Renderer::Renderer() :
272 context(0),
273 windowWidth(0),
274 windowHeight(0),
275 fov(FOV),
276 cosViewConeAngle(computeCosViewConeAngle(fov, 1, 1)),
277 screenDpi(96),
278 corrFac(1.12f),
279 faintestAutoMag45deg(7.0f),
280 renderMode(GL_FILL),
281 labelMode(NoLabels),
282 renderFlags(ShowStars | ShowPlanets),
283 orbitMask(Body::Planet | Body::Moon | Body::Stellar),
284 ambientLightLevel(0.1f),
285 fragmentShaderEnabled(false),
286 vertexShaderEnabled(false),
287 brightnessBias(0.0f),
288 saturationMagNight(1.0f),
289 saturationMag(1.0f),
290 starStyle(FuzzyPointStars),
291 starVertexBuffer(NULL),
292 pointStarVertexBuffer(NULL),
293 glareVertexBuffer(NULL),
294 useVertexPrograms(false),
295 useRescaleNormal(false),
296 usePointSprite(false),
297 textureResolution(medres),
298 useNewStarRendering(false),
299 frameCount(0),
300 lastOrbitCacheFlush(0),
301 minOrbitSize(MinOrbitSizeForLabel),
302 distanceLimit(1.0e6f),
303 minFeatureSize(MinFeatureSizeForLabel),
304 locationFilter(~0u),
305 colorTemp(NULL),
306 #ifdef USE_HDR
307 sceneTexture(0),
308 blurFormat(GL_RGBA),
309 useBlendSubtract(true),
310 useLuminanceAlpha(false),
311 bloomEnabled(true),
312 maxBodyMag(100.0f),
313 exposure(1.0f),
314 exposurePrev(1.0f),
315 brightPlus(0.0f),
316 #endif
317 videoSync(false),
318 settingsChanged(true),
319 objectAnnotationSetOpen(false)
320 {
321 starVertexBuffer = new StarVertexBuffer(2048);
322 pointStarVertexBuffer = new PointStarVertexBuffer(2048);
323 glareVertexBuffer = new PointStarVertexBuffer(2048);
324 skyVertices = new SkyVertex[MaxSkySlices * (MaxSkyRings + 1)];
325 skyIndices = new uint32[(MaxSkySlices + 1) * 2 * MaxSkyRings];
326 skyContour = new SkyContourPoint[MaxSkySlices + 1];
327 colorTemp = GetStarColorTable(ColorTable_Enhanced);
328 #ifdef DEBUG_HDR_FILE
329 HDR_LOG.open("hdr.log", ios_base::app);
330 #endif
331 #ifdef USE_HDR
332 blurTextures = new Texture*[BLUR_PASS_COUNT];
333 blurTempTexture = NULL;
334 for (size_t i = 0; i < BLUR_PASS_COUNT; ++i)
335 {
336 blurTextures[i] = NULL;
337 }
338 #endif
339 #ifdef USE_BLOOM_LISTS
340 for (size_t i = 0; i < (sizeof gaussianLists/sizeof(GLuint)); ++i)
341 {
342 gaussianLists[i] = 0;
343 }
344 #endif
345
346 for (int i = 0; i < (int) FontCount; i++)
347 {
348 font[i] = NULL;
349 }
350 }
351
352
~Renderer()353 Renderer::~Renderer()
354 {
355 if (starVertexBuffer != NULL)
356 delete starVertexBuffer;
357 if (pointStarVertexBuffer != NULL)
358 delete pointStarVertexBuffer;
359 delete[] skyVertices;
360 delete[] skyIndices;
361 delete[] skyContour;
362 #ifdef USE_BLOOM_LISTS
363 for (size_t i = 0; i < (sizeof gaussianLists/sizeof(GLuint)); ++i)
364 {
365 if (gaussianLists[i] != 0)
366 glDeleteLists(gaussianLists[i], 1);
367 }
368 #endif
369 #ifdef USE_HDR
370 for (size_t i = 0; i < BLUR_PASS_COUNT; ++i)
371 {
372 if (blurTextures[i] != NULL)
373 delete blurTextures[i];
374 }
375 delete [] blurTextures;
376 if (blurTempTexture)
377 delete blurTempTexture;
378
379 if (sceneTexture != 0)
380 glDeleteTextures(1, &sceneTexture);
381 #endif
382 }
383
384
DetailOptions()385 Renderer::DetailOptions::DetailOptions() :
386 ringSystemSections(100),
387 orbitPathSamplePoints(100),
388 shadowTextureSize(256),
389 eclipseTextureSize(128)
390 {
391 }
392
393
StarTextureEval(float u,float v,float,unsigned char * pixel)394 static void StarTextureEval(float u, float v, float,
395 unsigned char *pixel)
396 {
397 float r = 1 - (float) sqrt(u * u + v * v);
398 if (r < 0)
399 r = 0;
400 else if (r < 0.5f)
401 r = 2.0f * r;
402 else
403 r = 1;
404
405 int pixVal = (int) (r * 255.99f);
406 pixel[0] = pixVal;
407 pixel[1] = pixVal;
408 pixel[2] = pixVal;
409 }
410
GlareTextureEval(float u,float v,float,unsigned char * pixel)411 static void GlareTextureEval(float u, float v, float,
412 unsigned char *pixel)
413 {
414 float r = 0.9f - (float) sqrt(u * u + v * v);
415 if (r < 0)
416 r = 0;
417
418 int pixVal = (int) (r * 255.99f);
419 pixel[0] = 65;
420 pixel[1] = 64;
421 pixel[2] = 65;
422 pixel[3] = pixVal;
423 }
424
ShadowTextureEval(float u,float v,float,unsigned char * pixel)425 static void ShadowTextureEval(float u, float v, float,
426 unsigned char *pixel)
427 {
428 float r = (float) sqrt(u * u + v * v);
429
430 // Leave some white pixels around the edges to the shadow doesn't
431 // 'leak'. We'll also set the maximum mip map level for this texture to 3
432 // so we don't have problems with the edge texels at high mip map levels.
433 int pixVal = r < 15.0f / 16.0f ? 0 : 255;
434 pixel[0] = pixVal;
435 pixel[1] = pixVal;
436 pixel[2] = pixVal;
437 }
438
439
440 //! Lookup function for eclipse penumbras--the input is the amount of overlap
441 // between the occluder and sun disc, and the output is the fraction of
442 // full brightness.
PenumbraFunctionEval(float u,float,float,unsigned char * pixel)443 static void PenumbraFunctionEval(float u, float, float,
444 unsigned char *pixel)
445 {
446 u = (u + 1.0f) * 0.5f;
447
448 // Using the cube root produces a good visual result
449 unsigned char pixVal = (unsigned char) (::pow((double) u, 0.33) * 255.99);
450
451 pixel[0] = pixVal;
452 }
453
454
455 // ShadowTextureFunction is a function object for creating shadow textures
456 // used for rendering eclipses.
457 class ShadowTextureFunction : public TexelFunctionObject
458 {
459 public:
ShadowTextureFunction(float _umbra)460 ShadowTextureFunction(float _umbra) : umbra(_umbra) {};
461 virtual void operator()(float u, float v, float w, unsigned char* pixel);
462 float umbra;
463 };
464
operator ()(float u,float v,float,unsigned char * pixel)465 void ShadowTextureFunction::operator()(float u, float v, float,
466 unsigned char* pixel)
467 {
468 float r = (float) sqrt(u * u + v * v);
469 int pixVal = 255;
470
471 // Leave some white pixels around the edges to the shadow doesn't
472 // 'leak'. We'll also set the maximum mip map level for this texture to 3
473 // so we don't have problems with the edge texels at high mip map levels.
474 r = r / (15.0f / 16.0f);
475 if (r < 1)
476 {
477 // The pixel value should depend on the area of the sun which is
478 // occluded. We just fudge it here and use the square root of the
479 // radius.
480 if (r <= umbra)
481 pixVal = 0;
482 else
483 pixVal = (int) (sqrt((r - umbra) / (1 - umbra)) * 255.99f);
484 }
485
486 pixel[0] = pixVal;
487 pixel[1] = pixVal;
488 pixel[2] = pixVal;
489 };
490
491
492 class ShadowMaskTextureFunction : public TexelFunctionObject
493 {
494 public:
ShadowMaskTextureFunction()495 ShadowMaskTextureFunction() {};
496 virtual void operator()(float u, float v, float w, unsigned char* pixel);
497 float dummy;
498 };
499
operator ()(float u,float,float,unsigned char * pixel)500 void ShadowMaskTextureFunction::operator()(float u, float, float,
501 unsigned char* pixel)
502 {
503 unsigned char a = u > 0.0f ? 255 : 0;
504 pixel[0] = a;
505 pixel[1] = a;
506 pixel[2] = a;
507 pixel[3] = a;
508 }
509
510
IllumMapEval(float x,float y,float z,unsigned char * pixel)511 static void IllumMapEval(float x, float y, float z,
512 unsigned char* pixel)
513 {
514 Vec3f v(x, y, z);
515
516 pixel[0] = 128 + (int) (127 * v.x);
517 pixel[1] = 128 + (int) (127 * v.y);
518 pixel[2] = 128 + (int) (127 * v.z);
519 }
520
521
522 #if 0
523 // Not used yet.
524
525 // The RectToSpherical map converts XYZ coordinates to UV coordinates
526 // via a cube map lookup. However, a lot of GPUs don't support linear
527 // interpolation of textures with > 8 bits per component, which is
528 // inadequate precision for storing texture coordinates. To work around
529 // this, we'll store the u and v texture coordinates with two 8 bit
530 // coordinates each: rg for u, ba for v. The coordinates are unpacked
531 // as: u = r * 255/256 + g * 1/255
532 // v = b * 255/256 + a * 1/255
533 // This gives an effective precision of 16 bits for each texture coordinate.
534 static void RectToSphericalMapEval(float x, float y, float z,
535 unsigned char* pixel)
536 {
537 // Compute spherical coodinates (r is always 1)
538 double phi = asin(y);
539 double theta = atan2(z, -x);
540
541 // Convert to texture coordinates
542 double u = (theta / PI + 1.0) * 0.5;
543 double v = (-phi / PI + 0.5);
544
545 // Pack texture coordinates in red/green and blue/alpha
546 // u = red + green/256
547 // v = blue* + alpha/256
548 uint16 rg = (uint16) (u * 65535.99);
549 uint16 ba = (uint16) (v * 65535.99);
550 pixel[0] = rg >> 8;
551 pixel[1] = rg & 0xff;
552 pixel[2] = ba >> 8;
553 pixel[3] = ba & 0xff;
554 }
555 #endif
556
557
BuildGaussianDiscMipLevel(unsigned char * mipPixels,unsigned int log2size,float fwhm,float power)558 static void BuildGaussianDiscMipLevel(unsigned char* mipPixels,
559 unsigned int log2size,
560 float fwhm,
561 float power)
562 {
563 unsigned int size = 1 << log2size;
564 float sigma = fwhm / 2.3548f;
565 float isig2 = 1.0f / (2.0f * sigma * sigma);
566 float s = 1.0f / (sigma * (float) sqrt(2.0 * PI));
567
568 for (unsigned int i = 0; i < size; i++)
569 {
570 float y = (float) i - size / 2;
571 for (unsigned int j = 0; j < size; j++)
572 {
573 float x = (float) j - size / 2;
574 float r2 = x * x + y * y;
575 float f = s * (float) exp(-r2 * isig2) * power;
576
577 mipPixels[i * size + j] = (unsigned char) (255.99f * min(f, 1.0f));
578 }
579 }
580 }
581
582
BuildGlareMipLevel(unsigned char * mipPixels,unsigned int log2size,float scale,float base)583 static void BuildGlareMipLevel(unsigned char* mipPixels,
584 unsigned int log2size,
585 float scale,
586 float base)
587 {
588 unsigned int size = 1 << log2size;
589
590 for (unsigned int i = 0; i < size; i++)
591 {
592 float y = (float) i - size / 2;
593 for (unsigned int j = 0; j < size; j++)
594 {
595 float x = (float) j - size / 2;
596 float r = (float) sqrt(x * x + y * y);
597 float f = (float) pow(base, r * scale);
598 mipPixels[i * size + j] = (unsigned char) (255.99f * min(f, 1.0f));
599 }
600 }
601 }
602
603
604 #if 0
605 // An alternate glare function, based roughly on results in Spencer, G. et al,
606 // 1995, "Physically-Based Glare Effects for Digital Images"
607 static void BuildGlareMipLevel2(unsigned char* mipPixels,
608 unsigned int log2size,
609 float scale)
610 {
611 unsigned int size = 1 << log2size;
612
613 for (unsigned int i = 0; i < size; i++)
614 {
615 float y = (float) i - size / 2;
616 for (unsigned int j = 0; j < size; j++)
617 {
618 float x = (float) j - size / 2;
619 float r = (float) sqrt(x * x + y * y);
620 float f = 0.3f / (0.3f + r * r * scale * scale * 100);
621 /*
622 if (i == 0 || j == 0 || i == size - 1 || j == size - 1)
623 f = 1.0f;
624 */
625 mipPixels[i * size + j] = (unsigned char) (255.99f * min(f, 1.0f));
626 }
627 }
628 }
629 #endif
630
631
BuildGaussianDiscTexture(unsigned int log2size)632 static Texture* BuildGaussianDiscTexture(unsigned int log2size)
633 {
634 unsigned int size = 1 << log2size;
635 Image* img = new Image(GL_LUMINANCE, size, size, log2size + 1);
636
637 for (unsigned int mipLevel = 0; mipLevel <= log2size; mipLevel++)
638 {
639 float fwhm = (float) pow(2.0f, (float) (log2size - mipLevel)) * 0.3f;
640 BuildGaussianDiscMipLevel(img->getMipLevel(mipLevel),
641 log2size - mipLevel,
642 fwhm,
643 (float) pow(2.0f, (float) (log2size - mipLevel)));
644 }
645
646 ImageTexture* texture = new ImageTexture(*img,
647 Texture::BorderClamp,
648 Texture::DefaultMipMaps);
649 texture->setBorderColor(Color(0.0f, 0.0f, 0.0f, 0.0f));
650
651 delete img;
652
653 return texture;
654 }
655
656
BuildGaussianGlareTexture(unsigned int log2size)657 static Texture* BuildGaussianGlareTexture(unsigned int log2size)
658 {
659 unsigned int size = 1 << log2size;
660 Image* img = new Image(GL_LUMINANCE, size, size, log2size + 1);
661
662 for (unsigned int mipLevel = 0; mipLevel <= log2size; mipLevel++)
663 {
664 /*
665 // Optional gaussian glare
666 float fwhm = (float) pow(2.0f, (float) (log2size - mipLevel)) * 0.15f;
667 float power = (float) pow(2.0f, (float) (log2size - mipLevel)) * 0.15f;
668 BuildGaussianDiscMipLevel(img->getMipLevel(mipLevel),
669 log2size - mipLevel,
670 fwhm,
671 power);
672 */
673 BuildGlareMipLevel(img->getMipLevel(mipLevel),
674 log2size - mipLevel,
675 25.0f / (float) pow(2.0f, (float) (log2size - mipLevel)),
676 0.66f);
677 /*
678 BuildGlareMipLevel2(img->getMipLevel(mipLevel),
679 log2size - mipLevel,
680 1.0f / (float) pow(2.0f, (float) (log2size - mipLevel)));
681 */
682 }
683
684 ImageTexture* texture = new ImageTexture(*img,
685 Texture::BorderClamp,
686 Texture::DefaultMipMaps);
687 texture->setBorderColor(Color(0.0f, 0.0f, 0.0f, 0.0f));
688
689 delete img;
690
691 return texture;
692 }
693
694
translateLabelModeToClassMask(int labelMode)695 static int translateLabelModeToClassMask(int labelMode)
696 {
697 int classMask = 0;
698
699 if (labelMode & Renderer::PlanetLabels)
700 classMask |= Body::Planet;
701 if (labelMode & Renderer::DwarfPlanetLabels)
702 classMask |= Body::DwarfPlanet;
703 if (labelMode & Renderer::MoonLabels)
704 classMask |= Body::Moon;
705 if (labelMode & Renderer::MinorMoonLabels)
706 classMask |= Body::MinorMoon;
707 if (labelMode & Renderer::AsteroidLabels)
708 classMask |= Body::Asteroid;
709 if (labelMode & Renderer::CometLabels)
710 classMask |= Body::Comet;
711 if (labelMode & Renderer::SpacecraftLabels)
712 classMask |= Body::Spacecraft;
713
714 return classMask;
715 }
716
717
718 // Depth comparison function for render list entries
operator <(const RenderListEntry & a,const RenderListEntry & b)719 bool operator<(const RenderListEntry& a, const RenderListEntry& b)
720 {
721 // Operation is reversed because -z axis points into the screen
722 return a.centerZ - a.radius > b.centerZ - b.radius;
723 }
724
725
726 // Depth comparison for labels
727 // Note that it's essential to declare this operator as a member
728 // function of Renderer::Label; if it's not a class member, C++'s
729 // argument dependent lookup will not find the operator when it's
730 // used as a predicate for STL algorithms.
operator <(const Annotation & a) const731 bool Renderer::Annotation::operator<(const Annotation& a) const
732 {
733 // Operation is reversed because -z axis points into the screen
734 return position.z > a.position.z;
735 }
736
737 // Depth comparison for orbit paths
operator <(const Renderer::OrbitPathListEntry & o) const738 bool Renderer::OrbitPathListEntry::operator<(const Renderer::OrbitPathListEntry& o) const
739 {
740 // Operation is reversed because -z axis points into the screen
741 return centerZ - radius > o.centerZ - o.radius;
742 }
743
744
init(GLContext * _context,int winWidth,int winHeight,DetailOptions & _detailOptions)745 bool Renderer::init(GLContext* _context,
746 int winWidth, int winHeight,
747 DetailOptions& _detailOptions)
748 {
749 context = _context;
750 detailOptions = _detailOptions;
751
752 // Initialize static meshes and textures common to all instances of Renderer
753 if (!commonDataInitialized)
754 {
755 g_lodSphere = new LODSphereMesh();
756
757 starTex = CreateProceduralTexture(64, 64, GL_RGB, StarTextureEval);
758
759 glareTex = LoadTextureFromFile("textures/flare.jpg");
760 if (glareTex == NULL)
761 glareTex = CreateProceduralTexture(64, 64, GL_RGB, GlareTextureEval);
762
763 // Max mipmap level doesn't work reliably on all graphics
764 // cards. In particular, Rage 128 and TNT cards resort to software
765 // rendering when this feature is enabled. The only workaround is to
766 // disable mipmapping completely unless texture border clamping is
767 // supported, which solves the problem much more elegantly than all
768 // the mipmap level nonsense.
769 // shadowTex->setMaxMipMapLevel(3);
770 Texture::AddressMode shadowTexAddress = Texture::EdgeClamp;
771 Texture::MipMapMode shadowTexMip = Texture::NoMipMaps;
772 useClampToBorder = context->extensionSupported("GL_ARB_texture_border_clamp");
773 if (useClampToBorder)
774 {
775 shadowTexAddress = Texture::BorderClamp;
776 shadowTexMip = Texture::DefaultMipMaps;
777 }
778
779 shadowTex = CreateProceduralTexture(detailOptions.shadowTextureSize,
780 detailOptions.shadowTextureSize,
781 GL_RGB,
782 ShadowTextureEval,
783 shadowTexAddress, shadowTexMip);
784 shadowTex->setBorderColor(Color::White);
785
786 if (gaussianDiscTex == NULL)
787 gaussianDiscTex = BuildGaussianDiscTexture(8);
788 if (gaussianGlareTex == NULL)
789 gaussianGlareTex = BuildGaussianGlareTexture(9);
790
791 // Create the eclipse shadow textures
792 {
793 for (int i = 0; i < 4; i++)
794 {
795 ShadowTextureFunction func(i * 0.25f);
796 eclipseShadowTextures[i] =
797 CreateProceduralTexture(detailOptions.eclipseTextureSize,
798 detailOptions.eclipseTextureSize,
799 GL_RGB, func,
800 shadowTexAddress, shadowTexMip);
801 if (eclipseShadowTextures[i] != NULL)
802 {
803 // eclipseShadowTextures[i]->setMaxMipMapLevel(2);
804 eclipseShadowTextures[i]->setBorderColor(Color::White);
805 }
806 }
807 }
808
809 // Create the shadow mask texture
810 {
811 ShadowMaskTextureFunction func;
812 shadowMaskTexture = CreateProceduralTexture(128, 2, GL_RGBA, func);
813 //shadowMaskTexture->bindName();
814 }
815
816 // Create a function lookup table in a texture for use with
817 // fragment program eclipse shadows.
818 penumbraFunctionTexture = CreateProceduralTexture(512, 1, GL_LUMINANCE,
819 PenumbraFunctionEval,
820 Texture::EdgeClamp);
821
822 if (context->extensionSupported("GL_ARB_texture_cube_map"))
823 {
824 normalizationTex = CreateProceduralCubeMap(64, GL_RGB, IllumMapEval);
825 #if ADVANCED_CLOUD_SHADOWS
826 rectToSphericalTexture = CreateProceduralCubeMap(128, GL_RGBA, RectToSphericalMapEval);
827 #endif
828 }
829
830 #ifdef USE_HDR
831 genSceneTexture();
832 genBlurTextures();
833 #endif
834 commonDataInitialized = true;
835 }
836
837 #if 0
838 if (context->extensionSupported("GL_ARB_multisample"))
839 {
840 int nSamples = 0;
841 int sampleBuffers = 0;
842 int enabled = (int) glIsEnabled(GL_MULTISAMPLE_ARB);
843 glGetIntegerv(GL_SAMPLE_BUFFERS_ARB, &sampleBuffers);
844 glGetIntegerv(GL_SAMPLES_ARB, &nSamples);
845 clog << "AA samples: " << nSamples
846 << ", enabled=" << (int) enabled
847 << ", sample buffers=" << (sampleBuffers)
848 << "\n";
849 glEnable(GL_MULTISAMPLE_ARB);
850 }
851 #endif
852
853 if (context->extensionSupported("GL_EXT_rescale_normal"))
854 {
855 // We need this enabled because we use glScale, but only
856 // with uniform scale factors.
857 DPRINTF(1, "Renderer: EXT_rescale_normal supported.\n");
858 useRescaleNormal = true;
859 glEnable(GL_RESCALE_NORMAL_EXT);
860 }
861
862 if (context->extensionSupported("GL_ARB_point_sprite"))
863 {
864 DPRINTF(1, "Renderer: point sprites supported.\n");
865 usePointSprite = true;
866 }
867
868 if (context->extensionSupported("GL_EXT_separate_specular_color"))
869 {
870 glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL_EXT, GL_SEPARATE_SPECULAR_COLOR_EXT);
871 }
872
873 // Ugly renderer-specific bug workarounds follow . . .
874 char* glRenderer = (char*) glGetString(GL_RENDERER);
875 if (glRenderer != NULL)
876 {
877 // Fog is broken with vertex program emulation in most versions of
878 // the GF 1 and 2 drivers; we need to detect this and disable
879 // vertex programs which output fog coordinates
880 if (strstr(glRenderer, "GeForce3") != NULL ||
881 strstr(glRenderer, "GeForce4") != NULL)
882 {
883 buggyVertexProgramEmulation = false;
884 }
885
886 if (strstr(glRenderer, "Savage4") != NULL ||
887 strstr(glRenderer, "ProSavage") != NULL)
888 {
889 // S3 Savage4 drivers appear to rescale normals without reporting
890 // EXT_rescale_normal. Lighting will be messed up unless
891 // we set the useRescaleNormal flag.
892 useRescaleNormal = true;
893 }
894 #ifdef TARGET_OS_MAC
895 if (strstr(glRenderer, "ATI") != NULL ||
896 strstr(glRenderer, "GMA 900") != NULL)
897 {
898 // Some drivers on the Mac appear to limit point sprite size.
899 // This causes an abrupt size transition when going from billboards
900 // to sprites. Rather than incur overhead accounting for the size limit,
901 // do not use sprites on these renderers.
902 // Affected cards: ATI (various), etc
903 // Renderer strings are not unique.
904 usePointSprite = false;
905 }
906 #endif
907 }
908
909 // More ugly hacks; according to Matt Craighead at NVIDIA, an NVIDIA
910 // OpenGL driver that reports version 1.3.1 or greater will have working
911 // fog in emulated vertex programs.
912 char* glVersion = (char*) glGetString(GL_VERSION);
913 if (glVersion != NULL)
914 {
915 int major = 0, minor = 0, extra = 0;
916 int nScanned = sscanf(glVersion, "%d.%d.%d", &major, &minor, &extra);
917
918 if (nScanned >= 2)
919 {
920 if (major > 1 || minor > 3 || (minor == 3 && extra >= 1))
921 buggyVertexProgramEmulation = false;
922 }
923 }
924
925 #ifdef USE_HDR
926 useBlendSubtract = context->extensionSupported("GL_EXT_blend_subtract");
927 Image *testImg = new Image(GL_LUMINANCE_ALPHA, 1, 1);
928 ImageTexture *testTex = new ImageTexture(*testImg,
929 Texture::EdgeClamp,
930 Texture::NoMipMaps);
931 delete testImg;
932 GLint actualTexFormat = 0;
933 glEnable(GL_TEXTURE_2D);
934 testTex->bind();
935 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &actualTexFormat);
936 glBindTexture(GL_TEXTURE_2D, 0);
937 glDisable(GL_TEXTURE_2D);
938 switch (actualTexFormat)
939 {
940 case 2:
941 case GL_LUMINANCE_ALPHA:
942 case GL_LUMINANCE4_ALPHA4:
943 case GL_LUMINANCE6_ALPHA2:
944 case GL_LUMINANCE8_ALPHA8:
945 case GL_LUMINANCE12_ALPHA4:
946 case GL_LUMINANCE12_ALPHA12:
947 case GL_LUMINANCE16_ALPHA16:
948 useLuminanceAlpha = true;
949 break;
950 default:
951 useLuminanceAlpha = false;
952 break;
953 }
954
955 blurFormat = useLuminanceAlpha ? GL_LUMINANCE_ALPHA : GL_RGBA;
956 delete testTex;
957 #endif
958
959 glLoadIdentity();
960
961 glEnable(GL_CULL_FACE);
962 glCullFace(GL_BACK);
963
964 glEnable(GL_COLOR_MATERIAL);
965 glEnable(GL_LIGHTING);
966 glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
967
968 // LEQUAL rather than LESS required for multipass rendering
969 glDepthFunc(GL_LEQUAL);
970
971 resize(winWidth, winHeight);
972
973 return true;
974 }
975
976
resize(int width,int height)977 void Renderer::resize(int width, int height)
978 {
979 #ifdef USE_HDR
980 if (width == windowWidth && height == windowHeight)
981 return;
982 #endif
983 windowWidth = width;
984 windowHeight = height;
985 cosViewConeAngle = computeCosViewConeAngle(fov, windowWidth, windowHeight);
986 // glViewport(windowWidth, windowHeight);
987
988 #ifdef USE_HDR
989 if (commonDataInitialized)
990 {
991 genSceneTexture();
992 genBlurTextures();
993 }
994 #endif
995 }
996
calcPixelSize(float fovY,float windowHeight)997 float Renderer::calcPixelSize(float fovY, float windowHeight)
998 {
999 return 2 * (float) tan(degToRad(fovY / 2.0)) / (float) windowHeight;
1000 }
1001
setFieldOfView(float _fov)1002 void Renderer::setFieldOfView(float _fov)
1003 {
1004 fov = _fov;
1005 corrFac = (0.12f * fov/FOV * fov/FOV + 1.0f);
1006 cosViewConeAngle = computeCosViewConeAngle(fov, windowWidth, windowHeight);
1007 }
1008
getScreenDpi() const1009 int Renderer::getScreenDpi() const
1010 {
1011 return screenDpi;
1012 }
1013
setScreenDpi(int _dpi)1014 void Renderer::setScreenDpi(int _dpi)
1015 {
1016 screenDpi = _dpi;
1017 }
1018
setFaintestAM45deg(float _faintestAutoMag45deg)1019 void Renderer::setFaintestAM45deg(float _faintestAutoMag45deg)
1020 {
1021 faintestAutoMag45deg = _faintestAutoMag45deg;
1022 markSettingsChanged();
1023 }
1024
getFaintestAM45deg() const1025 float Renderer::getFaintestAM45deg() const
1026 {
1027 return faintestAutoMag45deg;
1028 }
1029
getResolution() const1030 unsigned int Renderer::getResolution() const
1031 {
1032 return textureResolution;
1033 }
1034
1035
setResolution(unsigned int resolution)1036 void Renderer::setResolution(unsigned int resolution)
1037 {
1038 if (resolution < TEXTURE_RESOLUTION)
1039 textureResolution = resolution;
1040 markSettingsChanged();
1041 }
1042
1043
getFont(FontStyle fs) const1044 TextureFont* Renderer::getFont(FontStyle fs) const
1045 {
1046 return font[(int) fs];
1047 }
1048
setFont(FontStyle fs,TextureFont * txf)1049 void Renderer::setFont(FontStyle fs, TextureFont* txf)
1050 {
1051 font[(int) fs] = txf;
1052 markSettingsChanged();
1053 }
1054
setRenderMode(int _renderMode)1055 void Renderer::setRenderMode(int _renderMode)
1056 {
1057 renderMode = _renderMode;
1058 markSettingsChanged();
1059 }
1060
getRenderFlags() const1061 int Renderer::getRenderFlags() const
1062 {
1063 return renderFlags;
1064 }
1065
setRenderFlags(int _renderFlags)1066 void Renderer::setRenderFlags(int _renderFlags)
1067 {
1068 renderFlags = _renderFlags;
1069 markSettingsChanged();
1070 }
1071
getLabelMode() const1072 int Renderer::getLabelMode() const
1073 {
1074 return labelMode;
1075 }
1076
setLabelMode(int _labelMode)1077 void Renderer::setLabelMode(int _labelMode)
1078 {
1079 labelMode = _labelMode;
1080 markSettingsChanged();
1081 }
1082
getOrbitMask() const1083 int Renderer::getOrbitMask() const
1084 {
1085 return orbitMask;
1086 }
1087
setOrbitMask(int mask)1088 void Renderer::setOrbitMask(int mask)
1089 {
1090 orbitMask = mask;
1091 markSettingsChanged();
1092 }
1093
1094
1095 const ColorTemperatureTable*
getStarColorTable() const1096 Renderer::getStarColorTable() const
1097 {
1098 return colorTemp;
1099 }
1100
1101
1102 void
setStarColorTable(const ColorTemperatureTable * ct)1103 Renderer::setStarColorTable(const ColorTemperatureTable* ct)
1104 {
1105 colorTemp = ct;
1106 markSettingsChanged();
1107 }
1108
1109
getVideoSync() const1110 bool Renderer::getVideoSync() const
1111 {
1112 return videoSync;
1113 }
1114
setVideoSync(bool sync)1115 void Renderer::setVideoSync(bool sync)
1116 {
1117 videoSync = sync;
1118 markSettingsChanged();
1119 }
1120
1121
getAmbientLightLevel() const1122 float Renderer::getAmbientLightLevel() const
1123 {
1124 return ambientLightLevel;
1125 }
1126
1127
setAmbientLightLevel(float level)1128 void Renderer::setAmbientLightLevel(float level)
1129 {
1130 ambientLightLevel = level;
1131 markSettingsChanged();
1132 }
1133
1134
getMinimumFeatureSize() const1135 float Renderer::getMinimumFeatureSize() const
1136 {
1137 return minFeatureSize;
1138 }
1139
1140
setMinimumFeatureSize(float pixels)1141 void Renderer::setMinimumFeatureSize(float pixels)
1142 {
1143 minFeatureSize = pixels;
1144 markSettingsChanged();
1145 }
1146
1147
getMinimumOrbitSize() const1148 float Renderer::getMinimumOrbitSize() const
1149 {
1150 return minOrbitSize;
1151 }
1152
1153 // Orbits and labels are only rendered when the orbit of the object
1154 // occupies some minimum number of pixels on screen.
setMinimumOrbitSize(float pixels)1155 void Renderer::setMinimumOrbitSize(float pixels)
1156 {
1157 minOrbitSize = pixels;
1158 markSettingsChanged();
1159 }
1160
1161
getDistanceLimit() const1162 float Renderer::getDistanceLimit() const
1163 {
1164 return distanceLimit;
1165 }
1166
1167
setDistanceLimit(float distanceLimit_)1168 void Renderer::setDistanceLimit(float distanceLimit_)
1169 {
1170 distanceLimit = distanceLimit_;
1171 markSettingsChanged();
1172 }
1173
1174
getFragmentShaderEnabled() const1175 bool Renderer::getFragmentShaderEnabled() const
1176 {
1177 return fragmentShaderEnabled;
1178 }
1179
setFragmentShaderEnabled(bool enable)1180 void Renderer::setFragmentShaderEnabled(bool enable)
1181 {
1182 fragmentShaderEnabled = enable && fragmentShaderSupported();
1183 markSettingsChanged();
1184 }
1185
fragmentShaderSupported() const1186 bool Renderer::fragmentShaderSupported() const
1187 {
1188 return context->bumpMappingSupported();
1189 }
1190
getVertexShaderEnabled() const1191 bool Renderer::getVertexShaderEnabled() const
1192 {
1193 return vertexShaderEnabled;
1194 }
1195
setVertexShaderEnabled(bool enable)1196 void Renderer::setVertexShaderEnabled(bool enable)
1197 {
1198 vertexShaderEnabled = enable && vertexShaderSupported();
1199 markSettingsChanged();
1200 }
1201
vertexShaderSupported() const1202 bool Renderer::vertexShaderSupported() const
1203 {
1204 return useVertexPrograms;
1205 }
1206
1207
addAnnotation(vector<Annotation> & annotations,const MarkerRepresentation * markerRep,const string & labelText,Color color,const Point3f & pos,LabelAlignment halign,LabelVerticalAlignment valign,float size)1208 void Renderer::addAnnotation(vector<Annotation>& annotations,
1209 const MarkerRepresentation* markerRep,
1210 const string& labelText,
1211 Color color,
1212 const Point3f& pos,
1213 LabelAlignment halign,
1214 LabelVerticalAlignment valign,
1215 float size)
1216 {
1217 double winX, winY, winZ;
1218 int view[4] = { 0, 0, 0, 0 };
1219 view[0] = -windowWidth / 2;
1220 view[1] = -windowHeight / 2;
1221 view[2] = windowWidth;
1222 view[3] = windowHeight;
1223 float depth = (float) (pos.x * modelMatrix[2] +
1224 pos.y * modelMatrix[6] +
1225 pos.z * modelMatrix[10]);
1226 if (gluProject(pos.x, pos.y, pos.z,
1227 modelMatrix,
1228 projMatrix,
1229 (const GLint*) view,
1230 &winX, &winY, &winZ) != GL_FALSE)
1231 {
1232 Annotation a;
1233
1234 a.labelText[0] = '\0';
1235 ReplaceGreekLetterAbbr(a.labelText, MaxLabelLength, labelText.c_str(), labelText.length());
1236 a.labelText[MaxLabelLength - 1] = '\0';
1237
1238 a.markerRep = markerRep;
1239 a.color = color;
1240 a.position = Point3f((float) winX, (float) winY, -depth);
1241 a.halign = halign;
1242 a.valign = valign;
1243 a.size = size;
1244 annotations.push_back(a);
1245 }
1246 }
1247
1248
addForegroundAnnotation(const MarkerRepresentation * markerRep,const string & labelText,Color color,const Point3f & pos,LabelAlignment halign,LabelVerticalAlignment valign,float size)1249 void Renderer::addForegroundAnnotation(const MarkerRepresentation* markerRep,
1250 const string& labelText,
1251 Color color,
1252 const Point3f& pos,
1253 LabelAlignment halign,
1254 LabelVerticalAlignment valign,
1255 float size)
1256 {
1257 addAnnotation(foregroundAnnotations, markerRep, labelText, color, pos, halign, valign, size);
1258 }
1259
1260
addBackgroundAnnotation(const MarkerRepresentation * markerRep,const string & labelText,Color color,const Point3f & pos,LabelAlignment halign,LabelVerticalAlignment valign,float size)1261 void Renderer::addBackgroundAnnotation(const MarkerRepresentation* markerRep,
1262 const string& labelText,
1263 Color color,
1264 const Point3f& pos,
1265 LabelAlignment halign,
1266 LabelVerticalAlignment valign,
1267 float size)
1268 {
1269 addAnnotation(backgroundAnnotations, markerRep, labelText, color, pos, halign, valign, size);
1270 }
1271
1272
addSortedAnnotation(const MarkerRepresentation * markerRep,const string & labelText,Color color,const Point3f & pos,LabelAlignment halign,LabelVerticalAlignment valign,float size)1273 void Renderer::addSortedAnnotation(const MarkerRepresentation* markerRep,
1274 const string& labelText,
1275 Color color,
1276 const Point3f& pos,
1277 LabelAlignment halign,
1278 LabelVerticalAlignment valign,
1279 float size)
1280 {
1281 double winX, winY, winZ;
1282 int view[4] = { 0, 0, 0, 0 };
1283 view[0] = -windowWidth / 2;
1284 view[1] = -windowHeight / 2;
1285 view[2] = windowWidth;
1286 view[3] = windowHeight;
1287 float depth = (float) (pos.x * modelMatrix[2] +
1288 pos.y * modelMatrix[6] +
1289 pos.z * modelMatrix[10]);
1290 if (gluProject(pos.x, pos.y, pos.z,
1291 modelMatrix,
1292 projMatrix,
1293 (const GLint*) view,
1294 &winX, &winY, &winZ) != GL_FALSE)
1295 {
1296 Annotation a;
1297
1298 a.labelText[0] = '\0';
1299 if (markerRep == NULL)
1300 {
1301 //l.text = ReplaceGreekLetterAbbr(_(text.c_str()));
1302 strncpy(a.labelText, labelText.c_str(), MaxLabelLength);
1303 a.labelText[MaxLabelLength - 1] = '\0';
1304 }
1305 a.markerRep = markerRep;
1306 a.color = color;
1307 a.position = Point3f((float) winX, (float) winY, -depth);
1308 a.halign = halign;
1309 a.valign = valign;
1310 a.size = size;
1311 depthSortedAnnotations.push_back(a);
1312 }
1313 }
1314
1315
clearAnnotations(vector<Annotation> & annotations)1316 void Renderer::clearAnnotations(vector<Annotation>& annotations)
1317 {
1318 annotations.clear();
1319 }
1320
1321
clearSortedAnnotations()1322 void Renderer::clearSortedAnnotations()
1323 {
1324 depthSortedAnnotations.clear();
1325 }
1326
1327
1328 // Return the orientation of the camera used to render the current
1329 // frame. Available only while rendering a frame.
getCameraOrientation() const1330 Quatf Renderer::getCameraOrientation() const
1331 {
1332 return m_cameraOrientation;
1333 }
1334
1335
getNearPlaneDistance() const1336 float Renderer::getNearPlaneDistance() const
1337 {
1338 return depthPartitions[currentIntervalIndex].nearZ;
1339 }
1340
1341
beginObjectAnnotations()1342 void Renderer::beginObjectAnnotations()
1343 {
1344 // It's an error to call beginObjectAnnotations a second time
1345 // without first calling end.
1346 assert(!objectAnnotationSetOpen);
1347 assert(objectAnnotations.empty());
1348
1349 objectAnnotations.clear();
1350 objectAnnotationSetOpen = true;
1351 }
1352
1353
endObjectAnnotations()1354 void Renderer::endObjectAnnotations()
1355 {
1356 objectAnnotationSetOpen = false;
1357
1358 if (!objectAnnotations.empty())
1359 {
1360 renderAnnotations(objectAnnotations.begin(),
1361 objectAnnotations.end(),
1362 -depthPartitions[currentIntervalIndex].nearZ,
1363 -depthPartitions[currentIntervalIndex].farZ,
1364 FontNormal);
1365
1366 objectAnnotations.clear();
1367 }
1368 }
1369
1370
addObjectAnnotation(const MarkerRepresentation * markerRep,const string & labelText,Color color,const Point3f & pos)1371 void Renderer::addObjectAnnotation(const MarkerRepresentation* markerRep,
1372 const string& labelText,
1373 Color color,
1374 const Point3f& pos)
1375 {
1376 assert(objectAnnotationSetOpen);
1377 if (objectAnnotationSetOpen)
1378 {
1379 double winX, winY, winZ;
1380 int view[4] = { 0, 0, 0, 0 };
1381 view[0] = -windowWidth / 2;
1382 view[1] = -windowHeight / 2;
1383 view[2] = windowWidth;
1384 view[3] = windowHeight;
1385 float depth = (float) (pos.x * modelMatrix[2] +
1386 pos.y * modelMatrix[6] +
1387 pos.z * modelMatrix[10]);
1388 if (gluProject(pos.x, pos.y, pos.z,
1389 modelMatrix,
1390 projMatrix,
1391 (const GLint*) view,
1392 &winX, &winY, &winZ) != GL_FALSE)
1393 {
1394
1395 Annotation a;
1396
1397 a.labelText[0] = '\0';
1398 if (!labelText.empty())
1399 {
1400 strncpy(a.labelText, labelText.c_str(), MaxLabelLength);
1401 a.labelText[MaxLabelLength - 1] = '\0';
1402 }
1403 a.markerRep = markerRep;
1404 a.color = color;
1405 a.position = Point3f((float) winX, (float) winY, -depth);
1406 a.size = 0.0f;
1407
1408 objectAnnotations.push_back(a);
1409 }
1410 }
1411 }
1412
1413
enableSmoothLines()1414 static void enableSmoothLines()
1415 {
1416 // glEnable(GL_BLEND);
1417 #ifdef USE_HDR
1418 glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
1419 #else
1420 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1421 #endif
1422 glEnable(GL_LINE_SMOOTH);
1423 glLineWidth(1.5f);
1424 }
1425
disableSmoothLines()1426 static void disableSmoothLines()
1427 {
1428 // glDisable(GL_BLEND);
1429 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1430 glDisable(GL_LINE_SMOOTH);
1431 glLineWidth(1.0f);
1432 }
1433
1434
1435 class OrbitSampler : public OrbitSampleProc
1436 {
1437 public:
1438 vector<Renderer::OrbitSample>* samples;
1439
OrbitSampler(vector<Renderer::OrbitSample> * _samples)1440 OrbitSampler(vector<Renderer::OrbitSample>* _samples) : samples(_samples) {};
sample(double t,const Point3d & p)1441 void sample(double t, const Point3d& p)
1442 {
1443 Renderer::OrbitSample samp;
1444 samp.pos = p;
1445 samp.t = t;
1446 samples->push_back(samp);
1447 };
1448 };
1449
1450
renderOrbitColor(const Body * body,bool selected,float opacity)1451 void renderOrbitColor(const Body *body, bool selected, float opacity)
1452 {
1453 Color orbitColor;
1454
1455 if (selected)
1456 {
1457 // Highlight the orbit of the selected object in red
1458 orbitColor = Renderer::SelectionOrbitColor;
1459 }
1460 else if (body != NULL && body->isOrbitColorOverridden())
1461 {
1462 orbitColor = body->getOrbitColor();
1463 }
1464 else
1465 {
1466 int classification;
1467 if (body != NULL)
1468 classification = body->getOrbitClassification();
1469 else
1470 classification = Body::Stellar;
1471
1472 switch (classification)
1473 {
1474 case Body::Moon:
1475 orbitColor = Renderer::MoonOrbitColor;
1476 break;
1477 case Body::MinorMoon:
1478 orbitColor = Renderer::MinorMoonOrbitColor;
1479 break;
1480 case Body::Asteroid:
1481 orbitColor = Renderer::AsteroidOrbitColor;
1482 break;
1483 case Body::Comet:
1484 orbitColor = Renderer::CometOrbitColor;
1485 break;
1486 case Body::Spacecraft:
1487 orbitColor = Renderer::SpacecraftOrbitColor;
1488 break;
1489 case Body::Stellar:
1490 orbitColor = Renderer::StarOrbitColor;
1491 break;
1492 case Body::DwarfPlanet:
1493 orbitColor = Renderer::DwarfPlanetOrbitColor;
1494 break;
1495 case Body::Planet:
1496 default:
1497 orbitColor = Renderer::PlanetOrbitColor;
1498 break;
1499 }
1500 }
1501
1502 #ifdef USE_HDR
1503 glColor(orbitColor, 1.f - opacity * orbitColor.alpha());
1504 #else
1505 glColor(orbitColor, opacity * orbitColor.alpha());
1506 #endif
1507 }
1508
1509
1510 // Subdivide the orbit into sections and compute a bounding volume for each section. The bounding
1511 // volumes used are capsules, the set of all points less than some constant distance from a line
1512 // segment.
computeOrbitSectionBoundingVolumes(Renderer::CachedOrbit & orbit)1513 static void computeOrbitSectionBoundingVolumes(Renderer::CachedOrbit& orbit)
1514 {
1515 const unsigned int MinOrbitSections = 6;
1516 const unsigned int MinSamplesPerSection = 32;
1517
1518 // Determine the number of trajectory samples to include in each bounding volume; typically,
1519 // the last volume will contain any leftover samples.
1520 unsigned int nSections = max((unsigned int) orbit.trajectory.size() / MinSamplesPerSection, MinOrbitSections);
1521 unsigned int samplesPerSection = orbit.trajectory.size() / nSections;
1522 if (samplesPerSection <= 1)
1523 {
1524 if (orbit.trajectory.size() == 0)
1525 nSections = 0;
1526 else
1527 nSections = 1;
1528 }
1529
1530 for (unsigned int i = 0; i < nSections; i++)
1531 {
1532 unsigned int nSamples;
1533 if (i != nSections - 1)
1534 nSamples = samplesPerSection;
1535 else
1536 nSamples = orbit.trajectory.size() - (nSections - 1) * samplesPerSection;
1537
1538 Renderer::OrbitSection section;
1539 section.firstSample = samplesPerSection * i;
1540 unsigned int lastSample = min((unsigned int) orbit.trajectory.size() - 1, section.firstSample + nSamples + 1);
1541
1542 // Set the initial axis and origin of the capsule bounding volume; they will be adjusted
1543 // to contain all points in the trajectory. The length of the axis may change, but the
1544 // direction will remain the same.
1545 Vec3d axis = orbit.trajectory[section.firstSample].pos - orbit.trajectory[lastSample].pos;
1546 Point3d orig = orbit.trajectory[section.firstSample].pos;
1547 double d = 1.0 / (axis * axis);
1548 double minT = 0.0;
1549 double maxT = 0.0;
1550 double maxDistSquared = 0.0;
1551
1552 for (unsigned int j = section.firstSample; j <= lastSample; j++)
1553 {
1554 Point3d p = orbit.trajectory[j].pos;
1555 double t = ((p - orig) * axis) * d;
1556 Vec3d pointToAxis = p - (orig + axis * t);
1557 double distSquared = pointToAxis * pointToAxis;
1558 if (t < minT)
1559 minT = t;
1560 if (t > maxT)
1561 maxT = t;
1562 if (distSquared > maxDistSquared)
1563 maxDistSquared = distSquared;
1564 }
1565
1566 section.boundingVolume.origin = orig + axis * minT;
1567 section.boundingVolume.axis = axis * (maxT - minT);
1568
1569 // Make the bounding volume a bit thicker to avoid roundoff problems, and
1570 // to account cases when interpolation adds points slightly outside the
1571 // volume defined by the sampled points.
1572 section.boundingVolume.radius = sqrt(maxDistSquared) * 1.1f;;
1573
1574 orbit.sections.push_back(section);
1575 }
1576 }
1577
1578
cubicInterpolate(const Point3d & p0,const Vec3d & v0,const Point3d & p1,const Vec3d & v1,double t)1579 static Point3d cubicInterpolate(const Point3d& p0, const Vec3d& v0,
1580 const Point3d& p1, const Vec3d& v1,
1581 double t)
1582 {
1583 return p0 + (((2.0 * (p0 - p1) + v1 + v0) * (t * t * t)) +
1584 ((3.0 * (p1 - p0) - 2.0 * v0 - v1) * (t * t)) +
1585 (v0 * t));
1586 }
1587
1588
1589 static int splinesRendered = 0;
1590 static int orbitsRendered = 0;
1591 static int orbitsSkipped = 0;
1592 static int sectionsCulled = 0;
renderOrbitSplineSegment(const Renderer::OrbitSample & p0,const Renderer::OrbitSample & p1,const Renderer::OrbitSample & p2,const Renderer::OrbitSample & p3,double nearZ,double farZ,unsigned int subdivisions,int lastOutcode,bool drawLastSegment)1593 static Point3d renderOrbitSplineSegment(const Renderer::OrbitSample& p0,
1594 const Renderer::OrbitSample& p1,
1595 const Renderer::OrbitSample& p2,
1596 const Renderer::OrbitSample& p3,
1597 double nearZ,
1598 double farZ,
1599 unsigned int subdivisions,
1600 int lastOutcode,
1601 bool drawLastSegment)
1602 {
1603 Vec3d v0 = (p2.pos - p0.pos) * ((p2.t - p1.t) / (p2.t - p0.t));
1604 Vec3d v1 = (p3.pos - p1.pos) * ((p2.t - p1.t) / (p3.t - p1.t));
1605 double dt = 1.0 / (double) subdivisions;
1606 if (drawLastSegment)
1607 subdivisions++;
1608
1609 splinesRendered++;
1610
1611 Point3d lastP = p1.pos;
1612
1613 for (unsigned int i = 0; i < subdivisions; i++)
1614 {
1615 Point3d p = cubicInterpolate(p1.pos, v0, p2.pos, v1, i * dt);
1616 int outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
1617
1618 if ((outcode | lastOutcode) == 0)
1619 {
1620 glVertex3d(p.x, p.y, p.z);
1621 }
1622 else if ((outcode & lastOutcode) == 0)
1623 {
1624 // Need to clip
1625 Point3d q0 = lastP;
1626 Point3d q1 = p;
1627
1628 if (lastOutcode != 0)
1629 {
1630 glBegin(GL_LINE_STRIP);
1631 double t;
1632 if (lastOutcode == 1)
1633 t = (nearZ - lastP.z) / (p.z - lastP.z);
1634 else
1635 t = (farZ - lastP.z) / (p.z - lastP.z);
1636 q0 = lastP + t * (p - lastP);
1637 }
1638
1639 if (outcode != 0)
1640 {
1641 double t;
1642 if (outcode == 1)
1643 t = (nearZ - lastP.z) / (p.z - lastP.z);
1644 else
1645 t = (farZ - lastP.z) / (p.z - lastP.z);
1646 q1 = lastP + t * (p - lastP);
1647 }
1648
1649 glVertex3d(q0.x, q0.y, q0.z);
1650 glVertex3d(q1.x, q1.y, q1.z);
1651
1652 if (outcode != 0)
1653 {
1654 glEnd();
1655 }
1656 }
1657
1658 lastOutcode = outcode;
1659 lastP = p;
1660 }
1661
1662 return lastP;
1663 }
1664
1665
1666 #if 0
1667 // Not yet used
1668 static Point3d renderOrbitSplineAdaptive(const Renderer::OrbitSample& p0,
1669 const Renderer::OrbitSample& p1,
1670 const Renderer::OrbitSample& p2,
1671 const Renderer::OrbitSample& p3,
1672 double nearZ,
1673 double farZ,
1674 unsigned int minSubdivisions,
1675 unsigned int maxSubdivisions,
1676 int lastOutcode,
1677 bool drawLastSegment)
1678 {
1679 Vec3d v0 = (p2.pos - p0.pos) * ((p2.t - p1.t) / (p2.t - p0.t));
1680 Vec3d v1 = (p3.pos - p1.pos) * ((p2.t - p1.t) / (p3.t - p1.t));
1681 double minDt = 1.0 / (double) maxSubdivisions;
1682 double maxDt = 1.0 / (double) minSubdivisions;
1683 double g = (p2.pos - p1.pos).length() * maxSubdivisions;
1684 double t = 0.0;
1685
1686 splinesRendered += 10000;
1687
1688 Point3d lastP = p1.pos;
1689
1690 while (t < 1.0)
1691 {
1692 t += max(minDt, max(lastP.distanceFromOrigin() / g, maxDt));
1693 if (drawLastSegment && t > 1.0)
1694 t = 1.0;
1695 else
1696 break;
1697
1698 Point3d p = cubicInterpolate(p1.pos, v0, p2.pos, v1, t);
1699 int outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
1700
1701 if ((outcode | lastOutcode) == 0)
1702 {
1703 glVertex3d(p.x, p.y, p.z);
1704 }
1705 else if ((outcode & lastOutcode) == 0)
1706 {
1707 // Need to clip
1708 Point3d q0 = lastP;
1709 Point3d q1 = p;
1710
1711 if (lastOutcode != 0)
1712 {
1713 glBegin(GL_LINE_STRIP);
1714 double t;
1715 if (lastOutcode == 1)
1716 t = (nearZ - lastP.z) / (p.z - lastP.z);
1717 else
1718 t = (farZ - lastP.z) / (p.z - lastP.z);
1719 q0 = lastP + t * (p - lastP);
1720 }
1721
1722 if (outcode != 0)
1723 {
1724 double t;
1725 if (outcode == 1)
1726 t = (nearZ - lastP.z) / (p.z - lastP.z);
1727 else
1728 t = (farZ - lastP.z) / (p.z - lastP.z);
1729 q1 = lastP + t * (p - lastP);
1730 }
1731
1732 glVertex3d(q0.x, q0.y, q0.z);
1733 glVertex3d(q1.x, q1.y, q1.z);
1734
1735 if (outcode != 0)
1736 {
1737 glEnd();
1738 }
1739 }
1740
1741 lastOutcode = outcode;
1742 lastP = p;
1743 }
1744
1745 return lastP;
1746 }
1747 #endif
1748
1749
renderOrbitSection(const Orbit & orbit,Renderer::CachedOrbit & cachedOrbit,unsigned int sectionNumber,const Mat4d & modelview,Point3d lastP,int lastOutcode,double nearZ,double farZ,uint32)1750 static Point3d renderOrbitSection(const Orbit& orbit,
1751 Renderer::CachedOrbit& cachedOrbit,
1752 unsigned int sectionNumber,
1753 const Mat4d& modelview,
1754 Point3d lastP,
1755 int lastOutcode,
1756 double nearZ,
1757 double farZ,
1758 uint32 /* renderFlags */)
1759 {
1760 vector<Renderer::OrbitSample>& trajectory(cachedOrbit.trajectory);
1761 unsigned int nPoints = cachedOrbit.trajectory.size();
1762
1763 unsigned int firstPoint = cachedOrbit.sections[sectionNumber].firstSample + 1;
1764 unsigned int lastPoint;
1765 if (sectionNumber != cachedOrbit.sections.size() - 1)
1766 lastPoint = cachedOrbit.sections[sectionNumber + 1].firstSample;
1767 else
1768 lastPoint = nPoints - 1;
1769
1770 double sectionRadius = cachedOrbit.sections[sectionNumber].boundingVolume.radius;
1771 double minSmoothZ = -sectionRadius * 8;
1772 double maxSmoothZ = nearZ + sectionRadius * 8;
1773
1774 for (unsigned int i = firstPoint; i <= lastPoint; i++)
1775 {
1776 Point3d p = trajectory[i].pos * modelview;
1777 int outcode;
1778
1779 // This segment of the orbit is very close to the camera and may appear
1780 // jagged. We'll add extra segments with cubic spline interpolation.
1781 // TODO: This calculation should depend on the field of view as well
1782 unsigned int splineSubdivisions = 0;
1783 if ((p.z > minSmoothZ || lastP.z > minSmoothZ) &&
1784 !(p.z > maxSmoothZ && lastP.z > maxSmoothZ))
1785 {
1786 double distFromEye = distanceToSegment(Point3d(0.0, 0.0, 0.0), lastP, p - lastP);
1787
1788 if (distFromEye < sectionRadius)
1789 splineSubdivisions = 64;
1790 else if (distFromEye < sectionRadius * 8)
1791 splineSubdivisions = (unsigned int) (sectionRadius / distFromEye * 64);
1792 }
1793
1794 if (splineSubdivisions > 1)
1795 {
1796 // Render this part of the orbit as a spline instead of a line segment
1797 Renderer::OrbitSample s0, s1, s2, s3;
1798 if (i > 1)
1799 {
1800 s0 = trajectory[i - 2];
1801 }
1802 else if (orbit.isPeriodic())
1803 {
1804 // Careful: use second to last sample, since first sample is duplicate of last
1805 s0 = trajectory[nPoints - 2];
1806 s0.t -= orbit.getPeriod();
1807 }
1808 else
1809 {
1810 s0 = trajectory[i - 1];
1811 }
1812
1813 if (i < trajectory.size() - 1)
1814 {
1815 s3 = trajectory[i + 1];
1816 }
1817 else if (orbit.isPeriodic())
1818 {
1819 // Careful: use second sample, since first sample is duplicate of last
1820 s3 = trajectory[1];
1821 s3.t += orbit.getPeriod();
1822 }
1823 else
1824 {
1825 s3 = trajectory[i];
1826 }
1827
1828 s1 = Renderer::OrbitSample(lastP, trajectory[i - 1].t);
1829 s2 = Renderer::OrbitSample(p, trajectory[i].t);
1830
1831 s0.pos = s0.pos * modelview;
1832 s3.pos = s3.pos * modelview;
1833
1834 bool drawLastSegment = i == nPoints - 1;
1835
1836 p = renderOrbitSplineSegment(s0, s1, s2, s3,
1837 nearZ, farZ,
1838 splineSubdivisions,
1839 lastOutcode,
1840 drawLastSegment);
1841 outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
1842 }
1843 else
1844 {
1845 // Just draw a line segment
1846
1847 // Compute the outcode mask for clipping:
1848 // 00 = between near and far
1849 // 01 = greater than nearZ (nearZ and farZ are always negative)
1850 // 10 = less than farZ
1851 // Given two outcodes o1 and o2 of line segment endpoints:
1852 // o1 | o2 == 0 means segment lies completely between near and far plans
1853 // o2 & o2 != 0 means segment lies completely outside planes
1854 // else we have to clip the line.
1855 outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
1856
1857 if ((outcode | lastOutcode) == 0)
1858 {
1859 // Segment is completely between near and far planes
1860 glVertex3d(p.x, p.y, p.z);
1861 }
1862 else if ((outcode & lastOutcode) == 0)
1863 {
1864 // Need to clip
1865 Point3d q0 = lastP;
1866 Point3d q1 = p;
1867
1868 // Clip against the enter plane
1869 if (lastOutcode != 0)
1870 {
1871 glBegin(GL_LINE_STRIP);
1872 double t;
1873 if (lastOutcode == 1)
1874 t = (nearZ - lastP.z) / (p.z - lastP.z);
1875 else
1876 t = (farZ - lastP.z) / (p.z - lastP.z);
1877 q0 = lastP + t * (p - lastP);
1878 }
1879
1880 // Clip against the exit plane
1881 if (outcode != 0)
1882 {
1883 double t;
1884 if (outcode == 1)
1885 t = (nearZ - lastP.z) / (p.z - lastP.z);
1886 else
1887 t = (farZ - lastP.z) / (p.z - lastP.z);
1888 q1 = lastP + t * (p - lastP);
1889 }
1890
1891 glVertex3d(q0.x, q0.y, q0.z);
1892 glVertex3d(q1.x, q1.y, q1.z);
1893
1894 if (outcode != 0)
1895 {
1896 glEnd();
1897 }
1898 }
1899 }
1900
1901 lastOutcode = outcode;
1902 lastP = p;
1903 }
1904
1905 return lastP;
1906 }
1907
1908
renderOrbit(const OrbitPathListEntry & orbitPath,double t,const Quatf & cameraOrientationf,const Frustum & frustum,float nearDist,float farDist)1909 void Renderer::renderOrbit(const OrbitPathListEntry& orbitPath,
1910 double t,
1911 const Quatf& cameraOrientationf,
1912 const Frustum& frustum,
1913 float nearDist,
1914 float farDist)
1915 {
1916 Body* body = orbitPath.body;
1917 Quatd cameraOrientation(cameraOrientationf.w, cameraOrientationf.x, cameraOrientationf.y, cameraOrientationf.z);
1918 double nearZ = -nearDist; // negate, becase z is into the screen in camera space
1919 double farZ = -farDist;
1920
1921 const Orbit* orbit = NULL;
1922 if (body != NULL)
1923 orbit = body->getOrbit(t);
1924 else
1925 orbit = orbitPath.star->getOrbit();
1926
1927 CachedOrbit* cachedOrbit = NULL;
1928 OrbitCache::iterator cached = orbitCache.find(orbit);
1929 if (cached != orbitCache.end())
1930 {
1931 cachedOrbit = cached->second;
1932 cachedOrbit->lastUsed = frameCount;
1933 }
1934
1935 // If it's not in the cache already
1936 if (cachedOrbit == NULL)
1937 {
1938 double startTime = t;
1939 int nSamples = detailOptions.orbitPathSamplePoints;
1940
1941 // Adjust the number of samples used for aperiodic orbits--these aren't
1942 // true orbits, but are sampled trajectories, generally of spacecraft.
1943 // Better control is really needed--some sort of adaptive sampling would
1944 // be ideal.
1945 if (!orbit->isPeriodic())
1946 {
1947 double begin = 0.0, end = 0.0;
1948 orbit->getValidRange(begin, end);
1949
1950 if (begin != end)
1951 {
1952 startTime = begin;
1953 nSamples = (int) (orbit->getPeriod() * 100.0);
1954 nSamples = max(min(nSamples, 1000), 100);
1955 }
1956 else
1957 {
1958 // If the orbit is aperiodic and doesn't have a
1959 // finite duration, we don't render it. A compromise
1960 // would be to pick some time window centered at the
1961 // current time, but we'd have to pick some arbitrary
1962 // duration.
1963 nSamples = 0;
1964 }
1965 }
1966
1967 cachedOrbit = new CachedOrbit();
1968 cachedOrbit->lastUsed = frameCount;
1969
1970 OrbitSampler sampler(&cachedOrbit->trajectory);
1971 orbit->sample(startTime,
1972 orbit->getPeriod(),
1973 nSamples,
1974 sampler);
1975
1976 // Add an extra sample to close a periodic orbit
1977 if (orbit->isPeriodic())
1978 {
1979 if (!cachedOrbit->trajectory.empty())
1980 {
1981 double lastSampleTime = cachedOrbit->trajectory[0].t + orbit->getPeriod();
1982 cachedOrbit->trajectory.push_back(OrbitSample(cachedOrbit->trajectory[0].pos, lastSampleTime));
1983 }
1984 }
1985
1986 computeOrbitSectionBoundingVolumes(*cachedOrbit);
1987
1988 // If the orbit cache is full, first try and eliminate some old orbits
1989 if (orbitCache.size() > OrbitCacheCullThreshold)
1990 {
1991 // Check for old orbits at most once per frame
1992 if (lastOrbitCacheFlush != frameCount)
1993 {
1994 for (OrbitCache::iterator iter = orbitCache.begin(); iter != orbitCache.end();)
1995 {
1996 // Tricky code to eliminate a node in the orbit cache without screwing
1997 // up the iterator. Should work in all STL implementations.
1998 if (frameCount - iter->second->lastUsed > OrbitCacheRetireAge)
1999 orbitCache.erase(iter++);
2000 else
2001 ++iter;
2002 }
2003 lastOrbitCacheFlush = frameCount;
2004 }
2005 }
2006
2007 orbitCache[orbit] = cachedOrbit;
2008 }
2009
2010 vector<OrbitSample>* trajectory = &cachedOrbit->trajectory;
2011
2012 // The rest of the function isn't designed for empty trajectories
2013 if (trajectory->empty())
2014 return;
2015
2016 // We perform vertex tranformations on the CPU because double precision is necessary to
2017 // render orbits properly. Start by computing the modelview matrix, to transform orbit
2018 // vertices into camera space.
2019 Mat4d modelview;
2020 {
2021 Quatd orientation(1.0);
2022 if (body != NULL)
2023 {
2024 orientation = body->getOrbitFrame(t)->getOrientation(t);
2025 }
2026
2027 // Equivalent to:
2028 // glRotate(cameraOrientation);
2029 // glTranslate(orbitPath.origin);
2030 // glRotate(~orientation);
2031 modelview =
2032 (orientation).toMatrix4() *
2033 Mat4d::translation(Point3d(orbitPath.origin.x, orbitPath.origin.y, orbitPath.origin.z)) *
2034 (~cameraOrientation).toMatrix4();
2035 }
2036
2037 glPushMatrix();
2038 glLoadIdentity();
2039
2040 bool highlight;
2041 if (body != NULL)
2042 highlight = highlightObject.body() == body;
2043 else
2044 highlight = highlightObject.star() == orbitPath.star;
2045 renderOrbitColor(body, highlight, orbitPath.opacity);
2046
2047 if ((renderFlags & ShowPartialTrajectories) == 0 || orbit->isPeriodic())
2048 {
2049 // Show the complete trajectory
2050 Point3d p;
2051 p = (*trajectory)[0].pos * modelview;
2052 int outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
2053
2054 if (outcode == 0)
2055 {
2056 glBegin(GL_LINE_STRIP);
2057 glVertex3d(p.x, p.y, p.z);
2058 }
2059
2060 Point3d lastP = p;
2061 int lastOutcode = outcode;
2062
2063 // The trajectory is subdivided into sections that each contain a number of samples.
2064 // Process each section in the trajectory, using its precomputed bounding volume to
2065 // quickly check for visibility.
2066 for (unsigned int i = 0; i < cachedOrbit->sections.size(); i++)
2067 {
2068 Capsuled& bv = cachedOrbit->sections[i].boundingVolume;
2069 Point3d orig = bv.origin * modelview;
2070 Vec3d axis = bv.axis * modelview;
2071 Capsulef bvf(Point3f((float) orig.x, (float) orig.y, (float) orig.z),
2072 Vec3f((float) axis.x, (float) axis.y, (float) axis.z),
2073 (float) bv.radius);
2074
2075 // TODO: Create a fast path for the case when the bounding volume lies completely
2076 // within the frustum and clipping can be ignored.
2077 if (frustum.testCapsule(bvf) != Frustum::Outside)
2078 {
2079 lastP = renderOrbitSection(*orbit,
2080 *cachedOrbit, i,
2081 modelview,
2082 lastP, lastOutcode,
2083 nearZ, farZ,
2084 renderFlags);
2085 lastOutcode = (lastP.z > nearZ ? 1 : 0) | (lastP.z < farZ ? 2 : 0);
2086 }
2087 else
2088 {
2089 // The section was culled because it lies completely outside the view frustum,
2090 // but we still need to do some work to keep the begin/end state of the line
2091 // strip current. We just need to process the final point in the section.
2092 unsigned int lastSample;
2093 if (i < cachedOrbit->sections.size() - 1)
2094 lastSample = cachedOrbit->sections[i + 1].firstSample;
2095 else
2096 lastSample = cachedOrbit->trajectory.size() - 1;
2097
2098 p = (*trajectory)[lastSample].pos * modelview;
2099 outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
2100
2101 if ((outcode | lastOutcode) == 0)
2102 {
2103 // Segment is completely between near and far planes
2104 glVertex3d(p.x, p.y, p.z);
2105 }
2106 else if ((outcode & lastOutcode) == 0)
2107 {
2108 // Need to clip
2109 Point3d q0 = lastP;
2110 Point3d q1 = p;
2111
2112 // Clip against the enter plane
2113 if (lastOutcode != 0)
2114 {
2115 glBegin(GL_LINE_STRIP);
2116 double t;
2117 if (lastOutcode == 1)
2118 t = (nearZ - lastP.z) / (p.z - lastP.z);
2119 else
2120 t = (farZ - lastP.z) / (p.z - lastP.z);
2121 q0 = lastP + t * (p - lastP);
2122 }
2123
2124 // Clip against the exit plane
2125 if (outcode != 0)
2126 {
2127 double t;
2128 if (outcode == 1)
2129 t = (nearZ - lastP.z) / (p.z - lastP.z);
2130 else
2131 t = (farZ - lastP.z) / (p.z - lastP.z);
2132 q1 = lastP + t * (p - lastP);
2133 }
2134
2135 glVertex3d(q0.x, q0.y, q0.z);
2136 glVertex3d(q1.x, q1.y, q1.z);
2137
2138 if (outcode != 0)
2139 {
2140 glEnd();
2141 }
2142 }
2143
2144 lastP = p;
2145 lastOutcode = outcode;
2146
2147 sectionsCulled++;
2148 }
2149 }
2150
2151 if (lastOutcode == 0)
2152 {
2153 glEnd();
2154 }
2155 }
2156 else
2157 {
2158 double endTime = t;
2159 bool endTimeReached = false;
2160
2161 Point3d p;
2162 p = (*trajectory)[0].pos * modelview;
2163 int outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
2164
2165 if (outcode == 0)
2166 {
2167 glBegin(GL_LINE_STRIP);
2168 glVertex3d(p.x, p.y, p.z);
2169 }
2170
2171 Point3d lastP = p;
2172 int lastOutcode = outcode;
2173
2174 unsigned int nPoints = trajectory->size();
2175 for (unsigned int i = 1; i < nPoints && !endTimeReached; i++)
2176 {
2177 if ((*trajectory)[i].t > endTime)
2178 {
2179 p = orbit->positionAtTime(endTime) * modelview;
2180 endTimeReached = true;
2181 }
2182 else
2183 {
2184 p = (*trajectory)[i].pos * modelview;
2185 }
2186
2187 outcode = (p.z > nearZ ? 1 : 0) | (p.z < farZ ? 2 : 0);
2188 if ((outcode | lastOutcode) == 0)
2189 {
2190 glVertex3d(p.x, p.y, p.z);
2191 }
2192 else if ((outcode & lastOutcode) == 0)
2193 {
2194 // Need to clip
2195
2196 Point3d p0 = lastP;
2197 Point3d p1 = p;
2198
2199 if (lastOutcode != 0)
2200 {
2201 glBegin(GL_LINE_STRIP);
2202 double t;
2203 if (lastOutcode == 1)
2204 t = (nearZ - lastP.z) / (p.z - lastP.z);
2205 else
2206 t = (farZ - lastP.z) / (p.z - lastP.z);
2207 p0 = lastP + t * (p - lastP);
2208 }
2209
2210 if (outcode != 0)
2211 {
2212 double t;
2213 if (outcode == 1)
2214 t = (nearZ - lastP.z) / (p.z - lastP.z);
2215 else
2216 t = (farZ - lastP.z) / (p.z - lastP.z);
2217 p1 = lastP + t * (p - lastP);
2218 }
2219
2220 glVertex3d(p0.x, p0.y, p0.z);
2221 glVertex3d(p1.x, p1.y, p1.z);
2222
2223 if (outcode != 0)
2224 {
2225 glEnd();
2226 }
2227 }
2228
2229 lastOutcode = outcode;
2230 lastP = p;
2231 }
2232
2233 if (lastOutcode == 0)
2234 {
2235 glEnd();
2236 }
2237 }
2238
2239 glPopMatrix();
2240 }
2241
2242
2243 // Convert a position in the universal coordinate system to astrocentric
2244 // coordinates, taking into account possible orbital motion of the star.
astrocentricPosition(const UniversalCoord & pos,const Star & star,double t)2245 static Point3d astrocentricPosition(const UniversalCoord& pos,
2246 const Star& star,
2247 double t)
2248 {
2249 UniversalCoord starPos = star.getPosition(t);
2250
2251 Vec3d v = pos - starPos;
2252 return Point3d(astro::microLightYearsToKilometers(v.x),
2253 astro::microLightYearsToKilometers(v.y),
2254 astro::microLightYearsToKilometers(v.z));
2255 }
2256
2257
autoMag(float & faintestMag)2258 void Renderer::autoMag(float& faintestMag)
2259 {
2260 float fieldCorr = 2.0f * FOV/(fov + FOV);
2261 faintestMag = (float) (faintestAutoMag45deg * sqrt(fieldCorr));
2262 saturationMag = saturationMagNight * (1.0f + fieldCorr * fieldCorr);
2263 }
2264
2265
2266 // Set up the light sources for rendering a solar system. The positions of
2267 // all nearby stars are converted from universal to viewer-centered
2268 // coordinates.
2269 static void
setupLightSources(const vector<const Star * > & nearStars,const UniversalCoord & observerPos,double t,vector<LightSource> & lightSources)2270 setupLightSources(const vector<const Star*>& nearStars,
2271 const UniversalCoord& observerPos,
2272 double t,
2273 vector<LightSource>& lightSources)
2274 {
2275 lightSources.clear();
2276
2277 for (vector<const Star*>::const_iterator iter = nearStars.begin();
2278 iter != nearStars.end(); iter++)
2279 {
2280 if ((*iter)->getVisibility())
2281 {
2282 Vec3d v = ((*iter)->getPosition(t) - observerPos) *
2283 astro::microLightYearsToKilometers(1.0);
2284
2285 LightSource ls;
2286 ls.position = v;
2287 ls.luminosity = (*iter)->getLuminosity();
2288 ls.radius = (*iter)->getRadius();
2289
2290 // If the star is sufficiently cool, change the light color
2291 // from white. Though our sun appears yellow, we still make
2292 // it and all hotter stars emit white light, as this is the
2293 // 'natural' light to which our eyes are accustomed. We also
2294 // assign a slight bluish tint to light from O and B type stars,
2295 // though these will almost never have planets for their light
2296 // to shine upon.
2297 float temp = (*iter)->getTemperature();
2298 if (temp > 30000.0f)
2299 ls.color = Color(0.8f, 0.8f, 1.0f);
2300 else if (temp > 10000.0f)
2301 ls.color = Color(0.9f, 0.9f, 1.0f);
2302 else if (temp > 5400.0f)
2303 ls.color = Color(1.0f, 1.0f, 1.0f);
2304 else if (temp > 3900.0f)
2305 ls.color = Color(1.0f, 0.9f, 0.8f);
2306 else if (temp > 2000.0f)
2307 ls.color = Color(1.0f, 0.7f, 0.7f);
2308 else
2309 ls.color = Color(1.0f, 0.4f, 0.4f);
2310
2311 lightSources.push_back(ls);
2312 }
2313 }
2314 }
2315
2316
2317 // Set up the potential secondary light sources for rendering solar system
2318 // bodies.
2319 static void
setupSecondaryLightSources(vector<SecondaryIlluminator> & secondaryIlluminators,const vector<LightSource> & primaryIlluminators)2320 setupSecondaryLightSources(vector<SecondaryIlluminator>& secondaryIlluminators,
2321 const vector<LightSource>& primaryIlluminators)
2322 {
2323 float au2 = square(astro::kilometersToAU(1.0f));
2324
2325 for (vector<SecondaryIlluminator>::iterator i = secondaryIlluminators.begin();
2326 i != secondaryIlluminators.end(); i++)
2327 {
2328 i->reflectedIrradiance = 0.0f;
2329
2330 for (vector<LightSource>::const_iterator j = primaryIlluminators.begin();
2331 j != primaryIlluminators.end(); j++)
2332 {
2333 i->reflectedIrradiance += j->luminosity / ((float) (i->position_v - j->position).lengthSquared() * au2);
2334 }
2335
2336 i->reflectedIrradiance *= i->body->getAlbedo();
2337 }
2338 }
2339
2340
2341 // Render an item from the render list
renderItem(const RenderListEntry & rle,const Observer & observer,const Quatf & cameraOrientation,float nearPlaneDistance,float farPlaneDistance)2342 void Renderer::renderItem(const RenderListEntry& rle,
2343 const Observer& observer,
2344 const Quatf& cameraOrientation,
2345 float nearPlaneDistance,
2346 float farPlaneDistance)
2347 {
2348 switch (rle.renderableType)
2349 {
2350 case RenderListEntry::RenderableStar:
2351 renderStar(*rle.star,
2352 rle.position,
2353 rle.distance,
2354 rle.appMag,
2355 cameraOrientation,
2356 observer.getTime(),
2357 nearPlaneDistance, farPlaneDistance);
2358 break;
2359
2360 case RenderListEntry::RenderableBody:
2361 renderPlanet(*rle.body,
2362 rle.position,
2363 rle.distance,
2364 rle.appMag,
2365 observer,
2366 cameraOrientation,
2367 nearPlaneDistance, farPlaneDistance);
2368 break;
2369
2370 case RenderListEntry::RenderableCometTail:
2371 renderCometTail(*rle.body,
2372 rle.position,
2373 observer.getTime(),
2374 rle.discSizeInPixels);
2375 break;
2376
2377 case RenderListEntry::RenderableReferenceMark:
2378 renderReferenceMark(*rle.refMark,
2379 rle.position,
2380 rle.distance,
2381 observer.getTime(),
2382 nearPlaneDistance);
2383 break;
2384
2385 default:
2386 break;
2387 }
2388 }
2389
2390
2391 #ifdef USE_HDR
genBlurTextures()2392 void Renderer::genBlurTextures()
2393 {
2394 for (size_t i = 0; i < BLUR_PASS_COUNT; ++i)
2395 {
2396 if (blurTextures[i] != NULL)
2397 {
2398 delete blurTextures[i];
2399 blurTextures[i] = NULL;
2400 }
2401 }
2402 if (blurTempTexture)
2403 {
2404 delete blurTempTexture;
2405 blurTempTexture = NULL;
2406 }
2407
2408 blurBaseWidth = sceneTexWidth, blurBaseHeight = sceneTexHeight;
2409
2410 if (blurBaseWidth > blurBaseHeight)
2411 {
2412 while (blurBaseWidth > BLUR_SIZE)
2413 {
2414 blurBaseWidth >>= 1;
2415 blurBaseHeight >>= 1;
2416 }
2417 }
2418 else
2419 {
2420 while (blurBaseHeight > BLUR_SIZE)
2421 {
2422 blurBaseWidth >>= 1;
2423 blurBaseHeight >>= 1;
2424 }
2425 }
2426 genBlurTexture(0);
2427 genBlurTexture(1);
2428
2429 Image *tempImg;
2430 ImageTexture *tempTexture;
2431 tempImg = new Image(GL_LUMINANCE, blurBaseWidth, blurBaseHeight);
2432 tempTexture = new ImageTexture(*tempImg, Texture::EdgeClamp, Texture::DefaultMipMaps);
2433 delete tempImg;
2434 if (tempTexture && tempTexture->getName() != 0)
2435 blurTempTexture = tempTexture;
2436 }
2437
genBlurTexture(int blurLevel)2438 void Renderer::genBlurTexture(int blurLevel)
2439 {
2440 Image *img;
2441 ImageTexture *texture;
2442
2443 #ifdef DEBUG_HDR
2444 HDR_LOG <<
2445 "Window width = " << windowWidth << ", " <<
2446 "Window height = " << windowHeight << ", " <<
2447 "Blur tex width = " << (blurBaseWidth>>blurLevel) << ", " <<
2448 "Blur tex height = " << (blurBaseHeight>>blurLevel) << endl;
2449 #endif
2450 img = new Image(blurFormat,
2451 blurBaseWidth>>blurLevel,
2452 blurBaseHeight>>blurLevel);
2453 texture = new ImageTexture(*img,
2454 Texture::EdgeClamp,
2455 Texture::NoMipMaps);
2456 delete img;
2457
2458 if (texture && texture->getName() != 0)
2459 blurTextures[blurLevel] = texture;
2460 }
2461
genSceneTexture()2462 void Renderer::genSceneTexture()
2463 {
2464 unsigned int *data;
2465 if (sceneTexture != 0)
2466 glDeleteTextures(1, &sceneTexture);
2467
2468 sceneTexWidth = 1;
2469 sceneTexHeight = 1;
2470 while (sceneTexWidth < windowWidth)
2471 sceneTexWidth <<= 1;
2472 while (sceneTexHeight < windowHeight)
2473 sceneTexHeight <<= 1;
2474 sceneTexWScale = (windowWidth > 0) ? (GLfloat)sceneTexWidth / (GLfloat)windowWidth :
2475 1.0f;
2476 sceneTexHScale = (windowHeight > 0) ? (GLfloat)sceneTexHeight / (GLfloat)windowHeight :
2477 1.0f;
2478 data = (unsigned int* )malloc(sceneTexWidth*sceneTexHeight*4*sizeof(unsigned int));
2479 memset(data, 0, sceneTexWidth*sceneTexHeight*4*sizeof(unsigned int));
2480
2481 glGenTextures(1, &sceneTexture);
2482 glBindTexture(GL_TEXTURE_2D, sceneTexture);
2483 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
2484 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
2485 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
2486 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
2487 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, sceneTexWidth, sceneTexHeight, 0,
2488 GL_RGBA, GL_UNSIGNED_BYTE, data);
2489
2490 free(data);
2491 #ifdef DEBUG_HDR
2492 static int genSceneTexCounter = 1;
2493 HDR_LOG <<
2494 "[" << genSceneTexCounter++ << "] " <<
2495 "Window width = " << windowWidth << ", " <<
2496 "Window height = " << windowHeight << ", " <<
2497 "Tex width = " << sceneTexWidth << ", " <<
2498 "Tex height = " << sceneTexHeight << endl;
2499 #endif
2500 }
2501
renderToBlurTexture(int blurLevel)2502 void Renderer::renderToBlurTexture(int blurLevel)
2503 {
2504 if (blurTextures[blurLevel] == NULL)
2505 return;
2506 GLsizei blurTexWidth = blurBaseWidth>>blurLevel;
2507 GLsizei blurTexHeight = blurBaseHeight>>blurLevel;
2508 GLsizei blurDrawWidth = (GLfloat)windowWidth/(GLfloat)sceneTexWidth * blurTexWidth;
2509 GLsizei blurDrawHeight = (GLfloat)windowHeight/(GLfloat)sceneTexHeight * blurTexHeight;
2510 GLfloat blurWScale = 1.f;
2511 GLfloat blurHScale = 1.f;
2512 GLfloat savedWScale = 1.f;
2513 GLfloat savedHScale = 1.f;
2514
2515 glPushAttrib(GL_COLOR_BUFFER_BIT | GL_VIEWPORT_BIT);
2516 glClearColor(0, 0, 0, 1.f);
2517 glViewport(0, 0, blurDrawWidth, blurDrawHeight);
2518 glBindTexture(GL_TEXTURE_2D, sceneTexture);
2519
2520 if (useBlendSubtract)
2521 {
2522 glBegin(GL_QUADS);
2523 drawBlendedVertices(0.0f, 0.0f, 1.0f);
2524 glEnd();
2525 // Do not need to scale alpha so mask it off
2526 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
2527 glEnable(GL_BLEND);
2528 savedWScale = sceneTexWScale;
2529 savedHScale = sceneTexHScale;
2530
2531 // Remove ldr part of image
2532 {
2533 const GLfloat bias = -0.5f;
2534 glBlendFunc(GL_ONE, GL_ONE);
2535 glx::glBlendEquationEXT(GL_FUNC_REVERSE_SUBTRACT_EXT);
2536 glColor4f(-bias, -bias, -bias, 0.0f);
2537
2538 glDisable(GL_TEXTURE_2D);
2539 glBegin(GL_QUADS);
2540 glVertex2f(0.0f, 0.0f);
2541 glVertex2f(1.f, 0.0f);
2542 glVertex2f(1.f, 1.f);
2543 glVertex2f(0.0f, 1.f);
2544 glEnd();
2545
2546 glEnable(GL_TEXTURE_2D);
2547 blurTextures[blurLevel]->bind();
2548 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2549 blurTexWidth, blurTexHeight, 0);
2550 }
2551
2552 // Scale back up hdr part
2553 {
2554 glx::glBlendEquationEXT(GL_FUNC_ADD_EXT);
2555 glBlendFunc(GL_DST_COLOR, GL_ONE);
2556
2557 glBegin(GL_QUADS);
2558 drawBlendedVertices(0.f, 0.f, 1.f); //x2
2559 drawBlendedVertices(0.f, 0.f, 1.f); //x2
2560 glEnd();
2561 }
2562
2563 glDisable(GL_BLEND);
2564
2565 if (!useLuminanceAlpha)
2566 {
2567 blurTempTexture->bind();
2568 glCopyTexImage2D(GL_TEXTURE_2D, blurLevel, GL_LUMINANCE, 0, 0,
2569 blurTexWidth, blurTexHeight, 0);
2570 // Erase color, replace with luminance image
2571 glBegin(GL_QUADS);
2572 glColor4f(0.f, 0.f, 0.f, 1.f);
2573 glVertex2f(0.0f, 0.0f);
2574 glVertex2f(1.0f, 0.0f);
2575 glVertex2f(1.0f, 1.0f);
2576 glVertex2f(0.0f, 1.0f);
2577 glEnd();
2578 glBegin(GL_QUADS);
2579 drawBlendedVertices(0.f, 0.f, 1.f);
2580 glEnd();
2581 }
2582
2583 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
2584 blurTextures[blurLevel]->bind();
2585 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2586 blurTexWidth, blurTexHeight, 0);
2587 }
2588 else
2589 {
2590 // GL_EXT_blend_subtract not supported
2591 // Use compatible (but slow) glPixelTransfer instead
2592 glBegin(GL_QUADS);
2593 drawBlendedVertices(0.0f, 0.0f, 1.0f);
2594 glEnd();
2595 savedWScale = sceneTexWScale;
2596 savedHScale = sceneTexHScale;
2597 sceneTexWScale = blurWScale;
2598 sceneTexHScale = blurHScale;
2599
2600 blurTextures[blurLevel]->bind();
2601 glPixelTransferf(GL_RED_SCALE, 8.f);
2602 glPixelTransferf(GL_GREEN_SCALE, 8.f);
2603 glPixelTransferf(GL_BLUE_SCALE, 8.f);
2604 glPixelTransferf(GL_RED_BIAS, -0.5f);
2605 glPixelTransferf(GL_GREEN_BIAS, -0.5f);
2606 glPixelTransferf(GL_BLUE_BIAS, -0.5f);
2607 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2608 blurTexWidth, blurTexHeight, 0);
2609 glPixelTransferf(GL_RED_SCALE, 1.f);
2610 glPixelTransferf(GL_GREEN_SCALE, 1.f);
2611 glPixelTransferf(GL_BLUE_SCALE, 1.f);
2612 glPixelTransferf(GL_RED_BIAS, 0.f);
2613 glPixelTransferf(GL_GREEN_BIAS, 0.f);
2614 glPixelTransferf(GL_BLUE_BIAS, 0.f);
2615 }
2616
2617 glClear(GL_COLOR_BUFFER_BIT);
2618
2619 glEnable(GL_BLEND);
2620 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2621
2622 GLfloat xdelta = 1.0f / (GLfloat)blurTexWidth;
2623 GLfloat ydelta = 1.0f / (GLfloat)blurTexHeight;
2624 blurWScale = ((GLfloat)blurTexWidth / (GLfloat)blurDrawWidth);
2625 blurHScale = ((GLfloat)blurTexHeight / (GLfloat)blurDrawHeight);
2626 sceneTexWScale = blurWScale;
2627 sceneTexHScale = blurHScale;
2628
2629 // Butterworth low pass filter to reduce flickering dots
2630 {
2631 glBegin(GL_QUADS);
2632 drawBlendedVertices(0.0f, 0.0f, .5f*1.f);
2633 drawBlendedVertices(-xdelta, 0.0f, .5f*0.333f);
2634 drawBlendedVertices( xdelta, 0.0f, .5f*0.25f);
2635 glEnd();
2636 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2637 blurTexWidth, blurTexHeight, 0);
2638 glBegin(GL_QUADS);
2639 drawBlendedVertices(0.0f, -ydelta, .5f*0.667f);
2640 drawBlendedVertices(0.0f, ydelta, .5f*0.333f);
2641 glEnd();
2642 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2643 blurTexWidth, blurTexHeight, 0);
2644 glClear(GL_COLOR_BUFFER_BIT);
2645 }
2646
2647 // Gaussian blur
2648 switch (blurLevel)
2649 {
2650 /*
2651 case 0:
2652 drawGaussian3x3(xdelta, ydelta, blurTexWidth, blurTexHeight, 1.f);
2653 break;
2654 */
2655 #ifdef TARGET_OS_MAC
2656 case 0:
2657 drawGaussian5x5(xdelta, ydelta, blurTexWidth, blurTexHeight, 1.f);
2658 break;
2659 case 1:
2660 drawGaussian9x9(xdelta, ydelta, blurTexWidth, blurTexHeight, .3f);
2661 break;
2662 #else
2663 // Gamma correct: windows=(mac^1.8)^(1/2.2)
2664 case 0:
2665 drawGaussian5x5(xdelta, ydelta, blurTexWidth, blurTexHeight, 1.f);
2666 break;
2667 case 1:
2668 drawGaussian9x9(xdelta, ydelta, blurTexWidth, blurTexHeight, .373f);
2669 break;
2670 #endif
2671 default:
2672 break;
2673 }
2674
2675 blurTextures[blurLevel]->bind();
2676 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2677 blurTexWidth, blurTexHeight, 0);
2678
2679 glDisable(GL_BLEND);
2680 glClear(GL_COLOR_BUFFER_BIT);
2681 glPopAttrib();
2682 sceneTexWScale = savedWScale;
2683 sceneTexHScale = savedHScale;
2684 }
2685
renderToTexture(const Observer & observer,const Universe & universe,float faintestMagNight,const Selection & sel)2686 void Renderer::renderToTexture(const Observer& observer,
2687 const Universe& universe,
2688 float faintestMagNight,
2689 const Selection& sel)
2690 {
2691 if (sceneTexture == 0)
2692 return;
2693 glPushAttrib(GL_COLOR_BUFFER_BIT);
2694
2695 draw(observer, universe, faintestMagNight, sel);
2696
2697 glBindTexture(GL_TEXTURE_2D, sceneTexture);
2698 glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0,
2699 sceneTexWidth, sceneTexHeight, 0);
2700 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
2701 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2702 glPopAttrib();
2703 }
2704
drawSceneTexture()2705 void Renderer::drawSceneTexture()
2706 {
2707 if (sceneTexture == 0)
2708 return;
2709 glBindTexture(GL_TEXTURE_2D, sceneTexture);
2710 glBegin(GL_QUADS);
2711 drawBlendedVertices(0.0f, 0.0f, 1.0f);
2712 glEnd();
2713 }
2714
drawBlendedVertices(float xdelta,float ydelta,float blend)2715 void Renderer::drawBlendedVertices(float xdelta, float ydelta, float blend)
2716 {
2717 glColor4f(1.0f, 1.0f, 1.0f, blend);
2718 glTexCoord2i(0, 0); glVertex2f(xdelta, ydelta);
2719 glTexCoord2i(1, 0); glVertex2f(sceneTexWScale+xdelta, ydelta);
2720 glTexCoord2i(1, 1); glVertex2f(sceneTexWScale+xdelta, sceneTexHScale+ydelta);
2721 glTexCoord2i(0, 1); glVertex2f(xdelta, sceneTexHScale+ydelta);
2722 }
2723
drawGaussian3x3(float xdelta,float ydelta,GLsizei width,GLsizei height,float blend)2724 void Renderer::drawGaussian3x3(float xdelta, float ydelta, GLsizei width, GLsizei height, float blend)
2725 {
2726 #ifdef USE_BLOOM_LISTS
2727 if (gaussianLists[0] == 0)
2728 {
2729 gaussianLists[0] = glGenLists(1);
2730 glNewList(gaussianLists[0], GL_COMPILE);
2731 #endif
2732 glBegin(GL_QUADS);
2733 drawBlendedVertices(0.0f, 0.0f, blend);
2734 drawBlendedVertices(-xdelta, 0.0f, 0.25f*blend);
2735 drawBlendedVertices( xdelta, 0.0f, 0.20f*blend);
2736 glEnd();
2737
2738 // Take result of horiz pass and apply vertical pass
2739 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2740 width, height, 0);
2741 glBegin(GL_QUADS);
2742 drawBlendedVertices(0.0f, -ydelta, 0.429f);
2743 drawBlendedVertices(0.0f, ydelta, 0.300f);
2744 glEnd();
2745 #ifdef USE_BLOOM_LISTS
2746 glEndList();
2747 }
2748 glCallList(gaussianLists[0]);
2749 #endif
2750 }
2751
drawGaussian5x5(float xdelta,float ydelta,GLsizei width,GLsizei height,float blend)2752 void Renderer::drawGaussian5x5(float xdelta, float ydelta, GLsizei width, GLsizei height, float blend)
2753 {
2754 #ifdef USE_BLOOM_LISTS
2755 if (gaussianLists[1] == 0)
2756 {
2757 gaussianLists[1] = glGenLists(1);
2758 glNewList(gaussianLists[1], GL_COMPILE);
2759 #endif
2760 glBegin(GL_QUADS);
2761 drawBlendedVertices(0.0f, 0.0f, blend);
2762 drawBlendedVertices(-xdelta, 0.0f, 0.475f*blend);
2763 drawBlendedVertices( xdelta, 0.0f, 0.475f*blend);
2764 drawBlendedVertices(-2.0f*xdelta, 0.0f, 0.075f*blend);
2765 drawBlendedVertices( 2.0f*xdelta, 0.0f, 0.075f*blend);
2766 glEnd();
2767 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2768 width, height, 0);
2769 glBegin(GL_QUADS);
2770 drawBlendedVertices(0.0f, -ydelta, 0.475f);
2771 drawBlendedVertices(0.0f, ydelta, 0.475f);
2772 drawBlendedVertices(0.0f, -2.0f*ydelta, 0.075f);
2773 drawBlendedVertices(0.0f, 2.0f*ydelta, 0.075f);
2774 glEnd();
2775 #ifdef USE_BLOOM_LISTS
2776 glEndList();
2777 }
2778 glCallList(gaussianLists[1]);
2779 #endif
2780 }
2781
drawGaussian9x9(float xdelta,float ydelta,GLsizei width,GLsizei height,float blend)2782 void Renderer::drawGaussian9x9(float xdelta, float ydelta, GLsizei width, GLsizei height, float blend)
2783 {
2784 #ifdef USE_BLOOM_LISTS
2785 if (gaussianLists[2] == 0)
2786 {
2787 gaussianLists[2] = glGenLists(1);
2788 glNewList(gaussianLists[2], GL_COMPILE);
2789 #endif
2790 glBegin(GL_QUADS);
2791 drawBlendedVertices(0.0f, 0.0f, blend);
2792 drawBlendedVertices(-xdelta, 0.0f, 0.632f*blend);
2793 drawBlendedVertices( xdelta, 0.0f, 0.632f*blend);
2794 drawBlendedVertices(-2.0f*xdelta, 0.0f, 0.159f*blend);
2795 drawBlendedVertices( 2.0f*xdelta, 0.0f, 0.159f*blend);
2796 drawBlendedVertices(-3.0f*xdelta, 0.0f, 0.016f*blend);
2797 drawBlendedVertices( 3.0f*xdelta, 0.0f, 0.016f*blend);
2798 glEnd();
2799
2800 glCopyTexImage2D(GL_TEXTURE_2D, 0, blurFormat, 0, 0,
2801 width, height, 0);
2802 glBegin(GL_QUADS);
2803 drawBlendedVertices(0.0f, -ydelta, 0.632f);
2804 drawBlendedVertices(0.0f, ydelta, 0.632f);
2805 drawBlendedVertices(0.0f, -2.0f*ydelta, 0.159f);
2806 drawBlendedVertices(0.0f, 2.0f*ydelta, 0.159f);
2807 drawBlendedVertices(0.0f, -3.0f*ydelta, 0.016f);
2808 drawBlendedVertices(0.0f, 3.0f*ydelta, 0.016f);
2809 glEnd();
2810 #ifdef USE_BLOOM_LISTS
2811 glEndList();
2812 }
2813 glCallList(gaussianLists[2]);
2814 #endif
2815 }
2816
drawBlur()2817 void Renderer::drawBlur()
2818 {
2819 blurTextures[0]->bind();
2820 glBegin(GL_QUADS);
2821 drawBlendedVertices(0.0f, 0.0f, 1.0f);
2822 glEnd();
2823 blurTextures[1]->bind();
2824 glBegin(GL_QUADS);
2825 drawBlendedVertices(0.0f, 0.0f, 1.0f);
2826 glEnd();
2827 }
2828
getBloomEnabled()2829 bool Renderer::getBloomEnabled()
2830 {
2831 return bloomEnabled;
2832 }
2833
setBloomEnabled(bool aBloomEnabled)2834 void Renderer::setBloomEnabled(bool aBloomEnabled)
2835 {
2836 bloomEnabled = aBloomEnabled;
2837 }
2838
increaseBrightness()2839 void Renderer::increaseBrightness()
2840 {
2841 brightPlus += 1.0f;
2842 }
2843
decreaseBrightness()2844 void Renderer::decreaseBrightness()
2845 {
2846 brightPlus -= 1.0f;
2847 }
2848
getBrightness()2849 float Renderer::getBrightness()
2850 {
2851 return brightPlus;
2852 }
2853 #endif // USE_HDR
2854
render(const Observer & observer,const Universe & universe,float faintestMagNight,const Selection & sel)2855 void Renderer::render(const Observer& observer,
2856 const Universe& universe,
2857 float faintestMagNight,
2858 const Selection& sel)
2859 {
2860 glMatrixMode(GL_PROJECTION);
2861 glLoadIdentity();
2862
2863 #ifdef USE_HDR
2864 renderToTexture(observer, universe, faintestMagNight, sel);
2865
2866 //------------- Post processing from here ------------//
2867 glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT);
2868 glEnable(GL_TEXTURE_2D);
2869 glDisable(GL_BLEND);
2870 glDisable(GL_LIGHTING);
2871 glDisable(GL_DEPTH_TEST);
2872 glDepthMask(GL_FALSE);
2873
2874 glMatrixMode(GL_PROJECTION);
2875 glPushMatrix();
2876 glLoadIdentity();
2877 glOrtho( 0.0, 1.0, 0.0, 1.0, -1.0, 1.0 );
2878 glMatrixMode (GL_MODELVIEW);
2879 glPushMatrix();
2880 glLoadIdentity();
2881
2882 if (bloomEnabled)
2883 {
2884 renderToBlurTexture(0);
2885 renderToBlurTexture(1);
2886 // renderToBlurTexture(2);
2887 }
2888
2889 drawSceneTexture();
2890
2891 glEnable(GL_BLEND);
2892 glBlendFunc(GL_ONE, GL_ONE);
2893
2894 #ifdef HDR_COMPRESS
2895 // Assume luminance 1.0 mapped to 128 previously
2896 // Compositing a 2nd copy doubles 128->255
2897 drawSceneTexture();
2898 #endif
2899
2900 if (bloomEnabled)
2901 {
2902 drawBlur();
2903 }
2904
2905 glMatrixMode(GL_PROJECTION);
2906 glPopMatrix();
2907 glMatrixMode(GL_MODELVIEW);
2908 glPopMatrix();
2909 glPopAttrib();
2910 #else
2911 draw(observer, universe, faintestMagNight, sel);
2912 #endif
2913 }
2914
draw(const Observer & observer,const Universe & universe,float faintestMagNight,const Selection & sel)2915 void Renderer::draw(const Observer& observer,
2916 const Universe& universe,
2917 float faintestMagNight,
2918 const Selection& sel)
2919 {
2920 // Get the observer's time
2921 double now = observer.getTime();
2922 realTime = observer.getRealTime();
2923
2924 frameCount++;
2925 settingsChanged = false;
2926
2927 // Compute the size of a pixel
2928 setFieldOfView(radToDeg(observer.getFOV()));
2929 pixelSize = calcPixelSize(fov, (float) windowHeight);
2930
2931 // Set up the projection we'll use for rendering stars.
2932 gluPerspective(fov,
2933 (float) windowWidth / (float) windowHeight,
2934 NEAR_DIST, FAR_DIST);
2935
2936 // Set the modelview matrix
2937 glMatrixMode(GL_MODELVIEW);
2938
2939 // Get the displayed surface texture set to use from the observer
2940 displayedSurface = observer.getDisplayedSurface();
2941
2942 locationFilter = observer.getLocationFilter();
2943
2944 if (usePointSprite && getGLContext()->getVertexProcessor() != NULL)
2945 {
2946 useNewStarRendering = true;
2947 }
2948 else
2949 {
2950 useNewStarRendering = false;
2951 }
2952
2953 // Highlight the selected object
2954 highlightObject = sel;
2955
2956 m_cameraOrientation = observer.getOrientationf();
2957
2958 // Get the view frustum used for culling in camera space.
2959 float viewAspectRatio = (float) windowWidth / (float) windowHeight;
2960 Frustum frustum(degToRad(fov),
2961 viewAspectRatio,
2962 MinNearPlaneDistance);
2963
2964 // Get the transformed frustum, used for culling in the astrocentric coordinate
2965 // system.
2966 Frustum xfrustum(degToRad(fov),
2967 viewAspectRatio,
2968 MinNearPlaneDistance);
2969 xfrustum.transform(conjugate(observer.getOrientationf()).toMatrix3());
2970
2971 // Set up the camera for star rendering; the units of this phase
2972 // are light years.
2973 Point3f observerPosLY = (Point3f) observer.getPosition();
2974 observerPosLY.x *= 1e-6f;
2975 observerPosLY.y *= 1e-6f;
2976 observerPosLY.z *= 1e-6f;
2977 glPushMatrix();
2978 glRotate(m_cameraOrientation);
2979
2980 // Get the model matrix *before* translation. We'll use this for
2981 // positioning star and planet labels.
2982 glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
2983 glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
2984
2985 clearSortedAnnotations();
2986
2987 // Put all solar system bodies into the render list. Stars close and
2988 // large enough to have discernible surface detail are also placed in
2989 // renderList.
2990 renderList.clear();
2991 orbitPathList.clear();
2992 lightSourceList.clear();
2993 secondaryIlluminators.clear();
2994
2995 // See if we want to use AutoMag.
2996 if ((renderFlags & ShowAutoMag) != 0)
2997 {
2998 autoMag(faintestMag);
2999 }
3000 else
3001 {
3002 faintestMag = faintestMagNight;
3003 saturationMag = saturationMagNight;
3004 }
3005
3006 faintestPlanetMag = faintestMag;
3007 #ifdef USE_HDR
3008 float maxBodyMagPrev = saturationMag;
3009 maxBodyMag = min(maxBodyMag, saturationMag);
3010 vector<RenderListEntry>::iterator closestBody;
3011 const Star *brightestStar = NULL;
3012 bool foundClosestBody = false;
3013 bool foundBrightestStar = false;
3014 #endif
3015
3016 if (renderFlags & ShowPlanets)
3017 {
3018 nearStars.clear();
3019 universe.getNearStars(observer.getPosition(), 1.0f, nearStars);
3020
3021 // Set up direct light sources (i.e. just stars at the moment)
3022 setupLightSources(nearStars, observer.getPosition(), now, lightSourceList);
3023
3024 // Traverse the frame trees of each nearby solar system and
3025 // build the list of objects to be rendered.
3026 for (vector<const Star*>::const_iterator iter = nearStars.begin();
3027 iter != nearStars.end(); iter++)
3028 {
3029 const Star* sun = *iter;
3030 SolarSystem* solarSystem = universe.getSolarSystem(sun);
3031 if (solarSystem != NULL)
3032 {
3033 FrameTree* solarSysTree = solarSystem->getFrameTree();
3034 if (solarSysTree != NULL)
3035 {
3036 if (solarSysTree->updateRequired())
3037 {
3038 // Tree has changed, so we must recompute bounding spheres.
3039 solarSysTree->recomputeBoundingSphere();
3040 solarSysTree->markUpdated();
3041 }
3042
3043 // Compute the position of the observer in astrocentric coordinates
3044 Point3d astrocentricObserverPos = astrocentricPosition(observer.getPosition(), *sun, now);
3045
3046 // Build render lists for bodies and orbits paths
3047 buildRenderLists(astrocentricObserverPos,
3048 xfrustum,
3049 Vec3d(0.0, 0.0, -1.0) * observer.getOrientation().toMatrix3(),
3050 Vec3d(0.0, 0.0, 0.0),
3051 solarSysTree,
3052 observer,
3053 now);
3054 if (renderFlags & ShowOrbits)
3055 {
3056 buildOrbitLists(astrocentricObserverPos,
3057 observer.getOrientationf(),
3058 xfrustum,
3059 solarSysTree,
3060 now);
3061 }
3062 }
3063 }
3064
3065 addStarOrbitToRenderList(*sun, observer, now);
3066 }
3067
3068 if ((labelMode & (BodyLabelMask)) != 0)
3069 {
3070 buildLabelLists(xfrustum, now);
3071 }
3072
3073 starTex->bind();
3074 }
3075
3076 setupSecondaryLightSources(secondaryIlluminators, lightSourceList);
3077
3078 #ifdef USE_HDR
3079 Mat3f viewMat = conjugate(observer.getOrientationf()).toMatrix3();
3080 float maxSpan = (float) sqrt(square((float) windowWidth) +
3081 square((float) windowHeight));
3082 float nearZcoeff = (float) cos(degToRad(fov / 2)) *
3083 ((float) windowHeight / maxSpan);
3084
3085 // Remove objects from the render list that lie completely outside the
3086 // view frustum.
3087 vector<RenderListEntry>::iterator notCulled = renderList.begin();
3088 for (vector<RenderListEntry>::iterator iter = renderList.begin();
3089 iter != renderList.end(); iter++)
3090 {
3091 Point3f center = iter->position * viewMat;
3092
3093 bool convex = true;
3094 float radius = 1.0f;
3095 float cullRadius = 1.0f;
3096 float cloudHeight = 0.0f;
3097
3098 switch (iter->renderableType)
3099 {
3100 case RenderListEntry::RenderableStar:
3101 continue;
3102
3103 case RenderListEntry::RenderableCometTail:
3104 radius = iter->radius;
3105 cullRadius = radius;
3106 convex = false;
3107 break;
3108
3109 case RenderListEntry::RenderableReferenceMark:
3110 radius = iter->radius;
3111 cullRadius = radius;
3112 convex = false;
3113 break;
3114
3115 case RenderListEntry::RenderableBody:
3116 default:
3117 radius = iter->body->getBoundingRadius();
3118 if (iter->body->getRings() != NULL)
3119 {
3120 radius = iter->body->getRings()->outerRadius;
3121 convex = false;
3122 }
3123
3124 if (!iter->body->isEllipsoid())
3125 convex = false;
3126
3127 cullRadius = radius;
3128 if (iter->body->getAtmosphere() != NULL)
3129 {
3130 cullRadius += iter->body->getAtmosphere()->height;
3131 cloudHeight = max(iter->body->getAtmosphere()->cloudHeight,
3132 iter->body->getAtmosphere()->mieScaleHeight * (float) -log(AtmosphereExtinctionThreshold));
3133 }
3134 break;
3135 }
3136
3137 // Test the object's bounding sphere against the view frustum
3138 if (frustum.testSphere(center, cullRadius) != Frustum::Outside)
3139 {
3140 float nearZ = center.distanceFromOrigin() - radius;
3141 nearZ = -nearZ * nearZcoeff;
3142
3143 if (nearZ > -MinNearPlaneDistance)
3144 iter->nearZ = -max(MinNearPlaneDistance, radius / 2000.0f);
3145 else
3146 iter->nearZ = nearZ;
3147
3148 if (!convex)
3149 {
3150 iter->farZ = center.z - radius;
3151 if (iter->farZ / iter->nearZ > MaxFarNearRatio * 0.5f)
3152 iter->nearZ = iter->farZ / (MaxFarNearRatio * 0.5f);
3153 }
3154 else
3155 {
3156 // Make the far plane as close as possible
3157 float d = center.distanceFromOrigin();
3158
3159 // Account for ellipsoidal objects
3160 float eradius = radius;
3161 if (iter->body != NULL)
3162 {
3163 Vec3f semiAxes = iter->body->getSemiAxes();
3164 float minSemiAxis = min(semiAxes.x, min(semiAxes.y, semiAxes.z));
3165 eradius *= minSemiAxis / radius;
3166 }
3167
3168 if (d > eradius)
3169 {
3170 iter->farZ = iter->centerZ - iter->radius;
3171 }
3172 else
3173 {
3174 // We're inside the bounding sphere (and, if the planet
3175 // is spherical, inside the planet.)
3176 iter->farZ = iter->nearZ * 2.0f;
3177 }
3178
3179 if (cloudHeight > 0.0f)
3180 {
3181 // If there's a cloud layer, we need to move the
3182 // far plane out so that the clouds aren't clipped
3183 float cloudLayerRadius = eradius + cloudHeight;
3184 iter->farZ -= (float) sqrt(square(cloudLayerRadius) -
3185 square(eradius));
3186 }
3187 }
3188
3189 *notCulled = *iter;
3190 notCulled++;
3191
3192 maxBodyMag = min(maxBodyMag, iter->appMag);
3193 foundClosestBody = true;
3194 }
3195 }
3196
3197 renderList.resize(notCulled - renderList.begin());
3198 saturationMag = maxBodyMag;
3199 #endif // USE_HDR
3200
3201 Color skyColor(0.0f, 0.0f, 0.0f);
3202
3203 // Scan through the render list to see if we're inside a planetary
3204 // atmosphere. If so, we need to adjust the sky color as well as the
3205 // limiting magnitude of stars (so stars aren't visible in the daytime
3206 // on planets with thick atmospheres.)
3207 if ((renderFlags & ShowAtmospheres) != 0)
3208 {
3209 for (vector<RenderListEntry>::iterator iter = renderList.begin();
3210 iter != renderList.end(); iter++)
3211 {
3212 if (iter->renderableType == RenderListEntry::RenderableBody && iter->body->getAtmosphere() != NULL)
3213 {
3214 // Compute the density of the atmosphere, and from that
3215 // the amount light scattering. It's complicated by the
3216 // possibility that the planet is oblate and a simple distance
3217 // to sphere calculation will not suffice.
3218 const Atmosphere* atmosphere = iter->body->getAtmosphere();
3219 float radius = iter->body->getRadius();
3220 Vec3f semiAxes = iter->body->getSemiAxes() * (1.0f / radius);
3221 Vec3f recipSemiAxes(1.0f / semiAxes.x,
3222 1.0f / semiAxes.y,
3223 1.0f / semiAxes.z);
3224 Mat3f A = Mat3f::scaling(recipSemiAxes);
3225 Vec3f eyeVec = iter->position - Point3f(0.0f, 0.0f, 0.0f);
3226 eyeVec *= (1.0f / radius);
3227
3228 // Compute the orientation of the planet before axial rotation
3229 Quatd qd = iter->body->getEclipticToEquatorial(now);
3230 Quatf q((float) qd.w, (float) qd.x, (float) qd.y, (float) qd.z);
3231 eyeVec = eyeVec * conjugate(q).toMatrix3();
3232
3233 // ellipDist is not the true distance from the surface unless
3234 // the planet is spherical. The quantity that we do compute
3235 // is the distance to the surface along a line from the eye
3236 // position to the center of the ellipsoid.
3237 float ellipDist = (float) sqrt((eyeVec * A) * (eyeVec * A)) - 1.0f;
3238 if (ellipDist < atmosphere->height / radius &&
3239 atmosphere->height > 0.0f)
3240 {
3241 float density = 1.0f - ellipDist /
3242 (atmosphere->height / radius);
3243 if (density > 1.0f)
3244 density = 1.0f;
3245
3246 Vec3f sunDir = iter->sun;
3247 Vec3f normal = Point3f(0.0f, 0.0f, 0.0f) - iter->position;
3248 sunDir.normalize();
3249 normal.normalize();
3250 #ifdef USE_HDR
3251 // Ignore magnitude of planet underneath when lighting atmosphere
3252 // Could be changed to simulate light pollution, etc
3253 maxBodyMag = maxBodyMagPrev;
3254 saturationMag = maxBodyMag;
3255 #endif
3256 float illumination = Math<float>::clamp((sunDir * normal) + 0.2f);
3257
3258 float lightness = illumination * density;
3259 faintestMag = faintestMag - 15.0f * lightness;
3260 saturationMag = saturationMag - 15.0f * lightness;
3261 }
3262 }
3263 }
3264 }
3265
3266 // Now we need to determine how to scale the brightness of stars. The
3267 // brightness will be proportional to the apparent magnitude, i.e.
3268 // a logarithmic function of the stars apparent brightness. This mimics
3269 // the response of the human eye. We sort of fudge things here and
3270 // maintain a minimum range of six magnitudes between faintest visible
3271 // and saturation; this keeps stars from popping in or out as the sun
3272 // sets or rises.
3273 #ifdef USE_HDR
3274 brightnessScale = 1.0f / (faintestMag - saturationMag);
3275 #else
3276 if (faintestMag - saturationMag >= 6.0f)
3277 brightnessScale = 1.0f / (faintestMag - saturationMag);
3278 else
3279 brightnessScale = 0.1667f;
3280 #endif
3281
3282 #ifdef USE_HDR
3283 exposurePrev = exposure;
3284 float exposureNow = 1.f / (1.f+exp((faintestMag - saturationMag + DEFAULT_EXPOSURE)/2.f));
3285 exposure = exposurePrev + (exposureNow - exposurePrev) * (1.f - exp(-1.f/(15.f * EXPOSURE_HALFLIFE)));
3286 brightnessScale /= exposure;
3287 #endif
3288
3289 #ifdef DEBUG_HDR_TONEMAP
3290 HDR_LOG <<
3291 // "brightnessScale = " << brightnessScale <<
3292 "faint = " << faintestMag << ", " <<
3293 "sat = " << saturationMag << ", " <<
3294 "exposure = " << (exposure+brightPlus) << endl;
3295 #endif
3296
3297 #ifdef HDR_COMPRESS
3298 ambientColor = Color(ambientLightLevel*.5f, ambientLightLevel*.5f, ambientLightLevel*.5f);
3299 #else
3300 ambientColor = Color(ambientLightLevel, ambientLightLevel, ambientLightLevel);
3301 #endif
3302
3303 // Create the ambient light source. For realistic scenes in space, this
3304 // should be black.
3305 glAmbientLightColor(ambientColor);
3306
3307 #ifdef USE_HDR
3308 glClearColor(skyColor.red(), skyColor.green(), skyColor.blue(), 0.0f);
3309 #else
3310 glClearColor(skyColor.red(), skyColor.green(), skyColor.blue(), 1);
3311 #endif
3312 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3313
3314 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
3315
3316 glDisable(GL_LIGHTING);
3317 glDepthMask(GL_FALSE);
3318 glEnable(GL_BLEND);
3319 glEnable(GL_TEXTURE_2D);
3320
3321 // Render sky grids first--these will always be in the background
3322 {
3323 glDisable(GL_TEXTURE_2D);
3324 if ((renderFlags & ShowSmoothLines) != 0)
3325 enableSmoothLines();
3326 renderSkyGrids(observer);
3327 if ((renderFlags & ShowSmoothLines) != 0)
3328 disableSmoothLines();
3329 glEnable(GL_BLEND);
3330 glEnable(GL_TEXTURE_2D);
3331 }
3332
3333 // Render deep sky objects
3334 if ((renderFlags & (ShowGalaxies |
3335 ShowGlobulars |
3336 ShowNebulae |
3337 ShowOpenClusters)) != 0 &&
3338 universe.getDSOCatalog() != NULL)
3339 {
3340 renderDeepSkyObjects(universe, observer, faintestMag);
3341 }
3342
3343 // Translate the camera before rendering the stars
3344 glPushMatrix();
3345
3346 // Render stars
3347 #ifdef USE_HDR
3348 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
3349 #endif
3350 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
3351
3352 if ((renderFlags & ShowStars) != 0 && universe.getStarCatalog() != NULL)
3353 {
3354 // Disable multisample rendering when drawing point stars
3355 bool toggleAA = (starStyle == Renderer::PointStars && glIsEnabled(GL_MULTISAMPLE_ARB));
3356 if (toggleAA)
3357 glDisable(GL_MULTISAMPLE_ARB);
3358
3359 if (useNewStarRendering)
3360 renderPointStars(*universe.getStarCatalog(), faintestMag, observer);
3361 else
3362 renderStars(*universe.getStarCatalog(), faintestMag, observer);
3363
3364 if (toggleAA)
3365 glEnable(GL_MULTISAMPLE_ARB);
3366 }
3367
3368 #ifdef USE_HDR
3369 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
3370 #endif
3371
3372 glTranslatef(-observerPosLY.x, -observerPosLY.y, -observerPosLY.z);
3373
3374 // Render asterisms
3375 if ((renderFlags & ShowDiagrams) != 0 && universe.getAsterisms() != NULL)
3376 {
3377 /* We'll linearly fade the lines as a function of the observer's
3378 distance to the origin of coordinates: */
3379 float opacity = 1.0f;
3380 float dist = observerPosLY.distanceFromOrigin() * 1e6f;
3381 if (dist > MaxAsterismLinesConstDist)
3382 {
3383 opacity = clamp((MaxAsterismLinesConstDist - dist) /
3384 (MaxAsterismLinesDist - MaxAsterismLinesConstDist) + 1);
3385 }
3386
3387 glColor(ConstellationColor, opacity);
3388 glDisable(GL_TEXTURE_2D);
3389 if ((renderFlags & ShowSmoothLines) != 0)
3390 enableSmoothLines();
3391 AsterismList* asterisms = universe.getAsterisms();
3392 for (AsterismList::const_iterator iter = asterisms->begin();
3393 iter != asterisms->end(); iter++)
3394 {
3395 Asterism* ast = *iter;
3396
3397 if (ast->getActive())
3398 {
3399 if (ast->isColorOverridden())
3400 glColor(ast->getOverrideColor(), opacity);
3401 else
3402 glColor(ConstellationColor, opacity);
3403
3404 for (int i = 0; i < ast->getChainCount(); i++)
3405 {
3406 const Asterism::Chain& chain = ast->getChain(i);
3407
3408 glBegin(GL_LINE_STRIP);
3409 for (Asterism::Chain::const_iterator iter = chain.begin();
3410 iter != chain.end(); iter++)
3411 glVertex(*iter);
3412 glEnd();
3413 }
3414 }
3415 }
3416
3417 if ((renderFlags & ShowSmoothLines) != 0)
3418 disableSmoothLines();
3419 }
3420
3421 if ((renderFlags & ShowBoundaries) != 0)
3422 {
3423 /* We'll linearly fade the boundaries as a function of the
3424 observer's distance to the origin of coordinates: */
3425 float opacity = 1.0f;
3426 float dist = observerPosLY.distanceFromOrigin() * 1e6f;
3427 if (dist > MaxAsterismLabelsConstDist)
3428 {
3429 opacity = clamp((MaxAsterismLabelsConstDist - dist) /
3430 (MaxAsterismLabelsDist - MaxAsterismLabelsConstDist) + 1);
3431 }
3432 glColor(BoundaryColor, opacity);
3433
3434 glDisable(GL_TEXTURE_2D);
3435 if ((renderFlags & ShowSmoothLines) != 0)
3436 enableSmoothLines();
3437 if (universe.getBoundaries() != NULL)
3438 universe.getBoundaries()->render();
3439 if ((renderFlags & ShowSmoothLines) != 0)
3440 disableSmoothLines();
3441 }
3442
3443 // Render star and deep sky object labels
3444 renderBackgroundAnnotations(FontNormal);
3445
3446 // Render constellations labels
3447 if ((labelMode & ConstellationLabels) != 0 && universe.getAsterisms() != NULL)
3448 {
3449 labelConstellations(*universe.getAsterisms(), observer);
3450 renderBackgroundAnnotations(FontLarge);
3451 }
3452
3453 // Pop observer translation
3454 glPopMatrix();
3455
3456 if ((renderFlags & ShowMarkers) != 0)
3457 {
3458 renderMarkers(*universe.getMarkers(),
3459 observer.getPosition(),
3460 observer.getOrientation(),
3461 now);
3462
3463 // Render background markers; rendering of other markers is deferred until
3464 // solar system objects are rendered.
3465 renderBackgroundAnnotations(FontNormal);
3466 }
3467
3468 // Draw the selection cursor
3469 bool selectionVisible = false;
3470 if (!sel.empty() && (renderFlags & ShowMarkers))
3471 {
3472 UniversalCoord uc = sel.getPosition(now);
3473 Vec3d offset = (uc - observer.getPosition()) * astro::microLightYearsToKilometers(1.0);
3474 static MarkerRepresentation cursorRep(MarkerRepresentation::Crosshair);
3475 selectionVisible = xfrustum.testSphere(Point3d(0, 0, 0) + offset, sel.radius()) != Frustum::Outside;
3476
3477 if (selectionVisible)
3478 {
3479 double distance = offset.length();
3480 float symbolSize = (float) (sel.radius() / distance) / pixelSize;
3481
3482 // Modify the marker position so that it is always in front of the marked object.
3483 double boundingRadius;
3484 if (sel.body() != NULL)
3485 boundingRadius = sel.body()->getBoundingRadius();
3486 else
3487 boundingRadius = sel.radius();
3488 offset *= (1.0 - boundingRadius * 1.01 / distance);
3489
3490 // The selection cursor is only partially visible when the selected object is obscured. To implement
3491 // this behavior we'll draw two markers at the same position: one that's always visible, and another one
3492 // that's depth sorted. When the selection is occluded, only the foreground marker is visible. Otherwise,
3493 // both markers are drawn and cursor appears much brighter as a result.
3494 if (distance < astro::lightYearsToKilometers(1.0))
3495 {
3496 addSortedAnnotation(&cursorRep, EMPTY_STRING, Color(SelectionCursorColor, 1.0f),
3497 Point3f((float) offset.x, (float) offset.y, (float) offset.z),
3498 AlignLeft, VerticalAlignTop, symbolSize);
3499 }
3500 else
3501 {
3502 addAnnotation(backgroundAnnotations, &cursorRep, EMPTY_STRING, Color(SelectionCursorColor, 1.0f),
3503 Point3f((float) offset.x, (float) offset.y, (float) offset.z),
3504 AlignLeft, VerticalAlignTop, symbolSize);
3505 }
3506
3507 Color occludedCursorColor(SelectionCursorColor.red(), SelectionCursorColor.green() + 0.3f, SelectionCursorColor.blue());
3508 addAnnotation(foregroundAnnotations,
3509 &cursorRep, EMPTY_STRING, Color(occludedCursorColor, 0.4f),
3510 Point3f((float) offset.x, (float) offset.y, (float) offset.z),
3511 AlignLeft, VerticalAlignTop, symbolSize);
3512 }
3513 }
3514
3515 glPolygonMode(GL_FRONT, (GLenum) renderMode);
3516 glPolygonMode(GL_BACK, (GLenum) renderMode);
3517
3518 {
3519 Mat3f viewMat = conjugate(observer.getOrientationf()).toMatrix3();
3520
3521 // Remove objects from the render list that lie completely outside the
3522 // view frustum.
3523 #ifdef USE_HDR
3524 maxBodyMag = maxBodyMagPrev;
3525 float starMaxMag = maxBodyMagPrev;
3526 notCulled = renderList.begin();
3527 #else
3528 vector<RenderListEntry>::iterator notCulled = renderList.begin();
3529 #endif
3530 for (vector<RenderListEntry>::iterator iter = renderList.begin();
3531 iter != renderList.end(); iter++)
3532 {
3533 #ifdef USE_HDR
3534 switch (iter->renderableType)
3535 {
3536 case RenderListEntry::RenderableStar:
3537 break;
3538 default:
3539 *notCulled = *iter;
3540 notCulled++;
3541 continue;
3542 }
3543 #endif
3544 Point3f center = iter->position * viewMat;
3545
3546 bool convex = true;
3547 float radius = 1.0f;
3548 float cullRadius = 1.0f;
3549 float cloudHeight = 0.0f;
3550
3551 #ifndef USE_HDR
3552 switch (iter->renderableType)
3553 {
3554 case RenderListEntry::RenderableStar:
3555 radius = iter->star->getRadius();
3556 cullRadius = radius * (1.0f + CoronaHeight);
3557 break;
3558
3559 case RenderListEntry::RenderableCometTail:
3560 radius = iter->radius;
3561 cullRadius = radius;
3562 convex = false;
3563 break;
3564
3565 case RenderListEntry::RenderableBody:
3566 radius = iter->body->getBoundingRadius();
3567 if (iter->body->getRings() != NULL)
3568 {
3569 radius = iter->body->getRings()->outerRadius;
3570 convex = false;
3571 }
3572
3573 if (!iter->body->isEllipsoid())
3574 convex = false;
3575
3576 cullRadius = radius;
3577 if (iter->body->getAtmosphere() != NULL)
3578 {
3579 cullRadius += iter->body->getAtmosphere()->height;
3580 cloudHeight = max(iter->body->getAtmosphere()->cloudHeight,
3581 iter->body->getAtmosphere()->mieScaleHeight * (float) -log(AtmosphereExtinctionThreshold));
3582 }
3583 break;
3584
3585 case RenderListEntry::RenderableReferenceMark:
3586 radius = iter->radius;
3587 cullRadius = radius;
3588 convex = false;
3589 break;
3590
3591 default:
3592 break;
3593 }
3594 #else
3595 radius = iter->star->getRadius();
3596 cullRadius = radius * (1.0f + CoronaHeight);
3597 #endif // USE_HDR
3598
3599 // Test the object's bounding sphere against the view frustum
3600 if (frustum.testSphere(center, cullRadius) != Frustum::Outside)
3601 {
3602 float nearZ = center.distanceFromOrigin() - radius;
3603 #ifdef USE_HDR
3604 nearZ = -nearZ * nearZcoeff;
3605 #else
3606 float maxSpan = (float) sqrt(square((float) windowWidth) +
3607 square((float) windowHeight));
3608
3609 nearZ = -nearZ * (float) cos(degToRad(fov / 2)) *
3610 ((float) windowHeight / maxSpan);
3611 #endif
3612 if (nearZ > -MinNearPlaneDistance)
3613 iter->nearZ = -max(MinNearPlaneDistance, radius / 2000.0f);
3614 else
3615 iter->nearZ = nearZ;
3616
3617 if (!convex)
3618 {
3619 iter->farZ = center.z - radius;
3620 if (iter->farZ / iter->nearZ > MaxFarNearRatio * 0.5f)
3621 iter->nearZ = iter->farZ / (MaxFarNearRatio * 0.5f);
3622 }
3623 else
3624 {
3625 // Make the far plane as close as possible
3626 float d = center.distanceFromOrigin();
3627
3628 // Account for ellipsoidal objects
3629 float eradius = radius;
3630 if (iter->renderableType == RenderListEntry::RenderableBody)
3631 {
3632 Vec3f semiAxes = iter->body->getSemiAxes();
3633 float minSemiAxis = min(semiAxes.x, min(semiAxes.y, semiAxes.z));
3634 eradius *= minSemiAxis / radius;
3635 }
3636
3637 if (d > eradius)
3638 {
3639 iter->farZ = iter->centerZ - iter->radius;
3640 }
3641 else
3642 {
3643 // We're inside the bounding sphere (and, if the planet
3644 // is spherical, inside the planet.)
3645 iter->farZ = iter->nearZ * 2.0f;
3646 }
3647
3648 if (cloudHeight > 0.0f)
3649 {
3650 // If there's a cloud layer, we need to move the
3651 // far plane out so that the clouds aren't clipped
3652 float cloudLayerRadius = eradius + cloudHeight;
3653 iter->farZ -= (float) sqrt(square(cloudLayerRadius) -
3654 square(eradius));
3655 }
3656 }
3657
3658 *notCulled = *iter;
3659 notCulled++;
3660 #ifdef USE_HDR
3661 if (iter->discSizeInPixels > 1.0f &&
3662 iter->appMag < starMaxMag)
3663 {
3664 starMaxMag = iter->appMag;
3665 brightestStar = iter->star;
3666 foundBrightestStar = true;
3667 }
3668 #endif
3669 }
3670 }
3671
3672 renderList.resize(notCulled - renderList.begin());
3673
3674 // The calls to buildRenderLists/renderStars filled renderList
3675 // with visible bodies. Sort it front to back, then
3676 // render each entry in reverse order (TODO: convenient, but not
3677 // ideal for performance; should render opaque objects front to
3678 // back, then translucent objects back to front. However, the
3679 // amount of overdraw in Celestia is typically low.)
3680 sort(renderList.begin(), renderList.end());
3681
3682 // Sort the annotations
3683 sort(depthSortedAnnotations.begin(), depthSortedAnnotations.end());
3684
3685 // Sort the orbit paths
3686 sort(orbitPathList.begin(), orbitPathList.end());
3687
3688 int nEntries = renderList.size();
3689
3690 #ifdef USE_HDR
3691 // Compute 1 eclipse between eye - closest body - brightest star
3692 // This prevents an eclipsed star from increasing exposure
3693 bool eyeNotEclipsed = true;
3694 closestBody = renderList.empty() ? renderList.end() : renderList.begin();
3695 if (foundClosestBody &&
3696 closestBody != renderList.end() &&
3697 closestBody->renderableType == RenderListEntry::RenderableBody &&
3698 closestBody->body && brightestStar)
3699 {
3700 const Body *body = closestBody->body;
3701 double scale = astro::microLightYearsToKilometers(1.0);
3702 Point3d posBody = body->getAstrocentricPosition(now);
3703 Point3d posStar;
3704 Point3d posEye = astrocentricPosition(observer.getPosition(), *brightestStar, now);
3705
3706 if (body->getSystem() &&
3707 body->getSystem()->getStar() &&
3708 body->getSystem()->getStar() != brightestStar)
3709 {
3710 UniversalCoord center = body->getSystem()->getStar()->getPosition(now);
3711 Vec3d v = brightestStar->getPosition(now) - center;
3712 posStar = Point3d(v.x, v.y, v.z);
3713 }
3714 else
3715 {
3716 posStar = brightestStar->getPosition(now);
3717 }
3718
3719 posStar.x /= scale;
3720 posStar.y /= scale;
3721 posStar.z /= scale;
3722 Vec3d lightToBodyDir = posBody - posStar;
3723 Vec3d bodyToEyeDir = posEye - posBody;
3724
3725 if (lightToBodyDir * bodyToEyeDir > 0.0)
3726 {
3727 double dist = distance(posEye,
3728 Ray3d(posBody, lightToBodyDir));
3729 if (dist < body->getRadius())
3730 eyeNotEclipsed = false;
3731 }
3732 }
3733
3734 if (eyeNotEclipsed)
3735 {
3736 maxBodyMag = min(maxBodyMag, starMaxMag);
3737 }
3738 #endif
3739
3740 // Since we're rendering objects of a huge range of sizes spread over
3741 // vast distances, we can't just rely on the hardware depth buffer to
3742 // handle hidden surface removal without a little help. We'll partition
3743 // the depth buffer into spans that can be rendered without running
3744 // into terrible depth buffer precision problems. Typically, each body
3745 // with an apparent size greater than one pixel is allocated its own
3746 // depth buffer interval. However, this will not correctly handle
3747 // overlapping objects. If two objects overlap in depth, we must
3748 // assign them to the same interval.
3749
3750 depthPartitions.clear();
3751 int nIntervals = 0;
3752 float prevNear = -1e12f; // ~ 1 light year
3753 if (nEntries > 0)
3754 prevNear = renderList[nEntries - 1].farZ * 1.01f;
3755
3756 int i;
3757
3758 // Completely partition the depth buffer. Scan from back to front
3759 // through all the renderable items that passed the culling test.
3760 for (i = nEntries - 1; i >= 0; i--)
3761 {
3762 // Only consider renderables that will occupy more than one pixel.
3763 if (renderList[i].discSizeInPixels > 1)
3764 {
3765 if (nIntervals == 0 || renderList[i].farZ >= depthPartitions[nIntervals - 1].nearZ)
3766 {
3767 // This object spans a depth interval that's disjoint with
3768 // the current interval, so create a new one for it, and
3769 // another interval to fill the gap between the last
3770 // interval.
3771 DepthBufferPartition partition;
3772 partition.index = nIntervals;
3773 partition.nearZ = renderList[i].farZ;
3774 partition.farZ = prevNear;
3775
3776 // Omit null intervals
3777 // TODO: Is this necessary? Shouldn't the >= test prevent this?
3778 if (partition.nearZ != partition.farZ)
3779 {
3780 depthPartitions.push_back(partition);
3781 nIntervals++;
3782 }
3783
3784 partition.index = nIntervals;
3785 partition.nearZ = renderList[i].nearZ;
3786 partition.farZ = renderList[i].farZ;
3787 depthPartitions.push_back(partition);
3788 nIntervals++;
3789
3790 prevNear = partition.nearZ;
3791 }
3792 else
3793 {
3794 // This object overlaps the current span; expand the
3795 // interval so that it completely contains the object.
3796 DepthBufferPartition& partition = depthPartitions[nIntervals - 1];
3797 partition.nearZ = max(partition.nearZ, renderList[i].nearZ);
3798 partition.farZ = min(partition.farZ, renderList[i].farZ);
3799 prevNear = partition.nearZ;
3800 }
3801 }
3802 }
3803
3804 // Scan the list of orbit paths and find the closest one. We'll need
3805 // adjust the nearest interval to accommodate it.
3806 float zNearest = prevNear;
3807 for (i = 0; i < (int) orbitPathList.size(); i++)
3808 {
3809 const OrbitPathListEntry& o = orbitPathList[i];
3810 float minNearDistance = min(-o.radius * 0.0001f, o.centerZ + o.radius);
3811 if (minNearDistance > zNearest)
3812 zNearest = minNearDistance;
3813 }
3814
3815
3816 // Adjust the nearest interval to include the closest marker (if it's
3817 // closer to the observer than anything else
3818 if (!depthSortedAnnotations.empty())
3819 {
3820 // Factor of 0.999 makes sure ensures that the near plane does not fall
3821 // exactly at the marker's z coordinate (in which case the marker
3822 // would be susceptible to being clipped.)
3823 if (-depthSortedAnnotations[0].position.z > zNearest)
3824 zNearest = -depthSortedAnnotations[0].position.z * 0.999f;
3825 }
3826
3827
3828 #if DEBUG_COALESCE
3829 clog << "nEntries: " << nEntries << ", zNearest: " << zNearest << ", prevNear: " << prevNear << "\n";
3830 #endif
3831
3832 // If the nearest distance wasn't set, nothing should appear
3833 // in the frontmost depth buffer interval (so we can set the near plane
3834 // of the front interval to whatever we want as long as it's less than
3835 // the far plane distance.
3836 if (zNearest == prevNear)
3837 zNearest = 0.0f;
3838
3839 // Add one last interval for the span from 0 to the front of the
3840 // nearest object
3841 {
3842 // TODO: closest object may not be at entry 0, since objects are
3843 // sorted by far distance.
3844 float closest = zNearest;
3845 if (nEntries > 0)
3846 {
3847 closest = max(closest, renderList[0].nearZ);
3848
3849 // Setting a the near plane distance to zero results in unreliable rendering, even
3850 // if we don't care about the depth buffer. Compromise and set the near plane
3851 // distance to a small fraction of distance to the nearest object.
3852 if (closest == 0.0f)
3853 {
3854 closest = renderList[0].nearZ * 0.01f;
3855 }
3856 }
3857
3858 DepthBufferPartition partition;
3859 partition.index = nIntervals;
3860 partition.nearZ = closest;
3861 partition.farZ = prevNear;
3862 depthPartitions.push_back(partition);
3863
3864 nIntervals++;
3865 }
3866
3867 // If orbits are enabled, adjust the farthest partition so that it
3868 // can contain the orbit.
3869 if (!orbitPathList.empty())
3870 {
3871 depthPartitions[0].farZ = min(depthPartitions[0].farZ,
3872 orbitPathList[orbitPathList.size() - 1].centerZ -
3873 orbitPathList[orbitPathList.size() - 1].radius);
3874 }
3875
3876 // We want to avoid overpartitioning the depth buffer. In this stage, we coalesce
3877 // partitions that have small spans in the depth buffer.
3878 // TODO: Implement this step!
3879
3880 vector<Annotation>::iterator annotation = depthSortedAnnotations.begin();
3881
3882 // Render everything that wasn't culled.
3883 float intervalSize = 1.0f / (float) max(1, nIntervals);
3884 i = nEntries - 1;
3885 for (int interval = 0; interval < nIntervals; interval++)
3886 {
3887 currentIntervalIndex = interval;
3888 beginObjectAnnotations();
3889
3890 float nearPlaneDistance = -depthPartitions[interval].nearZ;
3891 float farPlaneDistance = -depthPartitions[interval].farZ;
3892
3893 // Set the depth range for this interval--each interval is allocated an
3894 // equal section of the depth buffer.
3895 glDepthRange(1.0f - (float) (interval + 1) * intervalSize,
3896 1.0f - (float) interval * intervalSize);
3897
3898 // Set up a perspective projection using the current interval's near and
3899 // far clip planes.
3900 glMatrixMode(GL_PROJECTION);
3901 glLoadIdentity();
3902 gluPerspective(fov,
3903 (float) windowWidth / (float) windowHeight,
3904 nearPlaneDistance,
3905 farPlaneDistance);
3906 glMatrixMode(GL_MODELVIEW);
3907
3908 Frustum intervalFrustum(degToRad(fov),
3909 (float) windowWidth / (float) windowHeight,
3910 -depthPartitions[interval].nearZ,
3911 -depthPartitions[interval].farZ);
3912
3913
3914 #if DEBUG_COALESCE
3915 clog << "interval: " << interval <<
3916 ", near: " << -depthPartitions[interval].nearZ <<
3917 ", far: " << -depthPartitions[interval].farZ <<
3918 "\n";
3919 #endif
3920 int firstInInterval = i;
3921
3922 // Render just the opaque objects in the first pass
3923 while (i >= 0 && renderList[i].farZ < depthPartitions[interval].nearZ)
3924 {
3925 // This interval should completely contain the item
3926 // Unless it's just a point?
3927 //assert(renderList[i].nearZ <= depthPartitions[interval].near);
3928
3929 #if DEBUG_COALESCE
3930 switch (renderList[i].renderableType)
3931 {
3932 case RenderListEntry::RenderableBody:
3933 if (renderList[i].discSizeInPixels > 1)
3934 {
3935 clog << renderList[i].body->getName() << "\n";
3936 }
3937 else
3938 {
3939 clog << "point: " << renderList[i].body->getName() << "\n";
3940 }
3941 break;
3942
3943 case RenderListEntry::RenderableStar:
3944 if (renderList[i].discSizeInPixels > 1)
3945 {
3946 clog << "Star\n";
3947 }
3948 else
3949 {
3950 clog << "point: " << "Star" << "\n";
3951 }
3952 break;
3953
3954 default:
3955 break;
3956 }
3957 #endif
3958 // Treat objects that are smaller than one pixel as transparent and render
3959 // them in the second pass.
3960 if (renderList[i].isOpaque && renderList[i].discSizeInPixels > 1.0f)
3961 renderItem(renderList[i], observer, m_cameraOrientation, nearPlaneDistance, farPlaneDistance);
3962
3963 i--;
3964 }
3965
3966 // Render orbit paths
3967 if (!orbitPathList.empty())
3968 {
3969 glDisable(GL_LIGHTING);
3970 glDisable(GL_TEXTURE_2D);
3971 glEnable(GL_DEPTH_TEST);
3972 glDepthMask(GL_FALSE);
3973 #ifdef USE_HDR
3974 glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
3975 #else
3976 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3977 #endif
3978 if ((renderFlags & ShowSmoothLines) != 0)
3979 {
3980 enableSmoothLines();
3981 }
3982
3983 // Scan through the list of orbits and render any that overlap this interval
3984 for (vector<OrbitPathListEntry>::const_iterator orbitIter = orbitPathList.begin();
3985 orbitIter != orbitPathList.end(); orbitIter++)
3986 {
3987 // Test for overlap
3988 float nearZ = -orbitIter->centerZ - orbitIter->radius;
3989 float farZ = -orbitIter->centerZ + orbitIter->radius;
3990
3991 // Don't render orbits when they're completely outside this
3992 // depth interval. Also, don't render an orbit in this
3993 // interval if it is vastly larger than the interval
3994 // range; otherwise, the GPU will have precision troubles
3995 // when clipping, producing visual artifacts. The factor
3996 // of 1e5 may need some tuning.
3997 if (nearZ < farPlaneDistance && farZ > nearPlaneDistance &&
3998 orbitIter->radius < 1.0e8f * (farPlaneDistance - nearPlaneDistance))
3999 {
4000 #ifdef DEBUG_COALESCE
4001 switch (interval % 6)
4002 {
4003 case 0: glColor4f(1.0f, 0.0f, 0.0f, 1.0f); break;
4004 case 1: glColor4f(1.0f, 1.0f, 0.0f, 1.0f); break;
4005 case 2: glColor4f(0.0f, 1.0f, 0.0f, 1.0f); break;
4006 case 3: glColor4f(0.0f, 1.0f, 1.0f, 1.0f); break;
4007 case 4: glColor4f(0.0f, 0.0f, 1.0f, 1.0f); break;
4008 case 5: glColor4f(1.0f, 0.0f, 1.0f, 1.0f); break;
4009 default: glColor4f(1.0f, 1.0f, 1.0f, 1.0f); break;
4010 }
4011 #endif
4012 orbitsRendered++;
4013 renderOrbit(*orbitIter, now, m_cameraOrientation, intervalFrustum, nearPlaneDistance, farPlaneDistance);
4014
4015 #if DEBUG_COALESCE
4016 if (highlightObject.body() == orbitIter->body)
4017 {
4018 clog << "orbit, radius=" << orbitIter->radius << "\n";
4019 }
4020 #endif
4021 }
4022 else
4023 orbitsSkipped++;
4024 }
4025
4026 if ((renderFlags & ShowSmoothLines) != 0)
4027 disableSmoothLines();
4028 glDepthMask(GL_FALSE);
4029 }
4030
4031 // Render transparent objects in the second pass
4032 i = firstInInterval;
4033 while (i >= 0 && renderList[i].farZ < depthPartitions[interval].nearZ)
4034 {
4035 if (!renderList[i].isOpaque || renderList[i].discSizeInPixels <= 1.0f)
4036 renderItem(renderList[i], observer, m_cameraOrientation, nearPlaneDistance, farPlaneDistance);
4037
4038 i--;
4039 }
4040
4041 // Render annotations in this interval
4042 if ((renderFlags & ShowSmoothLines) != 0)
4043 enableSmoothLines();
4044 annotation = renderSortedAnnotations(annotation, -depthPartitions[interval].nearZ, -depthPartitions[interval].farZ, FontNormal);
4045 endObjectAnnotations();
4046 if ((renderFlags & ShowSmoothLines) != 0)
4047 disableSmoothLines();
4048 glDisable(GL_DEPTH_TEST);
4049 }
4050 #if 0
4051 // TODO: Debugging output for new orbit code; remove when development is complete
4052 clog << "orbits: " << orbitsRendered
4053 << ", splines: " << splinesRendered
4054 << ", skipped: " << orbitsSkipped
4055 << ", sections culled: " << sectionsCulled
4056 << ", nIntervals: " << nIntervals << "\n";
4057 #endif
4058 splinesRendered = 0;
4059 orbitsRendered = 0;
4060 orbitsSkipped = 0;
4061 sectionsCulled = 0;
4062
4063 // reset the depth range
4064 glDepthRange(0, 1);
4065 }
4066
4067 renderForegroundAnnotations(FontNormal);
4068
4069 glMatrixMode(GL_PROJECTION);
4070 glLoadIdentity();
4071 gluPerspective(fov,
4072 (float) windowWidth / (float) windowHeight,
4073 NEAR_DIST, FAR_DIST);
4074 glMatrixMode(GL_MODELVIEW);
4075
4076 if (!selectionVisible && (renderFlags & ShowMarkers))
4077 renderSelectionPointer(observer, now, xfrustum, sel);
4078
4079 // Pop camera orientation matrix
4080 glPopMatrix();
4081
4082 glEnable(GL_TEXTURE_2D);
4083 glDisable(GL_LIGHTING);
4084 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4085
4086 glPolygonMode(GL_FRONT, GL_FILL);
4087 glPolygonMode(GL_BACK, GL_FILL);
4088
4089 glDisable(GL_BLEND);
4090 glDepthMask(GL_TRUE);
4091 glEnable(GL_LIGHTING);
4092
4093 #if 0
4094 int errCode = glGetError();
4095 if (errCode != GL_NO_ERROR)
4096 {
4097 cout << "glError: " << (char*) gluErrorString(errCode) << '\n';
4098 }
4099 #endif
4100
4101 if (videoSync && glx::glXWaitVideoSyncSGI != NULL)
4102 {
4103 unsigned int count;
4104 glx::glXGetVideoSyncSGI(&count);
4105 glx::glXWaitVideoSyncSGI(2, (count+1) & 1, &count);
4106 }
4107 }
4108
4109
renderRingSystem(float innerRadius,float outerRadius,float beginAngle,float endAngle,unsigned int nSections)4110 static void renderRingSystem(float innerRadius,
4111 float outerRadius,
4112 float beginAngle,
4113 float endAngle,
4114 unsigned int nSections)
4115 {
4116 float angle = endAngle - beginAngle;
4117
4118 glBegin(GL_QUAD_STRIP);
4119 for (unsigned int i = 0; i <= nSections; i++)
4120 {
4121 float t = (float) i / (float) nSections;
4122 float theta = beginAngle + t * angle;
4123 float s = (float) sin(theta);
4124 float c = (float) cos(theta);
4125 glTexCoord2f(0, 0.5f);
4126 glVertex3f(c * innerRadius, 0, s * innerRadius);
4127 glTexCoord2f(1, 0.5f);
4128 glVertex3f(c * outerRadius, 0, s * outerRadius);
4129 }
4130 glEnd();
4131 }
4132
4133
4134 // If the an object occupies a pixel or less of screen space, we don't
4135 // render its mesh at all and just display a starlike point instead.
4136 // Switching between the particle and mesh renderings of an object is
4137 // jarring, however . . . so we'll blend in the particle view of the
4138 // object to smooth things out, making it dimmer as the disc size exceeds the
4139 // max disc size.
renderObjectAsPoint_nosprite(Point3f position,float radius,float appMag,float _faintestMag,float discSizeInPixels,Color color,const Quatf & cameraOrientation,bool useHalos)4140 void Renderer::renderObjectAsPoint_nosprite(Point3f position,
4141 float radius,
4142 float appMag,
4143 float _faintestMag,
4144 float discSizeInPixels,
4145 Color color,
4146 const Quatf& cameraOrientation,
4147 bool useHalos)
4148 {
4149 float maxDiscSize = 1.0f;
4150 float maxBlendDiscSize = maxDiscSize + 3.0f;
4151 float discSize = 1.0f;
4152
4153 if (discSizeInPixels < maxBlendDiscSize || useHalos)
4154 {
4155 float fade = 1.0f;
4156 if (discSizeInPixels > maxDiscSize)
4157 {
4158 fade = (maxBlendDiscSize - discSizeInPixels) /
4159 (maxBlendDiscSize - maxDiscSize - 1.0f);
4160 if (fade > 1)
4161 fade = 1;
4162 }
4163
4164 #ifdef USE_HDR
4165 float fieldCorr = 2.0f * FOV/(fov + FOV);
4166 float satPoint = saturationMagNight * (1.0f + fieldCorr * fieldCorr);
4167 #else
4168 float satPoint = saturationMag;
4169 #endif
4170 float a = (_faintestMag - appMag) * brightnessScale + brightnessBias;
4171 if (starStyle == ScaledDiscStars && a > 1.0f)
4172 discSize = min(discSize * (2.0f * a - 1.0f), maxDiscSize);
4173 a = clamp(a) * fade;
4174
4175 // We scale up the particle by a factor of 1.6 (at fov = 45deg)
4176 // so that it's more visible--the texture we use has fuzzy edges,
4177 // and if we render it in just one pixel, it's likely to disappear.
4178 Mat3f m = cameraOrientation.toMatrix3();
4179 Point3f center = position;
4180
4181 // Offset the glare sprite so that it lies in front of the object
4182 Vec3f direction(center.x, center.y, center.z);
4183 direction.normalize();
4184
4185 // Position the sprite on the the line between the viewer and the
4186 // object, and on a plane normal to the view direction.
4187 center = center + direction * (radius / ((Vec3f(0, 0, 1.0f) * m) * direction));
4188
4189 float centerZ = (center * m.transpose()).z;
4190 float size = discSize * pixelSize * 1.6f * centerZ / corrFac;
4191
4192 Vec3f v0 = Vec3f(-1, -1, 0) * m;
4193 Vec3f v1 = Vec3f( 1, -1, 0) * m;
4194 Vec3f v2 = Vec3f( 1, 1, 0) * m;
4195 Vec3f v3 = Vec3f(-1, 1, 0) * m;
4196
4197 glEnable(GL_DEPTH_TEST);
4198
4199 starTex->bind();
4200 glColor(color, a);
4201 glBegin(GL_QUADS);
4202 glTexCoord2f(0, 1);
4203 glVertex(center + (v0 * size));
4204 glTexCoord2f(1, 1);
4205 glVertex(center + (v1 * size));
4206 glTexCoord2f(1, 0);
4207 glVertex(center + (v2 * size));
4208 glTexCoord2f(0, 0);
4209 glVertex(center + (v3 * size));
4210 glEnd();
4211
4212 // If the object is brighter than magnitude 1, add a halo around it to
4213 // make it appear more brilliant. This is a hack to compensate for the
4214 // limited dynamic range of monitors.
4215 if (useHalos && appMag < satPoint)
4216 {
4217 float dist = center.distanceFromOrigin();
4218 float s = dist * 0.001f * (3 - (appMag - satPoint)) * 2;
4219 if (s > size * 3)
4220 size = s * 2.0f/(1.0f + FOV/fov);
4221 else
4222 size = size * 3;
4223
4224 float realSize = discSizeInPixels * pixelSize * dist;
4225 if (size < realSize * 6)
4226 size = realSize * 6;
4227
4228 a = GlareOpacity * clamp((appMag - satPoint) * -0.8f);
4229 gaussianGlareTex->bind();
4230 glColor(color, a);
4231 glBegin(GL_QUADS);
4232 glTexCoord2f(0, 1);
4233 glVertex(center + (v0 * size));
4234 glTexCoord2f(1, 1);
4235 glVertex(center + (v1 * size));
4236 glTexCoord2f(1, 0);
4237 glVertex(center + (v2 * size));
4238 glTexCoord2f(0, 0);
4239 glVertex(center + (v3 * size));
4240 glEnd();
4241 }
4242
4243 glDisable(GL_DEPTH_TEST);
4244 }
4245 }
4246
4247
4248 // If the an object occupies a pixel or less of screen space, we don't
4249 // render its mesh at all and just display a starlike point instead.
4250 // Switching between the particle and mesh renderings of an object is
4251 // jarring, however . . . so we'll blend in the particle view of the
4252 // object to smooth things out, making it dimmer as the disc size exceeds the
4253 // max disc size.
renderObjectAsPoint(Point3f position,float radius,float appMag,float _faintestMag,float discSizeInPixels,Color color,const Quatf & cameraOrientation,bool useHalos,bool emissive)4254 void Renderer::renderObjectAsPoint(Point3f position,
4255 float radius,
4256 float appMag,
4257 float _faintestMag,
4258 float discSizeInPixels,
4259 Color color,
4260 const Quatf& cameraOrientation,
4261 bool useHalos,
4262 bool emissive)
4263 {
4264 float maxDiscSize = (starStyle == ScaledDiscStars) ? MaxScaledDiscStarSize : 1.0f;
4265 float maxBlendDiscSize = maxDiscSize + 3.0f;
4266
4267 bool useScaledDiscs = starStyle == ScaledDiscStars;
4268
4269 if (discSizeInPixels < maxBlendDiscSize || useHalos)
4270 {
4271 float alpha = 1.0f;
4272 float fade = 1.0f;
4273 float size = BaseStarDiscSize;
4274 #ifdef USE_HDR
4275 float fieldCorr = 2.0f * FOV/(fov + FOV);
4276 float satPoint = saturationMagNight * (1.0f + fieldCorr * fieldCorr);
4277 satPoint += brightPlus;
4278 #else
4279 float satPoint = _faintestMag - (1.0f - brightnessBias) / brightnessScale;
4280 #endif
4281
4282 if (discSizeInPixels > maxDiscSize)
4283 {
4284 fade = (maxBlendDiscSize - discSizeInPixels) /
4285 (maxBlendDiscSize - maxDiscSize);
4286 if (fade > 1)
4287 fade = 1;
4288 }
4289
4290 alpha = (_faintestMag - appMag) * brightnessScale * 2.0f + brightnessBias;
4291
4292 float pointSize = size;
4293 float glareSize = 0.0f;
4294 float glareAlpha = 0.0f;
4295 if (useScaledDiscs)
4296 {
4297 if (alpha < 0.0f)
4298 {
4299 alpha = 0.0f;
4300 }
4301 else if (alpha > 1.0f)
4302 {
4303 float discScale = min(MaxScaledDiscStarSize, (float) pow(2.0f, 0.3f * (satPoint - appMag)));
4304 pointSize *= max(1.0f, discScale);
4305
4306 glareAlpha = min(0.5f, discScale / 4.0f);
4307 if (discSizeInPixels > MaxScaledDiscStarSize)
4308 {
4309 glareAlpha = min(glareAlpha,
4310 (MaxScaledDiscStarSize - discSizeInPixels) / MaxScaledDiscStarSize + 1.0f);
4311 }
4312 glareSize = pointSize * 3.0f;
4313
4314 alpha = 1.0f;
4315 }
4316 }
4317 else
4318 {
4319 if (alpha < 0.0f)
4320 {
4321 alpha = 0.0f;
4322 }
4323 else if (alpha > 1.0f)
4324 {
4325 float discScale = min(100.0f, satPoint - appMag + 2.0f);
4326 glareAlpha = min(GlareOpacity, (discScale - 2.0f) / 4.0f);
4327 glareSize = pointSize * discScale * 2.0f ;
4328 if (emissive)
4329 glareSize = max(glareSize, pointSize * discSizeInPixels * 3.0f);
4330 }
4331 }
4332
4333 alpha *= fade;
4334 if (!emissive)
4335 {
4336 glareSize = max(glareSize, pointSize * discSizeInPixels * 3.0f);
4337 glareAlpha *= fade;
4338 }
4339
4340 Mat3f m = cameraOrientation.toMatrix3();
4341 Point3f center = position;
4342
4343 // Offset the glare sprite so that it lies in front of the object
4344 Vec3f direction(center.x, center.y, center.z);
4345 direction.normalize();
4346
4347 // Position the sprite on the the line between the viewer and the
4348 // object, and on a plane normal to the view direction.
4349 center = center + direction * (radius / ((Vec3f(0, 0, 1.0f) * m) * direction));
4350
4351 glEnable(GL_DEPTH_TEST);
4352 #if !defined(NO_MAX_POINT_SIZE)
4353 // TODO: OpenGL appears to limit the max point size unless we
4354 // actually set up a shader that writes the pointsize values. To get
4355 // around this, we'll use billboards.
4356 Vec3f v0 = Vec3f(-1, -1, 0) * m;
4357 Vec3f v1 = Vec3f( 1, -1, 0) * m;
4358 Vec3f v2 = Vec3f( 1, 1, 0) * m;
4359 Vec3f v3 = Vec3f(-1, 1, 0) * m;
4360 float distanceAdjust = pixelSize * center.distanceFromOrigin() * 0.5f;
4361
4362 if (starStyle == PointStars)
4363 {
4364 glDisable(GL_TEXTURE_2D);
4365 glBegin(GL_POINTS);
4366 glColor(color, alpha);
4367 glVertex(center);
4368 glEnd();
4369 glEnable(GL_TEXTURE_2D);
4370 }
4371 else
4372 {
4373 gaussianDiscTex->bind();
4374
4375 pointSize *= distanceAdjust;
4376 glBegin(GL_QUADS);
4377 glColor(color, alpha);
4378 glTexCoord2f(0, 1);
4379 glVertex(center + (v0 * pointSize));
4380 glTexCoord2f(1, 1);
4381 glVertex(center + (v1 * pointSize));
4382 glTexCoord2f(1, 0);
4383 glVertex(center + (v2 * pointSize));
4384 glTexCoord2f(0, 0);
4385 glVertex(center + (v3 * pointSize));
4386 glEnd();
4387 }
4388
4389 // If the object is brighter than magnitude 1, add a halo around it to
4390 // make it appear more brilliant. This is a hack to compensate for the
4391 // limited dynamic range of monitors.
4392 //
4393 // TODO: Stars look fine but planets look unrealistically bright
4394 // with halos.
4395 if (useHalos && glareAlpha > 0.0f)
4396 {
4397 gaussianGlareTex->bind();
4398
4399 glareSize *= distanceAdjust;
4400 glBegin(GL_QUADS);
4401 glColor(color, glareAlpha);
4402 glTexCoord2f(0, 1);
4403 glVertex(center + (v0 * glareSize));
4404 glTexCoord2f(1, 1);
4405 glVertex(center + (v1 * glareSize));
4406 glTexCoord2f(1, 0);
4407 glVertex(center + (v2 * glareSize));
4408 glTexCoord2f(0, 0);
4409 glVertex(center + (v3 * glareSize));
4410 glEnd();
4411 }
4412 #else
4413 // Disabled because of point size limits
4414 glEnable(GL_POINT_SPRITE_ARB);
4415 glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
4416
4417 gaussianDiscTex->bind();
4418 glColor(color, alpha);
4419 glPointSize(pointSize);
4420 glBegin(GL_POINTS);
4421 glVertex(center);
4422 glEnd();
4423
4424 // If the object is brighter than magnitude 1, add a halo around it to
4425 // make it appear more brilliant. This is a hack to compensate for the
4426 // limited dynamic range of monitors.
4427 //
4428 // TODO: Stars look fine but planets look unrealistically bright
4429 // with halos.
4430 if (useHalos && glareAlpha > 0.0f)
4431 {
4432 gaussianGlareTex->bind();
4433
4434 glColor(color, glareAlpha);
4435 glPointSize(glareSize);
4436 glBegin(GL_POINTS);
4437 glVertex(center);
4438 glEnd();
4439 }
4440
4441 glDisable(GL_POINT_SPRITE_ARB);
4442 glDisable(GL_DEPTH_TEST);
4443 #endif // NO_MAX_POINT_SIZE
4444 }
4445 }
4446
4447
renderBumpMappedMesh(const GLContext & context,Texture & baseTexture,Texture & bumpTexture,Vec3f lightDirection,Quatf orientation,Color ambientColor,const Frustum & frustum,float lod)4448 static void renderBumpMappedMesh(const GLContext& context,
4449 Texture& baseTexture,
4450 Texture& bumpTexture,
4451 Vec3f lightDirection,
4452 Quatf orientation,
4453 Color ambientColor,
4454 const Frustum& frustum,
4455 float lod)
4456 {
4457 // We're doing our own per-pixel lighting, so disable GL's lighting
4458 glDisable(GL_LIGHTING);
4459
4460 // Render the base texture on the first pass . . . The color
4461 // should have already been set up by the caller.
4462 g_lodSphere->render(context,
4463 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
4464 frustum, lod,
4465 &baseTexture);
4466
4467 // The 'default' light vector for the bump map is (0, 0, 1). Determine
4468 // a rotation transformation that will move the sun direction to
4469 // this vector.
4470 Quatf lightOrientation = Quatf::vecToVecRotation(Vec3f(0.0f, 0.0f, 1.0f), lightDirection);
4471
4472 glEnable(GL_BLEND);
4473 glBlendFunc(GL_DST_COLOR, GL_ZERO);
4474
4475 // Set up the bump map with one directional light source
4476 SetupCombinersBumpMap(bumpTexture, *normalizationTex, ambientColor);
4477
4478 // The second set texture coordinates will contain the light
4479 // direction in tangent space. We'll generate the texture coordinates
4480 // from the surface normals using GL_NORMAL_MAP_EXT and then
4481 // use the texture matrix to rotate them into tangent space.
4482 // This method of generating tangent space light direction vectors
4483 // isn't as general as transforming the light direction by an
4484 // orthonormal basis for each mesh vertex, but it works well enough
4485 // for spheres illuminated by directional light sources.
4486 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
4487
4488 // Set up GL_NORMAL_MAP_EXT texture coordinate generation. This
4489 // mode is part of the cube map extension.
4490 glEnable(GL_TEXTURE_GEN_R);
4491 glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
4492 glEnable(GL_TEXTURE_GEN_S);
4493 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
4494 glEnable(GL_TEXTURE_GEN_T);
4495 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
4496
4497 // Set up the texture transformation--the light direction and the
4498 // viewer orientation both need to be considered.
4499 glMatrixMode(GL_TEXTURE);
4500 glScalef(-1.0f, 1.0f, 1.0f);
4501 glRotate(lightOrientation * ~orientation);
4502 glMatrixMode(GL_MODELVIEW);
4503 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
4504
4505 g_lodSphere->render(context,
4506 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
4507 frustum, lod,
4508 &bumpTexture);
4509
4510 // Reset the second texture unit
4511 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
4512 glMatrixMode(GL_TEXTURE);
4513 glLoadIdentity();
4514 glMatrixMode(GL_MODELVIEW);
4515 glDisable(GL_TEXTURE_GEN_R);
4516 glDisable(GL_TEXTURE_GEN_S);
4517 glDisable(GL_TEXTURE_GEN_T);
4518
4519 DisableCombiners();
4520 glDisable(GL_BLEND);
4521 }
4522
4523
renderSmoothMesh(const GLContext & context,Texture & baseTexture,Vec3f lightDirection,Quatf orientation,Color ambientColor,float lod,const Frustum & frustum,bool invert=false)4524 static void renderSmoothMesh(const GLContext& context,
4525 Texture& baseTexture,
4526 Vec3f lightDirection,
4527 Quatf orientation,
4528 Color ambientColor,
4529 float lod,
4530 const Frustum& frustum,
4531 bool invert = false)
4532 {
4533 Texture* textures[4];
4534
4535 // We're doing our own per-pixel lighting, so disable GL's lighting
4536 glDisable(GL_LIGHTING);
4537
4538 // The 'default' light vector for the bump map is (0, 0, 1). Determine
4539 // a rotation transformation that will move the sun direction to
4540 // this vector.
4541 Quatf lightOrientation = Quatf::vecToVecRotation(Vec3f(0.0f, 0.0f, 1.0f), lightDirection);
4542
4543 SetupCombinersSmooth(baseTexture, *normalizationTex, ambientColor, invert);
4544
4545 // The second set texture coordinates will contain the light
4546 // direction in tangent space. We'll generate the texture coordinates
4547 // from the surface normals using GL_NORMAL_MAP_EXT and then
4548 // use the texture matrix to rotate them into tangent space.
4549 // This method of generating tangent space light direction vectors
4550 // isn't as general as transforming the light direction by an
4551 // orthonormal basis for each mesh vertex, but it works well enough
4552 // for spheres illuminated by directional light sources.
4553 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
4554
4555 // Set up GL_NORMAL_MAP_EXT texture coordinate generation. This
4556 // mode is part of the cube map extension.
4557 glEnable(GL_TEXTURE_GEN_R);
4558 glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
4559 glEnable(GL_TEXTURE_GEN_S);
4560 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
4561 glEnable(GL_TEXTURE_GEN_T);
4562 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_ARB);
4563
4564 // Set up the texture transformation--the light direction and the
4565 // viewer orientation both need to be considered.
4566 glMatrixMode(GL_TEXTURE);
4567 glRotate(lightOrientation * ~orientation);
4568 glMatrixMode(GL_MODELVIEW);
4569 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
4570
4571 textures[0] = &baseTexture;
4572 g_lodSphere->render(context,
4573 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
4574 frustum, lod,
4575 textures, 1);
4576
4577 // Reset the second texture unit
4578 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
4579 glMatrixMode(GL_TEXTURE);
4580 glLoadIdentity();
4581 glMatrixMode(GL_MODELVIEW);
4582 glDisable(GL_TEXTURE_GEN_R);
4583 glDisable(GL_TEXTURE_GEN_S);
4584 glDisable(GL_TEXTURE_GEN_T);
4585
4586 DisableCombiners();
4587 }
4588
4589
4590 // Used to sort light sources in order of decreasing irradiance
4591 struct LightIrradiancePredicate
4592 {
4593 int unused;
4594
LightIrradiancePredicateLightIrradiancePredicate4595 LightIrradiancePredicate() {};
4596
operator ()LightIrradiancePredicate4597 bool operator()(const DirectionalLight& l0,
4598 const DirectionalLight& l1) const
4599 {
4600 return (l0.irradiance > l1.irradiance);
4601 }
4602 };
4603
4604
renderAtmosphere(const Atmosphere & atmosphere,Point3f center,float radius,const Vec3f & sunDirection,Color ambientColor,float fade,bool lit)4605 void renderAtmosphere(const Atmosphere& atmosphere,
4606 Point3f center,
4607 float radius,
4608 const Vec3f& sunDirection,
4609 Color ambientColor,
4610 float fade,
4611 bool lit)
4612 {
4613 if (atmosphere.height == 0.0f)
4614 return;
4615
4616 glDepthMask(GL_FALSE);
4617
4618 Vec3f eyeVec = center - Point3f(0.0f, 0.0f, 0.0f);
4619 double centerDist = eyeVec.length();
4620 // double surfaceDist = (double) centerDist - (double) radius;
4621
4622 Vec3f normal = eyeVec;
4623 normal = normal / (float) centerDist;
4624
4625 float tangentLength = (float) sqrt(square(centerDist) - square(radius));
4626 float atmRadius = tangentLength * radius / (float) centerDist;
4627 float atmOffsetFromCenter = square(radius) / (float) centerDist;
4628 Point3f atmCenter = center - atmOffsetFromCenter * normal;
4629
4630 Vec3f uAxis, vAxis;
4631 if (abs(normal.x) < abs(normal.y) && abs(normal.x) < abs(normal.z))
4632 {
4633 uAxis = Vec3f(1, 0, 0) ^ normal;
4634 uAxis.normalize();
4635 }
4636 else if (abs(eyeVec.y) < abs(normal.z))
4637 {
4638 uAxis = Vec3f(0, 1, 0) ^ normal;
4639 uAxis.normalize();
4640 }
4641 else
4642 {
4643 uAxis = Vec3f(0, 0, 1) ^ normal;
4644 uAxis.normalize();
4645 }
4646 vAxis = uAxis ^ normal;
4647
4648 float height = atmosphere.height / radius;
4649
4650 glBegin(GL_QUAD_STRIP);
4651 int divisions = 180;
4652 for (int i = 0; i <= divisions; i++)
4653 {
4654 float theta = (float) i / (float) divisions * 2 * (float) PI;
4655 Vec3f v = (float) cos(theta) * uAxis + (float) sin(theta) * vAxis;
4656 Point3f base = atmCenter + v * atmRadius;
4657 Vec3f toCenter = base - center;
4658
4659 float cosSunAngle = (toCenter * sunDirection) / radius;
4660 float brightness = 1.0f;
4661 float botColor[3];
4662 float topColor[3];
4663 botColor[0] = atmosphere.lowerColor.red();
4664 botColor[1] = atmosphere.lowerColor.green();
4665 botColor[2] = atmosphere.lowerColor.blue();
4666 topColor[0] = atmosphere.upperColor.red();
4667 topColor[1] = atmosphere.upperColor.green();
4668 topColor[2] = atmosphere.upperColor.blue();
4669
4670 if (cosSunAngle < 0.2f && lit)
4671 {
4672 if (cosSunAngle < -0.2f)
4673 {
4674 brightness = 0;
4675 }
4676 else
4677 {
4678 float t = (0.2f + cosSunAngle) * 2.5f;
4679 brightness = t;
4680 botColor[0] = Mathf::lerp(t, 1.0f, botColor[0]);
4681 botColor[1] = Mathf::lerp(t, 0.3f, botColor[1]);
4682 botColor[2] = Mathf::lerp(t, 0.0f, botColor[2]);
4683 topColor[0] = Mathf::lerp(t, 1.0f, topColor[0]);
4684 topColor[1] = Mathf::lerp(t, 0.3f, topColor[1]);
4685 topColor[2] = Mathf::lerp(t, 0.0f, topColor[2]);
4686 }
4687 }
4688
4689 glColor4f(botColor[0], botColor[1], botColor[2],
4690 0.85f * fade * brightness + ambientColor.red());
4691 glVertex(base - toCenter * height * 0.05f);
4692 glColor4f(topColor[0], topColor[1], topColor[2], 0.0f);
4693 glVertex(base + toCenter * height);
4694 }
4695 glEnd();
4696 }
4697
4698
ellipsoidTangent(const Vec3f & recipSemiAxes,const Vec3f & w,const Vec3f & e,const Vec3f & e_,float ee)4699 static Vec3f ellipsoidTangent(const Vec3f& recipSemiAxes,
4700 const Vec3f& w,
4701 const Vec3f& e,
4702 const Vec3f& e_,
4703 float ee)
4704 {
4705 // We want to find t such that -E(1-t) + Wt is the direction of a ray
4706 // tangent to the ellipsoid. A tangent ray will intersect the ellipsoid
4707 // at exactly one point. Finding the intersection between a ray and an
4708 // ellipsoid ultimately requires using the quadratic formula, which has
4709 // one solution when the discriminant (b^2 - 4ac) is zero. The code below
4710 // computes the value of t that results in a discriminant of zero.
4711 Vec3f w_(w.x * recipSemiAxes.x, w.y * recipSemiAxes.y, w.z * recipSemiAxes.z);
4712 float ww = w_ * w_;
4713 float ew = w_ * e_;
4714
4715 // Before elimination of terms:
4716 // float a = 4 * square(ee + ew) - 4 * (ee + 2 * ew + ww) * (ee - 1.0f);
4717 // float b = -8 * ee * (ee + ew) - 4 * (-2 * (ee + ew) * (ee - 1.0f));
4718 // float c = 4 * ee * ee - 4 * (ee * (ee - 1.0f));
4719
4720 // Simplify the below expression and eliminate the ee^2 terms; this
4721 // prevents precision errors, as ee tends to be a very large value.
4722 //T a = 4 * square(ee + ew) - 4 * (ee + 2 * ew + ww) * (ee - 1);
4723 //float a = 4 * square(ee + ew) - 4 * (ee + 2 * ew + ww) * (ee - 1.0f);
4724 float a = 4 * (square(ew) - ee * ww + ee + 2 * ew + ww);
4725 float b = -8 * (ee + ew);
4726 float c = 4 * ee;
4727
4728 float t = 0.0f;
4729 float discriminant = b * b - 4 * a * c;
4730
4731 if (discriminant < 0.0f)
4732 t = (-b + (float) sqrt(-discriminant)) / (2 * a); // Bad!
4733 else
4734 t = (-b + (float) sqrt(discriminant)) / (2 * a);
4735
4736 // V is the direction vector. We now need the point of intersection,
4737 // which we obtain by solving the quadratic equation for the ray-ellipse
4738 // intersection. Since we already know that the discriminant is zero,
4739 // the solution is just -b/2a
4740 Vec3f v = -e * (1 - t) + w * t;
4741 Vec3f v_(v.x * recipSemiAxes.x, v.y * recipSemiAxes.y, v.z * recipSemiAxes.z);
4742 float a1 = v_ * v_;
4743 float b1 = 2.0f * v_ * e_;
4744 float t1 = -b1 / (2 * a1);
4745
4746 return e + v * t1;
4747 }
4748
4749
renderEllipsoidAtmosphere(const Atmosphere & atmosphere,Point3f center,const Quatf & orientation,Vec3f semiAxes,const Vec3f & sunDirection,const LightingState & ls,float pixSize,bool lit)4750 void Renderer::renderEllipsoidAtmosphere(const Atmosphere& atmosphere,
4751 Point3f center,
4752 const Quatf& orientation,
4753 Vec3f semiAxes,
4754 const Vec3f& sunDirection,
4755 const LightingState& ls,
4756 float pixSize,
4757 bool lit)
4758 {
4759 if (atmosphere.height == 0.0f)
4760 return;
4761
4762 glDepthMask(GL_FALSE);
4763
4764 // Gradually fade in the atmosphere if it's thickness on screen is just
4765 // over one pixel.
4766 float fade = clamp(pixSize - 2);
4767
4768 Mat3f rot = orientation.toMatrix3();
4769 Mat3f irot = conjugate(orientation).toMatrix3();
4770
4771 Point3f eyePos(0.0f, 0.0f, 0.0f);
4772 float radius = max(semiAxes.x, max(semiAxes.y, semiAxes.z));
4773 Vec3f eyeVec = center - eyePos;
4774 eyeVec = eyeVec * irot;
4775 double centerDist = eyeVec.length();
4776
4777 float height = atmosphere.height / radius;
4778 Vec3f recipSemiAxes(1.0f / semiAxes.x, 1.0f / semiAxes.y, 1.0f / semiAxes.z);
4779
4780 Vec3f recipAtmSemiAxes = recipSemiAxes / (1.0f + height);
4781 Mat3f A = Mat3f::scaling(recipAtmSemiAxes);
4782 Mat3f A1 = Mat3f::scaling(recipSemiAxes);
4783
4784 // ellipDist is not the true distance from the surface unless the
4785 // planet is spherical. Computing the true distance requires finding
4786 // the roots of a sixth degree polynomial, and isn't actually what we
4787 // want anyhow since the atmosphere region is just the planet ellipsoid
4788 // multiplied by a uniform scale factor. The value that we do compute
4789 // is the distance to the surface along a line from the eye position to
4790 // the center of the ellipsoid.
4791 float ellipDist = (float) sqrt((eyeVec * A1) * (eyeVec * A1)) - 1.0f;
4792 bool within = ellipDist < height;
4793
4794 // Adjust the tesselation of the sky dome/ring based on distance from the
4795 // planet surface.
4796 int nSlices = MaxSkySlices;
4797 if (ellipDist < 0.25f)
4798 {
4799 nSlices = MinSkySlices + max(0, (int) ((ellipDist / 0.25f) * (MaxSkySlices - MinSkySlices)));
4800 nSlices &= ~1;
4801 }
4802
4803 int nRings = min(1 + (int) pixSize / 5, 6);
4804 int nHorizonRings = nRings;
4805 if (within)
4806 nRings += 12;
4807
4808 float horizonHeight = height;
4809 if (within)
4810 {
4811 if (ellipDist <= 0.0f)
4812 horizonHeight = 0.0f;
4813 else
4814 horizonHeight *= max((float) pow(ellipDist / height, 0.33f), 0.001f);
4815 }
4816
4817 Vec3f e = -eyeVec;
4818 Vec3f e_(e.x * recipSemiAxes.x, e.y * recipSemiAxes.y, e.z * recipSemiAxes.z);
4819 float ee = e_ * e_;
4820
4821 // Compute the cosine of the altitude of the sun. This is used to compute
4822 // the degree of sunset/sunrise coloration.
4823 float cosSunAltitude = 0.0f;
4824 {
4825 // Check for a sun either directly behind or in front of the viewer
4826 float cosSunAngle = (float) ((sunDirection * e) / centerDist);
4827 if (cosSunAngle < -1.0f + 1.0e-6f)
4828 {
4829 cosSunAltitude = 0.0f;
4830 }
4831 else if (cosSunAngle > 1.0f - 1.0e-6f)
4832 {
4833 cosSunAltitude = 0.0f;
4834 }
4835 else
4836 {
4837 Point3f tangentPoint = center +
4838 ellipsoidTangent(recipSemiAxes,
4839 (-sunDirection * irot) * (float) centerDist,
4840 e, e_, ee) * rot;
4841 Vec3f tangentDir = tangentPoint - eyePos;
4842 tangentDir.normalize();
4843 cosSunAltitude = sunDirection * tangentDir;
4844 }
4845 }
4846
4847 Vec3f normal = eyeVec;
4848 normal = normal / (float) centerDist;
4849
4850 Vec3f uAxis, vAxis;
4851 if (abs(normal.x) < abs(normal.y) && abs(normal.x) < abs(normal.z))
4852 {
4853 uAxis = Vec3f(1, 0, 0) ^ normal;
4854 uAxis.normalize();
4855 }
4856 else if (abs(eyeVec.y) < abs(normal.z))
4857 {
4858 uAxis = Vec3f(0, 1, 0) ^ normal;
4859 uAxis.normalize();
4860 }
4861 else
4862 {
4863 uAxis = Vec3f(0, 0, 1) ^ normal;
4864 uAxis.normalize();
4865 }
4866 vAxis = uAxis ^ normal;
4867
4868 // Compute the contour of the ellipsoid
4869 int i;
4870 for (i = 0; i <= nSlices; i++)
4871 {
4872 // We want rays with an origin at the eye point and tangent to the the
4873 // ellipsoid.
4874 float theta = (float) i / (float) nSlices * 2 * (float) PI;
4875 Vec3f w = (float) cos(theta) * uAxis + (float) sin(theta) * vAxis;
4876 w = w * (float) centerDist;
4877
4878 Vec3f toCenter = ellipsoidTangent(recipSemiAxes, w, e, e_, ee);
4879 skyContour[i].v = toCenter * rot;
4880 skyContour[i].centerDist = skyContour[i].v.length();
4881 skyContour[i].eyeDir = skyContour[i].v + (center - eyePos);
4882 skyContour[i].eyeDist = skyContour[i].eyeDir.length();
4883 skyContour[i].eyeDir.normalize();
4884
4885 float skyCapDist = (float) sqrt(square(skyContour[i].eyeDist) +
4886 square(horizonHeight * radius));
4887 skyContour[i].cosSkyCapAltitude = skyContour[i].eyeDist /
4888 skyCapDist;
4889 }
4890
4891
4892 Vec3f botColor(atmosphere.lowerColor.red(),
4893 atmosphere.lowerColor.green(),
4894 atmosphere.lowerColor.blue());
4895 Vec3f topColor(atmosphere.upperColor.red(),
4896 atmosphere.upperColor.green(),
4897 atmosphere.upperColor.blue());
4898 Vec3f sunsetColor(atmosphere.sunsetColor.red(),
4899 atmosphere.sunsetColor.green(),
4900 atmosphere.sunsetColor.blue());
4901 if (within)
4902 {
4903 Vec3f skyColor(atmosphere.skyColor.red(),
4904 atmosphere.skyColor.green(),
4905 atmosphere.skyColor.blue());
4906 if (ellipDist < 0.0f)
4907 topColor = skyColor;
4908 else
4909 topColor = skyColor + (topColor - skyColor) * (ellipDist / height);
4910 }
4911
4912 if (ls.nLights == 0 && lit)
4913 {
4914 Vec3f black(0.0f, 0.0f, 0.0f);
4915 botColor = topColor = sunsetColor = black;
4916 }
4917
4918 Vec3f zenith = (skyContour[0].v + skyContour[nSlices / 2].v);
4919 zenith.normalize();
4920 zenith *= skyContour[0].centerDist * (1.0f + horizonHeight * 2.0f);
4921
4922 float minOpacity = within ? (1.0f - ellipDist / height) * 0.75f : 0.0f;
4923 float sunset = cosSunAltitude < 0.9f ? 0.0f : (cosSunAltitude - 0.9f) * 10.0f;
4924
4925 // Build the list of vertices
4926 SkyVertex* vtx = skyVertices;
4927 for (i = 0; i <= nRings; i++)
4928 {
4929 float h = min(1.0f, (float) i / (float) nHorizonRings);
4930 float hh = (float) sqrt(h);
4931 float u = i <= nHorizonRings ? 0.0f :
4932 (float) (i - nHorizonRings) / (float) (nRings - nHorizonRings);
4933 float r = Mathf::lerp(h, 1.0f - (horizonHeight * 0.05f), 1.0f + horizonHeight);
4934 float atten = 1.0f - hh;
4935
4936 for (int j = 0; j < nSlices; j++)
4937 {
4938 Vec3f v;
4939 if (i <= nHorizonRings)
4940 v = skyContour[j].v * r;
4941 else
4942 v = (skyContour[j].v * (1.0f - u) + zenith * u) * r;
4943 Point3f p = center + v;
4944
4945
4946 Vec3f viewDir(p.x, p.y, p.z);
4947 viewDir.normalize();
4948 float cosSunAngle = viewDir * sunDirection;
4949 float cosAltitude = viewDir * skyContour[j].eyeDir;
4950 float brightness = 1.0f;
4951 float coloration = 0.0f;
4952 if (lit)
4953 {
4954 if (sunset > 0.0f && cosSunAngle > 0.7f && cosAltitude > 0.98f)
4955 {
4956 coloration = (1.0f / 0.30f) * (cosSunAngle - 0.70f);
4957 coloration *= 50.0f * (cosAltitude - 0.98f);
4958 coloration *= sunset;
4959 }
4960
4961 cosSunAngle = (skyContour[j].v * sunDirection) / skyContour[j].centerDist;
4962 if (cosSunAngle > -0.2f)
4963 {
4964 if (cosSunAngle < 0.3f)
4965 brightness = (cosSunAngle + 0.2f) * 2.0f;
4966 else
4967 brightness = 1.0f;
4968 }
4969 else
4970 {
4971 brightness = 0.0f;
4972 }
4973 }
4974
4975 vtx->x = p.x;
4976 vtx->y = p.y;
4977 vtx->z = p.z;
4978
4979 #if 0
4980 // Better way of generating sky color gradients--based on
4981 // altitude angle.
4982 if (!within)
4983 {
4984 hh = (1.0f - cosAltitude) / (1.0f - skyContour[j].cosSkyCapAltitude);
4985 }
4986 else
4987 {
4988 float top = pow((ellipDist / height), 0.125f) * skyContour[j].cosSkyCapAltitude;
4989 if (cosAltitude < top)
4990 hh = 1.0f;
4991 else
4992 hh = (1.0f - cosAltitude) / (1.0f - top);
4993 }
4994 hh = sqrt(hh);
4995 //hh = (float) pow(hh, 0.25f);
4996 #endif
4997
4998 atten = 1.0f - hh;
4999 Vec3f color = (1.0f - hh) * botColor + hh * topColor;
5000 brightness *= minOpacity + (1.0f - minOpacity) * fade * atten;
5001 if (coloration != 0.0f)
5002 color = (1.0f - coloration) * color + coloration * sunsetColor;
5003
5004 #ifdef HDR_COMPRESS
5005 brightness *= 0.5f;
5006 #endif
5007 Color(brightness * color.x,
5008 brightness * color.y,
5009 brightness * color.z,
5010 fade * (minOpacity + (1.0f - minOpacity)) * atten).get(vtx->color);
5011 vtx++;
5012 }
5013 }
5014
5015 // Create the index list
5016 int index = 0;
5017 for (i = 0; i < nRings; i++)
5018 {
5019 int baseVertex = i * nSlices;
5020 for (int j = 0; j < nSlices; j++)
5021 {
5022 skyIndices[index++] = baseVertex + j;
5023 skyIndices[index++] = baseVertex + nSlices + j;
5024 }
5025 skyIndices[index++] = baseVertex;
5026 skyIndices[index++] = baseVertex + nSlices;
5027 }
5028
5029 glEnableClientState(GL_VERTEX_ARRAY);
5030 glVertexPointer(3, GL_FLOAT, sizeof(SkyVertex), &skyVertices[0].x);
5031 glEnableClientState(GL_COLOR_ARRAY);
5032 glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(SkyVertex),
5033 static_cast<void*>(&skyVertices[0].color));
5034
5035 for (i = 0; i < nRings; i++)
5036 {
5037 glDrawElements(GL_QUAD_STRIP,
5038 (nSlices + 1) * 2,
5039 GL_UNSIGNED_INT,
5040 &skyIndices[(nSlices + 1) * 2 * i]);
5041 }
5042
5043 glDisableClientState(GL_COLOR_ARRAY);
5044 }
5045
5046
renderCompass(Point3f center,const Quatf & orientation,Vec3f semiAxes,float pixelSize)5047 void renderCompass(Point3f center,
5048 const Quatf& orientation,
5049 Vec3f semiAxes,
5050 float pixelSize)
5051 {
5052 Mat3f rot = orientation.toMatrix3();
5053 Mat3f irot = conjugate(orientation).toMatrix3();
5054
5055 Point3f eyePos(0.0f, 0.0f, 0.0f);
5056 float radius = max(semiAxes.x, max(semiAxes.y, semiAxes.z));
5057 Vec3f eyeVec = center - eyePos;
5058 eyeVec = eyeVec * irot;
5059 double centerDist = eyeVec.length();
5060
5061 float height = 1.0f / radius;
5062 Vec3f recipSemiAxes(1.0f / semiAxes.x,
5063 1.0f / semiAxes.y,
5064 1.0f / semiAxes.z);
5065
5066 Vec3f recipAtmSemiAxes = recipSemiAxes / (1.0f + height);
5067 Mat3f A = Mat3f::scaling(recipAtmSemiAxes);
5068 Mat3f A1 = Mat3f::scaling(recipSemiAxes);
5069
5070 const int nCompassPoints = 16;
5071 Vec3f compassPoints[nCompassPoints];
5072
5073
5074 // ellipDist is not the true distance from the surface unless the
5075 // planet is spherical. Computing the true distance requires finding
5076 // the roots of a sixth degree polynomial, and isn't actually what we
5077 // want anyhow since the atmosphere region is just the planet ellipsoid
5078 // multiplied by a uniform scale factor. The value that we do compute
5079 // is the distance to the surface along a line from the eye position to
5080 // the center of the ellipsoid.
5081
5082 /*float ellipDist = (float) sqrt((eyeVec * A1) * (eyeVec * A1)) - 1.0f; Unused*/
5083
5084 Vec3f e = -eyeVec;
5085 Vec3f e_(e.x * recipSemiAxes.x, e.y * recipSemiAxes.y, e.z * recipSemiAxes.z);
5086 float ee = e_ * e_;
5087
5088 Vec3f normal = eyeVec;
5089 normal = normal / (float) centerDist;
5090
5091 Vec3f uAxis, vAxis;
5092 Vec3f northPole(0.0f, 1.0f, 0.0f);
5093 vAxis = normal ^ northPole;
5094 vAxis.normalize();
5095 uAxis = vAxis ^ normal;
5096
5097 // Compute the compass points
5098 int i;
5099 for (i = 0; i < nCompassPoints; i++)
5100 {
5101 // We want rays with an origin at the eye point and tangent to the the
5102 // ellipsoid.
5103 float theta = (float) i / (float) nCompassPoints * 2 * (float) PI;
5104 Vec3f w = (float) cos(theta) * uAxis + (float) sin(theta) * vAxis;
5105 w = w * (float) centerDist;
5106
5107 Vec3f toCenter = ellipsoidTangent(recipSemiAxes, w, e, e_, ee);
5108 compassPoints[i] = toCenter * rot;
5109 }
5110
5111 glColor(compassColor);
5112 glBegin(GL_LINES);
5113 glDisable(GL_LIGHTING);
5114 for (i = 0; i < nCompassPoints; i++)
5115 {
5116 float distance = (center + compassPoints[i]).distanceFromOrigin();
5117
5118 float length = distance * pixelSize * 8.0f;
5119 if (i % 4 == 0)
5120 length *= 3.0f;
5121 else if (i % 2 == 0)
5122 length *= 2.0f;
5123
5124 glVertex(center + compassPoints[i]);
5125 glVertex(center + compassPoints[i] * (1.0f + length));
5126 }
5127 glEnd();
5128 }
5129
5130
setupNightTextureCombine()5131 static void setupNightTextureCombine()
5132 {
5133 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5134 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
5135 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_ONE_MINUS_SRC_COLOR);
5136 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
5137 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
5138 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
5139 }
5140
5141
setupBumpTexenv()5142 static void setupBumpTexenv()
5143 {
5144 // Set up the texenv_combine extension to do DOT3 bump mapping.
5145 // No support for ambient light yet.
5146 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5147
5148 // The primary color contains the light direction in surface
5149 // space, and texture0 is a normal map. The lighting is
5150 // calculated by computing the dot product.
5151 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_ARB);
5152 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
5153 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
5154 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
5155 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
5156
5157 // In the final stage, modulate the lighting value by the
5158 // base texture color.
5159 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
5160 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
5161 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
5162 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
5163 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PREVIOUS_EXT);
5164 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
5165 glEnable(GL_TEXTURE_2D);
5166
5167 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
5168 }
5169
5170
5171 #if 0
5172 static void setupBumpTexenvAmbient(Color ambientColor)
5173 {
5174 float texenvConst[4];
5175 texenvConst[0] = ambientColor.red();
5176 texenvConst[1] = ambientColor.green();
5177 texenvConst[2] = ambientColor.blue();
5178 texenvConst[3] = ambientColor.alpha();
5179
5180 // Set up the texenv_combine extension to do DOT3 bump mapping.
5181 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5182
5183 // The primary color contains the light direction in surface
5184 // space, and texture0 is a normal map. The lighting is
5185 // calculated by computing the dot product.
5186 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
5187 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_DOT3_RGB_ARB);
5188 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
5189 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
5190 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
5191 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
5192
5193 // Add in the ambient color
5194 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
5195 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, texenvConst);
5196 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5197 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
5198 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
5199 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
5200 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_CONSTANT_EXT);
5201 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
5202 glEnable(GL_TEXTURE_2D);
5203
5204 // In the final stage, modulate the lighting value by the
5205 // base texture color.
5206 glx::glActiveTextureARB(GL_TEXTURE2_ARB);
5207 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5208 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
5209 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
5210 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
5211 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
5212 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
5213 glEnable(GL_TEXTURE_2D);
5214
5215 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
5216 }
5217 #endif
5218
5219
setupTexenvAmbient(Color ambientColor)5220 static void setupTexenvAmbient(Color ambientColor)
5221 {
5222 float texenvConst[4];
5223 texenvConst[0] = ambientColor.red();
5224 texenvConst[1] = ambientColor.green();
5225 texenvConst[2] = ambientColor.blue();
5226 texenvConst[3] = ambientColor.alpha();
5227
5228 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5229
5230 // The primary color contains the light direction in surface
5231 // space, and texture0 is a normal map. The lighting is
5232 // calculated by computing the dot product.
5233 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
5234 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, texenvConst);
5235 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5236 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
5237 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE);
5238 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
5239 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_CONSTANT_EXT);
5240 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
5241 glEnable(GL_TEXTURE_2D);
5242 }
5243
5244
setupTexenvGlossMapAlpha()5245 static void setupTexenvGlossMapAlpha()
5246 {
5247 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
5248 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
5249 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PRIMARY_COLOR_EXT);
5250 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
5251 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
5252 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_ALPHA);
5253
5254 }
5255
5256
setLightParameters_VP(VertexProcessor & vproc,const LightingState & ls,Color materialDiffuse,Color materialSpecular)5257 static void setLightParameters_VP(VertexProcessor& vproc,
5258 const LightingState& ls,
5259 Color materialDiffuse,
5260 Color materialSpecular)
5261 {
5262 Vec3f diffuseColor(materialDiffuse.red(),
5263 materialDiffuse.green(),
5264 materialDiffuse.blue());
5265 #ifdef HDR_COMPRESS
5266 Vec3f specularColor(materialSpecular.red() * 0.5f,
5267 materialSpecular.green() * 0.5f,
5268 materialSpecular.blue() * 0.5f);
5269 #else
5270 Vec3f specularColor(materialSpecular.red(),
5271 materialSpecular.green(),
5272 materialSpecular.blue());
5273 #endif
5274 for (unsigned int i = 0; i < ls.nLights; i++)
5275 {
5276 const DirectionalLight& light = ls.lights[i];
5277
5278 Vec3f lightColor = Vec3f(light.color.red(),
5279 light.color.green(),
5280 light.color.blue()) * light.irradiance;
5281 Vec3f diffuse(diffuseColor.x * lightColor.x,
5282 diffuseColor.y * lightColor.y,
5283 diffuseColor.z * lightColor.z);
5284 Vec3f specular(specularColor.x * lightColor.x,
5285 specularColor.y * lightColor.y,
5286 specularColor.z * lightColor.z);
5287
5288 // Just handle two light sources for now
5289 if (i == 0)
5290 {
5291 vproc.parameter(vp::LightDirection0, ls.lights[0].direction_obj);
5292 vproc.parameter(vp::DiffuseColor0, diffuse);
5293 vproc.parameter(vp::SpecularColor0, specular);
5294 }
5295 else if (i == 1)
5296 {
5297 vproc.parameter(vp::LightDirection1, ls.lights[1].direction_obj);
5298 vproc.parameter(vp::DiffuseColor1, diffuse);
5299 vproc.parameter(vp::SpecularColor1, specular);
5300 }
5301 }
5302 }
5303
5304
renderModelDefault(Geometry * geometry,const RenderInfo & ri,bool lit,ResourceHandle texOverride)5305 static void renderModelDefault(Geometry* geometry,
5306 const RenderInfo& ri,
5307 bool lit,
5308 ResourceHandle texOverride)
5309 {
5310 FixedFunctionRenderContext rc;
5311 Mesh::Material m;
5312
5313 rc.setLighting(lit);
5314
5315 if (ri.baseTex == NULL)
5316 {
5317 glDisable(GL_TEXTURE_2D);
5318 }
5319 else
5320 {
5321 glEnable(GL_TEXTURE_2D);
5322 ri.baseTex->bind();
5323 }
5324
5325 glColor(ri.color);
5326
5327 if (ri.baseTex != NULL)
5328 {
5329 m.diffuse = ri.color;
5330 m.specular = ri.specularColor;
5331 m.specularPower = ri.specularPower;
5332 m.maps[Mesh::DiffuseMap] = texOverride;
5333 rc.setMaterial(&m);
5334 rc.lock();
5335 }
5336
5337 geometry->render(rc);
5338 if (geometry->usesTextureType(Mesh::EmissiveMap))
5339 {
5340 glDisable(GL_LIGHTING);
5341 glEnable(GL_BLEND);
5342 glBlendFunc(GL_ONE, GL_ONE);
5343 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
5344 rc.setRenderPass(RenderContext::EmissivePass);
5345 rc.setMaterial(NULL);
5346
5347 geometry->render(rc);
5348
5349 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5350 }
5351
5352 // Reset the material
5353 float black[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
5354 float zero = 0.0f;
5355 glColor4fv(black);
5356 glMaterialfv(GL_FRONT, GL_EMISSION, black);
5357 glMaterialfv(GL_FRONT, GL_SPECULAR, black);
5358 glMaterialfv(GL_FRONT, GL_SHININESS, &zero);
5359 }
5360
5361
renderSphereDefault(const RenderInfo & ri,const Frustum & frustum,bool lit,const GLContext & context)5362 static void renderSphereDefault(const RenderInfo& ri,
5363 const Frustum& frustum,
5364 bool lit,
5365 const GLContext& context)
5366 {
5367 if (lit)
5368 glEnable(GL_LIGHTING);
5369 else
5370 glDisable(GL_LIGHTING);
5371
5372 if (ri.baseTex == NULL)
5373 {
5374 glDisable(GL_TEXTURE_2D);
5375 }
5376 else
5377 {
5378 glEnable(GL_TEXTURE_2D);
5379 ri.baseTex->bind();
5380 }
5381
5382 glColor(ri.color);
5383
5384 g_lodSphere->render(context,
5385 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5386 frustum, ri.pixWidth,
5387 ri.baseTex);
5388 if (ri.nightTex != NULL && ri.useTexEnvCombine)
5389 {
5390 ri.nightTex->bind();
5391 #ifdef USE_HDR
5392 #ifdef HDR_COMPRESS
5393 Color nightColor(ri.color.red() * 2.f,
5394 ri.color.green() * 2.f,
5395 ri.color.blue() * 2.f,
5396 ri.nightLightScale); // Modulate brightness using alpha
5397 #else
5398 Color nightColor(ri.color.red(),
5399 ri.color.green(),
5400 ri.color.blue(),
5401 ri.nightLightScale); // Modulate brightness using alpha
5402 #endif
5403 glColor(nightColor);
5404 #endif
5405 setupNightTextureCombine();
5406 glEnable(GL_BLEND);
5407 #ifdef USE_HDR
5408 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
5409 #else
5410 glBlendFunc(GL_ONE, GL_ONE);
5411 #endif
5412 glAmbientLightColor(Color::Black); // Disable ambient light
5413 g_lodSphere->render(context,
5414 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5415 frustum, ri.pixWidth,
5416 ri.nightTex);
5417 glAmbientLightColor(ri.ambientColor);
5418 #ifdef USE_HDR
5419 glColor(ri.color);
5420 #endif
5421 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5422 }
5423
5424 if (ri.overlayTex != NULL)
5425 {
5426 ri.overlayTex->bind();
5427 glEnable(GL_BLEND);
5428 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
5429 g_lodSphere->render(context,
5430 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5431 frustum, ri.pixWidth,
5432 ri.overlayTex);
5433 glBlendFunc(GL_ONE, GL_ONE);
5434 }
5435 }
5436
5437
5438 // DEPRECATED -- renderSphere_Combiners_VP should be used instead; only
5439 // very old drivers don't support vertex programs.
renderSphere_Combiners(const RenderInfo & ri,const Frustum & frustum,const GLContext & context)5440 static void renderSphere_Combiners(const RenderInfo& ri,
5441 const Frustum& frustum,
5442 const GLContext& context)
5443 {
5444 glDisable(GL_LIGHTING);
5445
5446 if (ri.baseTex == NULL)
5447 {
5448 glDisable(GL_TEXTURE_2D);
5449 }
5450 else
5451 {
5452 glEnable(GL_TEXTURE_2D);
5453 ri.baseTex->bind();
5454 }
5455
5456 glColor(ri.color * ri.sunColor);
5457
5458 // Don't use a normal map if it's a dxt5nm map--only the GLSL path
5459 // can handle them.
5460 if (ri.bumpTex != NULL &&
5461 (ri.bumpTex->getFormatOptions() & Texture::DXT5NormalMap) == 0)
5462 {
5463 renderBumpMappedMesh(context,
5464 *(ri.baseTex),
5465 *(ri.bumpTex),
5466 ri.sunDir_eye,
5467 ri.orientation,
5468 ri.ambientColor,
5469 frustum,
5470 ri.pixWidth);
5471 }
5472 else if (ri.baseTex != NULL)
5473 {
5474 renderSmoothMesh(context,
5475 *(ri.baseTex),
5476 ri.sunDir_eye,
5477 ri.orientation,
5478 ri.ambientColor,
5479 ri.pixWidth,
5480 frustum);
5481 }
5482 else
5483 {
5484 glEnable(GL_LIGHTING);
5485 g_lodSphere->render(context, frustum, ri.pixWidth, NULL, 0);
5486 }
5487
5488 if (ri.nightTex != NULL)
5489 {
5490 ri.nightTex->bind();
5491 glEnable(GL_BLEND);
5492 glBlendFunc(GL_ONE, GL_ONE);
5493 renderSmoothMesh(context,
5494 *(ri.nightTex),
5495 ri.sunDir_eye,
5496 ri.orientation,
5497 Color::Black,
5498 ri.pixWidth,
5499 frustum,
5500 true);
5501 }
5502
5503 if (ri.overlayTex != NULL)
5504 {
5505 glEnable(GL_LIGHTING);
5506 ri.overlayTex->bind();
5507 glEnable(GL_BLEND);
5508 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
5509 g_lodSphere->render(context,
5510 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5511 frustum, ri.pixWidth,
5512 ri.overlayTex);
5513 #if 0
5514 renderSmoothMesh(context,
5515 *(ri.overlayTex),
5516 ri.sunDir_eye,
5517 ri.orientation,
5518 ri.ambientColor,
5519 ri.pixWidth,
5520 frustum);
5521 #endif
5522 glBlendFunc(GL_ONE, GL_ONE);
5523 }
5524
5525 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
5526 }
5527
5528
renderSphere_DOT3_VP(const RenderInfo & ri,const LightingState & ls,const Frustum & frustum,const GLContext & context)5529 static void renderSphere_DOT3_VP(const RenderInfo& ri,
5530 const LightingState& ls,
5531 const Frustum& frustum,
5532 const GLContext& context)
5533 {
5534 VertexProcessor* vproc = context.getVertexProcessor();
5535 assert(vproc != NULL);
5536
5537 if (ri.baseTex == NULL)
5538 {
5539 glDisable(GL_TEXTURE_2D);
5540 }
5541 else
5542 {
5543 glEnable(GL_TEXTURE_2D);
5544 ri.baseTex->bind();
5545 }
5546
5547 vproc->enable();
5548 vproc->parameter(vp::EyePosition, ri.eyePos_obj);
5549 setLightParameters_VP(*vproc, ls, ri.color, ri.specularColor);
5550
5551 Color ambient(ri.ambientColor * ri.color);
5552 #ifdef USE_HDR
5553 ambient = ri.ambientColor;
5554 #endif
5555 vproc->parameter(vp::AmbientColor, ambient);
5556 vproc->parameter(vp::SpecularExponent, 0.0f, 1.0f, 0.5f, ri.specularPower);
5557
5558 // Don't use a normal map if it's a dxt5nm map--only the GLSL path
5559 // can handle them.
5560 if (ri.bumpTex != NULL &&
5561 (ri.bumpTex->getFormatOptions() & Texture::DXT5NormalMap) == 0 &&
5562 ri.baseTex != NULL)
5563 {
5564 // We don't yet handle the case where there's a bump map but no
5565 // base texture.
5566 #ifdef HDR_COMPRESS
5567 vproc->use(vp::diffuseBumpHDR);
5568 #else
5569 vproc->use(vp::diffuseBump);
5570 #endif
5571 if (ri.ambientColor != Color::Black)
5572 {
5573 // If there's ambient light, we'll need to render in two passes:
5574 // one for the ambient light, and the second for light from the star.
5575 // We could do this in a single pass using three texture stages, but
5576 // this isn't won't work with hardware that only supported two
5577 // texture stages.
5578
5579 // Render the base texture modulated by the ambient color
5580 setupTexenvAmbient(ambient);
5581 g_lodSphere->render(context,
5582 LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
5583 frustum, ri.pixWidth,
5584 ri.baseTex);
5585
5586 // Add the light from the sun
5587 glEnable(GL_BLEND);
5588 glBlendFunc(GL_ONE, GL_ONE);
5589 setupBumpTexenv();
5590 g_lodSphere->render(context,
5591 LODSphereMesh::Normals | LODSphereMesh::Tangents |
5592 LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
5593 frustum, ri.pixWidth,
5594 ri.bumpTex, ri.baseTex);
5595 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5596 glDisable(GL_BLEND);
5597 }
5598 else
5599 {
5600 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
5601 ri.baseTex->bind();
5602 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
5603 ri.bumpTex->bind();
5604 setupBumpTexenv();
5605 g_lodSphere->render(context,
5606 LODSphereMesh::Normals | LODSphereMesh::Tangents |
5607 LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
5608 frustum, ri.pixWidth,
5609 ri.bumpTex, ri.baseTex);
5610 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5611 }
5612 }
5613 else
5614 {
5615 if (ls.nLights > 1)
5616 vproc->use(vp::diffuse_2light);
5617 else
5618 vproc->use(vp::diffuse);
5619 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5620 g_lodSphere->render(context,
5621 LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
5622 LODSphereMesh::VertexProgParams,
5623 frustum, ri.pixWidth,
5624 ri.baseTex);
5625 }
5626
5627 // Render a specular pass; can't be done in one pass because
5628 // specular needs to be modulated with a gloss map.
5629 if (ri.specularColor != Color::Black)
5630 {
5631 glEnable(GL_BLEND);
5632 glBlendFunc(GL_ONE, GL_ONE);
5633 vproc->use(vp::glossMap);
5634
5635 if (ri.glossTex != NULL)
5636 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5637 else
5638 setupTexenvGlossMapAlpha();
5639
5640 g_lodSphere->render(context,
5641 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5642 frustum, ri.pixWidth,
5643 ri.glossTex != NULL ? ri.glossTex : ri.baseTex);
5644
5645 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5646 glDisable(GL_BLEND);
5647 }
5648
5649 if (ri.nightTex != NULL)
5650 {
5651 ri.nightTex->bind();
5652 #ifdef USE_HDR
5653 // Scale night light intensity
5654 #ifdef HDR_COMPRESS
5655 Color nightColor0(ls.lights[0].color.red() *ri.color.red() *2.f,
5656 ls.lights[0].color.green()*ri.color.green()*2.f,
5657 ls.lights[0].color.blue() *ri.color.blue() *2.f,
5658 ri.nightLightScale); // Modulate brightness with alpha
5659 #else
5660 Color nightColor0(ls.lights[0].color.red() *ri.color.red(),
5661 ls.lights[0].color.green()*ri.color.green(),
5662 ls.lights[0].color.blue() *ri.color.blue(),
5663 ri.nightLightScale); // Modulate brightness with alpha
5664 #endif
5665 vproc->parameter(vp::DiffuseColor0, nightColor0);
5666 #endif
5667 if (ls.nLights > 1)
5668 {
5669 #ifdef USE_HDR
5670 #ifdef HDR_COMPRESS
5671 Color nightColor1(ls.lights[1].color.red() *ri.color.red() *2.f,
5672 ls.lights[1].color.green()*ri.color.green()*2.f,
5673 ls.lights[1].color.blue() *ri.color.blue() *2.f,
5674 ri.nightLightScale);
5675 #else
5676 Color nightColor1(ls.lights[1].color.red() *ri.color.red(),
5677 ls.lights[1].color.green()*ri.color.green(),
5678 ls.lights[1].color.blue() *ri.color.blue(),
5679 ri.nightLightScale);
5680 #endif
5681 vproc->parameter(vp::DiffuseColor0, nightColor1);
5682 #endif
5683 #ifdef HDR_COMPRESS
5684 vproc->use(vp::nightLights_2lightHDR);
5685 #else
5686 vproc->use(vp::nightLights_2light);
5687 #endif
5688 }
5689 else
5690 {
5691 #ifdef HDR_COMPRESS
5692 vproc->use(vp::nightLightsHDR);
5693 #else
5694 vproc->use(vp::nightLights);
5695 #endif
5696 }
5697 #ifdef USE_HDR
5698 glEnable(GL_BLEND);
5699 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
5700 #else
5701 setupNightTextureCombine();
5702 glEnable(GL_BLEND);
5703 glBlendFunc(GL_ONE, GL_ONE);
5704 #endif
5705 g_lodSphere->render(context,
5706 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5707 frustum, ri.pixWidth,
5708 ri.nightTex);
5709 #ifdef USE_HDR
5710 vproc->parameter(vp::DiffuseColor0, ls.lights[0].color * ri.color);
5711 if (ls.nLights > 1)
5712 vproc->parameter(vp::DiffuseColor1, ls.lights[1].color * ri.color);
5713 #endif
5714 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5715 }
5716
5717 if (ri.overlayTex != NULL)
5718 {
5719 ri.overlayTex->bind();
5720 vproc->use(vp::diffuse);
5721 glEnable(GL_BLEND);
5722 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
5723 g_lodSphere->render(context,
5724 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5725 frustum, ri.pixWidth,
5726 ri.overlayTex);
5727 glBlendFunc(GL_ONE, GL_ONE);
5728 }
5729
5730 vproc->disable();
5731 }
5732
5733
renderSphere_Combiners_VP(const RenderInfo & ri,const LightingState & ls,const Frustum & frustum,const GLContext & context)5734 static void renderSphere_Combiners_VP(const RenderInfo& ri,
5735 const LightingState& ls,
5736 const Frustum& frustum,
5737 const GLContext& context)
5738 {
5739 Texture* textures[4];
5740 VertexProcessor* vproc = context.getVertexProcessor();
5741 assert(vproc != NULL);
5742
5743 if (ri.baseTex == NULL)
5744 {
5745 glDisable(GL_TEXTURE_2D);
5746 }
5747 else
5748 {
5749 glEnable(GL_TEXTURE_2D);
5750 ri.baseTex->bind();
5751 }
5752
5753 // Set up the fog parameters if the haze density is non-zero
5754 float hazeDensity = ri.hazeColor.alpha();
5755 #ifdef HDR_COMPRESS
5756 Color hazeColor(ri.hazeColor.red() * 0.5f,
5757 ri.hazeColor.green() * 0.5f,
5758 ri.hazeColor.blue() * 0.5f,
5759 hazeDensity);
5760 #else
5761 Color hazeColor = ri.hazeColor;
5762 #endif
5763
5764 if (hazeDensity > 0.0f && !buggyVertexProgramEmulation)
5765 {
5766 glEnable(GL_FOG);
5767 float fogColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
5768 fogColor[0] = hazeColor.red();
5769 fogColor[1] = hazeColor.green();
5770 fogColor[2] = hazeColor.blue();
5771 glFogfv(GL_FOG_COLOR, fogColor);
5772 glFogi(GL_FOG_MODE, GL_LINEAR);
5773 glFogf(GL_FOG_START, 0.0);
5774 glFogf(GL_FOG_END, 1.0f / hazeDensity);
5775 }
5776
5777 vproc->enable();
5778
5779 vproc->parameter(vp::EyePosition, ri.eyePos_obj);
5780 setLightParameters_VP(*vproc, ls, ri.color, ri.specularColor);
5781
5782 vproc->parameter(vp::SpecularExponent, 0.0f, 1.0f, 0.5f, ri.specularPower);
5783 vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
5784 vproc->parameter(vp::HazeColor, hazeColor);
5785
5786 // Don't use a normal map if it's a dxt5nm map--only the GLSL path
5787 // can handle them.
5788 if (ri.bumpTex != NULL &&
5789 (ri.bumpTex->getFormatOptions() & Texture::DXT5NormalMap) == 0)
5790 {
5791 if (hazeDensity > 0.0f)
5792 {
5793 #ifdef HDR_COMPRESS
5794 vproc->use(vp::diffuseBumpHazeHDR);
5795 #else
5796 vproc->use(vp::diffuseBumpHaze);
5797 #endif
5798 }
5799 else
5800 {
5801 #ifdef HDR_COMPRESS
5802 vproc->use(vp::diffuseBumpHDR);
5803 #else
5804 vproc->use(vp::diffuseBump);
5805 #endif
5806 }
5807 SetupCombinersDecalAndBumpMap(*(ri.bumpTex),
5808 ri.ambientColor * ri.color,
5809 ri.sunColor * ri.color);
5810 g_lodSphere->render(context,
5811 LODSphereMesh::Normals | LODSphereMesh::Tangents |
5812 LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
5813 frustum, ri.pixWidth,
5814 ri.baseTex, ri.bumpTex);
5815 DisableCombiners();
5816
5817 // Render a specular pass
5818 if (ri.specularColor != Color::Black)
5819 {
5820 glEnable(GL_BLEND);
5821 glBlendFunc(GL_ONE, GL_ONE);
5822 glEnable(GL_COLOR_SUM_EXT);
5823 vproc->use(vp::specular);
5824
5825 // Disable ambient and diffuse
5826 vproc->parameter(vp::AmbientColor, Color::Black);
5827 vproc->parameter(vp::DiffuseColor0, Color::Black);
5828 SetupCombinersGlossMap(ri.glossTex != NULL ? GL_TEXTURE0_ARB : 0);
5829
5830 textures[0] = ri.glossTex != NULL ? ri.glossTex : ri.baseTex;
5831 g_lodSphere->render(context,
5832 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5833 frustum, ri.pixWidth,
5834 textures, 1);
5835
5836 // re-enable diffuse
5837 vproc->parameter(vp::DiffuseColor0, ri.sunColor * ri.color);
5838
5839 DisableCombiners();
5840 glDisable(GL_COLOR_SUM_EXT);
5841 glDisable(GL_BLEND);
5842 }
5843 }
5844 else if (ri.specularColor != Color::Black)
5845 {
5846 glEnable(GL_COLOR_SUM_EXT);
5847 if (ls.nLights > 1)
5848 vproc->use(vp::specular_2light);
5849 else
5850 vproc->use(vp::specular);
5851 SetupCombinersGlossMapWithFog(ri.glossTex != NULL ? GL_TEXTURE1_ARB : 0);
5852 unsigned int attributes = LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
5853 LODSphereMesh::VertexProgParams;
5854 g_lodSphere->render(context,
5855 attributes, frustum, ri.pixWidth,
5856 ri.baseTex, ri.glossTex);
5857 DisableCombiners();
5858 glDisable(GL_COLOR_SUM_EXT);
5859 }
5860 else
5861 {
5862 if (ls.nLights > 1)
5863 {
5864 if (hazeDensity > 0.0f)
5865 vproc->use(vp::diffuseHaze_2light);
5866 else
5867 vproc->use(vp::diffuse_2light);
5868 }
5869 else
5870 {
5871 if (hazeDensity > 0.0f)
5872 vproc->use(vp::diffuseHaze);
5873 else
5874 vproc->use(vp::diffuse);
5875 }
5876
5877 g_lodSphere->render(context,
5878 LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
5879 LODSphereMesh::VertexProgParams,
5880 frustum, ri.pixWidth,
5881 ri.baseTex);
5882 }
5883
5884 if (hazeDensity > 0.0f)
5885 glDisable(GL_FOG);
5886
5887 if (ri.nightTex != NULL)
5888 {
5889 ri.nightTex->bind();
5890 #ifdef USE_HDR
5891 // Scale night light intensity
5892 #ifdef HDR_COMPRESS
5893 Color nightColor0(ls.lights[0].color.red() *ri.color.red() *2.f,
5894 ls.lights[0].color.green()*ri.color.green()*2.f,
5895 ls.lights[0].color.blue() *ri.color.blue() *2.f,
5896 ri.nightLightScale); // Modulate brightness with alpha
5897 #else
5898 Color nightColor0(ls.lights[0].color.red() *ri.color.red(),
5899 ls.lights[0].color.green()*ri.color.green(),
5900 ls.lights[0].color.blue() *ri.color.blue(),
5901 ri.nightLightScale); // Modulate brightness with alpha
5902 #endif
5903 vproc->parameter(vp::DiffuseColor0, nightColor0);
5904 #endif
5905 if (ls.nLights > 1)
5906 {
5907 #ifdef USE_HDR
5908 #ifdef HDR_COMPRESS
5909 Color nightColor1(ls.lights[1].color.red() *ri.color.red() *2.f,
5910 ls.lights[1].color.green()*ri.color.green()*2.f,
5911 ls.lights[1].color.blue() *ri.color.blue() *2.f,
5912 ri.nightLightScale);
5913 #else
5914 Color nightColor1(ls.lights[1].color.red() *ri.color.red(),
5915 ls.lights[1].color.green()*ri.color.green(),
5916 ls.lights[1].color.blue() *ri.color.blue(),
5917 ri.nightLightScale);
5918 #endif
5919 vproc->parameter(vp::DiffuseColor0, nightColor1);
5920 #endif
5921 #ifdef HDR_COMPRESS
5922 vproc->use(vp::nightLights_2lightHDR);
5923 #else
5924 vproc->use(vp::nightLights_2light);
5925 #endif
5926 }
5927 else
5928 {
5929 #ifdef HDR_COMPRESS
5930 vproc->use(vp::nightLightsHDR);
5931 #else
5932 vproc->use(vp::nightLights);
5933 #endif
5934 }
5935 #ifdef USE_HDR
5936 glEnable(GL_BLEND);
5937 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
5938 #else
5939 setupNightTextureCombine();
5940 glEnable(GL_BLEND);
5941 glBlendFunc(GL_ONE, GL_ONE);
5942 #endif
5943 g_lodSphere->render(context,
5944 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5945 frustum, ri.pixWidth,
5946 ri.nightTex);
5947 #ifdef USE_HDR
5948 vproc->parameter(vp::DiffuseColor0, ri.sunColor * ri.color);
5949 #endif
5950 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
5951 }
5952
5953 if (ri.overlayTex != NULL)
5954 {
5955 ri.overlayTex->bind();
5956 vproc->use(vp::diffuse);
5957 glEnable(GL_BLEND);
5958 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
5959 g_lodSphere->render(context,
5960 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
5961 frustum, ri.pixWidth,
5962 ri.overlayTex);
5963 glBlendFunc(GL_ONE, GL_ONE);
5964 }
5965
5966 vproc->disable();
5967 }
5968
5969
5970 // Render a planet sphere using both fragment and vertex programs
renderSphere_FP_VP(const RenderInfo & ri,const Frustum & frustum,const GLContext & context)5971 static void renderSphere_FP_VP(const RenderInfo& ri,
5972 const Frustum& frustum,
5973 const GLContext& context)
5974 {
5975 Texture* textures[4];
5976 VertexProcessor* vproc = context.getVertexProcessor();
5977 FragmentProcessor* fproc = context.getFragmentProcessor();
5978 assert(vproc != NULL && fproc != NULL);
5979
5980 if (ri.baseTex == NULL)
5981 {
5982 glDisable(GL_TEXTURE_2D);
5983 }
5984 else
5985 {
5986 glEnable(GL_TEXTURE_2D);
5987 ri.baseTex->bind();
5988 }
5989
5990 // Compute the half angle vector required for specular lighting
5991 Vec3f halfAngle_obj = ri.eyeDir_obj + ri.sunDir_obj;
5992 if (halfAngle_obj.length() != 0.0f)
5993 halfAngle_obj.normalize();
5994
5995 // Set up the fog parameters if the haze density is non-zero
5996 float hazeDensity = ri.hazeColor.alpha();
5997
5998 if (hazeDensity > 0.0f)
5999 {
6000 glEnable(GL_FOG);
6001 float fogColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
6002 fogColor[0] = ri.hazeColor.red();
6003 fogColor[1] = ri.hazeColor.green();
6004 fogColor[2] = ri.hazeColor.blue();
6005 glFogfv(GL_FOG_COLOR, fogColor);
6006 glFogi(GL_FOG_MODE, GL_LINEAR);
6007 glFogf(GL_FOG_START, 0.0);
6008 glFogf(GL_FOG_END, 1.0f / hazeDensity);
6009 }
6010
6011 vproc->enable();
6012
6013 vproc->parameter(vp::EyePosition, ri.eyePos_obj);
6014 vproc->parameter(vp::LightDirection0, ri.sunDir_obj);
6015 vproc->parameter(vp::DiffuseColor0, ri.sunColor * ri.color);
6016 vproc->parameter(vp::SpecularExponent, 0.0f, 1.0f, 0.5f, ri.specularPower);
6017 vproc->parameter(vp::SpecularColor0, ri.sunColor * ri.specularColor);
6018 vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
6019 vproc->parameter(vp::HazeColor, ri.hazeColor);
6020
6021 if (ri.bumpTex != NULL)
6022 {
6023 fproc->enable();
6024
6025 if (hazeDensity > 0.0f)
6026 vproc->use(vp::diffuseBumpHaze);
6027 else
6028 vproc->use(vp::diffuseBump);
6029 fproc->use(fp::texDiffuseBump);
6030 g_lodSphere->render(context,
6031 LODSphereMesh::Normals | LODSphereMesh::Tangents |
6032 LODSphereMesh::TexCoords0 | LODSphereMesh::VertexProgParams,
6033 frustum, ri.pixWidth,
6034 ri.baseTex, ri.bumpTex);
6035 fproc->disable();
6036
6037 // Render a specular pass
6038 if (ri.specularColor != Color::Black)
6039 {
6040 glEnable(GL_BLEND);
6041 glBlendFunc(GL_ONE, GL_ONE);
6042 glEnable(GL_COLOR_SUM_EXT);
6043 vproc->use(vp::specular);
6044
6045 // Disable ambient and diffuse
6046 vproc->parameter(vp::AmbientColor, Color::Black);
6047 vproc->parameter(vp::DiffuseColor0, Color::Black);
6048 SetupCombinersGlossMap(ri.glossTex != NULL ? GL_TEXTURE0_ARB : 0);
6049
6050 textures[0] = ri.glossTex != NULL ? ri.glossTex : ri.baseTex;
6051 g_lodSphere->render(context,
6052 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
6053 frustum, ri.pixWidth,
6054 textures, 1);
6055
6056 // re-enable diffuse
6057 vproc->parameter(vp::DiffuseColor0, ri.sunColor * ri.color);
6058
6059 DisableCombiners();
6060 glDisable(GL_COLOR_SUM_EXT);
6061 glDisable(GL_BLEND);
6062 }
6063 }
6064 else if (ri.specularColor != Color::Black)
6065 {
6066 fproc->enable();
6067 if (ri.glossTex == NULL)
6068 {
6069 vproc->use(vp::perFragmentSpecularAlpha);
6070 fproc->use(fp::texSpecularAlpha);
6071 }
6072 else
6073 {
6074 vproc->use(vp::perFragmentSpecular);
6075 fproc->use(fp::texSpecular);
6076 }
6077 fproc->parameter(fp::DiffuseColor, ri.sunColor * ri.color);
6078 fproc->parameter(fp::SunDirection, ri.sunDir_obj);
6079 fproc->parameter(fp::SpecularColor, ri.specularColor);
6080 fproc->parameter(fp::SpecularExponent, ri.specularPower, 0.0f, 0.0f, 0.0f);
6081 fproc->parameter(fp::AmbientColor, ri.ambientColor);
6082
6083 unsigned int attributes = LODSphereMesh::Normals |
6084 LODSphereMesh::TexCoords0 |
6085 LODSphereMesh::VertexProgParams;
6086 g_lodSphere->render(context,
6087 attributes, frustum, ri.pixWidth,
6088 ri.baseTex, ri.glossTex);
6089 fproc->disable();
6090 }
6091 else
6092 {
6093 fproc->enable();
6094 if (hazeDensity > 0.0f)
6095 vproc->use(vp::diffuseHaze);
6096 else
6097 vproc->use(vp::diffuse);
6098 fproc->use(fp::texDiffuse);
6099 g_lodSphere->render(context,
6100 LODSphereMesh::Normals | LODSphereMesh::TexCoords0 |
6101 LODSphereMesh::VertexProgParams,
6102 frustum, ri.pixWidth,
6103 ri.baseTex);
6104 fproc->disable();
6105 }
6106
6107 if (hazeDensity > 0.0f)
6108 glDisable(GL_FOG);
6109
6110 if (ri.nightTex != NULL)
6111 {
6112 ri.nightTex->bind();
6113 vproc->use(vp::nightLights);
6114 setupNightTextureCombine();
6115 glEnable(GL_BLEND);
6116 glBlendFunc(GL_ONE, GL_ONE);
6117 g_lodSphere->render(context,
6118 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
6119 frustum, ri.pixWidth,
6120 ri.nightTex);
6121 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
6122 }
6123
6124 if (ri.overlayTex != NULL)
6125 {
6126 ri.overlayTex->bind();
6127 vproc->use(vp::diffuse);
6128 glEnable(GL_BLEND);
6129 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
6130 g_lodSphere->render(context,
6131 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
6132 frustum, ri.pixWidth,
6133 ri.overlayTex);
6134 glBlendFunc(GL_ONE, GL_ONE);
6135 }
6136
6137 vproc->disable();
6138 }
6139
6140
texGenPlane(GLenum coord,GLenum mode,const Vec4f & plane)6141 static void texGenPlane(GLenum coord, GLenum mode, const Vec4f& plane)
6142 {
6143 float f[4];
6144 f[0] = plane.x; f[1] = plane.y; f[2] = plane.z; f[3] = plane.w;
6145 glTexGenfv(coord, mode, f);
6146 }
6147
renderShadowedGeometryDefault(Geometry * geometry,const RenderInfo & ri,const Frustum & frustum,float * sPlane,float * tPlane,const Vec3f & lightDir,bool useShadowMask,const GLContext & context)6148 static void renderShadowedGeometryDefault(Geometry* geometry,
6149 const RenderInfo& ri,
6150 const Frustum& frustum,
6151 float *sPlane,
6152 float *tPlane,
6153 const Vec3f& lightDir,
6154 bool useShadowMask,
6155 const GLContext& context)
6156 {
6157 glEnable(GL_TEXTURE_GEN_S);
6158 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
6159 glTexGenfv(GL_S, GL_OBJECT_PLANE, sPlane);
6160 //texGenPlane(GL_S, GL_OBJECT_PLANE, sPlane);
6161 glEnable(GL_TEXTURE_GEN_T);
6162 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
6163 glTexGenfv(GL_T, GL_OBJECT_PLANE, tPlane);
6164
6165 if (useShadowMask)
6166 {
6167 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
6168 glEnable(GL_TEXTURE_GEN_S);
6169 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
6170 texGenPlane(GL_S, GL_OBJECT_PLANE,
6171 Vec4f(lightDir.x, lightDir.y, lightDir.z, 0.5f));
6172 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
6173 }
6174
6175 glColor4f(1, 1, 1, 1);
6176 glDisable(GL_LIGHTING);
6177
6178 if (geometry == NULL)
6179 {
6180 g_lodSphere->render(context,
6181 LODSphereMesh::Normals | LODSphereMesh::Multipass,
6182 frustum, ri.pixWidth, NULL);
6183 }
6184 else
6185 {
6186 FixedFunctionRenderContext rc;
6187 geometry->render(rc);
6188 }
6189 glEnable(GL_LIGHTING);
6190
6191 if (useShadowMask)
6192 {
6193 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
6194 glDisable(GL_TEXTURE_GEN_S);
6195 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
6196 }
6197 glDisable(GL_TEXTURE_GEN_S);
6198 glDisable(GL_TEXTURE_GEN_T);
6199 }
6200
6201
renderShadowedGeometryVertexShader(const RenderInfo & ri,const Frustum & frustum,float * sPlane,float * tPlane,Vec3f & lightDir,const GLContext & context)6202 static void renderShadowedGeometryVertexShader(const RenderInfo& ri,
6203 const Frustum& frustum,
6204 float* sPlane, float* tPlane,
6205 Vec3f& lightDir,
6206 const GLContext& context)
6207 {
6208 VertexProcessor* vproc = context.getVertexProcessor();
6209 assert(vproc != NULL);
6210
6211 vproc->enable();
6212 vproc->parameter(vp::LightDirection0, lightDir);
6213 vproc->parameter(vp::TexGen_S, sPlane);
6214 vproc->parameter(vp::TexGen_T, tPlane);
6215 vproc->use(vp::shadowTexture);
6216
6217 g_lodSphere->render(context,
6218 LODSphereMesh::Normals | LODSphereMesh::Multipass, frustum,
6219 ri.pixWidth, NULL);
6220
6221 vproc->disable();
6222 }
6223
6224
renderRings(RingSystem & rings,RenderInfo & ri,float planetRadius,float planetOblateness,unsigned int textureResolution,bool renderShadow,const GLContext & context,unsigned int nSections)6225 static void renderRings(RingSystem& rings,
6226 RenderInfo& ri,
6227 float planetRadius,
6228 float planetOblateness,
6229 unsigned int textureResolution,
6230 bool renderShadow,
6231 const GLContext& context,
6232 unsigned int nSections)
6233 {
6234 float inner = rings.innerRadius / planetRadius;
6235 float outer = rings.outerRadius / planetRadius;
6236
6237 // Ring Illumination:
6238 // Since a ring system is composed of millions of individual
6239 // particles, it's not at all realistic to model it as a flat
6240 // Lambertian surface. We'll approximate the llumination
6241 // function by assuming that the ring system contains Lambertian
6242 // particles, and that the brightness at some point in the ring
6243 // system is proportional to the illuminated fraction of a
6244 // particle there. In fact, we'll simplify things further and
6245 // set the illumination of the entire ring system to the same
6246 // value, computing the illuminated fraction of a hypothetical
6247 // particle located at the center of the planet. This
6248 // approximation breaks down when you get close to the planet.
6249 float ringIllumination = 0.0f;
6250 {
6251 float illumFraction = (1.0f + ri.eyeDir_obj * ri.sunDir_obj) / 2.0f;
6252 // Just use the illuminated fraction for now . . .
6253 ringIllumination = illumFraction;
6254 }
6255
6256 GLContext::VertexPath vpath = context.getVertexPath();
6257 VertexProcessor* vproc = context.getVertexProcessor();
6258 FragmentProcessor* fproc = context.getFragmentProcessor();
6259
6260 if (vproc != NULL)
6261 {
6262 vproc->enable();
6263 vproc->use(vp::ringIllum);
6264 vproc->parameter(vp::LightDirection0, ri.sunDir_obj);
6265 vproc->parameter(vp::DiffuseColor0, ri.sunColor * rings.color);
6266 vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
6267 vproc->parameter(vp::Constant0, Vec3f(0, 0.5, 1.0));
6268 }
6269
6270 // If we have multi-texture support, we'll use the second texture unit
6271 // to render the shadow of the planet on the rings. This is a bit of
6272 // a hack, and assumes that the planet is ellipsoidal in shape,
6273 // and only works for a planet illuminated by a single sun where the
6274 // distance to the sun is very large relative to its diameter.
6275 if (renderShadow)
6276 {
6277 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
6278 glEnable(GL_TEXTURE_2D);
6279 shadowTex->bind();
6280
6281 float sPlane[4] = { 0, 0, 0, 0.5f };
6282 float tPlane[4] = { 0, 0, 0, 0.5f };
6283
6284 // Compute the projection vectors based on the sun direction.
6285 // I'm being a little careless here--if the sun direction lies
6286 // along the y-axis, this will fail. It's unlikely that a
6287 // planet would ever orbit underneath its sun (an orbital
6288 // inclination of 90 degrees), but this should be made
6289 // more robust anyway.
6290 Vec3f axis = Vec3f(0, 1, 0) ^ ri.sunDir_obj;
6291 float cosAngle = Vec3f(0.0f, 1.0f, 0.0f) * ri.sunDir_obj;
6292 /*float angle = (float) acos(cosAngle); Unused*/
6293 axis.normalize();
6294
6295 float sScale = 1.0f;
6296 float tScale = 1.0f;
6297 if (fproc == NULL)
6298 {
6299 // When fragment programs aren't used, we render shadows with circular
6300 // textures. We scale up the texture slightly to account for the
6301 // padding pixels near the texture borders.
6302 sScale *= ShadowTextureScale;
6303 tScale *= ShadowTextureScale;
6304 }
6305
6306 if (planetOblateness != 0.0f)
6307 {
6308 // For oblate planets, the size of the shadow volume will vary based
6309 // on the light direction.
6310
6311 // A vertical slice of the planet is an ellipse
6312 float a = 1.0f; // semimajor axis
6313 float b = a * (1.0f - planetOblateness); // semiminor axis
6314 float ecc2 = 1.0f - (b * b) / (a * a); // square of eccentricity
6315
6316 // Calculate the radius of the ellipse at the incident angle of the
6317 // light on the ring plane + 90 degrees.
6318 float r = a * (float) sqrt((1.0f - ecc2) /
6319 (1.0f - ecc2 * square(cosAngle)));
6320
6321 tScale *= a / r;
6322 }
6323
6324 // The s axis is perpendicular to the shadow axis in the plane of the
6325 // of the rings, and the t axis completes the orthonormal basis.
6326 Vec3f sAxis = axis * 0.5f;
6327 Vec3f tAxis = (axis ^ ri.sunDir_obj) * 0.5f * tScale;
6328
6329 sPlane[0] = sAxis.x; sPlane[1] = sAxis.y; sPlane[2] = sAxis.z;
6330 tPlane[0] = tAxis.x; tPlane[1] = tAxis.y; tPlane[2] = tAxis.z;
6331
6332 if (vproc != NULL)
6333 {
6334 vproc->parameter(vp::TexGen_S, sPlane);
6335 vproc->parameter(vp::TexGen_T, tPlane);
6336 }
6337 else
6338 {
6339 glEnable(GL_TEXTURE_GEN_S);
6340 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
6341 glTexGenfv(GL_S, GL_EYE_PLANE, sPlane);
6342 glEnable(GL_TEXTURE_GEN_T);
6343 glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
6344 glTexGenfv(GL_T, GL_EYE_PLANE, tPlane);
6345 }
6346
6347 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
6348
6349 if (fproc != NULL)
6350 {
6351 float r0 = 0.24f;
6352 float r1 = 0.25f;
6353 float bias = 1.0f / (1.0f - r1 / r0);
6354 float scale = -bias / r0;
6355
6356 fproc->enable();
6357 fproc->use(fp::sphereShadowOnRings);
6358 fproc->parameter(fp::ShadowParams0, scale, bias, 0.0f, 0.0f);
6359 fproc->parameter(fp::AmbientColor, ri.ambientColor * ri.color);
6360 }
6361 }
6362
6363 glEnable(GL_BLEND);
6364 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
6365
6366 Texture* ringsTex = rings.texture.find(textureResolution);
6367
6368 if (ringsTex != NULL)
6369 {
6370 glEnable(GL_TEXTURE_2D);
6371 ringsTex->bind();
6372 }
6373 else
6374 {
6375 glDisable(GL_TEXTURE_2D);
6376 }
6377
6378 // Perform our own lighting for the rings.
6379 // TODO: Don't forget about light source color (required when we
6380 // paying attention to star color.)
6381 if (vpath == GLContext::VPath_Basic)
6382 {
6383 glDisable(GL_LIGHTING);
6384 Vec3f litColor(rings.color.red(), rings.color.green(), rings.color.blue());
6385 litColor = litColor * ringIllumination +
6386 Vec3f(ri.ambientColor.red(), ri.ambientColor.green(),
6387 ri.ambientColor.blue());
6388 glColor4f(litColor.x, litColor.y, litColor.z, 1.0f);
6389 }
6390
6391 // This gets tricky . . . we render the rings in two parts. One
6392 // part is potentially shadowed by the planet, and we need to
6393 // render that part with the projected shadow texture enabled.
6394 // The other part isn't shadowed, but will appear so if we don't
6395 // first disable the shadow texture. The problem is that the
6396 // shadow texture will affect anything along the line between the
6397 // sun and the planet, regardless of whether it's in front or
6398 // behind the planet.
6399
6400 // Compute the angle of the sun projected on the ring plane
6401 float sunAngle = (float) atan2(ri.sunDir_obj.z, ri.sunDir_obj.x);
6402
6403 // If there's a fragment program, it will handle the ambient term--make
6404 // sure that we don't add it both in the fragment and vertex programs.
6405 if (vproc != NULL && fproc != NULL)
6406 glAmbientLightColor(Color::Black);
6407
6408 renderRingSystem(inner, outer,
6409 (float) (sunAngle + PI / 2),
6410 (float) (sunAngle + 3 * PI / 2),
6411 nSections / 2);
6412 renderRingSystem(inner, outer,
6413 (float) (sunAngle + 3 * PI / 2),
6414 (float) (sunAngle + PI / 2),
6415 nSections / 2);
6416
6417 if (vproc != NULL && fproc != NULL)
6418 glAmbientLightColor(ri.ambientColor * ri.color);
6419
6420 // Disable the second texture unit if it was used
6421 if (renderShadow)
6422 {
6423 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
6424 glDisable(GL_TEXTURE_2D);
6425 glDisable(GL_TEXTURE_GEN_S);
6426 glDisable(GL_TEXTURE_GEN_T);
6427 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
6428
6429 if (fproc != NULL)
6430 fproc->disable();
6431 }
6432
6433 // Render the unshadowed side
6434 renderRingSystem(inner, outer,
6435 (float) (sunAngle - PI / 2),
6436 (float) (sunAngle + PI / 2),
6437 nSections / 2);
6438 renderRingSystem(inner, outer,
6439 (float) (sunAngle + PI / 2),
6440 (float) (sunAngle - PI / 2),
6441 nSections / 2);
6442 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
6443
6444 if (vproc != NULL)
6445 vproc->disable();
6446 }
6447
6448
6449 static void
renderEclipseShadows(Geometry * geometry,vector<EclipseShadow> & eclipseShadows,RenderInfo & ri,float planetRadius,Mat4f & planetMat,Frustum & viewFrustum,const GLContext & context)6450 renderEclipseShadows(Geometry* geometry,
6451 vector<EclipseShadow>& eclipseShadows,
6452 RenderInfo& ri,
6453 float planetRadius,
6454 Mat4f& planetMat,
6455 Frustum& viewFrustum,
6456 const GLContext& context)
6457 {
6458 // Eclipse shadows on mesh objects aren't working yet.
6459 if (geometry != NULL)
6460 return;
6461
6462 for (vector<EclipseShadow>::iterator iter = eclipseShadows.begin();
6463 iter != eclipseShadows.end(); iter++)
6464 {
6465 EclipseShadow shadow = *iter;
6466
6467 #ifdef DEBUG_ECLIPSE_SHADOWS
6468 // Eclipse debugging: render the central axis of the eclipse
6469 // shadow volume.
6470 glDisable(GL_TEXTURE_2D);
6471 glColor4f(1, 0, 0, 1);
6472 Point3f blorp = shadow.origin * planetMat;
6473 Vec3f blah = shadow.direction * planetMat;
6474 blorp.x /= planetRadius; blorp.y /= planetRadius; blorp.z /= planetRadius;
6475 float foo = blorp.distanceFromOrigin();
6476 glBegin(GL_LINES);
6477 glVertex(blorp);
6478 glVertex(blorp + foo * blah);
6479 glEnd();
6480 glEnable(GL_TEXTURE_2D);
6481 #endif
6482
6483 // Determine which eclipse shadow texture to use. This is only
6484 // a very rough approximation to reality. Since there are an
6485 // infinite number of possible eclipse volumes, what we should be
6486 // doing is generating the eclipse textures on the fly using
6487 // render-to-texture. But for now, we'll just choose from a fixed
6488 // set of eclipse shadow textures based on the relative size of
6489 // the umbra and penumbra.
6490 Texture* eclipseTex = NULL;
6491 float umbra = shadow.umbraRadius / shadow.penumbraRadius;
6492 if (umbra < 0.1f)
6493 eclipseTex = eclipseShadowTextures[0];
6494 else if (umbra < 0.35f)
6495 eclipseTex = eclipseShadowTextures[1];
6496 else if (umbra < 0.6f)
6497 eclipseTex = eclipseShadowTextures[2];
6498 else if (umbra < 0.9f)
6499 eclipseTex = eclipseShadowTextures[3];
6500 else
6501 eclipseTex = shadowTex;
6502
6503 // Compute the transformation to use for generating texture
6504 // coordinates from the object vertices.
6505 Point3f origin = shadow.origin * planetMat;
6506 Vec3f dir = shadow.direction * planetMat;
6507 float scale = planetRadius / shadow.penumbraRadius;
6508 Vec3f axis = Vec3f(0, 1, 0) ^ dir;
6509 float angle = (float) acos(Vec3f(0, 1, 0) * dir);
6510 axis.normalize();
6511 Mat4f mat = Mat4f::rotation(axis, -angle);
6512 Vec3f sAxis = Vec3f(0.5f * scale, 0, 0) * mat;
6513 Vec3f tAxis = Vec3f(0, 0, 0.5f * scale) * mat;
6514
6515 float sPlane[4] = { 0, 0, 0, 0 };
6516 float tPlane[4] = { 0, 0, 0, 0 };
6517 sPlane[0] = sAxis.x; sPlane[1] = sAxis.y; sPlane[2] = sAxis.z;
6518 tPlane[0] = tAxis.x; tPlane[1] = tAxis.y; tPlane[2] = tAxis.z;
6519 sPlane[3] = (Point3f(0, 0, 0) - origin) * sAxis / planetRadius + 0.5f;
6520 tPlane[3] = (Point3f(0, 0, 0) - origin) * tAxis / planetRadius + 0.5f;
6521
6522 // TODO: Multiple eclipse shadows should be rendered in a single
6523 // pass using multitexture.
6524 if (eclipseTex != NULL)
6525 eclipseTex->bind();
6526 // shadowMaskTexture->bind();
6527 glEnable(GL_BLEND);
6528 glBlendFunc(GL_ZERO, GL_SRC_COLOR);
6529
6530 // If the ambient light level is greater than zero, reduce the
6531 // darkness of the shadows.
6532 if (ri.useTexEnvCombine)
6533 {
6534 float color[4] = { ri.ambientColor.red(), ri.ambientColor.green(),
6535 ri.ambientColor.blue(), 1.0f };
6536 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
6537 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
6538 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_CONSTANT_EXT);
6539 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
6540 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
6541 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
6542 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
6543
6544 // The second texture unit has the shadow 'mask'
6545 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
6546 glEnable(GL_TEXTURE_2D);
6547 shadowMaskTexture->bind();
6548 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
6549 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
6550 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
6551 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
6552 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
6553 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
6554 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
6555 }
6556
6557 // Since invariance between nVidia's vertex programs and the
6558 // standard transformation pipeline isn't guaranteed, we have to
6559 // make sure to use the same transformation engine on subsequent
6560 // rendering passes as we did on the initial one.
6561 if (context.getVertexPath() != GLContext::VPath_Basic && geometry == NULL)
6562 {
6563 renderShadowedGeometryVertexShader(ri, viewFrustum,
6564 sPlane, tPlane,
6565 dir,
6566 context);
6567 }
6568 else
6569 {
6570 renderShadowedGeometryDefault(geometry, ri, viewFrustum,
6571 sPlane, tPlane,
6572 dir,
6573 ri.useTexEnvCombine,
6574 context);
6575 }
6576
6577 if (ri.useTexEnvCombine)
6578 {
6579 // Disable second texture unit
6580 glx::glActiveTextureARB(GL_TEXTURE1_ARB);
6581 glDisable(GL_TEXTURE_2D);
6582 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
6583 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
6584
6585 float color[4] = { 0, 0, 0, 0 };
6586 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
6587 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
6588 }
6589
6590 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
6591 glDisable(GL_BLEND);
6592 }
6593 }
6594
6595
6596 static void
renderEclipseShadows_Shaders(Geometry * geometry,vector<EclipseShadow> & eclipseShadows,RenderInfo & ri,float planetRadius,Mat4f & planetMat,Frustum & viewFrustum,const GLContext & context)6597 renderEclipseShadows_Shaders(Geometry* geometry,
6598 vector<EclipseShadow>& eclipseShadows,
6599 RenderInfo& ri,
6600 float planetRadius,
6601 Mat4f& planetMat,
6602 Frustum& viewFrustum,
6603 const GLContext& context)
6604 {
6605 // Eclipse shadows on mesh objects aren't working yet.
6606 if (geometry != NULL)
6607 return;
6608
6609 glEnable(GL_TEXTURE_2D);
6610 penumbraFunctionTexture->bind();
6611
6612 glEnable(GL_BLEND);
6613 glBlendFunc(GL_ZERO, GL_SRC_COLOR);
6614
6615 float sPlanes[4][4];
6616 float tPlanes[4][4];
6617 float shadowParams[4][4];
6618
6619 int n = 0;
6620 for (vector<EclipseShadow>::iterator iter = eclipseShadows.begin();
6621 iter != eclipseShadows.end() && n < 4; iter++, n++)
6622 {
6623 EclipseShadow shadow = *iter;
6624
6625 float R2 = 0.25f;
6626 float umbra = shadow.umbraRadius / shadow.penumbraRadius;
6627 umbra = umbra * umbra;
6628 if (umbra < 0.0001f)
6629 umbra = 0.0001f;
6630 else if (umbra > 0.99f)
6631 umbra = 0.99f;
6632
6633 float umbraRadius = R2 * umbra;
6634 float penumbraRadius = R2;
6635 float shadowBias = 1.0f / (1.0f - penumbraRadius / umbraRadius);
6636 float shadowScale = -shadowBias / umbraRadius;
6637
6638 shadowParams[n][0] = shadowScale;
6639 shadowParams[n][1] = shadowBias;
6640 shadowParams[n][2] = 0.0f;
6641 shadowParams[n][3] = 0.0f;
6642
6643 // Compute the transformation to use for generating texture
6644 // coordinates from the object vertices.
6645 Point3f origin = shadow.origin * planetMat;
6646 Vec3f dir = shadow.direction * planetMat;
6647 float scale = planetRadius / shadow.penumbraRadius;
6648 Vec3f axis = Vec3f(0, 1, 0) ^ dir;
6649 float angle = (float) acos(Vec3f(0, 1, 0) * dir);
6650 axis.normalize();
6651 Mat4f mat = Mat4f::rotation(axis, -angle);
6652 Vec3f sAxis = Vec3f(0.5f * scale, 0, 0) * mat;
6653 Vec3f tAxis = Vec3f(0, 0, 0.5f * scale) * mat;
6654
6655 sPlanes[n][0] = sAxis.x;
6656 sPlanes[n][1] = sAxis.y;
6657 sPlanes[n][2] = sAxis.z;
6658 sPlanes[n][3] = (Point3f(0, 0, 0) - origin) * sAxis / planetRadius + 0.5f;
6659 tPlanes[n][0] = tAxis.x;
6660 tPlanes[n][1] = tAxis.y;
6661 tPlanes[n][2] = tAxis.z;
6662 tPlanes[n][3] = (Point3f(0, 0, 0) - origin) * tAxis / planetRadius + 0.5f;
6663 }
6664
6665
6666 VertexProcessor* vproc = context.getVertexProcessor();
6667 FragmentProcessor* fproc = context.getFragmentProcessor();
6668
6669 vproc->enable();
6670 vproc->use(vp::multiShadow);
6671
6672 fproc->enable();
6673 if (n == 1)
6674 fproc->use(fp::eclipseShadow1);
6675 else
6676 fproc->use(fp::eclipseShadow2);
6677
6678 fproc->parameter(fp::ShadowParams0, shadowParams[0]);
6679 vproc->parameter(vp::TexGen_S, sPlanes[0]);
6680 vproc->parameter(vp::TexGen_T, tPlanes[0]);
6681 if (n >= 2)
6682 {
6683 fproc->parameter(fp::ShadowParams1, shadowParams[1]);
6684 vproc->parameter(vp::TexGen_S2, sPlanes[1]);
6685 vproc->parameter(vp::TexGen_T2, tPlanes[1]);
6686 }
6687 if (n >= 3)
6688 {
6689 //fproc->parameter(fp::ShadowParams2, shadowParams[2]);
6690 vproc->parameter(vp::TexGen_S3, sPlanes[2]);
6691 vproc->parameter(vp::TexGen_T3, tPlanes[2]);
6692 }
6693 if (n >= 4)
6694 {
6695 //fproc->parameter(fp::ShadowParams3, shadowParams[3]);
6696 vproc->parameter(vp::TexGen_S4, sPlanes[3]);
6697 vproc->parameter(vp::TexGen_T4, tPlanes[3]);
6698 }
6699
6700 //vproc->parameter(vp::LightDirection0, lightDir);
6701
6702 g_lodSphere->render(context,
6703 LODSphereMesh::Normals | LODSphereMesh::Multipass,
6704 viewFrustum,
6705 ri.pixWidth, NULL);
6706
6707 vproc->disable();
6708 fproc->disable();
6709
6710 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
6711 glDisable(GL_BLEND);
6712 }
6713
6714
6715 static void
renderRingShadowsVS(Geometry *,const RingSystem & rings,const Vec3f &,RenderInfo & ri,float planetRadius,float,Mat4f &,Frustum & viewFrustum,const GLContext & context)6716 renderRingShadowsVS(Geometry* /*geometry*/, //TODO: Remove unused parameters??
6717 const RingSystem& rings,
6718 const Vec3f& /*sunDir*/,
6719 RenderInfo& ri,
6720 float planetRadius,
6721 float /*oblateness*/,
6722 Mat4f& /*planetMat*/,
6723 Frustum& viewFrustum,
6724 const GLContext& context)
6725 {
6726 // Compute the transformation to use for generating texture
6727 // coordinates from the object vertices.
6728 float ringWidth = rings.outerRadius - rings.innerRadius;
6729 float s = ri.sunDir_obj.y;
6730 float scale = (abs(s) < 0.001f) ? 1000.0f : 1.0f / s;
6731
6732 if (abs(s) > 1.0f - 1.0e-4f)
6733 {
6734 // Planet is illuminated almost directly from above, so
6735 // no ring shadow will be cast on the planet. Conveniently
6736 // avoids some potential division by zero when ray-casting.
6737 return;
6738 }
6739
6740 glEnable(GL_BLEND);
6741 glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
6742
6743 // If the ambient light level is greater than zero, reduce the
6744 // darkness of the shadows.
6745 float color[4] = { ri.ambientColor.red(), ri.ambientColor.green(),
6746 ri.ambientColor.blue(), 1.0f };
6747 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
6748 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
6749 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_CONSTANT_EXT);
6750 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR);
6751 glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
6752 glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR);
6753 glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);
6754
6755 // Tweak the texture--set clamp to border and a border color with
6756 // a zero alpha. If a graphics card doesn't support clamp to border,
6757 // it doesn't get to play. It's possible to get reasonable behavior
6758 // by turning off mipmaps and assuming transparent rows of pixels for
6759 // the top and bottom of the ring textures . . . maybe later.
6760 float bc[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
6761 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bc);
6762 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER_ARB);
6763
6764 // Ring shadows look strange if they're always completely black. Vary
6765 // the darkness of the shadow based on the angle between the sun and the
6766 // ring plane. There's some justification for this--the larger the angle
6767 // between the sun and the ring plane (normal), the more ring material
6768 // there is to travel through.
6769 //float alpha = (1.0f - abs(ri.sunDir_obj.y)) * 1.0f;
6770 // ...but, images from Cassini are showing very dark ring shadows, so we'll
6771 // go with that.
6772 float alpha = 1.0f;
6773
6774 VertexProcessor* vproc = context.getVertexProcessor();
6775 assert(vproc != NULL);
6776
6777 vproc->enable();
6778 vproc->use(vp::ringShadow);
6779 vproc->parameter(vp::LightDirection0, ri.sunDir_obj);
6780 vproc->parameter(vp::DiffuseColor0, 1, 1, 1, alpha); // color = white
6781 vproc->parameter(vp::TexGen_S,
6782 rings.innerRadius / planetRadius,
6783 1.0f / (ringWidth / planetRadius),
6784 0.0f, 0.5f);
6785 vproc->parameter(vp::TexGen_T, scale, 0, 0, 0);
6786 g_lodSphere->render(context, LODSphereMesh::Multipass,
6787 viewFrustum, ri.pixWidth, NULL);
6788 vproc->disable();
6789
6790 // Restore the texture combiners
6791 if (ri.useTexEnvCombine)
6792 {
6793 float color[4] = { 0, 0, 0, 0 };
6794 glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color);
6795 glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
6796 }
6797
6798 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
6799 glDisable(GL_BLEND);
6800 }
6801
6802
renderLocations(const Body & body,const Vec3d & bodyPosition,const Quatd & bodyOrientation)6803 void Renderer::renderLocations(const Body& body,
6804 const Vec3d& bodyPosition,
6805 const Quatd& bodyOrientation)
6806 {
6807 const vector<Location*>* locations = body.getLocations();
6808 if (locations == NULL)
6809 return;
6810
6811 Vec3f semiAxes = body.getSemiAxes();
6812
6813 float nearDist = getNearPlaneDistance();
6814 double boundingRadius = max(semiAxes.x, max(semiAxes.y, semiAxes.z));
6815
6816 Vec3d bodyCenter(bodyPosition.x, bodyPosition.y, bodyPosition.z);
6817 Vec3d viewRayOrigin = -bodyCenter * (~bodyOrientation).toMatrix3();
6818 double labelOffset = 0.0001;
6819
6820 Vec3f vn = Vec3f(0.0f, 0.0f, -1.0f) * getCameraOrientation().toMatrix3();
6821 Vec3d viewNormal(vn.x, vn.y, vn.z);
6822
6823 Ellipsoidd bodyEllipsoid(Vec3d(semiAxes.x, semiAxes.y, semiAxes.z));
6824
6825 Mat3d bodyMatrix = bodyOrientation.toMatrix3();
6826
6827 for (vector<Location*>::const_iterator iter = locations->begin();
6828 iter != locations->end(); iter++)
6829 {
6830 const Location& location = **iter;
6831
6832 if (location.getFeatureType() & locationFilter)
6833 {
6834 // Get the position of the location with respect to the planet center
6835 Vec3f ppos = location.getPosition();
6836
6837 // Compute the bodycentric position of the location
6838 Vec3d locPos = Vec3d(ppos.x, ppos.y, ppos.z);
6839
6840 // Get the planetocentric position of the label. Add a slight scale factor
6841 // to keep the point from being exactly on the surface.
6842 Vec3d pcLabelPos = locPos * (1.0 + labelOffset);
6843
6844 // Get the camera space label position
6845 Vec3d labelPos = bodyCenter + locPos * bodyMatrix;
6846
6847 float effSize = location.getImportance();
6848 if (effSize < 0.0f)
6849 effSize = location.getSize();
6850
6851 float pixSize = effSize / (float) (labelPos.length() * pixelSize);
6852
6853 if (pixSize > minFeatureSize && labelPos * viewNormal > 0.0)
6854 {
6855 // Labels on non-ellipsoidal bodies need special handling; the
6856 // ellipsoid visibility test will always fail for them, since they
6857 // will lie on the surface of the mesh, which is inside the
6858 // the bounding ellipsoid. The following code projects location positions
6859 // onto the bounding sphere.
6860 if (!body.isEllipsoid())
6861 {
6862 double r = locPos.length();
6863 if (r < boundingRadius)
6864 pcLabelPos = locPos * (boundingRadius * 1.01 / r);
6865 }
6866
6867 double t = 0.0;
6868
6869 // Test for an intersection of the eye-to-location ray with
6870 // the planet ellipsoid. If we hit the planet first, then
6871 // the label is obscured by the planet. An exact calculation
6872 // for irregular objects would be too expensive, and the
6873 // ellipsoid approximation works reasonably well for them.
6874 Ray3d testRay(Point3d(viewRayOrigin.x, viewRayOrigin.y, viewRayOrigin.z),
6875 pcLabelPos - viewRayOrigin);
6876 bool hit = testIntersection(testRay, bodyEllipsoid, t);
6877 Vec3d blah = labelPos - viewRayOrigin;
6878
6879 if (!hit || t >= 1.0)
6880 {
6881 // Calculate the intersection of the eye-to-label ray with the plane perpendicular to
6882 // the view normal that touches the front of the object's bounding sphere
6883 double planetZ = viewNormal * bodyCenter - boundingRadius;
6884 if (planetZ < -nearDist * 1.001)
6885 planetZ = -nearDist * 1.001;
6886 double z = viewNormal * labelPos;
6887 labelPos *= planetZ / z;
6888
6889 uint32 featureType = location.getFeatureType();
6890 MarkerRepresentation* locationMarker = NULL;
6891 if (featureType & Location::City)
6892 locationMarker = &cityRep;
6893 else if (featureType & (Location::LandingSite | Location::Observatory))
6894 locationMarker = &observatoryRep;
6895 else if (featureType & (Location::Crater | Location::Patera))
6896 locationMarker = &craterRep;
6897 else if (featureType & (Location::Mons | Location::Tholus))
6898 locationMarker = &mountainRep;
6899 else if (featureType & (Location::EruptiveCenter))
6900 locationMarker = &genericLocationRep;
6901
6902 Color labelColor = location.isLabelColorOverridden() ? location.getLabelColor() : LocationLabelColor;
6903 addObjectAnnotation(locationMarker,
6904 location.getName(true),
6905 labelColor,
6906 Point3f((float) labelPos.x, (float) labelPos.y, (float) labelPos.z));
6907 }
6908 }
6909 }
6910 }
6911 }
6912
6913
6914 // Estimate the fraction of light reflected from a sphere that
6915 // reaches an object at the specified position relative to that
6916 // sphere.
6917 //
6918 // This is function is just a rough approximation to the actual
6919 // lighting integral, but it reproduces the important features
6920 // of the way that phase and distance affect reflected light:
6921 // - Higher phase angles mean less reflected light
6922 // - The closer an object is to the reflector, the less
6923 // area of the reflector that is visible.
6924 //
6925 // We approximate the reflected light by taking a weighted average
6926 // of the reflected light at three points on the reflector: the
6927 // light receiver's sub-point, and the two horizon points in the
6928 // plane of the light vector and receiver-to-reflector vector.
6929 //
6930 // The reflecting object is assumed to be spherical and perfectly
6931 // Lambertian.
6932 static float
estimateReflectedLightFraction(const Vec3d & toSun,const Vec3d & toObject,float radius)6933 estimateReflectedLightFraction(const Vec3d& toSun,
6934 const Vec3d& toObject,
6935 float radius)
6936 {
6937 // Theta is half the arc length visible to the reflector
6938 double d = toObject.length();
6939 float cosTheta = (float) (radius / d);
6940 if (cosTheta > 0.999f)
6941 cosTheta = 0.999f;
6942
6943 // Phi is the angle between the light vector and receiver-to-reflector vector.
6944 // cos(phi) is thus the illumination at the sub-point. The horizon points are
6945 // at phi+theta and phi-theta.
6946 float cosPhi = (float) ((toSun * toObject) / (d * toSun.length()));
6947
6948 // Use a trigonometric identity to compute cos(phi +/- theta):
6949 // cos(phi + theta) = cos(phi) * cos(theta) - sin(phi) * sin(theta)
6950
6951 // s = sin(phi) * sin(theta)
6952 float s = (float) sqrt((1.0f - cosPhi * cosPhi) * (1.0f - cosTheta * cosTheta));
6953
6954 float cosPhi1 = cosPhi * cosTheta - s; // cos(phi + theta)
6955 float cosPhi2 = cosPhi * cosTheta + s; // cos(phi - theta)
6956
6957 // Calculate a weighted average of illumination at the three points
6958 return (2.0f * max(cosPhi, 0.0f) + max(cosPhi1, 0.0f) + max(cosPhi2, 0.0f)) * 0.25f;
6959 }
6960
6961
6962 static void
setupObjectLighting(const vector<LightSource> & suns,const vector<SecondaryIlluminator> & secondaryIlluminators,const Quatf & objOrientation,const Vec3f & objScale,const Point3f & objPosition_eye,bool isNormalized,const float faintestMag,const float saturationMag,const float appMag,LightingState & ls)6963 setupObjectLighting(const vector<LightSource>& suns,
6964 const vector<SecondaryIlluminator>& secondaryIlluminators,
6965 const Quatf& objOrientation,
6966 const Vec3f& objScale,
6967 const Point3f& objPosition_eye,
6968 bool isNormalized,
6969 #ifdef USE_HDR
6970 const float faintestMag,
6971 const float saturationMag,
6972 const float appMag,
6973 #endif
6974 LightingState& ls)
6975 {
6976 unsigned int nLights = min(MaxLights, (unsigned int) suns.size());
6977 if (nLights == 0)
6978 return;
6979
6980 #ifdef USE_HDR
6981 float exposureFactor = (faintestMag - appMag)/(faintestMag - saturationMag + 0.001f);
6982 #endif
6983
6984 unsigned int i;
6985 for (i = 0; i < nLights; i++)
6986 {
6987 Vec3d dir = suns[i].position - Vec3d(objPosition_eye.x, objPosition_eye.y, objPosition_eye.z);
6988
6989 ls.lights[i].direction_eye =
6990 Vec3f((float) dir.x, (float) dir.y, (float) dir.z);
6991 float distance = ls.lights[i].direction_eye.length();
6992 ls.lights[i].direction_eye *= 1.0f / distance;
6993 distance = astro::kilometersToAU((float) dir.length());
6994 ls.lights[i].irradiance = suns[i].luminosity / (distance * distance);
6995 ls.lights[i].color = suns[i].color;
6996
6997 // Store the position and apparent size because we'll need them for
6998 // testing for eclipses.
6999 ls.lights[i].position = dir;
7000 ls.lights[i].apparentSize = (float) (suns[i].radius / dir.length());
7001 ls.lights[i].castsShadows = true;
7002 }
7003
7004 // Include effects of secondary illumination (i.e. planetshine)
7005 if (!secondaryIlluminators.empty() && i < MaxLights - 1)
7006 {
7007 float maxIrr = 0.0f;
7008 unsigned int maxIrrSource = 0;
7009 Vec3d objpos(objPosition_eye.x, objPosition_eye.y, objPosition_eye.z);
7010
7011 // Only account for light from the brightest secondary source
7012 for (vector<SecondaryIlluminator>::const_iterator iter = secondaryIlluminators.begin();
7013 iter != secondaryIlluminators.end(); iter++)
7014 {
7015 Vec3d toIllum = iter->position_v - objpos; // reflector-to-object vector
7016 float distSquared = (float) toIllum.lengthSquared() / square(iter->radius);
7017
7018 if (distSquared > 0.01f)
7019 {
7020 // Irradiance falls off with distance^2
7021 float irr = iter->reflectedIrradiance / distSquared;
7022
7023 // Phase effects will always leave the irradiance unaffected or reduce it;
7024 // don't bother calculating them if we've already found a brighter secondary
7025 // source.
7026 if (irr > maxIrr)
7027 {
7028 // Account for the phase
7029 Vec3d toSun = objpos - suns[0].position;
7030 irr *= estimateReflectedLightFraction(toSun, toIllum, iter->radius);
7031 if (irr > maxIrr)
7032 {
7033 maxIrr = irr;
7034 maxIrrSource = iter - secondaryIlluminators.begin();
7035 }
7036 }
7037 }
7038 }
7039 #if DEBUG_SECONDARY_ILLUMINATION
7040 clog << "maxIrr = " << maxIrr << ", "
7041 << secondaryIlluminators[maxIrrSource].body->getName() << ", "
7042 << secondaryIlluminators[maxIrrSource].reflectedIrradiance << endl;
7043 #endif
7044
7045 if (maxIrr > 0.0f)
7046 {
7047 Vec3d toIllum = secondaryIlluminators[maxIrrSource].position_v - objpos;
7048
7049 ls.lights[i].direction_eye = Vec3f((float) toIllum.x, (float) toIllum.y, (float) toIllum.z);
7050 ls.lights[i].direction_eye.normalize();
7051 ls.lights[i].irradiance = maxIrr;
7052 ls.lights[i].color = secondaryIlluminators[maxIrrSource].body->getSurface().color;
7053 ls.lights[i].apparentSize = 0.0f;
7054 ls.lights[i].castsShadows = false;
7055 i++;
7056 nLights++;
7057 }
7058 }
7059
7060 // Sort light sources by brightness. Light zero should always be the
7061 // brightest. Optimize common cases of one and two lights.
7062 if (nLights == 2)
7063 {
7064 if (ls.lights[0].irradiance < ls.lights[1].irradiance)
7065 swap(ls.lights[0], ls.lights[1]);
7066 }
7067 else if (nLights > 2)
7068 {
7069 sort(ls.lights, ls.lights + nLights, LightIrradiancePredicate());
7070 }
7071
7072 // Compute the total irradiance
7073 float totalIrradiance = 0.0f;
7074 for (i = 0; i < nLights; i++)
7075 totalIrradiance += ls.lights[i].irradiance;
7076
7077 // Compute a gamma factor to make dim light sources visible. This is
7078 // intended to approximate what we see with our eyes--for example,
7079 // Earth-shine is visible on the night side of the Moon, even though
7080 // the amount of reflected light from the Earth is 1/10000 of what
7081 // the Moon receives directly from the Sun.
7082 //
7083 // TODO: Skip this step when high dynamic range rendering to floating point
7084 // buffers is enabled.
7085 float minVisibleFraction = 1.0f / 10000.0f;
7086 float minDisplayableValue = 1.0f / 255.0f;
7087 float gamma = (float) (log(minDisplayableValue) / log(minVisibleFraction));
7088 float minVisibleIrradiance = minVisibleFraction * totalIrradiance;
7089
7090 Mat3f m = (~objOrientation).toMatrix3();
7091
7092 // Gamma scale and normalize the light sources; cull light sources that
7093 // aren't bright enough to contribute the final pixels rendered into the
7094 // frame buffer.
7095 ls.nLights = 0;
7096 for (i = 0; i < nLights && ls.lights[i].irradiance > minVisibleIrradiance; i++)
7097 {
7098 #ifdef USE_HDR
7099 ls.lights[i].irradiance *= exposureFactor / totalIrradiance;
7100 #else
7101 ls.lights[i].irradiance =
7102 (float) pow(ls.lights[i].irradiance / totalIrradiance, gamma);
7103 #endif
7104
7105 // Compute the direction of the light in object space
7106 ls.lights[i].direction_obj = ls.lights[i].direction_eye * m;
7107
7108 ls.nLights++;
7109 }
7110
7111 ls.eyePos_obj = Point3f(-objPosition_eye.x / objScale.x,
7112 -objPosition_eye.y / objScale.y,
7113 -objPosition_eye.z / objScale.z) * m;
7114 ls.eyeDir_obj = (Point3f(0.0f, 0.0f, 0.0f) - objPosition_eye) * m;
7115 ls.eyeDir_obj.normalize();
7116
7117 // When the camera is very far from the object, some view-dependent
7118 // calculations in the shaders can exhibit precision problems. This
7119 // occurs with atmospheres, where the scale height of the atmosphere
7120 // is very small relative to the planet radius. To address the problem,
7121 // we'll clamp the eye distance to some maximum value. The effect of the
7122 // adjustment should be impercetible, since at large distances rays from
7123 // the camera to object vertices are all nearly parallel to each other.
7124 float eyeFromCenterDistance = ls.eyePos_obj.distanceFromOrigin();
7125 if (eyeFromCenterDistance > 100.0f && isNormalized)
7126 {
7127 float s = 100.0f / eyeFromCenterDistance;
7128 ls.eyePos_obj.x *= s;
7129 ls.eyePos_obj.y *= s;
7130 ls.eyePos_obj.z *= s;
7131 }
7132
7133 ls.ambientColor = Vec3f(0.0f, 0.0f, 0.0f);
7134
7135 #if 0
7136 // Old code: linear scaling approach
7137
7138 // After sorting, the first light is always the brightest
7139 float maxIrradiance = ls.lights[0].irradiance;
7140
7141 // Normalize the brightnesses of the light sources.
7142 // TODO: Investigate logarithmic functions for scaling light brightness, to
7143 // better simulate what the human eye would see.
7144 ls.nLights = 0;
7145 for (i = 0; i < nLights; i++)
7146 {
7147 ls.lights[i].irradiance /= maxIrradiance;
7148
7149 // Cull light sources that don't contribute significantly (less than
7150 // the resolution of an 8-bit color channel.)
7151 if (ls.lights[i].irradiance < 1.0f / 255.0f)
7152 break;
7153
7154 // Compute the direction of the light in object space
7155 ls.lights[i].direction_obj = ls.lights[i].direction_eye * m;
7156
7157 ls.nLights++;
7158 }
7159 #endif
7160 }
7161
7162
renderObject(Point3f pos,float distance,double now,Quatf cameraOrientation,float nearPlaneDistance,float farPlaneDistance,RenderProperties & obj,const LightingState & ls)7163 void Renderer::renderObject(Point3f pos,
7164 float distance,
7165 double now,
7166 Quatf cameraOrientation,
7167 float nearPlaneDistance,
7168 float farPlaneDistance,
7169 RenderProperties& obj,
7170 const LightingState& ls)
7171 {
7172 RenderInfo ri;
7173
7174 float altitude = distance - obj.radius;
7175 float discSizeInPixels = obj.radius /
7176 (max(nearPlaneDistance, altitude) * pixelSize);
7177
7178 ri.sunDir_eye = Vec3f(0.0f, 1.0f, 0.0f);
7179 ri.sunDir_obj = Vec3f(0.0f, 1.0f, 0.0f);
7180 ri.sunColor = Color(0.0f, 0.0f, 0.0f);
7181 if (ls.nLights > 0)
7182 {
7183 ri.sunDir_eye = ls.lights[0].direction_eye;
7184 ri.sunDir_obj = ls.lights[0].direction_obj;
7185 ri.sunColor = ls.lights[0].color;// * ls.lights[0].intensity;
7186 }
7187
7188 // Enable depth buffering
7189 glEnable(GL_DEPTH_TEST);
7190 glDepthMask(GL_TRUE);
7191
7192 glDisable(GL_BLEND);
7193
7194 // Get the object's geometry; NULL indicates that object is an
7195 // ellipsoid.
7196 Geometry* geometry = NULL;
7197 if (obj.geometry != InvalidResource)
7198 {
7199 // This is a model loaded from a file
7200 geometry = GetGeometryManager()->find(obj.geometry);
7201 }
7202
7203 // Get the textures . . .
7204 if (obj.surface->baseTexture.tex[textureResolution] != InvalidResource)
7205 ri.baseTex = obj.surface->baseTexture.find(textureResolution);
7206 if ((obj.surface->appearanceFlags & Surface::ApplyBumpMap) != 0 &&
7207 context->bumpMappingSupported() &&
7208 obj.surface->bumpTexture.tex[textureResolution] != InvalidResource)
7209 ri.bumpTex = obj.surface->bumpTexture.find(textureResolution);
7210 if ((obj.surface->appearanceFlags & Surface::ApplyNightMap) != 0 &&
7211 (renderFlags & ShowNightMaps) != 0)
7212 ri.nightTex = obj.surface->nightTexture.find(textureResolution);
7213 if ((obj.surface->appearanceFlags & Surface::SeparateSpecularMap) != 0)
7214 ri.glossTex = obj.surface->specularTexture.find(textureResolution);
7215 if ((obj.surface->appearanceFlags & Surface::ApplyOverlay) != 0)
7216 ri.overlayTex = obj.surface->overlayTexture.find(textureResolution);
7217
7218 // Apply the modelview transform for the object
7219 glPushMatrix();
7220 glTranslate(pos);
7221 glRotate(~obj.orientation);
7222
7223 // Scaling will be nonuniform for nonspherical planets. As long as the
7224 // deviation from spherical isn't too large, the nonuniform scale factor
7225 // shouldn't mess up the lighting calculations enough to be noticeable
7226 // (and we turn on renormalization anyhow, which most graphics cards
7227 // support.)
7228 float radius = obj.radius;
7229 Vec3f scaleFactors;
7230 float geometryScale;
7231 if (geometry == NULL || geometry->isNormalized())
7232 {
7233 geometryScale = obj.radius;
7234 scaleFactors = obj.radius * obj.semiAxes;
7235 ri.pointScale = 2.0f * obj.radius / pixelSize;
7236 }
7237 else
7238 {
7239 geometryScale = obj.geometryScale;
7240 scaleFactors = Vec3f(geometryScale, geometryScale, geometryScale);
7241 ri.pointScale = 2.0f * geometryScale / pixelSize;
7242 }
7243 glScale(scaleFactors);
7244
7245 Mat4f planetMat = (~obj.orientation).toMatrix4();
7246 ri.eyeDir_obj = (Point3f(0, 0, 0) - pos) * planetMat;
7247 ri.eyeDir_obj.normalize();
7248 ri.eyePos_obj = Point3f(-pos.x / scaleFactors.x,
7249 -pos.y / scaleFactors.y,
7250 -pos.z / scaleFactors.z) * planetMat;
7251
7252 ri.orientation = cameraOrientation * ~obj.orientation;
7253
7254 ri.pixWidth = discSizeInPixels;
7255
7256 // Set up the colors
7257 if (ri.baseTex == NULL ||
7258 (obj.surface->appearanceFlags & Surface::BlendTexture) != 0)
7259 {
7260 ri.color = obj.surface->color;
7261 }
7262
7263 ri.ambientColor = ambientColor;
7264 ri.hazeColor = obj.surface->hazeColor;
7265 ri.specularColor = obj.surface->specularColor;
7266 ri.specularPower = obj.surface->specularPower;
7267 ri.useTexEnvCombine = context->getRenderPath() != GLContext::GLPath_Basic;
7268 ri.lunarLambert = obj.surface->lunarLambert;
7269 #ifdef USE_HDR
7270 ri.nightLightScale = obj.surface->nightLightRadiance * exposure * 1.e5f * .5f;
7271 #endif
7272
7273 // See if the surface should be lit
7274 bool lit = (obj.surface->appearanceFlags & Surface::Emissive) == 0;
7275
7276 // Set the OpenGL light state
7277 unsigned int i;
7278 for (i = 0; i < ls.nLights; i++)
7279 {
7280 const DirectionalLight& light = ls.lights[i];
7281
7282 glLightDirection(GL_LIGHT0 + i, ls.lights[i].direction_obj);
7283
7284 // RANT ALERT!
7285 // This sucks, but it's necessary. glScale is used to scale a unit
7286 // sphere up to planet size. Since normals are transformed by the
7287 // inverse transpose of the model matrix, this means they end up
7288 // getting scaled by a factor of 1.0 / planet radius (in km). This
7289 // has terrible effects on lighting: the planet appears almost
7290 // completely dark. To get around this, the GL_rescale_normal
7291 // extension was introduced and eventually incorporated into into the
7292 // OpenGL 1.2 standard. Of course, not everyone implemented this
7293 // incredibly simple and essential little extension. Microsoft is
7294 // notorious for half-assed support of OpenGL, but 3dfx should have
7295 // known better: no Voodoo 1/2/3 drivers seem to support this
7296 // extension. The following is an attempt to get around the problem by
7297 // scaling the light brightness by the planet radius. According to the
7298 // OpenGL spec, this should work fine, as clamping of colors to [0, 1]
7299 // occurs *after* lighting. It works fine on my GeForce3 when I
7300 // disable EXT_rescale_normal, but I'm not certain whether other
7301 // drivers are as well behaved as nVidia's.
7302 //
7303 // Addendum: Unsurprisingly, using color values outside [0, 1] produces
7304 // problems on Savage4 cards.
7305
7306 Vec3f lightColor = Vec3f(light.color.red(),
7307 light.color.green(),
7308 light.color.blue()) * light.irradiance;
7309 if (useRescaleNormal)
7310 {
7311 glLightColor(GL_LIGHT0 + i, GL_DIFFUSE, lightColor);
7312 glLightColor(GL_LIGHT0 + i, GL_SPECULAR, lightColor);
7313 }
7314 else
7315 {
7316 glLightColor(GL_LIGHT0 + i, GL_DIFFUSE, lightColor * radius);
7317 }
7318 glEnable(GL_LIGHT0 + i);
7319 }
7320
7321 // Compute the inverse model/view matrix
7322 Mat4f invMV = (cameraOrientation.toMatrix4() *
7323 Mat4f::translation(Point3f(-pos.x, -pos.y, -pos.z)) *
7324 planetMat *
7325 Mat4f::scaling(1.0f / radius));
7326
7327 // The sphere rendering code uses the view frustum to determine which
7328 // patches are visible. In order to avoid rendering patches that can't
7329 // be seen, make the far plane of the frustum as close to the viewer
7330 // as possible.
7331 float frustumFarPlane = farPlaneDistance;
7332 if (obj.geometry == InvalidResource)
7333 {
7334 // Only adjust the far plane for ellipsoidal objects
7335 float d = pos.distanceFromOrigin();
7336
7337 // Account for non-spherical objects
7338 float eradius = min(scaleFactors.x, min(scaleFactors.y, scaleFactors.z));
7339
7340 if (d > eradius)
7341 {
7342 // Include a fudge factor to eliminate overaggressive clipping
7343 // due to limited floating point precision
7344 frustumFarPlane = (float) sqrt(square(d) - square(eradius)) * 1.1f;
7345 }
7346 else
7347 {
7348 // We're inside the bounding sphere; leave the far plane alone
7349 }
7350
7351 if (obj.atmosphere != NULL)
7352 {
7353 float atmosphereHeight = max(obj.atmosphere->cloudHeight,
7354 obj.atmosphere->mieScaleHeight * (float) -log(AtmosphereExtinctionThreshold));
7355 if (atmosphereHeight > 0.0f)
7356 {
7357 // If there's an atmosphere, we need to move the far plane
7358 // out so that the clouds and atmosphere shell aren't clipped.
7359 float atmosphereRadius = eradius + atmosphereHeight;
7360 frustumFarPlane += (float) sqrt(square(atmosphereRadius) -
7361 square(eradius));
7362 }
7363 }
7364 }
7365
7366 // Transform the frustum into object coordinates using the
7367 // inverse model/view matrix.
7368 Frustum viewFrustum(degToRad(fov),
7369 (float) windowWidth / (float) windowHeight,
7370 nearPlaneDistance, frustumFarPlane);
7371 viewFrustum.transform(invMV);
7372
7373 // Get cloud layer parameters
7374 Texture* cloudTex = NULL;
7375 Texture* cloudNormalMap = NULL;
7376 float cloudTexOffset = 0.0f;
7377 if (obj.atmosphere != NULL)
7378 {
7379 Atmosphere* atmosphere = const_cast<Atmosphere*>(obj.atmosphere); // Ugly cast required because MultiResTexture::find() is non-const
7380 if ((renderFlags & ShowCloudMaps) != 0)
7381 {
7382 if (atmosphere->cloudTexture.tex[textureResolution] != InvalidResource)
7383 cloudTex = atmosphere->cloudTexture.find(textureResolution);
7384 if (atmosphere->cloudNormalMap.tex[textureResolution] != InvalidResource)
7385 cloudNormalMap = atmosphere->cloudNormalMap.find(textureResolution);
7386 }
7387 if (atmosphere->cloudSpeed != 0.0f)
7388 cloudTexOffset = (float) (-pfmod(now * atmosphere->cloudSpeed / (2 * PI), 1.0));
7389 }
7390
7391 if (obj.geometry == InvalidResource)
7392 {
7393 // A null model indicates that this body is a sphere
7394 if (lit)
7395 {
7396 switch (context->getRenderPath())
7397 {
7398 case GLContext::GLPath_GLSL:
7399 renderSphere_GLSL(ri, ls, obj.rings,
7400 const_cast<Atmosphere*>(obj.atmosphere), cloudTexOffset,
7401 obj.radius,
7402 textureResolution,
7403 renderFlags,
7404 planetMat, viewFrustum, *context);
7405 break;
7406
7407 case GLContext::GLPath_NV30:
7408 renderSphere_FP_VP(ri, viewFrustum, *context);
7409 break;
7410
7411 case GLContext::GLPath_NvCombiner_ARBVP:
7412 case GLContext::GLPath_NvCombiner_NvVP:
7413 renderSphere_Combiners_VP(ri, ls, viewFrustum, *context);
7414 break;
7415
7416 case GLContext::GLPath_NvCombiner:
7417 renderSphere_Combiners(ri, viewFrustum, *context);
7418 break;
7419
7420 case GLContext::GLPath_DOT3_ARBVP:
7421 renderSphere_DOT3_VP(ri, ls, viewFrustum, *context);
7422 break;
7423
7424 default:
7425 renderSphereDefault(ri, viewFrustum, true, *context);
7426 }
7427 }
7428 else
7429 {
7430 renderSphereDefault(ri, viewFrustum, false, *context);
7431 }
7432 }
7433 else
7434 {
7435 if (geometry != NULL)
7436 {
7437 ResourceHandle texOverride = obj.surface->baseTexture.tex[textureResolution];
7438
7439 if (context->getRenderPath() == GLContext::GLPath_GLSL)
7440 {
7441 if (lit)
7442 {
7443 renderGeometry_GLSL(geometry,
7444 ri,
7445 texOverride,
7446 ls,
7447 obj.atmosphere,
7448 geometryScale,
7449 renderFlags,
7450 planetMat,
7451 astro::daysToSecs(now - astro::J2000));
7452 }
7453 else
7454 {
7455 renderGeometry_GLSL_Unlit(geometry,
7456 ri,
7457 texOverride,
7458 geometryScale,
7459 renderFlags,
7460 planetMat,
7461 astro::daysToSecs(now - astro::J2000));
7462 }
7463
7464 for (unsigned int i = 1; i < 8;/*context->getMaxTextures();*/ i++)
7465 {
7466 glx::glActiveTextureARB(GL_TEXTURE0_ARB + i);
7467 glDisable(GL_TEXTURE_2D);
7468 }
7469 glx::glActiveTextureARB(GL_TEXTURE0_ARB);
7470 glEnable(GL_TEXTURE_2D);
7471 glx::glUseProgramObjectARB(0);
7472 }
7473 else
7474 {
7475 renderModelDefault(geometry, ri, lit, texOverride);
7476 }
7477 }
7478 }
7479
7480 if (obj.rings != NULL && distance <= obj.rings->innerRadius)
7481 {
7482 if (context->getRenderPath() == GLContext::GLPath_GLSL)
7483 {
7484 renderRings_GLSL(*obj.rings, ri, ls,
7485 radius, 1.0f - obj.semiAxes.y,
7486 textureResolution,
7487 (renderFlags & ShowRingShadows) != 0 && lit,
7488 detailOptions.ringSystemSections);
7489 }
7490 else
7491 {
7492 renderRings(*obj.rings, ri, radius, 1.0f - obj.semiAxes.y,
7493 textureResolution,
7494 context->getMaxTextures() > 1 &&
7495 (renderFlags & ShowRingShadows) != 0 && lit,
7496 *context,
7497 detailOptions.ringSystemSections);
7498 }
7499 }
7500
7501 if (obj.atmosphere != NULL)
7502 {
7503 Atmosphere* atmosphere = const_cast<Atmosphere *>(obj.atmosphere);
7504
7505 // Compute the apparent thickness in pixels of the atmosphere.
7506 // If it's only one pixel thick, it can look quite unsightly
7507 // due to aliasing. To avoid popping, we gradually fade in the
7508 // atmosphere as it grows from two to three pixels thick.
7509 float fade;
7510 float thicknessInPixels = 0.0f;
7511 if (distance - radius > 0.0f)
7512 {
7513 thicknessInPixels = atmosphere->height /
7514 ((distance - radius) * pixelSize);
7515 fade = clamp(thicknessInPixels - 2);
7516 }
7517 else
7518 {
7519 fade = 1.0f;
7520 }
7521
7522 if (fade > 0 && (renderFlags & ShowAtmospheres) != 0)
7523 {
7524 // Only use new atmosphere code in OpenGL 2.0 path when new style parameters are defined.
7525 // TODO: convert old style atmopshere parameters
7526 if (context->getRenderPath() == GLContext::GLPath_GLSL &&
7527 atmosphere->mieScaleHeight > 0.0f)
7528 {
7529 float atmScale = 1.0f + atmosphere->height / radius;
7530
7531 renderAtmosphere_GLSL(ri, ls,
7532 atmosphere,
7533 radius * atmScale,
7534 planetMat,
7535 viewFrustum,
7536 *context);
7537 }
7538 else
7539 {
7540 glPushMatrix();
7541 glLoadIdentity();
7542 glDisable(GL_LIGHTING);
7543 glDisable(GL_TEXTURE_2D);
7544 glEnable(GL_BLEND);
7545 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
7546
7547 glRotate(cameraOrientation);
7548
7549 renderEllipsoidAtmosphere(*atmosphere,
7550 pos,
7551 obj.orientation,
7552 scaleFactors,
7553 ri.sunDir_eye,
7554 ls,
7555 thicknessInPixels,
7556 lit);
7557 glEnable(GL_TEXTURE_2D);
7558 glPopMatrix();
7559 }
7560 }
7561
7562 // If there's a cloud layer, we'll render it now.
7563 if (cloudTex != NULL)
7564 {
7565 glPushMatrix();
7566
7567 float cloudScale = 1.0f + atmosphere->cloudHeight / radius;
7568 glScalef(cloudScale, cloudScale, cloudScale);
7569
7570 // If we're beneath the cloud level, render the interior of
7571 // the cloud sphere.
7572 if (distance - radius < atmosphere->cloudHeight)
7573 glFrontFace(GL_CW);
7574
7575 if (atmosphere->cloudSpeed != 0.0f)
7576 {
7577 // Make the clouds appear to rotate above the surface of
7578 // the planet. This is easier to do with the texture
7579 // matrix than the model matrix because changing the
7580 // texture matrix doesn't require us to compute a second
7581 // set of model space rendering parameters.
7582 glMatrixMode(GL_TEXTURE);
7583 glTranslatef(cloudTexOffset, 0.0f, 0.0f);
7584 glMatrixMode(GL_MODELVIEW);
7585 }
7586
7587 glEnable(GL_LIGHTING);
7588 glDepthMask(GL_FALSE);
7589 cloudTex->bind();
7590 glEnable(GL_BLEND);
7591 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
7592 #ifdef HDR_COMPRESS
7593 glColor4f(0.5f, 0.5f, 0.5f, 1);
7594 #else
7595 glColor4f(1, 1, 1, 1);
7596 #endif
7597
7598 // Cloud layers can be trouble for the depth buffer, since they tend
7599 // to be very close to the surface of a planet relative to the radius
7600 // of the planet. We'll help out by offsetting the cloud layer toward
7601 // the viewer.
7602 if (distance > radius * 1.1f)
7603 {
7604 glEnable(GL_POLYGON_OFFSET_FILL);
7605 glPolygonOffset(-1.0f, -1.0f);
7606 }
7607
7608 if (lit)
7609 {
7610 if (context->getRenderPath() == GLContext::GLPath_GLSL)
7611 {
7612 renderClouds_GLSL(ri, ls,
7613 atmosphere,
7614 cloudTex,
7615 cloudNormalMap,
7616 cloudTexOffset,
7617 obj.rings,
7618 radius,
7619 textureResolution,
7620 renderFlags,
7621 planetMat,
7622 viewFrustum,
7623 *context);
7624 }
7625 else
7626 {
7627 VertexProcessor* vproc = context->getVertexProcessor();
7628 if (vproc != NULL)
7629 {
7630 vproc->enable();
7631 vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color);
7632 vproc->parameter(vp::TextureTranslation,
7633 cloudTexOffset, 0.0f, 0.0f, 0.0f);
7634 if (ls.nLights > 1)
7635 vproc->use(vp::diffuseTexOffset_2light);
7636 else
7637 vproc->use(vp::diffuseTexOffset);
7638 setLightParameters_VP(*vproc, ls, ri.color, Color::Black);
7639 }
7640
7641 g_lodSphere->render(*context,
7642 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
7643 viewFrustum,
7644 ri.pixWidth,
7645 cloudTex);
7646
7647 if (vproc != NULL)
7648 vproc->disable();
7649 }
7650 }
7651 else
7652 {
7653 glDisable(GL_LIGHTING);
7654 g_lodSphere->render(*context,
7655 LODSphereMesh::Normals | LODSphereMesh::TexCoords0,
7656 viewFrustum,
7657 ri.pixWidth,
7658 cloudTex);
7659 glEnable(GL_LIGHTING);
7660 }
7661
7662 glDisable(GL_POLYGON_OFFSET_FILL);
7663
7664 // Reset the texture matrix
7665 glMatrixMode(GL_TEXTURE);
7666 glLoadIdentity();
7667 glMatrixMode(GL_MODELVIEW);
7668
7669 glDepthMask(GL_TRUE);
7670 glFrontFace(GL_CCW);
7671
7672 glPopMatrix();
7673 }
7674 }
7675
7676 // No separate shadow rendering pass required for GLSL path
7677 if (ls.shadows[0] != NULL &&
7678 ls.shadows[0]->size() != 0 &&
7679 (obj.surface->appearanceFlags & Surface::Emissive) == 0 &&
7680 context->getRenderPath() != GLContext::GLPath_GLSL)
7681 {
7682 if (context->getVertexProcessor() != NULL &&
7683 context->getFragmentProcessor() != NULL)
7684 {
7685 renderEclipseShadows_Shaders(geometry,
7686 *ls.shadows[0],
7687 ri,
7688 radius, planetMat, viewFrustum,
7689 *context);
7690 }
7691 else
7692 {
7693 renderEclipseShadows(geometry,
7694 *ls.shadows[0],
7695 ri,
7696 radius, planetMat, viewFrustum,
7697 *context);
7698 }
7699 }
7700
7701 if (obj.rings != NULL &&
7702 (obj.surface->appearanceFlags & Surface::Emissive) == 0 &&
7703 (renderFlags & ShowRingShadows) != 0)
7704 {
7705 Texture* ringsTex = obj.rings->texture.find(textureResolution);
7706 if (ringsTex != NULL)
7707 {
7708 Vec3f sunDir = pos - Point3f(0, 0, 0);
7709 sunDir.normalize();
7710
7711 glEnable(GL_TEXTURE_2D);
7712 ringsTex->bind();
7713
7714 if (useClampToBorder &&
7715 context->getVertexPath() != GLContext::VPath_Basic &&
7716 context->getRenderPath() != GLContext::GLPath_GLSL)
7717 {
7718 renderRingShadowsVS(geometry,
7719 *obj.rings,
7720 sunDir,
7721 ri,
7722 radius, 1.0f - obj.semiAxes.y,
7723 planetMat, viewFrustum,
7724 *context);
7725 }
7726 }
7727 }
7728
7729 if (obj.rings != NULL && distance > obj.rings->innerRadius)
7730 {
7731 glDepthMask(GL_FALSE);
7732 if (context->getRenderPath() == GLContext::GLPath_GLSL)
7733 {
7734 renderRings_GLSL(*obj.rings, ri, ls,
7735 radius, 1.0f - obj.semiAxes.y,
7736 textureResolution,
7737 (renderFlags & ShowRingShadows) != 0 && lit,
7738 detailOptions.ringSystemSections);
7739 }
7740 else
7741 {
7742 renderRings(*obj.rings, ri, radius, 1.0f - obj.semiAxes.y,
7743 textureResolution,
7744 (context->hasMultitexture() &&
7745 (renderFlags & ShowRingShadows) != 0 && lit),
7746 *context,
7747 detailOptions.ringSystemSections);
7748 }
7749 }
7750
7751 // Disable all light sources other than the first
7752 for (i = 0; i < ls.nLights; i++)
7753 glDisable(GL_LIGHT0 + i);
7754
7755 glPopMatrix();
7756 glDisable(GL_DEPTH_TEST);
7757 glDepthMask(GL_FALSE);
7758 glDisable(GL_LIGHTING);
7759 glEnable(GL_BLEND);
7760 }
7761
7762
testEclipse(const Body & receiver,const Body & caster,const DirectionalLight & light,double now,vector<EclipseShadow> & shadows)7763 bool Renderer::testEclipse(const Body& receiver,
7764 const Body& caster,
7765 const DirectionalLight& light,
7766 double now,
7767 vector<EclipseShadow>& shadows)
7768 {
7769 // Ignore situations where the shadow casting body is much smaller than
7770 // the receiver, as these shadows aren't likely to be relevant. Also,
7771 // ignore eclipses where the caster is not an ellipsoid, since we can't
7772 // generate correct shadows in this case.
7773 if (caster.getRadius() >= receiver.getRadius() * MinRelativeOccluderRadius &&
7774 caster.hasVisibleGeometry() &&
7775 caster.extant(now) &&
7776 caster.isEllipsoid())
7777 {
7778 // All of the eclipse related code assumes that both the caster
7779 // and receiver are spherical. Irregular receivers will work more
7780 // or less correctly, but casters that are sufficiently non-spherical
7781 // will produce obviously incorrect shadows. Another assumption we
7782 // make is that the distance between the caster and receiver is much
7783 // less than the distance between the sun and the receiver. This
7784 // approximation works everywhere in the solar system, and is likely
7785 // valid for any orbitally stable pair of objects orbiting a star.
7786 Point3d posReceiver = receiver.getAstrocentricPosition(now);
7787 Point3d posCaster = caster.getAstrocentricPosition(now);
7788
7789 //const Star* sun = receiver.getSystem()->getStar();
7790 //assert(sun != NULL);
7791 //double distToSun = posReceiver.distanceFromOrigin();
7792 //float appSunRadius = (float) (sun->getRadius() / distToSun);
7793 float appSunRadius = light.apparentSize;
7794
7795 Vec3d dir = posCaster - posReceiver;
7796 double distToCaster = dir.length() - receiver.getRadius();
7797 float appOccluderRadius = (float) (caster.getRadius() / distToCaster);
7798
7799 // The shadow radius is the radius of the occluder plus some additional
7800 // amount that depends upon the apparent radius of the sun. For
7801 // a sun that's distant/small and effectively a point, the shadow
7802 // radius will be the same as the radius of the occluder.
7803 float shadowRadius = (1 + appSunRadius / appOccluderRadius) *
7804 caster.getRadius();
7805
7806 // Test whether a shadow is cast on the receiver. We want to know
7807 // if the receiver lies within the shadow volume of the caster. Since
7808 // we're assuming that everything is a sphere and the sun is far
7809 // away relative to the caster, the shadow volume is a
7810 // cylinder capped at one end. Testing for the intersection of a
7811 // singly capped cylinder is as simple as checking the distance
7812 // from the center of the receiver to the axis of the shadow cylinder.
7813 // If the distance is less than the sum of the caster's and receiver's
7814 // radii, then we have an eclipse. We also need to verify that the
7815 // receiver is behind the caster when seen from the light source.
7816 float R = receiver.getRadius() + shadowRadius;
7817
7818 // The stored light position is receiver-relative; thus the caster-to-light
7819 // direction is casterPos - (receiverPos + lightPos)
7820 Point3d lightPosition = posReceiver + light.position;
7821 Vec3d lightToCasterDir = posCaster - lightPosition;
7822 Vec3d receiverToCasterDir = posReceiver - posCaster;
7823
7824 double dist = distance(posReceiver,
7825 Ray3d(posCaster, lightToCasterDir));
7826 if (dist < R && lightToCasterDir * receiverToCasterDir > 0.0)
7827 {
7828 Vec3d sunDir = lightToCasterDir;
7829 sunDir.normalize();
7830
7831 EclipseShadow shadow;
7832 shadow.origin = Point3f((float) dir.x,
7833 (float) dir.y,
7834 (float) dir.z);
7835 shadow.direction = Vec3f((float) sunDir.x,
7836 (float) sunDir.y,
7837 (float) sunDir.z);
7838 shadow.penumbraRadius = shadowRadius;
7839
7840 // The umbra radius will be positive if the apparent size of the occluder
7841 // is greater than the apparent size of the sun, zero if they're equal,
7842 // and negative when the eclipse is partial. The absolute value of the
7843 // umbra radius is the radius of the shadow region with constant depth:
7844 // for total eclipses, this area is actually the umbra, with a depth of
7845 // 1. For annular eclipses and transits, it is less than 1.
7846 shadow.umbraRadius = caster.getRadius() *
7847 (appOccluderRadius - appSunRadius) / appOccluderRadius;
7848 shadow.maxDepth = std::min(1.0f, square(appOccluderRadius / appSunRadius));
7849
7850 // Ignore transits that don't produce a visible shadow.
7851 if (shadow.maxDepth > 1.0f / 256.0f)
7852 shadows.push_back(shadow);
7853
7854 return true;
7855 }
7856 }
7857
7858 return false;
7859 }
7860
7861
renderPlanet(Body & body,Point3f pos,float distance,float appMag,const Observer & observer,const Quatf & cameraOrientation,float nearPlaneDistance,float farPlaneDistance)7862 void Renderer::renderPlanet(Body& body,
7863 Point3f pos,
7864 float distance,
7865 float appMag,
7866 const Observer& observer,
7867 const Quatf& cameraOrientation,
7868 float nearPlaneDistance,
7869 float farPlaneDistance)
7870 {
7871 double now = observer.getTime();
7872 float altitude = distance - body.getRadius();
7873 float discSizeInPixels = body.getRadius() /
7874 (max(nearPlaneDistance, altitude) * pixelSize);
7875
7876 if (discSizeInPixels > 1 && body.hasVisibleGeometry())
7877 {
7878 RenderProperties rp;
7879
7880 if (displayedSurface.empty())
7881 {
7882 rp.surface = const_cast<Surface*>(&body.getSurface());
7883 }
7884 else
7885 {
7886 rp.surface = body.getAlternateSurface(displayedSurface);
7887 if (rp.surface == NULL)
7888 rp.surface = const_cast<Surface*>(&body.getSurface());
7889 }
7890 rp.atmosphere = body.getAtmosphere();
7891 rp.rings = body.getRings();
7892 rp.radius = body.getRadius();
7893 rp.geometry = body.getGeometry();
7894 rp.semiAxes = body.getSemiAxes() * (1.0f / rp.radius);
7895 rp.geometryScale = body.getGeometryScale();
7896
7897 Quatd q = body.getRotationModel(now)->spin(now) *
7898 body.getEclipticToEquatorial(now);
7899
7900 rp.orientation = body.getOrientation() *
7901 Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z);
7902
7903 if (body.getLocations() != NULL && (labelMode & LocationLabels) != 0)
7904 body.computeLocations();
7905
7906 Vec3f scaleFactors;
7907 bool isNormalized = false;
7908 Geometry* geometry = NULL;
7909 if (rp.geometry != InvalidResource)
7910 geometry = GetGeometryManager()->find(rp.geometry);
7911 if (geometry == NULL || geometry->isNormalized())
7912 {
7913 scaleFactors = rp.semiAxes * rp.radius;
7914 isNormalized = true;
7915 }
7916 else
7917 {
7918 float scale = rp.geometryScale;
7919 scaleFactors = Vec3f(scale, scale, scale);
7920 }
7921
7922 LightingState lights;
7923 setupObjectLighting(lightSourceList,
7924 secondaryIlluminators,
7925 rp.orientation,
7926 scaleFactors,
7927 pos,
7928 isNormalized,
7929 #ifdef USE_HDR
7930 faintestMag,
7931 DEFAULT_EXPOSURE + brightPlus, //exposure + brightPlus,
7932 appMag,
7933 #endif
7934 lights);
7935 assert(lights.nLights <= MaxLights);
7936
7937 lights.ambientColor = Vec3f(ambientColor.red(),
7938 ambientColor.green(),
7939 ambientColor.blue());
7940
7941 {
7942 // Clear out the list of eclipse shadows
7943 for (unsigned int li = 0; li < lights.nLights; li++)
7944 {
7945 eclipseShadows[li].clear();
7946 lights.shadows[li] = &eclipseShadows[li];
7947 }
7948 }
7949
7950
7951 // Calculate eclipse circumstances
7952 if ((renderFlags & ShowEclipseShadows) != 0 &&
7953 body.getSystem() != NULL)
7954 {
7955 PlanetarySystem* system = body.getSystem();
7956
7957 if (system->getPrimaryBody() == NULL &&
7958 body.getSatellites() != NULL)
7959 {
7960 // The body is a planet. Check for eclipse shadows
7961 // from all of its satellites.
7962 PlanetarySystem* satellites = body.getSatellites();
7963 if (satellites != NULL)
7964 {
7965 int nSatellites = satellites->getSystemSize();
7966 for (unsigned int li = 0; li < lights.nLights; li++)
7967 {
7968 if (lights.lights[li].castsShadows)
7969 {
7970 for (int i = 0; i < nSatellites; i++)
7971 {
7972 testEclipse(body, *satellites->getBody(i),
7973 lights.lights[li],
7974 now, *lights.shadows[li]);
7975 }
7976 }
7977 }
7978 }
7979 }
7980 else if (system->getPrimaryBody() != NULL)
7981 {
7982 for (unsigned int li = 0; li < lights.nLights; li++)
7983 {
7984 if (lights.lights[li].castsShadows)
7985 {
7986 // The body is a moon. Check for eclipse shadows from
7987 // the parent planet and all satellites in the system.
7988 // Traverse up the hierarchy so that any parent objects
7989 // of the parent are also considered (TODO: their child
7990 // objects will not be checked for shadows.)
7991 Body* planet = system->getPrimaryBody();
7992 while (planet != NULL)
7993 {
7994 testEclipse(body, *planet, lights.lights[li],
7995 now, *lights.shadows[li]);
7996 if (planet->getSystem() != NULL)
7997 planet = planet->getSystem()->getPrimaryBody();
7998 else
7999 planet = NULL;
8000 }
8001
8002 int nSatellites = system->getSystemSize();
8003 for (int i = 0; i < nSatellites; i++)
8004 {
8005 if (system->getBody(i) != &body)
8006 {
8007 testEclipse(body, *system->getBody(i),
8008 lights.lights[li],
8009 now, *lights.shadows[li]);
8010 }
8011 }
8012 }
8013 }
8014 }
8015 }
8016
8017 renderObject(pos, distance, now,
8018 cameraOrientation, nearPlaneDistance, farPlaneDistance,
8019 rp, lights);
8020
8021 if (body.getLocations() != NULL && (labelMode & LocationLabels) != 0)
8022 {
8023 // Set up location markers for this body
8024 mountainRep = MarkerRepresentation(MarkerRepresentation::Triangle, 8.0f, LocationLabelColor);
8025 craterRep = MarkerRepresentation(MarkerRepresentation::Circle, 8.0f, LocationLabelColor);
8026 observatoryRep = MarkerRepresentation(MarkerRepresentation::Plus, 8.0f, LocationLabelColor);
8027 cityRep = MarkerRepresentation(MarkerRepresentation::X, 3.0f, LocationLabelColor);
8028 genericLocationRep = MarkerRepresentation(MarkerRepresentation::Square, 8.0f, LocationLabelColor);
8029
8030 glEnable(GL_DEPTH_TEST);
8031 glDepthMask(GL_FALSE);
8032 glDisable(GL_BLEND);
8033
8034 // We need a double precision body-relative position of the
8035 // observer, otherwise location labels will tend to jitter.
8036 Vec3d posd = (body.getPosition(observer.getTime()) -
8037 observer.getPosition()) * astro::microLightYearsToKilometers(1.0);
8038 renderLocations(body, posd, q);
8039
8040 glDisable(GL_DEPTH_TEST);
8041 }
8042 }
8043
8044 glEnable(GL_TEXTURE_2D);
8045 glEnable(GL_BLEND);
8046 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
8047 #ifdef USE_HDR
8048 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
8049 #endif
8050
8051 if (body.isVisibleAsPoint())
8052 {
8053 if (useNewStarRendering)
8054 {
8055 renderObjectAsPoint(pos,
8056 body.getRadius(),
8057 appMag,
8058 faintestMag,
8059 discSizeInPixels,
8060 body.getSurface().color,
8061 cameraOrientation,
8062 false, false);
8063 }
8064 else
8065 {
8066 renderObjectAsPoint_nosprite(pos,
8067 body.getRadius(),
8068 appMag,
8069 faintestMag,
8070 discSizeInPixels,
8071 body.getSurface().color,
8072 cameraOrientation,
8073 false);
8074 }
8075 }
8076 #ifdef USE_HDR
8077 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
8078 #endif
8079 }
8080
8081
renderStar(const Star & star,Point3f pos,float distance,float appMag,Quatf cameraOrientation,double now,float nearPlaneDistance,float farPlaneDistance)8082 void Renderer::renderStar(const Star& star,
8083 Point3f pos,
8084 float distance,
8085 float appMag,
8086 Quatf cameraOrientation,
8087 double now,
8088 float nearPlaneDistance,
8089 float farPlaneDistance)
8090 {
8091 if (!star.getVisibility())
8092 return;
8093
8094 Color color = colorTemp->lookupColor(star.getTemperature());
8095 float radius = star.getRadius();
8096 float discSizeInPixels = radius / (distance * pixelSize);
8097
8098 if (discSizeInPixels > 1)
8099 {
8100 Surface surface;
8101 RenderProperties rp;
8102
8103 surface.color = color;
8104
8105 MultiResTexture mtex = star.getTexture();
8106 if (mtex.tex[textureResolution] != InvalidResource)
8107 {
8108 surface.baseTexture = mtex;
8109 }
8110 else
8111 {
8112 surface.baseTexture = InvalidResource;
8113 }
8114 surface.appearanceFlags |= Surface::ApplyBaseTexture;
8115 surface.appearanceFlags |= Surface::Emissive;
8116
8117 rp.surface = &surface;
8118 rp.rings = NULL;
8119 rp.radius = star.getRadius();
8120 rp.semiAxes = star.getEllipsoidSemiAxes();
8121 rp.geometry = star.getGeometry();
8122
8123 #ifndef USE_HDR
8124 Atmosphere atmosphere;
8125 Color atmColor(color.red() * 0.5f, color.green() * 0.5f, color.blue() * 0.5f);
8126 atmosphere.height = radius * CoronaHeight;
8127 atmosphere.lowerColor = atmColor;
8128 atmosphere.upperColor = atmColor;
8129 atmosphere.skyColor = atmColor;
8130
8131 // Use atmosphere effect to give stars a fuzzy fringe
8132 if (rp.geometry == InvalidResource)
8133 rp.atmosphere = &atmosphere;
8134 else
8135 #endif
8136 rp.atmosphere = NULL;
8137
8138 Quatd q = star.getRotationModel()->orientationAtTime(now);
8139 rp.orientation = Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z);
8140
8141 renderObject(pos, distance, now,
8142 cameraOrientation, nearPlaneDistance, farPlaneDistance,
8143 rp, LightingState());
8144 }
8145
8146 glEnable(GL_TEXTURE_2D);
8147 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
8148 #ifdef USE_HDR
8149 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
8150 #endif
8151
8152 #ifndef USE_HDR
8153 if (useNewStarRendering)
8154 {
8155 #endif
8156 renderObjectAsPoint(pos,
8157 star.getRadius(),
8158 appMag,
8159 faintestMag,
8160 discSizeInPixels,
8161 color,
8162 cameraOrientation,
8163 true, true);
8164 #ifndef USE_HDR
8165 }
8166 else
8167 {
8168 renderObjectAsPoint_nosprite(pos,
8169 star.getRadius(),
8170 appMag,
8171 faintestPlanetMag,
8172 discSizeInPixels,
8173 color,
8174 cameraOrientation,
8175 true);
8176 }
8177 #endif
8178 #ifdef USE_HDR
8179 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
8180 #endif
8181 }
8182
8183
8184 static const int MaxCometTailPoints = 120;
8185 static const int CometTailSlices = 48;
8186 struct CometTailVertex
8187 {
8188 Point3f point;
8189 Vec3f normal;
8190 Point3f paraboloidPoint;
8191 float brightness;
8192 };
8193
8194 static CometTailVertex cometTailVertices[CometTailSlices * MaxCometTailPoints];
8195
ProcessCometTailVertex(const CometTailVertex & v,const Vec3f & viewDir,float fadeDistFromSun)8196 static void ProcessCometTailVertex(const CometTailVertex& v,
8197 const Vec3f& viewDir,
8198 float fadeDistFromSun)
8199 {
8200 // If fadeDistFromSun = x/x0 >= 1.0, comet tail starts fading,
8201 // i.e. fadeFactor quickly transits from 1 to 0.
8202
8203 float fadeFactor = 0.5f - 0.5f * (float) tanh(fadeDistFromSun - 1.0f / fadeDistFromSun);
8204 float shade = abs(viewDir * v.normal * v.brightness * fadeFactor);
8205 glColor4f(0.5f, 0.5f, 0.75f, shade);
8206 glVertex(v.point);
8207 }
8208
8209 #if 0
8210 static void ProcessCometTailVertex(const CometTailVertex& v,
8211 const Point3f& cameraPos)
8212 {
8213 Vec3f viewDir = v.point - cameraPos;
8214 viewDir.normalize();
8215 float shade = abs(viewDir * v.normal * v.brightness);
8216 glColor4f(0.0f, 0.5f, 1.0f, shade);
8217 glVertex(v.point);
8218 }
8219 #endif
8220
8221 #if 0
8222 static void ProcessCometTailVertex(const CometTailVertex& v,
8223 const Point3f& eyePos_obj,
8224 float b,
8225 float h)
8226 {
8227 float shade = 0.0f;
8228 Vec3f R = v.paraboloidPoint - eyePos_obj;
8229 float c0 = b * (square(eyePos_obj.x) + square(eyePos_obj.y)) + eyePos_obj.z;
8230 float c1 = 2 * b * (R.x * eyePos_obj.x + R.y * eyePos_obj.y) - R.z;
8231 float c2 = b * (square(R.x) + square(R.y));
8232
8233 float disc = square(c1) - 4 * c0 * c2;
8234
8235 if (disc < 0.0f)
8236 {
8237 shade = 0.0f;
8238 }
8239 else
8240 {
8241 disc = (float) sqrt(disc);
8242 float s = 1.0f / (2 * c2);
8243 float t0 = (h - eyePos_obj.z) / R.z;
8244 float t1 = (c1 - disc) * s;
8245 float t2 = (c1 + disc) * s;
8246 /*float u0 = max(t0, 0.0f); Unused*/
8247 float u1, u2;
8248
8249 if (R.z < 0.0f)
8250 {
8251 u1 = max(t1, t0);
8252 u2 = max(t2, t0);
8253 }
8254 else
8255 {
8256 u1 = min(t1, t0);
8257 u2 = min(t2, t0);
8258 }
8259 u1 = max(0.0f, u1);
8260 u2 = max(0.0f, u2);
8261
8262 shade = u2 - u1;
8263 }
8264
8265 glColor4f(0.0f, 0.5f, 1.0f, shade);
8266 glVertex(v.point);
8267 }
8268 #endif
8269
8270
8271 // Compute a rough estimate of the visible length of the dust tail.
8272 // TODO: This is old code that needs to be rewritten. For one thing,
8273 // the length is inversely proportional to the distance from the sun,
8274 // whereas the 1/distance^2 is probably more realistic. There should
8275 // also be another parameter that specifies how active the comet is.
cometDustTailLength(float distanceToSun,float radius)8276 static float cometDustTailLength(float distanceToSun,
8277 float radius)
8278 {
8279 return (1.0e8f / distanceToSun) * (radius / 5.0f) * 1.0e7f;
8280 }
8281
8282
8283 // TODO: Remove unused parameters??
renderCometTail(const Body & body,Point3f pos,double now,float discSizeInPixels)8284 void Renderer::renderCometTail(const Body& body,
8285 Point3f pos,
8286 double now,
8287 float discSizeInPixels)
8288 {
8289 Point3f cometPoints[MaxCometTailPoints];
8290 Point3d pos0 = body.getOrbit(now)->positionAtTime(now);
8291 Point3d pos1 = body.getOrbit(now)->positionAtTime(now - 0.01);
8292 Vec3d vd = pos1 - pos0;
8293 double t = now;
8294 /*float f = 1.0e15f; Unused*/
8295 /*int nSteps = MaxCometTailPoints; Unused*/
8296 /*float dt = 10000000.0f / (nSteps * (float) vd.length() * 100.0f); Unused*/
8297 float distanceFromSun, irradiance_max = 0.0f;
8298 unsigned int li_eff = 0; // Select the first sun as default to
8299 // shut up compiler warnings
8300
8301 // Adjust the amount of triangles used for the comet tail based on
8302 // the screen size of the comet.
8303 float lod = min(1.0f, max(0.2f, discSizeInPixels / 1000.0f));
8304 int nTailPoints = (int) (MaxCometTailPoints * lod);
8305 int nTailSlices = (int) (CometTailSlices * lod);
8306
8307 // Find the sun with the largest irrradiance of light onto the comet
8308 // as function of the comet's position;
8309 // irradiance = sun's luminosity / square(distanceFromSun);
8310
8311 for (unsigned int li = 0; li < lightSourceList.size(); li++)
8312 {
8313 distanceFromSun = (float) (Vec3d(pos.x, pos.y, pos.z) - lightSourceList[li].position).length();
8314 float irradiance = lightSourceList[li].luminosity / square(distanceFromSun);
8315 if (irradiance > irradiance_max)
8316 {
8317 li_eff = li;
8318 irradiance_max = irradiance;
8319 }
8320 }
8321 float fadeDistance = 1.0f / (float) (COMET_TAIL_ATTEN_DIST_SOL * sqrt(irradiance_max));
8322
8323 // direction to sun with dominant light irradiance:
8324
8325 Vec3d sd = Vec3d(pos.x, pos.y, pos.z) - lightSourceList[li_eff].position;
8326 Vec3f sunDir = Vec3f((float) sd.x, (float) sd.y, (float) sd.z);
8327 sunDir.normalize();
8328
8329 float dustTailLength = cometDustTailLength((float) pos0.distanceFromOrigin(), body.getRadius());
8330 float dustTailRadius = dustTailLength * 0.1f;
8331
8332 Point3f origin(0, 0, 0);
8333 origin -= sunDir * (body.getRadius() * 100);
8334
8335 int i;
8336 for (i = 0; i < nTailPoints; i++)
8337 {
8338 float alpha = (float) i / (float) nTailPoints;
8339 alpha = alpha * alpha;
8340 cometPoints[i] = origin + sunDir * (dustTailLength * alpha);
8341 }
8342
8343 // We need three axes to define the coordinate system for rendering the
8344 // comet. The first axis is the velocity. We choose the second one
8345 // based on the orientation of the comet. And the third is just the cross
8346 // product of the first and second axes.
8347 Quatd qd = body.getEclipticToEquatorial(t);
8348 Quatf q((float) qd.w, (float) qd.x, (float) qd.y, (float) qd.z);
8349 Vec3f v = cometPoints[1] - cometPoints[0];
8350 v.normalize();
8351 Vec3f u0 = Vec3f(0, 1, 0) * q.toMatrix3();
8352 Vec3f u1 = Vec3f(1, 0, 0) * q.toMatrix3();
8353 Vec3f u;
8354 if (abs(u0 * v) < abs(u1 * v))
8355 u = v ^ u0;
8356 else
8357 u = v ^ u1;
8358 u.normalize();
8359 Vec3f w = u ^ v;
8360
8361 glColor4f(0.0f, 1.0f, 1.0f, 0.5f);
8362 glPushMatrix();
8363 glTranslate(pos);
8364
8365 // glx::glActiveTextureARB(GL_TEXTURE0_ARB);
8366 glDisable(GL_TEXTURE_2D);
8367 glDisable(GL_LIGHTING);
8368 glDepthMask(GL_FALSE);
8369 glEnable(GL_BLEND);
8370 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
8371
8372 for (i = 0; i < nTailPoints; i++)
8373 {
8374 float brightness = 1.0f - (float) i / (float) (nTailPoints - 1);
8375 Vec3f v0, v1;
8376 float sectionLength;
8377 if (i != 0 && i != nTailPoints - 1)
8378 {
8379 v0 = cometPoints[i] - cometPoints[i - 1];
8380 v1 = cometPoints[i + 1] - cometPoints[i];
8381 sectionLength = v0.length();
8382 v0.normalize();
8383 v1.normalize();
8384 Vec3f axis = v1 ^ v0;
8385 float axisLength = axis.length();
8386 if (axisLength > 1e-5f)
8387 {
8388 axis *= 1.0f / axisLength;
8389 q.setAxisAngle(axis, (float) asin(axisLength));
8390 Mat3f m = q.toMatrix3();
8391
8392 u = u * m;
8393 v = v * m;
8394 w = w * m;
8395 }
8396 }
8397 else if (i == 0)
8398 {
8399 v0 = cometPoints[i + 1] - cometPoints[i];
8400 sectionLength = v0.length();
8401 v0.normalize();
8402 v1 = v0;
8403 }
8404 else
8405 {
8406 v0 = v1 = cometPoints[i] - cometPoints[i - 1];
8407 sectionLength = v0.length();
8408 v0.normalize();
8409 v1 = v0;
8410 }
8411
8412 float radius = (float) i / (float) nTailPoints *
8413 dustTailRadius;
8414 float dr = (dustTailRadius / (float) nTailPoints) /
8415 sectionLength;
8416
8417 float w0 = (float) atan(dr);
8418 float d = std::sqrt(1.0f + w0 * w0);
8419 float w1 = 1.0f / d;
8420 w0 = w0 / d;
8421
8422 // Special case the first vertex in the comet tail
8423 if (i == 0)
8424 {
8425 w0 = 1;
8426 w1 = 0.0f;
8427 }
8428
8429 for (int j = 0; j < nTailSlices; j++)
8430 {
8431 float theta = (float) (2 * PI * (float) j / nTailSlices);
8432 float s = (float) sin(theta);
8433 float c = (float) cos(theta);
8434 CometTailVertex* vtx = &cometTailVertices[i * nTailSlices + j];
8435 vtx->normal = u * (s * w1) + w * (c * w1) + v * w0;
8436 s *= radius;
8437 c *= radius;
8438
8439 vtx->point = Point3f(cometPoints[i].x + u.x * s + w.x * c,
8440 cometPoints[i].y + u.y * s + w.y * c,
8441 cometPoints[i].z + u.z * s + w.z * c);
8442 vtx->brightness = brightness;
8443 vtx->paraboloidPoint =
8444 Point3f(c, s, square((float) i / (float) MaxCometTailPoints));
8445 }
8446 }
8447
8448 Vec3f viewDir = pos - Point3f(0.0f, 0.0f, 0.0f);
8449 viewDir.normalize();
8450
8451 glDisable(GL_CULL_FACE);
8452 for (i = 0; i < nTailPoints - 1; i++)
8453 {
8454 glBegin(GL_QUAD_STRIP);
8455 int n = i * nTailSlices;
8456 for (int j = 0; j < nTailSlices; j++)
8457 {
8458 ProcessCometTailVertex(cometTailVertices[n + j], viewDir, fadeDistance);
8459 ProcessCometTailVertex(cometTailVertices[n + j + nTailSlices],
8460 viewDir, fadeDistance);
8461 }
8462 ProcessCometTailVertex(cometTailVertices[n], viewDir, fadeDistance);
8463 ProcessCometTailVertex(cometTailVertices[n + nTailSlices],
8464 viewDir, fadeDistance);
8465 glEnd();
8466 }
8467 glEnable(GL_CULL_FACE);
8468
8469 glBegin(GL_LINE_STRIP);
8470 for (i = 0; i < nTailPoints; i++)
8471 {
8472 glVertex(cometPoints[i]);
8473 }
8474 glEnd();
8475
8476 glEnable(GL_TEXTURE_2D);
8477 glEnable(GL_BLEND);
8478
8479 glPopMatrix();
8480 }
8481
8482
8483 // Render a reference mark
renderReferenceMark(const ReferenceMark & refMark,Point3f pos,float distance,double now,float nearPlaneDistance)8484 void Renderer::renderReferenceMark(const ReferenceMark& refMark,
8485 Point3f pos,
8486 float distance,
8487 double now,
8488 float nearPlaneDistance)
8489 {
8490 float altitude = distance - refMark.boundingSphereRadius();
8491 float discSizeInPixels = refMark.boundingSphereRadius() /
8492 (max(nearPlaneDistance, altitude) * pixelSize);
8493
8494 if (discSizeInPixels <= 1)
8495 return;
8496
8497 // Apply the modelview transform for the object
8498 glPushMatrix();
8499 glTranslate(pos);
8500
8501 refMark.render(this, pos, discSizeInPixels, now);
8502
8503 glPopMatrix();
8504
8505 glDisable(GL_DEPTH_TEST);
8506 glDepthMask(GL_FALSE);
8507 glEnable(GL_TEXTURE_2D);
8508 glEnable(GL_BLEND);
8509 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
8510 }
8511
8512
8513 // Helper function to compute the luminosity of a perfectly
8514 // reflective disc with the specified radius. This is used as an upper
8515 // bound for the apparent brightness of an object when culling
8516 // invisible objects.
luminosityAtOpposition(float sunLuminosity,float distanceFromSun,float objRadius)8517 static float luminosityAtOpposition(float sunLuminosity,
8518 float distanceFromSun,
8519 float objRadius)
8520 {
8521 // Compute the total power of the star in Watts
8522 double power = astro::SOLAR_POWER * sunLuminosity;
8523
8524 // Compute the irradiance at the body's distance from the star
8525 double irradiance = power / sphereArea(distanceFromSun * 1000);
8526
8527 // Compute the total energy hitting the planet; assume an albedo of 1.0, so
8528 // reflected energy = incident energy.
8529 double incidentEnergy = irradiance * circleArea(objRadius * 1000);
8530
8531 // Compute the luminosity (i.e. power relative to solar power)
8532 return (float) (incidentEnergy / astro::SOLAR_POWER);
8533 }
8534
8535
addRenderListEntries(RenderListEntry & rle,Body & body,bool isLabeled)8536 void Renderer::addRenderListEntries(RenderListEntry& rle,
8537 Body& body,
8538 bool isLabeled)
8539 {
8540 bool visibleAsPoint = rle.appMag < faintestPlanetMag && body.isVisibleAsPoint();
8541
8542 if (rle.discSizeInPixels > 1 || visibleAsPoint || isLabeled)
8543 {
8544 rle.renderableType = RenderListEntry::RenderableBody;
8545 rle.body = &body;
8546
8547 if (body.getGeometry() != InvalidResource && rle.discSizeInPixels > 1)
8548 {
8549 Geometry* geometry = GetGeometryManager()->find(body.getGeometry());
8550 if (geometry == NULL)
8551 rle.isOpaque = true;
8552 else
8553 rle.isOpaque = geometry->isOpaque();
8554 }
8555 else
8556 {
8557 rle.isOpaque = true;
8558 }
8559 rle.radius = body.getRadius();
8560 renderList.push_back(rle);
8561 }
8562
8563 if (body.getClassification() == Body::Comet && (renderFlags & ShowCometTails) != 0)
8564 {
8565 float radius = cometDustTailLength(rle.sun.length(), body.getRadius());
8566 float discSize = (radius / (float) rle.distance) / pixelSize;
8567 if (discSize > 1)
8568 {
8569 rle.renderableType = RenderListEntry::RenderableCometTail;
8570 rle.body = &body;
8571 rle.isOpaque = false;
8572 rle.radius = radius;
8573 rle.discSizeInPixels = discSize;
8574 renderList.push_back(rle);
8575 }
8576 }
8577
8578 const list<ReferenceMark*>* refMarks = body.getReferenceMarks();
8579 if (refMarks != NULL)
8580 {
8581 for (list<ReferenceMark*>::const_iterator iter = refMarks->begin();
8582 iter != refMarks->end(); ++iter)
8583 {
8584 const ReferenceMark* rm = *iter;
8585
8586 rle.renderableType = RenderListEntry::RenderableReferenceMark;
8587 rle.refMark = rm;
8588 rle.isOpaque = rm->isOpaque();
8589 rle.radius = rm->boundingSphereRadius();
8590 renderList.push_back(rle);
8591 }
8592 }
8593 }
8594
8595
buildRenderLists(const Point3d & astrocentricObserverPos,const Frustum & viewFrustum,const Vec3d & viewPlaneNormal,const Vec3d & frameCenter,const FrameTree * tree,const Observer & observer,double now)8596 void Renderer::buildRenderLists(const Point3d& astrocentricObserverPos,
8597 const Frustum& viewFrustum,
8598 const Vec3d& viewPlaneNormal,
8599 const Vec3d& frameCenter,
8600 const FrameTree* tree,
8601 const Observer& observer,
8602 double now)
8603 {
8604 int labelClassMask = translateLabelModeToClassMask(labelMode);
8605
8606 Mat3f viewMat = observer.getOrientationf().toMatrix3();
8607 Vec3f viewMatZ(viewMat[2][0], viewMat[2][1], viewMat[2][2]);
8608 double invCosViewAngle = 1.0 / cosViewConeAngle;
8609 double sinViewAngle = sqrt(1.0 - square(cosViewConeAngle));
8610
8611 unsigned int nChildren = tree != NULL ? tree->childCount() : 0;
8612 for (unsigned int i = 0; i < nChildren; i++)
8613 {
8614 const TimelinePhase* phase = tree->getChild(i);
8615
8616 // No need to do anything if the phase isn't active now
8617 if (!phase->includes(now))
8618 continue;
8619
8620 Body* body = phase->body();
8621
8622 // pos_s: sun-relative position of object
8623 // pos_v: viewer-relative position of object
8624
8625 // Get the position of the body relative to the sun.
8626 Point3d p = phase->orbit()->positionAtTime(now);
8627 ReferenceFrame* frame = phase->orbitFrame();
8628 Vec3d pos_s = frameCenter + Vec3d(p.x, p.y, p.z) * frame->getOrientation(now).toMatrix3();
8629
8630 // We now have the positions of the observer and the planet relative
8631 // to the sun. From these, compute the position of the body
8632 // relative to the observer.
8633 Vec3d pos_v = Point3d(pos_s.x, pos_s.y, pos_s.z) - astrocentricObserverPos;
8634
8635 // dist_vn: distance along view normal from the viewer to the
8636 // projection of the object's center.
8637 double dist_vn = viewPlaneNormal * pos_v;
8638
8639 // Vector from object center to its projection on the view normal.
8640 Vec3d toViewNormal = pos_v - dist_vn * viewPlaneNormal;
8641
8642 float cullingRadius = body->getCullingRadius();
8643
8644 // The result of the planetshine test can be reused for the view cone
8645 // test, but only when the object's light influence sphere is larger
8646 // than the geometry. This is not
8647 bool viewConeTestFailed = false;
8648 if (body->isSecondaryIlluminator())
8649 {
8650 float influenceRadius = body->getBoundingRadius() + (body->getRadius() * PLANETSHINE_DISTANCE_LIMIT_FACTOR);
8651 if (dist_vn > -influenceRadius)
8652 {
8653 double maxPerpDist = (influenceRadius + dist_vn * sinViewAngle) * invCosViewAngle;
8654 double perpDistSq = toViewNormal * toViewNormal;
8655 if (perpDistSq < maxPerpDist * maxPerpDist)
8656 {
8657 if ((body->getRadius() / (float) pos_v.length()) / pixelSize > PLANETSHINE_PIXEL_SIZE_LIMIT)
8658 {
8659 // add to planetshine list if larger than 1/10 pixel
8660 #if DEBUG_SECONDARY_ILLUMINATION
8661 clog << "Planetshine: " << body->getName()
8662 << ", " << body->getRadius() / (float) pos_v.length() / pixelSize << endl;
8663 #endif
8664 SecondaryIlluminator illum;
8665 illum.body = body;
8666 illum.position_v = pos_v;
8667 illum.radius = body->getRadius();
8668 secondaryIlluminators.push_back(illum);
8669 }
8670 }
8671 else
8672 {
8673 viewConeTestFailed = influenceRadius > cullingRadius;
8674 }
8675 }
8676 else
8677 {
8678 viewConeTestFailed = influenceRadius > cullingRadius;
8679 }
8680 }
8681
8682 bool insideViewCone = false;
8683 if (!viewConeTestFailed)
8684 {
8685 float radius = body->getCullingRadius();
8686 if (dist_vn > -radius)
8687 {
8688 double maxPerpDist = (radius + dist_vn * sinViewAngle) * invCosViewAngle;
8689 double perpDistSq = toViewNormal * toViewNormal;
8690 insideViewCone = perpDistSq < maxPerpDist * maxPerpDist;
8691 }
8692 }
8693
8694 if (insideViewCone)
8695 {
8696 // Calculate the distance to the viewer
8697 double dist_v = pos_v.length();
8698
8699 // Calculate the size of the planet/moon disc in pixels
8700 float discSize = (body->getCullingRadius() / (float) dist_v) / pixelSize;
8701
8702 // Compute the apparent magnitude; instead of summing the reflected
8703 // light from all nearby stars, we just consider the one with the
8704 // highest apparent brightness.
8705 float appMag = 100.0f;
8706 for (unsigned int li = 0; li < lightSourceList.size(); li++)
8707 {
8708 Vec3d sunPos = pos_v - lightSourceList[li].position;
8709 appMag = min(appMag, body->getApparentMagnitude(lightSourceList[li].luminosity, sunPos, pos_v));
8710 }
8711
8712 bool visibleAsPoint = appMag < faintestPlanetMag && body->isVisibleAsPoint();
8713 bool isLabeled = (body->getOrbitClassification() & labelClassMask) != 0;
8714 bool visible = body->isVisible();
8715
8716 if ((discSize > 1 || visibleAsPoint || isLabeled) && visible)
8717 {
8718 RenderListEntry rle;
8719
8720 rle.position = Point3f((float) pos_v.x, (float) pos_v.y, (float) pos_v.z);
8721 rle.distance = (float) dist_v;
8722 rle.centerZ = Vec3f((float) pos_v.x, (float) pos_v.y, (float) pos_v.z) * viewMatZ;
8723 rle.appMag = appMag;
8724 rle.discSizeInPixels = body->getRadius() / ((float) dist_v * pixelSize);
8725
8726 // TODO: Remove this. It's only used in two places: for calculating comet tail
8727 // length, and for calculating sky brightness to adjust the limiting magnitude.
8728 // In both cases, it's the wrong quantity to use (e.g. for objects with orbits
8729 // defined relative to the SSB.)
8730 rle.sun = Vec3f((float) -pos_s.x, (float) -pos_s.y, (float) -pos_s.z);
8731
8732 addRenderListEntries(rle, *body, isLabeled);
8733 }
8734 }
8735
8736 const FrameTree* subtree = body->getFrameTree();
8737 if (subtree != NULL)
8738 {
8739 double dist_v = pos_v.length();
8740 bool traverseSubtree = false;
8741
8742 // There are two different tests available to determine whether we can reject
8743 // the object's subtree. If the subtree contains no light reflecting objects,
8744 // then render the subtree only when:
8745 // - the subtree bounding sphere intersects the view frustum, and
8746 // - the subtree contains an object bright or large enough to be visible.
8747 // Otherwise, render the subtree when any of the above conditions are
8748 // true or when a subtree object could potentially illuminate something
8749 // in the view cone.
8750 float minPossibleDistance = (float) (dist_v - subtree->boundingSphereRadius());
8751 float brightestPossible = 0.0;
8752 float largestPossible = 0.0;
8753
8754 // If the viewer is not within the subtree bounding sphere, see if we can cull it because
8755 // it contains no objects brighter than the limiting magnitude and no objects that will
8756 // be larger than one pixel in size.
8757 if (minPossibleDistance > 1.0f)
8758 {
8759 // Figure out the magnitude of the brightest possible object in the subtree.
8760
8761 // Compute the luminosity from reflected light of the largest object in the subtree
8762 float lum = 0.0f;
8763 for (unsigned int li = 0; li < lightSourceList.size(); li++)
8764 {
8765 Vec3d sunPos = pos_v - lightSourceList[li].position;
8766 lum += luminosityAtOpposition(lightSourceList[li].luminosity, (float) sunPos.length(), (float) subtree->maxChildRadius());
8767 }
8768 brightestPossible = astro::lumToAppMag(lum, astro::kilometersToLightYears(minPossibleDistance));
8769 largestPossible = (float) subtree->maxChildRadius() / (float) minPossibleDistance / pixelSize;
8770 }
8771 else
8772 {
8773 // Viewer is within the bounding sphere, so the object could be very close.
8774 // Assume that an object in the subree could be very bright or large,
8775 // so no culling will occur.
8776 brightestPossible = -100.0f;
8777 largestPossible = 100.0f;
8778 }
8779
8780 if (brightestPossible < faintestPlanetMag || largestPossible > 1.0f)
8781 {
8782 // See if the object or any of its children are within the view frustum
8783 if (viewFrustum.testSphere(Point3f((float) pos_v.x, (float) pos_v.y, (float) pos_v.z), (float) subtree->boundingSphereRadius()) != Frustum::Outside)
8784 {
8785 traverseSubtree = true;
8786 }
8787 }
8788
8789 // If the subtree contains secondary illuminators, do one last check if it hasn't
8790 // already been determined if we need to traverse the subtree: see if something
8791 // in the subtree could possibly contribute significant illumination to an
8792 // object in the view cone.
8793 if (subtree->containsSecondaryIlluminators() &&
8794 !traverseSubtree &&
8795 largestPossible > PLANETSHINE_PIXEL_SIZE_LIMIT)
8796 {
8797 float influenceRadius = (float) (subtree->boundingSphereRadius() +
8798 (subtree->maxChildRadius() * PLANETSHINE_DISTANCE_LIMIT_FACTOR));
8799 if (dist_vn > -influenceRadius)
8800 {
8801 double maxPerpDist = (influenceRadius + dist_vn * sinViewAngle) * invCosViewAngle;
8802 double perpDistSq = toViewNormal * toViewNormal;
8803 if (perpDistSq < maxPerpDist * maxPerpDist)
8804 traverseSubtree = true;
8805 }
8806 }
8807
8808 if (traverseSubtree)
8809 {
8810 buildRenderLists(astrocentricObserverPos,
8811 viewFrustum,
8812 viewPlaneNormal,
8813 pos_s,
8814 subtree,
8815 observer,
8816 now);
8817 }
8818 } // end subtree traverse
8819
8820 }
8821 }
8822
8823
buildOrbitLists(const Point3d & astrocentricObserverPos,const Quatf & observerOrientation,const Frustum & viewFrustum,const FrameTree * tree,double now)8824 void Renderer::buildOrbitLists(const Point3d& astrocentricObserverPos,
8825 const Quatf& observerOrientation,
8826 const Frustum& viewFrustum,
8827 const FrameTree* tree,
8828 double now)
8829 {
8830 Mat3f viewMat = observerOrientation.toMatrix3();
8831 Vec3f viewMatZ(viewMat[2][0], viewMat[2][1], viewMat[2][2]);
8832
8833 unsigned int nChildren = tree != NULL ? tree->childCount() : 0;
8834 for (unsigned int i = 0; i < nChildren; i++)
8835 {
8836 const TimelinePhase* phase = tree->getChild(i);
8837
8838 // No need to do anything if the phase isn't active now
8839 if (!phase->includes(now))
8840 continue;
8841
8842 Body* body = phase->body();
8843
8844 // pos_s: sun-relative position of object
8845 // pos_v: viewer-relative position of object
8846
8847 // Get the position of the body relative to the sun.
8848 Point3d pos_s = body->getAstrocentricPosition(now);
8849
8850 // We now have the positions of the observer and the planet relative
8851 // to the sun. From these, compute the position of the body
8852 // relative to the observer.
8853 Vec3d pos_v = pos_s - astrocentricObserverPos;
8854
8855 // Only show orbits for major bodies or selected objects.
8856 Body::VisibilityPolicy orbitVis = body->getOrbitVisibility();
8857
8858 if (body->isVisible() &&
8859 (body == highlightObject.body() ||
8860 orbitVis == Body::AlwaysVisible ||
8861 (orbitVis == Body::UseClassVisibility && (body->getOrbitClassification() & orbitMask) != 0)))
8862 {
8863 Point3d orbitOrigin(0.0, 0.0, 0.0);
8864 Selection centerObject = phase->orbitFrame()->getCenter();
8865 if (centerObject.body() != NULL)
8866 {
8867 orbitOrigin = centerObject.body()->getAstrocentricPosition(now);
8868 }
8869
8870 // Calculate the origin of the orbit relative to the observer
8871 Vec3d relOrigin = orbitOrigin - astrocentricObserverPos;
8872 Vec3f origf((float) relOrigin.x, (float) relOrigin.y, (float) relOrigin.z);
8873
8874 // Compute the size of the orbit in pixels
8875 double originDistance = pos_v.length();
8876 double boundingRadius = body->getOrbit(now)->getBoundingRadius();
8877 float orbitRadiusInPixels = (float) (boundingRadius / (originDistance * pixelSize));
8878
8879 if (orbitRadiusInPixels > minOrbitSize)
8880 {
8881 // Add the orbit of this body to the list of orbits to be rendered
8882 OrbitPathListEntry path;
8883 path.body = body;
8884 path.star = NULL;
8885 path.centerZ = origf * viewMatZ;
8886 path.radius = (float) boundingRadius;
8887 path.origin = Point3f(origf.x, origf.y, origf.z);
8888 path.opacity = sizeFade(orbitRadiusInPixels, minOrbitSize, 2.0f);
8889 orbitPathList.push_back(path);
8890 }
8891 }
8892
8893 const FrameTree* subtree = body->getFrameTree();
8894 if (subtree != NULL)
8895 {
8896 // Only try to render orbits of child objects when:
8897 // - The apparent size of the subtree bounding sphere is large enough that
8898 // orbit paths will be visible, and
8899 // - The subtree bounding sphere isn't outside the view frustum
8900 double dist_v = pos_v.length();
8901 float distanceToBoundingSphere = (float) (dist_v - subtree->boundingSphereRadius());
8902 bool traverseSubtree = false;
8903 if (distanceToBoundingSphere > 0.0f)
8904 {
8905 // We're inside the subtree's bounding sphere
8906 traverseSubtree = true;
8907 }
8908 else
8909 {
8910 float maxPossibleOrbitSize = (float) subtree->boundingSphereRadius() / ((float) dist_v * pixelSize);
8911 if (maxPossibleOrbitSize > minOrbitSize)
8912 traverseSubtree = true;
8913 }
8914
8915 if (traverseSubtree)
8916 {
8917 // See if the object or any of its children are within the view frustum
8918 if (viewFrustum.testSphere(Point3f((float) pos_v.x, (float) pos_v.y, (float) pos_v.z), (float) subtree->boundingSphereRadius()) != Frustum::Outside)
8919 {
8920 buildOrbitLists(astrocentricObserverPos,
8921 observerOrientation,
8922 viewFrustum,
8923 subtree,
8924 now);
8925 }
8926 }
8927 } // end subtree traverse
8928 }
8929 }
8930
8931
buildLabelLists(const Frustum & viewFrustum,double now)8932 void Renderer::buildLabelLists(const Frustum& viewFrustum,
8933 double now)
8934 {
8935 int labelClassMask = translateLabelModeToClassMask(labelMode);
8936 Body* lastPrimary = NULL;
8937 Sphered primarySphere;
8938
8939 for (vector<RenderListEntry>::const_iterator iter = renderList.begin();
8940 iter != renderList.end(); iter++)
8941 {
8942 int classification = iter->body->getOrbitClassification();
8943
8944 if (iter->renderableType == RenderListEntry::RenderableBody &&
8945 (classification & labelClassMask) &&
8946 viewFrustum.testSphere(iter->position, iter->radius) != Frustum::Outside)
8947 {
8948 const Body* body = iter->body;
8949 Vec3f pos(iter->position.x, iter->position.y, iter->position.z);
8950
8951 float boundingRadiusSize = (float) (body->getOrbit(now)->getBoundingRadius() / iter->distance) / pixelSize;
8952 if (boundingRadiusSize > minOrbitSize)
8953 {
8954 Color labelColor;
8955 float opacity = sizeFade(boundingRadiusSize, minOrbitSize, 2.0f);
8956
8957 switch (classification)
8958 {
8959 case Body::Planet:
8960 labelColor = PlanetLabelColor;
8961 break;
8962 case Body::DwarfPlanet:
8963 labelColor = DwarfPlanetLabelColor;
8964 break;
8965 case Body::Moon:
8966 labelColor = MoonLabelColor;
8967 break;
8968 case Body::MinorMoon:
8969 labelColor = MinorMoonLabelColor;
8970 break;
8971 case Body::Asteroid:
8972 labelColor = AsteroidLabelColor;
8973 break;
8974 case Body::Comet:
8975 labelColor = CometLabelColor;
8976 break;
8977 case Body::Spacecraft:
8978 labelColor = SpacecraftLabelColor;
8979 break;
8980 default:
8981 labelColor = Color::Black;
8982 break;
8983 }
8984
8985 labelColor = Color(labelColor, opacity * labelColor.alpha());
8986
8987 if (!body->getName().empty())
8988 {
8989 bool isBehindPrimary = false;
8990
8991 const TimelinePhase* phase = body->getTimeline()->findPhase(now);
8992 Body* primary = phase->orbitFrame()->getCenter().body();
8993 if (primary != NULL && (primary->getClassification() & Body::Invisible) != 0)
8994 {
8995 Body* parent = phase->orbitFrame()->getCenter().body();
8996 if (parent != NULL)
8997 primary = parent;
8998 }
8999
9000 // Position the label slightly in front of the object along a line from
9001 // object center to viewer.
9002 pos = pos * (1.0f - body->getBoundingRadius() * 1.01f / pos.length());
9003
9004 // Try and position the label so that it's not partially
9005 // occluded by other objects. We'll consider just the object
9006 // that the labeled body is orbiting (its primary) as a
9007 // potential occluder. If a ray from the viewer to labeled
9008 // object center intersects the occluder first, skip
9009 // rendering the object label. Otherwise, ensure that the
9010 // label is completely in front of the primary by projecting
9011 // it onto the plane tangent to the primary at the
9012 // viewer-primary intersection point. Whew. Don't do any of
9013 // this if the primary isn't an ellipsoid.
9014 //
9015 // This only handles the problem of partial label occlusion
9016 // for low orbiting and surface positioned objects, but that
9017 // case is *much* more common than other possibilities.
9018 if (primary != NULL && primary->isEllipsoid())
9019 {
9020 // In the typical case, we're rendering labels for many
9021 // objects that orbit the same primary. Avoid repeatedly
9022 // calling getPosition() by caching the last primary
9023 // position.
9024 if (primary != lastPrimary)
9025 {
9026 Point3d p = phase->orbit()->positionAtTime(now) *
9027 phase->orbitFrame()->getOrientation(now).toMatrix3();
9028 Vec3d v(iter->position.x - p.x, iter->position.y - p.y, iter->position.z - p.z);
9029
9030 primarySphere = Sphered(Point3d(v.x, v.y, v.z),
9031 primary->getRadius());
9032 lastPrimary = primary;
9033 }
9034
9035 Ray3d testRay(Point3d(0.0, 0.0, 0.0), Vec3d(pos.x, pos.y, pos.z));
9036
9037 // Test the viewer-to-labeled object ray against
9038 // the primary sphere (TODO: handle ellipsoids)
9039 double t = 0.0;
9040 if (testIntersection(testRay, primarySphere, t))
9041 {
9042 // Center of labeled object is behind primary
9043 // sphere; mark it for rejection.
9044 isBehindPrimary = t < 1.0;
9045 }
9046
9047 if (!isBehindPrimary)
9048 {
9049 // Not rejected. Compute the plane tangent to
9050 // the primary at the viewer-to-primary
9051 // intersection point.
9052 Vec3d primaryVec(primarySphere.center.x,
9053 primarySphere.center.y,
9054 primarySphere.center.z);
9055 double distToPrimary = primaryVec.length();
9056 Planed primaryTangentPlane(primaryVec, primaryVec * (primaryVec * (1.0 - primarySphere.radius / distToPrimary)));
9057
9058 // Compute the intersection of the viewer-to-labeled
9059 // object ray with the tangent plane.
9060 float u = (float) (primaryTangentPlane.d / (primaryTangentPlane.normal * Vec3d(pos.x, pos.y, pos.z)));
9061
9062 // If the intersection point is closer to the viewer
9063 // than the label, then project the label onto the
9064 // tangent plane.
9065 if (u < 1.0f && u > 0.0f)
9066 {
9067 pos = pos * u;
9068 }
9069 }
9070 }
9071
9072 addSortedAnnotation(NULL, body->getName(true), labelColor,
9073 Point3f(pos.x, pos.y, pos.z));
9074 }
9075 }
9076 }
9077 } // for each render list entry
9078 }
9079
9080
9081 // Add a star orbit to the render list
addStarOrbitToRenderList(const Star & star,const Observer & observer,double now)9082 void Renderer::addStarOrbitToRenderList(const Star& star,
9083 const Observer& observer,
9084 double now)
9085 {
9086 // If the star isn't fixed, add its orbit to the render list
9087 if ((renderFlags & ShowOrbits) != 0 &&
9088 ((orbitMask & Body::Stellar) != 0 || highlightObject.star() == &star))
9089 {
9090 Mat3f viewMat = observer.getOrientationf().toMatrix3();
9091 Vec3f viewMatZ(viewMat[2][0], viewMat[2][1], viewMat[2][2]);
9092
9093 if (star.getOrbit() != NULL)
9094 {
9095 // Get orbit origin relative to the observer
9096 Vec3d orbitOrigin = star.getOrbitBarycenterPosition(now) - observer.getPosition();
9097 orbitOrigin *= astro::microLightYearsToKilometers(1.0);
9098
9099 Vec3f origf((float) orbitOrigin.x, (float) orbitOrigin.y, (float) orbitOrigin.z);
9100
9101 // Compute the size of the orbit in pixels
9102 double originDistance = orbitOrigin.length();
9103 double boundingRadius = star.getOrbit()->getBoundingRadius();
9104 float orbitRadiusInPixels = (float) (boundingRadius / (originDistance * pixelSize));
9105
9106 if (orbitRadiusInPixels > minOrbitSize)
9107 {
9108 // Add the orbit of this body to the list of orbits to be rendered
9109 OrbitPathListEntry path;
9110 path.star = ☆
9111 path.body = NULL;
9112 path.centerZ = origf * viewMatZ;
9113 path.radius = (float) boundingRadius;
9114 path.origin = Point3f(origf.x, origf.y, origf.z);
9115 path.opacity = sizeFade(orbitRadiusInPixels, minOrbitSize, 2.0f);
9116 orbitPathList.push_back(path);
9117 }
9118 }
9119 }
9120 }
9121
9122
9123 template <class OBJ, class PREC> class ObjectRenderer : public OctreeProcessor<OBJ, PREC>
9124 {
9125 public:
9126 ObjectRenderer(const PREC distanceLimit);
9127
process(const OBJ &,PREC,float)9128 void process(const OBJ&, PREC, float) {};
9129
9130 public:
9131 const Observer* observer;
9132
9133 GLContext* context;
9134 Renderer* renderer;
9135
9136 Vec3f viewNormal;
9137
9138 float fov;
9139 float size;
9140 float pixelSize;
9141 float faintestMag;
9142 float faintestMagNight;
9143 float saturationMag;
9144 #ifdef USE_HDR
9145 float exposure;
9146 #endif
9147 float brightnessScale;
9148 float brightnessBias;
9149 float distanceLimit;
9150
9151 // Objects brighter than labelThresholdMag will be labeled
9152 float labelThresholdMag;
9153
9154 // These are not fully used by this template's descendants
9155 // but we place them here just in case a more sophisticated
9156 // rendering scheme is implemented:
9157 int nRendered;
9158 int nClose;
9159 int nBright;
9160 int nProcessed;
9161 int nLabelled;
9162
9163 int renderFlags;
9164 int labelMode;
9165 };
9166
9167
9168 template <class OBJ, class PREC>
ObjectRenderer(const PREC _distanceLimit)9169 ObjectRenderer<OBJ, PREC>::ObjectRenderer(const PREC _distanceLimit) :
9170 distanceLimit((float) _distanceLimit),
9171 #ifdef USE_HDR
9172 exposure (0.0f),
9173 #endif
9174 nRendered (0),
9175 nClose (0),
9176 nBright (0),
9177 nProcessed (0),
9178 nLabelled (0)
9179 {
9180 }
9181
9182
9183 class StarRenderer : public ObjectRenderer<Star, float>
9184 {
9185 public:
9186 StarRenderer();
9187
9188 void process(const Star& star, float distance, float appMag);
9189
9190 public:
9191 Point3d obsPos;
9192
9193 vector<Renderer::Particle>* glareParticles;
9194 vector<RenderListEntry>* renderList;
9195 Renderer::StarVertexBuffer* starVertexBuffer;
9196 Renderer::PointStarVertexBuffer* pointStarVertexBuffer;
9197
9198 const StarDatabase* starDB;
9199
9200 bool useScaledDiscs;
9201 GLenum starPrimitive;
9202 float maxDiscSize;
9203
9204 float cosFOV;
9205
9206 const ColorTemperatureTable* colorTemp;
9207 #ifdef DEBUG_HDR_ADAPT
9208 float minMag;
9209 float maxMag;
9210 float minAlpha;
9211 float maxAlpha;
9212 float maxSize;
9213 float above;
9214 unsigned long countAboveN;
9215 unsigned long total;
9216 #endif
9217 };
9218
9219
StarRenderer()9220 StarRenderer::StarRenderer() :
9221 ObjectRenderer<Star, float>(STAR_DISTANCE_LIMIT),
9222 starVertexBuffer (NULL),
9223 pointStarVertexBuffer(NULL),
9224 useScaledDiscs (false),
9225 maxDiscSize (1.0f),
9226 cosFOV (1.0f),
9227 colorTemp (NULL)
9228 {
9229 }
9230
9231
process(const Star & star,float distance,float appMag)9232 void StarRenderer::process(const Star& star, float distance, float appMag)
9233 {
9234 nProcessed++;
9235
9236 Point3f starPos = star.getPosition();
9237 Vec3f relPos((float) ((double) starPos.x - obsPos.x),
9238 (float) ((double) starPos.y - obsPos.y),
9239 (float) ((double) starPos.z - obsPos.z));
9240 float orbitalRadius = star.getOrbitalRadius();
9241 bool hasOrbit = orbitalRadius > 0.0f;
9242
9243 if (distance > distanceLimit)
9244 return;
9245
9246 if (relPos * viewNormal > 0 || relPos.x * relPos.x < 0.1f || hasOrbit)
9247 {
9248 #ifdef HDR_COMPRESS
9249 Color starColorFull = colorTemp->lookupColor(star.getTemperature());
9250 Color starColor(starColorFull.red() * 0.5f,
9251 starColorFull.green() * 0.5f,
9252 starColorFull.blue() * 0.5f);
9253 #else
9254 Color starColor = colorTemp->lookupColor(star.getTemperature());
9255 #endif
9256 float renderDistance = distance;
9257 float s = renderDistance * size;
9258 float discSizeInPixels = 0.0f;
9259 float orbitSizeInPixels = 0.0f;
9260
9261 if (hasOrbit)
9262 orbitSizeInPixels = orbitalRadius / (distance * pixelSize);
9263
9264 // Special handling for stars less than one light year away . . .
9265 // We can't just go ahead and render a nearby star in the usual way
9266 // for two reasons:
9267 // * It may be clipped by the near plane
9268 // * It may be large enough that we should render it as a mesh
9269 // instead of a particle
9270 // It's possible that the second condition might apply for stars
9271 // further than one light year away if the star is huge, the fov is
9272 // very small and the resolution is high. We'll ignore this for now
9273 // and use the most inexpensive test possible . . .
9274 if (distance < 1.0f || orbitSizeInPixels > 1.0f)
9275 {
9276 // Compute the position of the observer relative to the star.
9277 // This is a much more accurate (and expensive) distance
9278 // calculation than the previous one which used the observer's
9279 // position rounded off to floats.
9280 Point3d hPos = astrocentricPosition(observer->getPosition(),
9281 star,
9282 observer->getTime());
9283 relPos = Vec3f((float) hPos.x, (float) hPos.y, (float) hPos.z) *
9284 -astro::kilometersToLightYears(1.0f),
9285 distance = relPos.length();
9286
9287 // Recompute apparent magnitude using new distance computation
9288 appMag = astro::absToAppMag(star.getAbsoluteMagnitude(), distance);
9289
9290 float f = RenderDistance / distance;
9291 renderDistance = RenderDistance;
9292 starPos = obsPos + relPos * f;
9293
9294 float radius = star.getRadius();
9295 discSizeInPixels = radius / astro::lightYearsToKilometers(distance) / pixelSize;
9296 ++nClose;
9297 }
9298
9299 // Place labels for stars brighter than the specified label threshold brightness
9300 if ((labelMode & Renderer::StarLabels) && appMag < labelThresholdMag)
9301 {
9302 Vec3f starDir = relPos;
9303 starDir.normalize();
9304 if (dot(starDir, viewNormal) > cosFOV)
9305 {
9306 char nameBuffer[Renderer::MaxLabelLength];
9307 starDB->getStarName(star, nameBuffer, sizeof(nameBuffer), true);
9308 float distr = 3.5f * (labelThresholdMag - appMag)/labelThresholdMag;
9309 if (distr > 1.0f)
9310 distr = 1.0f;
9311 renderer->addBackgroundAnnotation(NULL, nameBuffer,
9312 Color(Renderer::StarLabelColor, distr * Renderer::StarLabelColor.alpha()),
9313 Point3f(relPos.x, relPos.y, relPos.z));
9314 nLabelled++;
9315 }
9316 }
9317
9318 // Stars closer than the maximum solar system size are actually
9319 // added to the render list and depth sorted, since they may occlude
9320 // planets.
9321 if (distance > MaxSolarSystemSize)
9322 {
9323 #ifdef USE_HDR
9324 float alpha = exposure*(faintestMag - appMag)/(faintestMag - saturationMag + 0.001f);
9325 #else
9326 float alpha = (faintestMag - appMag) * brightnessScale + brightnessBias;
9327 #endif
9328 #ifdef DEBUG_HDR_ADAPT
9329 minMag = max(minMag, appMag);
9330 maxMag = min(maxMag, appMag);
9331 minAlpha = min(minAlpha, alpha);
9332 maxAlpha = max(maxAlpha, alpha);
9333 ++total;
9334 if (alpha > above)
9335 {
9336 ++countAboveN;
9337 }
9338 #endif
9339 float pointSize;
9340
9341 if (useScaledDiscs)
9342 {
9343 float discSize = size;
9344 if (alpha < 0.0f)
9345 {
9346 alpha = 0.0f;
9347 }
9348 else if (alpha > 1.0f)
9349 {
9350 discSize = min(discSize * (2.0f * alpha - 1.0f), maxDiscSize);
9351 alpha = 1.0f;
9352 }
9353 pointSize = discSize;
9354 }
9355 else
9356 {
9357 alpha = clamp(alpha);
9358 pointSize = size;
9359 #ifdef DEBUG_HDR_ADAPT
9360 maxSize = max(maxSize, pointSize);
9361 #endif
9362 }
9363
9364 if (starPrimitive == GL_POINTS)
9365 {
9366 pointStarVertexBuffer->addStar(relPos,
9367 Color(starColor, alpha),
9368 pointSize);
9369 }
9370 else
9371 {
9372 starVertexBuffer->addStar(relPos,
9373 Color(starColor, alpha),
9374 pointSize * renderDistance);
9375 }
9376
9377 ++nRendered;
9378
9379 // If the star is brighter than the saturation magnitude, add a
9380 // halo around it to make it appear more brilliant. This is a
9381 // hack to compensate for the limited dynamic range of monitors.
9382 if (appMag < saturationMag)
9383 {
9384 Renderer::Particle p;
9385 p.center = Point3f(relPos.x, relPos.y, relPos.z);
9386 p.size = size;
9387 p.color = Color(starColor, alpha);
9388
9389 alpha = GlareOpacity * clamp((appMag - saturationMag) * -0.8f);
9390 s = renderDistance * 0.001f * (3 - (appMag - saturationMag)) * 2;
9391 if (s > p.size * 3)
9392 {
9393 p.size = s * 2.0f/(1.0f + FOV/fov);
9394 }
9395 else
9396 {
9397 if (s > p.size * 3)
9398 p.size = s * 2.0f; //2.0f/(1.0f +FOV/fov);
9399 else
9400 p.size = p.size * 3;
9401 p.size *= 1.6f;
9402 }
9403
9404 p.color = Color(starColor, alpha);
9405 glareParticles->insert(glareParticles->end(), p);
9406 ++nBright;
9407 }
9408 }
9409 else
9410 {
9411 Mat3f viewMat = observer->getOrientationf().toMatrix3();
9412 Vec3f viewMatZ(viewMat[2][0], viewMat[2][1], viewMat[2][2]);
9413
9414 RenderListEntry rle;
9415 rle.renderableType = RenderListEntry::RenderableStar;
9416 rle.star = ☆
9417 rle.isOpaque = true;
9418
9419 // Objects in the render list are always rendered relative to
9420 // a viewer at the origin--this is different than for distant
9421 // stars.
9422 float scale = astro::lightYearsToKilometers(1.0f);
9423 rle.position = Point3f(relPos.x * scale, relPos.y * scale, relPos.z * scale);
9424 rle.centerZ = Vec3f(rle.position.x, rle.position.y, rle.position.z) * viewMatZ;
9425 rle.distance = rle.position.distanceFromOrigin();
9426 rle.radius = star.getRadius();
9427 rle.discSizeInPixels = discSizeInPixels;
9428 rle.appMag = appMag;
9429 renderList->insert(renderList->end(), rle);
9430 }
9431 }
9432 }
9433
9434
9435 class PointStarRenderer : public ObjectRenderer<Star, float>
9436 {
9437 public:
9438 PointStarRenderer();
9439
9440 void process(const Star& star, float distance, float appMag);
9441
9442 public:
9443 Point3d obsPos;
9444
9445 vector<RenderListEntry>* renderList;
9446 Renderer::PointStarVertexBuffer* starVertexBuffer;
9447 Renderer::PointStarVertexBuffer* glareVertexBuffer;
9448
9449 const StarDatabase* starDB;
9450
9451 bool useScaledDiscs;
9452 GLenum starPrimitive;
9453 float maxDiscSize;
9454
9455 float cosFOV;
9456
9457 const ColorTemperatureTable* colorTemp;
9458 #ifdef DEBUG_HDR_ADAPT
9459 float minMag;
9460 float maxMag;
9461 float minAlpha;
9462 float maxAlpha;
9463 float maxSize;
9464 float above;
9465 unsigned long countAboveN;
9466 unsigned long total;
9467 #endif
9468 };
9469
9470
PointStarRenderer()9471 PointStarRenderer::PointStarRenderer() :
9472 ObjectRenderer<Star, float>(STAR_DISTANCE_LIMIT),
9473 starVertexBuffer (NULL),
9474 useScaledDiscs (false),
9475 maxDiscSize (1.0f),
9476 cosFOV (1.0f),
9477 colorTemp (NULL)
9478 {
9479 }
9480
9481
process(const Star & star,float distance,float appMag)9482 void PointStarRenderer::process(const Star& star, float distance, float appMag)
9483 {
9484 nProcessed++;
9485
9486 Point3f starPos = star.getPosition();
9487 Vec3f relPos((float) ((double) starPos.x - obsPos.x),
9488 (float) ((double) starPos.y - obsPos.y),
9489 (float) ((double) starPos.z - obsPos.z));
9490 float orbitalRadius = star.getOrbitalRadius();
9491 bool hasOrbit = orbitalRadius > 0.0f;
9492
9493 if (distance > distanceLimit)
9494 return;
9495
9496
9497 // A very rough check to see if the star may be visible: is the star in
9498 // front of the viewer? If the star might be close (relPos.x^2 < 0.1) or
9499 // is moving in an orbit, we'll always regard it as potentially visible.
9500 // TODO: consider normalizing relPos and comparing relPos*viewNormal against
9501 // cosFOV--this will cull many more stars than relPos*viewNormal, at the
9502 // cost of a normalize per star.
9503 if (relPos * viewNormal > 0 || relPos.x * relPos.x < 0.1f || hasOrbit)
9504 {
9505 #ifdef HDR_COMPRESS
9506 Color starColorFull = colorTemp->lookupColor(star.getTemperature());
9507 Color starColor(starColorFull.red() * 0.5f,
9508 starColorFull.green() * 0.5f,
9509 starColorFull.blue() * 0.5f);
9510 #else
9511 Color starColor = colorTemp->lookupColor(star.getTemperature());
9512 #endif
9513 float renderDistance = distance;
9514 /*float s = renderDistance * size; Unused*/
9515 float discSizeInPixels = 0.0f;
9516 float orbitSizeInPixels = 0.0f;
9517
9518 if (hasOrbit)
9519 orbitSizeInPixels = orbitalRadius / (distance * pixelSize);
9520
9521 // Special handling for stars less than one light year away . . .
9522 // We can't just go ahead and render a nearby star in the usual way
9523 // for two reasons:
9524 // * It may be clipped by the near plane
9525 // * It may be large enough that we should render it as a mesh
9526 // instead of a particle
9527 // It's possible that the second condition might apply for stars
9528 // further than one light year away if the star is huge, the fov is
9529 // very small and the resolution is high. We'll ignore this for now
9530 // and use the most inexpensive test possible . . .
9531 if (distance < 1.0f || orbitSizeInPixels > 1.0f)
9532 {
9533 // Compute the position of the observer relative to the star.
9534 // This is a much more accurate (and expensive) distance
9535 // calculation than the previous one which used the observer's
9536 // position rounded off to floats.
9537 Point3d hPos = astrocentricPosition(observer->getPosition(),
9538 star,
9539 observer->getTime());
9540 relPos = Vec3f((float) hPos.x, (float) hPos.y, (float) hPos.z) *
9541 -astro::kilometersToLightYears(1.0f),
9542 distance = relPos.length();
9543
9544 // Recompute apparent magnitude using new distance computation
9545 appMag = astro::absToAppMag(star.getAbsoluteMagnitude(), distance);
9546
9547 float f = RenderDistance / distance;
9548 renderDistance = RenderDistance;
9549 starPos = obsPos + relPos * f;
9550
9551 float radius = star.getRadius();
9552 discSizeInPixels = radius / astro::lightYearsToKilometers(distance) / pixelSize;
9553 ++nClose;
9554 }
9555
9556 // Place labels for stars brighter than the specified label threshold brightness
9557 if ((labelMode & Renderer::StarLabels) && appMag < labelThresholdMag)
9558 {
9559 Vec3f starDir = relPos;
9560 starDir.normalize();
9561 if (dot(starDir, viewNormal) > cosFOV)
9562 {
9563 char nameBuffer[Renderer::MaxLabelLength];
9564 starDB->getStarName(star, nameBuffer, sizeof(nameBuffer), true);
9565 float distr = 3.5f * (labelThresholdMag - appMag)/labelThresholdMag;
9566 if (distr > 1.0f)
9567 distr = 1.0f;
9568 renderer->addBackgroundAnnotation(NULL, nameBuffer,
9569 Color(Renderer::StarLabelColor, distr * Renderer::StarLabelColor.alpha()),
9570 Point3f(relPos.x, relPos.y, relPos.z));
9571 nLabelled++;
9572 }
9573 }
9574
9575 // Stars closer than the maximum solar system size are actually
9576 // added to the render list and depth sorted, since they may occlude
9577 // planets.
9578 if (distance > MaxSolarSystemSize)
9579 {
9580 #ifdef USE_HDR
9581 float satPoint = saturationMag;
9582 float alpha = exposure*(faintestMag - appMag)/(faintestMag - saturationMag + 0.001f);
9583 #else
9584 float satPoint = faintestMag - (1.0f - brightnessBias) / brightnessScale; // TODO: precompute this value
9585 float alpha = (faintestMag - appMag) * brightnessScale + brightnessBias;
9586 #endif
9587 #ifdef DEBUG_HDR_ADAPT
9588 minMag = max(minMag, appMag);
9589 maxMag = min(maxMag, appMag);
9590 minAlpha = min(minAlpha, alpha);
9591 maxAlpha = max(maxAlpha, alpha);
9592 ++total;
9593 if (alpha > above)
9594 {
9595 ++countAboveN;
9596 }
9597 #endif
9598
9599 if (useScaledDiscs)
9600 {
9601 float discSize = size;
9602 if (alpha < 0.0f)
9603 {
9604 alpha = 0.0f;
9605 }
9606 else if (alpha > 1.0f)
9607 {
9608 float discScale = min(MaxScaledDiscStarSize, (float) pow(2.0f, 0.3f * (satPoint - appMag)));
9609 discSize *= discScale;
9610
9611 float glareAlpha = min(0.5f, discScale / 4.0f);
9612 glareVertexBuffer->addStar(relPos, Color(starColor, glareAlpha), discSize * 3.0f);
9613
9614 alpha = 1.0f;
9615 }
9616 starVertexBuffer->addStar(relPos, Color(starColor, alpha), discSize);
9617 }
9618 else
9619 {
9620 if (alpha < 0.0f)
9621 {
9622 alpha = 0.0f;
9623 }
9624 else if (alpha > 1.0f)
9625 {
9626 float discScale = min(100.0f, satPoint - appMag + 2.0f);
9627 float glareAlpha = min(GlareOpacity, (discScale - 2.0f) / 4.0f);
9628 glareVertexBuffer->addStar(relPos, Color(starColor, glareAlpha), 2.0f * discScale * size);
9629 #ifdef DEBUG_HDR_ADAPT
9630 maxSize = max(maxSize, 2.0f * discScale * size);
9631 #endif
9632 }
9633 starVertexBuffer->addStar(relPos, Color(starColor, alpha), size);
9634 }
9635
9636 ++nRendered;
9637 }
9638 else
9639 {
9640 Mat3f viewMat = observer->getOrientationf().toMatrix3();
9641 Vec3f viewMatZ(viewMat[2][0], viewMat[2][1], viewMat[2][2]);
9642
9643 RenderListEntry rle;
9644 rle.renderableType = RenderListEntry::RenderableStar;
9645 rle.star = ☆
9646
9647 // Objects in the render list are always rendered relative to
9648 // a viewer at the origin--this is different than for distant
9649 // stars.
9650 float scale = astro::lightYearsToKilometers(1.0f);
9651 rle.position = Point3f(relPos.x * scale, relPos.y * scale, relPos.z * scale);
9652 rle.centerZ = Vec3f(rle.position.x, rle.position.y, rle.position.z) * viewMatZ;
9653 rle.distance = rle.position.distanceFromOrigin();
9654 rle.radius = star.getRadius();
9655 rle.discSizeInPixels = discSizeInPixels;
9656 rle.appMag = appMag;
9657 renderList->insert(renderList->end(), rle);
9658 }
9659 }
9660 }
9661
9662
microLYToLY(const Point3<T> & p)9663 template<class T> static Point3<T> microLYToLY(const Point3<T>& p)
9664 {
9665 return Point3<T>(p.x * (T) 1e-6, p.y * (T) 1e-6, p.z * (T) 1e-6);
9666 }
9667
9668
9669 // Calculate the maximum field of view (from top left corner to bottom right) of
9670 // a frustum with the specified aspect ratio (width/height) and vertical field of
9671 // view. We follow the convention used elsewhere and use units of degrees for
9672 // the field of view angle.
calcMaxFOV(double fovY_degrees,double aspectRatio)9673 static double calcMaxFOV(double fovY_degrees, double aspectRatio)
9674 {
9675 double l = 1.0 / tan(degToRad(fovY_degrees / 2.0));
9676 return radToDeg(atan(sqrt(aspectRatio * aspectRatio + 1.0) / l)) * 2.0;
9677 }
9678
9679
renderStars(const StarDatabase & starDB,float faintestMagNight,const Observer & observer)9680 void Renderer::renderStars(const StarDatabase& starDB,
9681 float faintestMagNight,
9682 const Observer& observer)
9683 {
9684 StarRenderer starRenderer;
9685 Point3d obsPos = microLYToLY((Point3d) observer.getPosition());
9686
9687
9688 starRenderer.context = context;
9689 starRenderer.renderer = this;
9690 starRenderer.starDB = &starDB;
9691 starRenderer.observer = &observer;
9692 starRenderer.obsPos = obsPos;
9693 starRenderer.viewNormal = Vec3f(0, 0, -1) * observer.getOrientationf().toMatrix3();
9694 starRenderer.glareParticles = &glareParticles;
9695 starRenderer.renderList = &renderList;
9696 starRenderer.starVertexBuffer = starVertexBuffer;
9697 starRenderer.pointStarVertexBuffer = pointStarVertexBuffer;
9698 starRenderer.fov = fov;
9699 starRenderer.cosFOV = (float) cos(degToRad(calcMaxFOV(fov, (float) windowWidth / (float) windowHeight)) / 2.0f);
9700
9701 // size/pixelSize =0.86 at 120deg, 1.43 at 45deg and 1.6 at 0deg.
9702 starRenderer.size = pixelSize * 1.6f / corrFac;
9703 starRenderer.pixelSize = pixelSize;
9704 starRenderer.brightnessScale = brightnessScale * corrFac;
9705 starRenderer.brightnessBias = brightnessBias;
9706 starRenderer.faintestMag = faintestMag;
9707 starRenderer.faintestMagNight = faintestMagNight;
9708 starRenderer.saturationMag = saturationMag;
9709 #ifdef USE_HDR
9710 starRenderer.exposure = exposure + brightPlus;
9711 #endif
9712 #ifdef DEBUG_HDR_ADAPT
9713 starRenderer.minMag = -100.f;
9714 starRenderer.maxMag = 100.f;
9715 starRenderer.minAlpha = 1.f;
9716 starRenderer.maxAlpha = 0.f;
9717 starRenderer.maxSize = 0.f;
9718 starRenderer.above = 1.f;
9719 starRenderer.countAboveN = 0L;
9720 starRenderer.total = 0L;
9721 #endif
9722 starRenderer.distanceLimit = distanceLimit;
9723 starRenderer.labelMode = labelMode;
9724
9725 // = 1.0 at startup
9726 float effDistanceToScreen = mmToInches((float) REF_DISTANCE_TO_SCREEN) * pixelSize * getScreenDpi();
9727 starRenderer.labelThresholdMag = max(1.0f, (faintestMag - 4.0f) * (1.0f - 0.5f * (float) log10(effDistanceToScreen)));
9728
9729 if (starStyle == PointStars || useNewStarRendering)
9730 {
9731 starRenderer.starPrimitive = GL_POINTS;
9732 //starRenderer.size = 3.2f;
9733 }
9734 else
9735 {
9736 starRenderer.starPrimitive = GL_QUADS;
9737 }
9738
9739 if (starStyle == ScaledDiscStars)
9740 {
9741 starRenderer.useScaledDiscs = true;
9742 starRenderer.brightnessScale *= 2.0f;
9743 starRenderer.maxDiscSize = starRenderer.size * MaxScaledDiscStarSize;
9744 }
9745
9746 starRenderer.colorTemp = colorTemp;
9747
9748 glareParticles.clear();
9749
9750 starVertexBuffer->setBillboardOrientation(observer.getOrientationf());
9751
9752 glEnable(GL_TEXTURE_2D);
9753
9754 if (useNewStarRendering)
9755 gaussianDiscTex->bind();
9756 else
9757 starTex->bind();
9758 if (starRenderer.starPrimitive == GL_POINTS)
9759 {
9760 // Point primitives (either real points or point sprites)
9761 if (starStyle == PointStars)
9762 starRenderer.pointStarVertexBuffer->startPoints(*context);
9763 else
9764 starRenderer.pointStarVertexBuffer->startSprites(*context);
9765 }
9766 else
9767 {
9768 // Use quad primitives
9769 starRenderer.starVertexBuffer->start();
9770 }
9771 starDB.findVisibleStars(starRenderer,
9772 Point3f((float) obsPos.x, (float) obsPos.y, (float) obsPos.z),
9773 observer.getOrientationf(),
9774 degToRad(fov),
9775 (float) windowWidth / (float) windowHeight,
9776 faintestMagNight);
9777 #ifdef DEBUG_HDR_ADAPT
9778 HDR_LOG <<
9779 "* minMag = " << starRenderer.minMag << ", " <<
9780 "maxMag = " << starRenderer.maxMag << ", " <<
9781 "percent above " << starRenderer.above << " = " <<
9782 (100.0*(double)starRenderer.countAboveN/(double)starRenderer.total) << endl;
9783 #endif
9784
9785 if (starRenderer.starPrimitive == GL_POINTS)
9786 starRenderer.pointStarVertexBuffer->finish();
9787 else
9788 starRenderer.starVertexBuffer->finish();
9789
9790 gaussianGlareTex->bind();
9791 renderParticles(glareParticles, observer.getOrientationf());
9792 }
9793
9794
renderPointStars(const StarDatabase & starDB,float faintestMagNight,const Observer & observer)9795 void Renderer::renderPointStars(const StarDatabase& starDB,
9796 float faintestMagNight,
9797 const Observer& observer)
9798 {
9799 Point3d obsPos = microLYToLY((Point3d) observer.getPosition());
9800
9801 PointStarRenderer starRenderer;
9802 starRenderer.context = context;
9803 starRenderer.renderer = this;
9804 starRenderer.starDB = &starDB;
9805 starRenderer.observer = &observer;
9806 starRenderer.obsPos = obsPos;
9807 starRenderer.viewNormal = Vec3f(0, 0, -1) * observer.getOrientationf().toMatrix3();
9808 starRenderer.renderList = &renderList;
9809 starRenderer.starVertexBuffer = pointStarVertexBuffer;
9810 starRenderer.glareVertexBuffer = glareVertexBuffer;
9811 starRenderer.fov = fov;
9812 starRenderer.cosFOV = (float) cos(degToRad(calcMaxFOV(fov, (float) windowWidth / (float) windowHeight)) / 2.0f);
9813
9814 starRenderer.pixelSize = pixelSize;
9815 starRenderer.brightnessScale = brightnessScale * corrFac;
9816 starRenderer.brightnessBias = brightnessBias;
9817 starRenderer.faintestMag = faintestMag;
9818 starRenderer.faintestMagNight = faintestMagNight;
9819 starRenderer.saturationMag = saturationMag;
9820 #ifdef USE_HDR
9821 starRenderer.exposure = exposure + brightPlus;
9822 #endif
9823 #ifdef DEBUG_HDR_ADAPT
9824 starRenderer.minMag = -100.f;
9825 starRenderer.maxMag = 100.f;
9826 starRenderer.minAlpha = 1.f;
9827 starRenderer.maxAlpha = 0.f;
9828 starRenderer.maxSize = 0.f;
9829 starRenderer.above = 1.0f;
9830 starRenderer.countAboveN = 0L;
9831 starRenderer.total = 0L;
9832 #endif
9833 starRenderer.distanceLimit = distanceLimit;
9834 starRenderer.labelMode = labelMode;
9835
9836 // = 1.0 at startup
9837 float effDistanceToScreen = mmToInches((float) REF_DISTANCE_TO_SCREEN) * pixelSize * getScreenDpi();
9838 starRenderer.labelThresholdMag = 1.2f * max(1.0f, (faintestMag - 4.0f) * (1.0f - 0.5f * (float) log10(effDistanceToScreen)));
9839
9840 starRenderer.size = BaseStarDiscSize;
9841 if (starStyle == ScaledDiscStars)
9842 {
9843 starRenderer.useScaledDiscs = true;
9844 starRenderer.brightnessScale *= 2.0f;
9845 starRenderer.maxDiscSize = starRenderer.size * MaxScaledDiscStarSize;
9846 }
9847 else if (starStyle == FuzzyPointStars)
9848 {
9849 starRenderer.brightnessScale *= 1.0f;
9850 }
9851
9852 starRenderer.colorTemp = colorTemp;
9853
9854 glEnable(GL_TEXTURE_2D);
9855 gaussianDiscTex->bind();
9856 starRenderer.starVertexBuffer->setTexture(gaussianDiscTex);
9857 starRenderer.glareVertexBuffer->setTexture(gaussianGlareTex);
9858
9859 starRenderer.glareVertexBuffer->startSprites(*context);
9860 if (starStyle == PointStars)
9861 starRenderer.starVertexBuffer->startPoints(*context);
9862 else
9863 starRenderer.starVertexBuffer->startSprites(*context);
9864
9865 starDB.findVisibleStars(starRenderer,
9866 Point3f((float) obsPos.x, (float) obsPos.y, (float) obsPos.z),
9867 observer.getOrientationf(),
9868 degToRad(fov),
9869 (float) windowWidth / (float) windowHeight,
9870 faintestMagNight);
9871
9872 starRenderer.starVertexBuffer->render();
9873 starRenderer.glareVertexBuffer->render();
9874 starRenderer.starVertexBuffer->finish();
9875 starRenderer.glareVertexBuffer->finish();
9876 }
9877
9878
9879 class DSORenderer : public ObjectRenderer<DeepSkyObject*, double>
9880 {
9881 public:
9882 DSORenderer();
9883
9884 void process(DeepSkyObject* const &, double, float);
9885
9886 public:
9887 Point3d obsPos;
9888 DSODatabase* dsoDB;
9889 Frustum frustum;
9890
9891 Mat3f orientationMatrix;
9892
9893 int wWidth;
9894 int wHeight;
9895
9896 double avgAbsMag;
9897 };
9898
9899
DSORenderer()9900 DSORenderer::DSORenderer() :
9901 ObjectRenderer<DeepSkyObject*, double>(DSO_OCTREE_ROOT_SIZE),
9902 frustum(degToRad(45.0f), 1.0f, 1.0f)
9903 {
9904 }
9905
9906
process(DeepSkyObject * const & dso,double distanceToDSO,float absMag)9907 void DSORenderer::process(DeepSkyObject* const & dso,
9908 double distanceToDSO,
9909 float absMag)
9910 {
9911 if (distanceToDSO > distanceLimit)
9912 return;
9913
9914 Point3d dsoPos = dso->getPosition();
9915 Vec3f relPos = Vec3f((float)(dsoPos.x - obsPos.x),
9916 (float)(dsoPos.y - obsPos.y),
9917 (float)(dsoPos.z - obsPos.z));
9918
9919 Point3d center = Point3d(0.0f, 0.0f, 0.0f) + relPos * orientationMatrix;
9920
9921 double enhance = 4.0, pc10 = 32.6167;
9922
9923 // The parameter 'enhance' adjusts the DSO brightness as viewed from "inside"
9924 // (e.g. MilkyWay as seen from Earth). It provides an enhanced apparent core
9925 // brightness appMag ~ absMag - enhance. 'enhance' thus serves to uniformly
9926 // enhance the too low sprite luminosity at close distance.
9927
9928 float appMag = (distanceToDSO >= pc10)? (float) astro::absToAppMag((double) absMag, distanceToDSO): absMag + (float) (enhance * tanh(distanceToDSO/pc10 - 1.0));
9929
9930 // Test the object's bounding sphere against the view frustum. If we
9931 // avoid this stage, overcrowded octree cells may hit performance badly:
9932 // each object (even if it's not visible) would be sent to the OpenGL
9933 // pipeline.
9934
9935
9936 if ((renderFlags & dso->getRenderMask()) && dso->isVisible())
9937 {
9938 double dsoRadius = dso->getBoundingSphereRadius();
9939
9940 if (frustum.testSphere(center, dsoRadius) != Frustum::Outside)
9941 {
9942 // Input: display looks satisfactory for 0.2 < brightness < O(1.0)
9943 // Ansatz: brightness = a - b * appMag(distanceToDSO), emulating eye sensitivity...
9944 // determine a,b such that
9945 // a - b * absMag = absMag / avgAbsMag ~ 1; a - b * faintestMag = 0.2.
9946 // The 2nd eq. guarantees that the faintest galaxies are still visible.
9947
9948 if(!strcmp(dso->getObjTypeName(),"globular"))
9949 avgAbsMag = -6.86; // average over 150 globulars in globulars.dsc.
9950 else if (!strcmp(dso->getObjTypeName(),"galaxy"))
9951 avgAbsMag = -19.04; // average over 10937 galaxies in galaxies.dsc.
9952
9953
9954 float r = absMag / (float) avgAbsMag;
9955 float brightness = r - (r - 0.2f) * (absMag - appMag) / (absMag - faintestMag);
9956
9957 // obviously, brightness(appMag = absMag) = r and
9958 // brightness(appMag = faintestMag) = 0.2, as desired.
9959
9960 brightness = 2.3f * brightness * (faintestMag - 4.75f) / renderer->getFaintestAM45deg();
9961
9962 #ifdef USE_HDR
9963 brightness *= exposure;
9964 #endif
9965 if (brightness < 0)
9966 brightness = 0;
9967
9968 if (dsoRadius < 1000.0)
9969 {
9970 // Small objects may be prone to clipping; give them special
9971 // handling. We don't want to always set the projection
9972 // matrix, since that could be expensive with large galaxy
9973 // catalogs.
9974 float nearZ = (float) (distanceToDSO / 2);
9975 float farZ = (float) (distanceToDSO + dsoRadius * 2 * CubeCornerToCenterDistance);
9976 if (nearZ < dsoRadius * 0.001)
9977 {
9978 nearZ = (float) (dsoRadius * 0.001);
9979 farZ = nearZ * 10000.0f;
9980 }
9981
9982 glMatrixMode(GL_PROJECTION);
9983 glPushMatrix();
9984 glLoadIdentity();
9985 gluPerspective(fov,
9986 (float) wWidth / (float) wHeight,
9987 nearZ,
9988 farZ);
9989 glMatrixMode(GL_MODELVIEW);
9990 }
9991
9992 glPushMatrix();
9993 glTranslate(relPos);
9994
9995 dso->render(*context,
9996 relPos,
9997 observer->getOrientationf(),
9998 (float) brightness,
9999 pixelSize);
10000 glPopMatrix();
10001
10002 #if 1
10003 if (dsoRadius < 1000.0)
10004 {
10005 glMatrixMode(GL_PROJECTION);
10006 glPopMatrix();
10007 glMatrixMode(GL_MODELVIEW);
10008 }
10009 #endif
10010 } // frustum test
10011 } // renderFlags check
10012
10013 // Only render those labels that are in front of the camera:
10014 // Place labels for DSOs brighter than the specified label threshold brightness
10015 //
10016 unsigned int labelMask = dso->getLabelMask();
10017
10018 if ((labelMask & labelMode) && dot(relPos, viewNormal) > 0 && dso->isVisible())
10019 {
10020 Color labelColor;
10021 float appMagEff = 6.0f;
10022 float step = 6.0f;
10023 float symbolSize = 0.0f;
10024 MarkerRepresentation* rep = NULL;
10025
10026 // Use magnitude based fading for galaxies, and distance based
10027 // fading for nebulae and open clusters.
10028 switch (labelMask)
10029 {
10030 case Renderer::NebulaLabels:
10031 rep = &renderer->nebulaRep;
10032 labelColor = Renderer::NebulaLabelColor;
10033 appMagEff = astro::absToAppMag(-7.5f, (float) distanceToDSO);
10034 symbolSize = (float) (dso->getRadius() / distanceToDSO) / pixelSize;
10035 step = 6.0f;
10036 break;
10037 case Renderer::OpenClusterLabels:
10038 rep = &renderer->openClusterRep;
10039 labelColor = Renderer::OpenClusterLabelColor;
10040 appMagEff = astro::absToAppMag(-6.0f, (float) distanceToDSO);
10041 symbolSize = (float) (dso->getRadius() / distanceToDSO) / pixelSize;
10042 step = 4.0f;
10043 break;
10044 case Renderer::GalaxyLabels:
10045 labelColor = Renderer::GalaxyLabelColor;
10046 appMagEff = appMag;
10047 step = 6.0f;
10048 break;
10049 case Renderer::GlobularLabels:
10050 labelColor = Renderer::GlobularLabelColor;
10051 appMagEff = appMag;
10052 step = 3.0f;
10053 break;
10054 default:
10055 // Unrecognized object class
10056 labelColor = Color::White;
10057 appMagEff = appMag;
10058 step = 6.0f;
10059 break;
10060 }
10061
10062 if (appMagEff < labelThresholdMag)
10063 {
10064 // introduce distance dependent label transparency.
10065 float distr = step * (labelThresholdMag - appMagEff) / labelThresholdMag;
10066 if (distr > 1.0f)
10067 distr = 1.0f;
10068
10069 renderer->addBackgroundAnnotation(rep,
10070 dsoDB->getDSOName(dso, true),
10071 Color(labelColor, distr * labelColor.alpha()),
10072 Point3f(relPos.x, relPos.y, relPos.z),
10073 Renderer::AlignLeft, Renderer::VerticalAlignCenter, symbolSize);
10074 }
10075 }
10076 }
10077
10078
renderDeepSkyObjects(const Universe & universe,const Observer & observer,const float faintestMagNight)10079 void Renderer::renderDeepSkyObjects(const Universe& universe,
10080 const Observer& observer,
10081 const float faintestMagNight)
10082 {
10083 DSORenderer dsoRenderer;
10084
10085 Point3d obsPos = (Point3d) observer.getPosition();
10086 obsPos.x *= 1e-6;
10087 obsPos.y *= 1e-6;
10088 obsPos.z *= 1e-6;
10089
10090 DSODatabase* dsoDB = universe.getDSOCatalog();
10091
10092 dsoRenderer.context = context;
10093 dsoRenderer.renderer = this;
10094 dsoRenderer.dsoDB = dsoDB;
10095 dsoRenderer.orientationMatrix = conjugate(observer.getOrientationf()).toMatrix3();
10096 dsoRenderer.observer = &observer;
10097 dsoRenderer.obsPos = obsPos;
10098 dsoRenderer.viewNormal = Vec3f(0, 0, -1) * observer.getOrientationf().toMatrix3();
10099 dsoRenderer.fov = fov;
10100 // size/pixelSize =0.86 at 120deg, 1.43 at 45deg and 1.6 at 0deg.
10101 dsoRenderer.size = pixelSize * 1.6f / corrFac;
10102 dsoRenderer.pixelSize = pixelSize;
10103 dsoRenderer.brightnessScale = brightnessScale * corrFac;
10104 dsoRenderer.brightnessBias = brightnessBias;
10105 dsoRenderer.avgAbsMag = dsoDB->getAverageAbsoluteMagnitude();
10106 dsoRenderer.faintestMag = faintestMag;
10107 dsoRenderer.faintestMagNight = faintestMagNight;
10108 dsoRenderer.saturationMag = saturationMag;
10109 #ifdef USE_HDR
10110 dsoRenderer.exposure = exposure + brightPlus;
10111 #endif
10112 dsoRenderer.renderFlags = renderFlags;
10113 dsoRenderer.labelMode = labelMode;
10114 dsoRenderer.wWidth = windowWidth;
10115 dsoRenderer.wHeight = windowHeight;
10116
10117 dsoRenderer.frustum = Frustum(degToRad(fov),
10118 (float) windowWidth / (float) windowHeight,
10119 MinNearPlaneDistance);
10120 // Use pixelSize * screenDpi instead of FoV, to eliminate windowHeight dependence.
10121 // = 1.0 at startup
10122 float effDistanceToScreen = mmToInches((float) REF_DISTANCE_TO_SCREEN) * pixelSize * getScreenDpi();
10123
10124 dsoRenderer.labelThresholdMag = 2.0f * max(1.0f, (faintestMag - 4.0f) * (1.0f - 0.5f * (float) log10(effDistanceToScreen)));
10125
10126 galaxyRep = MarkerRepresentation(MarkerRepresentation::Triangle, 8.0f, GalaxyLabelColor);
10127 nebulaRep = MarkerRepresentation(MarkerRepresentation::Square, 8.0f, NebulaLabelColor);
10128 openClusterRep = MarkerRepresentation(MarkerRepresentation::Circle, 8.0f, OpenClusterLabelColor);
10129 globularRep = MarkerRepresentation(MarkerRepresentation::Circle, 8.0f, OpenClusterLabelColor);
10130
10131 // Render any line primitives with smooth lines
10132 // (mostly to make graticules look good.)
10133 if ((renderFlags & ShowSmoothLines) != 0)
10134 enableSmoothLines();
10135
10136 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
10137
10138 dsoDB->findVisibleDSOs(dsoRenderer,
10139 obsPos,
10140 observer.getOrientationf(),
10141 degToRad(fov),
10142 (float) windowWidth / (float) windowHeight,
10143 2 * faintestMagNight);
10144
10145 if ((renderFlags & ShowSmoothLines) != 0)
10146 disableSmoothLines();
10147 }
10148
10149
toStandardCoords(const Vec3d & v)10150 static Vec3d toStandardCoords(const Vec3d& v)
10151 {
10152 return Vec3d(v.x, -v.z, v.y);
10153 }
10154
10155
renderSkyGrids(const Observer & observer)10156 void Renderer::renderSkyGrids(const Observer& observer)
10157 {
10158 if (renderFlags & ShowCelestialSphere)
10159 {
10160 SkyGrid grid;
10161 grid.setOrientation(Quatd::xrotation(astro::J2000Obliquity));
10162 grid.setLineColor(EquatorialGridColor);
10163 grid.setLabelColor(EquatorialGridLabelColor);
10164 grid.render(*this, observer, windowWidth, windowHeight);
10165 }
10166
10167 if (renderFlags & ShowGalacticGrid)
10168 {
10169 SkyGrid galacticGrid;
10170 galacticGrid.setOrientation(~(astro::eclipticToEquatorial() * astro::equatorialToGalactic()));
10171 galacticGrid.setLineColor(GalacticGridColor);
10172 galacticGrid.setLabelColor(GalacticGridLabelColor);
10173 galacticGrid.setLongitudeUnits(SkyGrid::LongitudeDegrees);
10174 galacticGrid.render(*this, observer, windowWidth, windowHeight);
10175 }
10176
10177 if (renderFlags & ShowEclipticGrid)
10178 {
10179 SkyGrid grid;
10180 grid.setOrientation(Quatd(1.0));
10181 grid.setLineColor(EclipticGridColor);
10182 grid.setLabelColor(EclipticGridLabelColor);
10183 grid.setLongitudeUnits(SkyGrid::LongitudeDegrees);
10184 grid.render(*this, observer, windowWidth, windowHeight);
10185 }
10186
10187 if (renderFlags & ShowHorizonGrid)
10188 {
10189 double tdb = observer.getTime();
10190 const ObserverFrame* frame = observer.getFrame();
10191 Body* body = frame->getRefObject().body();
10192
10193 if (body)
10194 {
10195 SkyGrid grid;
10196 grid.setLineColor(HorizonGridColor);
10197 grid.setLabelColor(HorizonGridLabelColor);
10198 grid.setLongitudeUnits(SkyGrid::LongitudeDegrees);
10199 grid.setLongitudeDirection(SkyGrid::IncreasingClockwise);
10200
10201 Vec3d zenithDirection = observer.getPosition() - body->getPosition(tdb);
10202 zenithDirection.normalize();
10203
10204 Vec3d northPole = Vec3d(0.0, 1.0, 0.0) * (body->getEclipticToEquatorial(tdb)).toMatrix3();
10205 zenithDirection = toStandardCoords(zenithDirection);
10206 northPole = toStandardCoords(northPole);
10207
10208 Vec3d v = zenithDirection ^ northPole;
10209
10210 // Horizontal coordinate system not well defined when observer
10211 // is at a pole.
10212 double tolerance = 1.0e-10;
10213 if (v.length() > tolerance && v.length() < 1.0 - tolerance)
10214 {
10215 v.normalize();
10216 Vec3d u = v ^ zenithDirection;
10217
10218 Mat3d m = Mat3d(u, v, zenithDirection);
10219 Quatd q = Quatd::matrixToQuaternion(m);
10220 grid.setOrientation(q);
10221
10222 grid.render(*this, observer, windowWidth, windowHeight);
10223 }
10224 }
10225 }
10226
10227 if (renderFlags & ShowEcliptic)
10228 {
10229 // Draw the J2000.0 ecliptic; trivial, since this forms the basis for
10230 // Celestia's coordinate system.
10231 const int subdivision = 200;
10232 glColor(EclipticColor);
10233 glBegin(GL_LINE_LOOP);
10234 for (int i = 0; i < subdivision; i++)
10235 {
10236 double theta = (double) i / (double) subdivision * 2 * PI;
10237 glVertex3f((float) cos(theta) * 1000.0f, 0.0f, (float) sin(theta) * 1000.0f);
10238 }
10239 glEnd();
10240 }
10241 }
10242
10243
10244 /*! Draw an arrow at the view border pointing to an offscreen selection. This method
10245 * should only be called when the selection lies outside the view frustum.
10246 */
renderSelectionPointer(const Observer & observer,double now,const Frustum & viewFrustum,const Selection & sel)10247 void Renderer::renderSelectionPointer(const Observer& observer,
10248 double now,
10249 const Frustum& viewFrustum,
10250 const Selection& sel)
10251 {
10252 const float cursorDistance = 20.0f;
10253 if (sel.empty())
10254 return;
10255
10256 Mat3f m = observer.getOrientationf().toMatrix3();
10257 Vec3f u = Vec3f(1, 0, 0) * m;
10258 Vec3f v = Vec3f(0, 1, 0) * m;
10259
10260 // Get the position of the cursor relative to the eye
10261 Vec3d position = (sel.getPosition(now) - observer.getPosition()) * astro::microLightYearsToKilometers(1.0);
10262 double distance = position.length();
10263 bool isVisible = viewFrustum.testSphere(Point3d(0, 0, 0) + position, sel.radius()) != Frustum::Outside;
10264 position *= cursorDistance / distance;
10265
10266 #ifdef USE_HDR
10267 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
10268 #endif
10269 glDisable(GL_DEPTH_TEST);
10270 glDisable(GL_TEXTURE_2D);
10271 glEnable(GL_BLEND);
10272 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
10273
10274 if (!isVisible)
10275 {
10276 double viewAspectRatio = (double) windowWidth / (double) windowHeight;
10277 double vfov = observer.getFOV();
10278 float h = (float) tan(vfov / 2);
10279 float w = (float) (h * viewAspectRatio);
10280 float diag = std::sqrt(h * h + w * w);
10281
10282 Vec3f posf((float) position.x, (float) position.y, (float) position.z);
10283 posf *= (1.0f / cursorDistance);
10284 float x = u * posf;
10285 float y = v * posf;
10286 float angle = std::atan2(y, x);
10287 float c = std::cos(angle);
10288 float s = std::sin(angle);
10289
10290 float t = 1.0f;
10291 float x0 = c * diag;
10292 float y0 = s * diag;
10293 if (std::abs(x0) < w)
10294 t = h / std::abs(y0);
10295 else
10296 t = w / std::abs(x0);
10297 x0 *= t;
10298 y0 *= t;
10299 glColor(SelectionCursorColor, 0.6f);
10300 Vec3f center = Vec3f(0, 0, -1) * m;
10301
10302 glPushMatrix();
10303 glTranslatef((float) center.x, (float) center.y, (float) center.z);
10304
10305 Vec3f p0(0.0f, 0.0f, 0.0f);
10306 Vec3f p1(-20.0f * pixelSize, 6.0f * pixelSize, 0.0f);
10307 Vec3f p2(-20.0f * pixelSize, -6.0f * pixelSize, 0.0f);
10308
10309 glBegin(GL_TRIANGLES);
10310 glVertex((p0.x * c - p0.y * s + x0) * u + (p0.x * s + p0.y * c + y0) * v);
10311 glVertex((p1.x * c - p1.y * s + x0) * u + (p1.x * s + p1.y * c + y0) * v);
10312 glVertex((p2.x * c - p2.y * s + x0) * u + (p2.x * s + p2.y * c + y0) * v);
10313 glEnd();
10314
10315 glPopMatrix();
10316 }
10317
10318 #ifdef USE_HDR
10319 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
10320 #endif
10321
10322 glEnable(GL_TEXTURE_2D);
10323 }
10324
10325
labelConstellations(const AsterismList & asterisms,const Observer & observer)10326 void Renderer::labelConstellations(const AsterismList& asterisms,
10327 const Observer& observer)
10328 {
10329 Point3f observerPos = (Point3f) observer.getPosition();
10330
10331 for (AsterismList::const_iterator iter = asterisms.begin();
10332 iter != asterisms.end(); iter++)
10333 {
10334 Asterism* ast = *iter;
10335 if (ast->getChainCount() > 0 && ast->getActive())
10336 {
10337 const Asterism::Chain& chain = ast->getChain(0);
10338
10339 if (chain.size() > 0)
10340 {
10341 // The constellation label is positioned at the average
10342 // position of all stars in the first chain. This usually
10343 // gives reasonable results.
10344 Vec3f avg(0, 0, 0);
10345 for (Asterism::Chain::const_iterator iter = chain.begin();
10346 iter != chain.end(); iter++)
10347 avg += (*iter - Point3f(0, 0, 0));
10348
10349 avg = avg / (float) chain.size();
10350
10351 // Draw all constellation labels at the same distance
10352 avg.normalize();
10353 avg = avg * 1.0e10f;
10354
10355 Vec3f rpos = Point3f(avg.x, avg.y, avg.z) - observerPos;
10356
10357 if ((observer.getOrientationf().toMatrix3() * rpos).z < 0)
10358 {
10359 // We'll linearly fade the labels as a function of the
10360 // observer's distance to the origin of coordinates:
10361 float opacity = 1.0f;
10362 float dist = observerPos.distanceFromOrigin();
10363 if (dist > MaxAsterismLabelsConstDist)
10364 {
10365 opacity = clamp((MaxAsterismLabelsConstDist - dist) /
10366 (MaxAsterismLabelsDist - MaxAsterismLabelsConstDist) + 1);
10367 }
10368
10369 // Use the default label color unless the constellation has an
10370 // override color set.
10371 Color labelColor = ConstellationLabelColor;
10372 if (ast->isColorOverridden())
10373 labelColor = ast->getOverrideColor();
10374
10375 addBackgroundAnnotation(NULL,
10376 ast->getName((labelMode & I18nConstellationLabels) != 0),
10377 Color(labelColor, opacity),
10378 Point3f(rpos.x, rpos.y, rpos.z),
10379 AlignCenter, VerticalAlignCenter);
10380 }
10381 }
10382 }
10383 }
10384 }
10385
10386
renderParticles(const vector<Particle> & particles,Quatf orientation)10387 void Renderer::renderParticles(const vector<Particle>& particles,
10388 Quatf orientation)
10389 {
10390 int nParticles = particles.size();
10391
10392 {
10393 Mat3f m = orientation.toMatrix3();
10394 Vec3f v0 = Vec3f(-1, -1, 0) * m;
10395 Vec3f v1 = Vec3f( 1, -1, 0) * m;
10396 Vec3f v2 = Vec3f( 1, 1, 0) * m;
10397 Vec3f v3 = Vec3f(-1, 1, 0) * m;
10398
10399 glBegin(GL_QUADS);
10400 for (int i = 0; i < nParticles; i++)
10401 {
10402 Point3f center = particles[i].center;
10403 float size = particles[i].size;
10404
10405 glColor(particles[i].color);
10406 glTexCoord2f(0, 1);
10407 glVertex(center + (v0 * size));
10408 glTexCoord2f(1, 1);
10409 glVertex(center + (v1 * size));
10410 glTexCoord2f(1, 0);
10411 glVertex(center + (v2 * size));
10412 glTexCoord2f(0, 0);
10413 glVertex(center + (v3 * size));
10414 }
10415 glEnd();
10416 }
10417 }
10418
10419
renderCrosshair(float pixelSize,double tsec)10420 static void renderCrosshair(float pixelSize, double tsec)
10421 {
10422 const float cursorMinRadius = 6.0f;
10423 const float cursorRadiusVariability = 4.0f;
10424 const float minCursorWidth = 7.0f;
10425 const float cursorPulsePeriod = 1.5f;
10426
10427 float selectionSizeInPixels = pixelSize;
10428 float cursorRadius = selectionSizeInPixels + cursorMinRadius;
10429 cursorRadius += cursorRadiusVariability * (float) (0.5 + 0.5 * std::sin(tsec * 2 * PI / cursorPulsePeriod));
10430
10431 // Enlarge the size of the cross hair sligtly when the selection
10432 // has a large apparent size
10433 float cursorGrow = max(1.0f, min(2.5f, (selectionSizeInPixels - 10.0f) / 100.0f));
10434
10435 float h = 2.0f * cursorGrow;
10436 float cursorWidth = minCursorWidth * cursorGrow;
10437 float r0 = cursorRadius;
10438 float r1 = cursorRadius + cursorWidth;
10439
10440 const unsigned int markCount = 4;
10441 Vec3f p0(r0, 0.0f, 0.0f);
10442 Vec3f p1(r1, -h, 0.0f);
10443 Vec3f p2(r1, h, 0.0f);
10444
10445 glBegin(GL_TRIANGLES);
10446 for (unsigned int i = 0; i < markCount; i++)
10447 {
10448 float theta = (float) (PI / 4.0) + (float) i / (float) markCount * (float) (2 * PI);
10449 float c = std::cos(theta);
10450 float s = std::sin(theta);
10451 glVertex3f(p0.x * c - p0.y * s, p0.x * s + p0.y * c, 0.0f);
10452 glVertex3f(p1.x * c - p1.y * s, p1.x * s + p1.y * c, 0.0f);
10453 glVertex3f(p2.x * c - p2.y * s, p2.x * s + p2.y * c, 0.0f);
10454 }
10455 glEnd();
10456 }
10457
10458
renderAnnotations(const vector<Annotation> & annotations,FontStyle fs)10459 void Renderer::renderAnnotations(const vector<Annotation>& annotations, FontStyle fs)
10460 {
10461 if (font[fs] == NULL)
10462 return;
10463
10464 // Enable line smoothing for rendering symbols
10465 if ((renderFlags & ShowSmoothLines) != 0)
10466 enableSmoothLines();
10467
10468 #ifdef USE_HDR
10469 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
10470 #endif
10471 glEnable(GL_TEXTURE_2D);
10472 font[fs]->bind();
10473 glEnable(GL_BLEND);
10474 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
10475
10476 glMatrixMode(GL_PROJECTION);
10477 glPushMatrix();
10478 glLoadIdentity();
10479 gluOrtho2D(0, windowWidth, 0, windowHeight);
10480 glMatrixMode(GL_MODELVIEW);
10481 glPushMatrix();
10482 glLoadIdentity();
10483 glTranslatef(GLfloat((int) (windowWidth / 2)),
10484 GLfloat((int) (windowHeight / 2)), 0);
10485
10486 for (int i = 0; i < (int) annotations.size(); i++)
10487 {
10488 if (annotations[i].markerRep != NULL)
10489 {
10490 glPushMatrix();
10491 const MarkerRepresentation& markerRep = *annotations[i].markerRep;
10492
10493 float size = markerRep.size();
10494 if (annotations[i].size > 0.0f)
10495 {
10496 size = annotations[i].size;
10497 }
10498
10499 glColor(annotations[i].color);
10500 glTranslatef((GLfloat) (int) annotations[i].position.x,
10501 (GLfloat) (int) annotations[i].position.y, 0.0f);
10502
10503 glDisable(GL_TEXTURE_2D);
10504 if (markerRep.symbol() == MarkerRepresentation::Crosshair)
10505 renderCrosshair(size, realTime);
10506 else
10507 markerRep.render(size);
10508 glEnable(GL_TEXTURE_2D);
10509
10510 if (!markerRep.label().empty())
10511 {
10512 int labelOffset = (int) markerRep.size() / 2;
10513 glTranslatef(labelOffset + PixelOffset, -labelOffset - font[fs]->getHeight() + PixelOffset, 0.0f);
10514 font[fs]->render(markerRep.label());
10515 }
10516 glPopMatrix();
10517 }
10518
10519 if (annotations[i].labelText[0] != '\0')
10520 {
10521 glPushMatrix();
10522 int labelWidth = 0;
10523 int hOffset = 2;
10524 int vOffset = 0;
10525
10526 switch (annotations[i].halign)
10527 {
10528 case AlignCenter:
10529 labelWidth = (font[fs]->getWidth(annotations[i].labelText));
10530 hOffset = -labelWidth / 2;
10531 break;
10532
10533 case AlignRight:
10534 labelWidth = (font[fs]->getWidth(annotations[i].labelText));
10535 hOffset = -(labelWidth + 2);
10536 break;
10537
10538 case AlignLeft:
10539 if (annotations[i].markerRep != NULL)
10540 hOffset = 2 + (int) annotations[i].markerRep->size() / 2;
10541 break;
10542 }
10543
10544 switch (annotations[i].valign)
10545 {
10546 case AlignCenter:
10547 vOffset = -font[fs]->getHeight() / 2;
10548 break;
10549 case VerticalAlignTop:
10550 vOffset = -font[fs]->getHeight();
10551 break;
10552 case VerticalAlignBottom:
10553 vOffset = 0;
10554 break;
10555 }
10556
10557 glColor(annotations[i].color);
10558 glTranslatef((int) annotations[i].position.x + hOffset + PixelOffset,
10559 (int) annotations[i].position.y + vOffset + PixelOffset, 0.0f);
10560 // EK TODO: Check where to replace (see '_(' above)
10561 font[fs]->render(annotations[i].labelText);
10562 glPopMatrix();
10563 }
10564 }
10565
10566 glPopMatrix();
10567 glMatrixMode(GL_PROJECTION);
10568 glPopMatrix();
10569 glMatrixMode(GL_MODELVIEW);
10570 #ifdef USE_HDR
10571 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
10572 #endif
10573
10574 if ((renderFlags & ShowSmoothLines) != 0)
10575 disableSmoothLines();
10576 }
10577
10578
10579 void
renderBackgroundAnnotations(FontStyle fs)10580 Renderer::renderBackgroundAnnotations(FontStyle fs)
10581 {
10582 glEnable(GL_DEPTH_TEST);
10583 renderAnnotations(backgroundAnnotations, fs);
10584 glDisable(GL_DEPTH_TEST);
10585
10586 clearAnnotations(backgroundAnnotations);
10587 }
10588
10589
10590 void
renderForegroundAnnotations(FontStyle fs)10591 Renderer::renderForegroundAnnotations(FontStyle fs)
10592 {
10593 glDisable(GL_DEPTH_TEST);
10594 renderAnnotations(foregroundAnnotations, fs);
10595
10596 clearAnnotations(foregroundAnnotations);
10597 }
10598
10599
10600 vector<Renderer::Annotation>::iterator
renderSortedAnnotations(vector<Annotation>::iterator iter,float nearDist,float farDist,FontStyle fs)10601 Renderer::renderSortedAnnotations(vector<Annotation>::iterator iter,
10602 float nearDist,
10603 float farDist,
10604 FontStyle fs)
10605 {
10606 if (font[fs] == NULL)
10607 return iter;
10608
10609 glEnable(GL_DEPTH_TEST);
10610 glEnable(GL_TEXTURE_2D);
10611 font[fs]->bind();
10612 glEnable(GL_BLEND);
10613 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
10614
10615 glMatrixMode(GL_PROJECTION);
10616 glPushMatrix();
10617 glLoadIdentity();
10618 gluOrtho2D(0, windowWidth, 0, windowHeight);
10619 glMatrixMode(GL_MODELVIEW);
10620 glPushMatrix();
10621 glLoadIdentity();
10622 glTranslatef(GLfloat((int) (windowWidth / 2)),
10623 GLfloat((int) (windowHeight / 2)), 0);
10624
10625 // Precompute values that will be used to generate the normalized device z value;
10626 // we're effectively just handling the projection instead of OpenGL. We use an orthographic
10627 // projection matrix in order to get the label text position exactly right but need to mimic
10628 // the depth coordinate generation of a perspective projection.
10629 float d1 = -(farDist + nearDist) / (farDist - nearDist);
10630 float d2 = -2.0f * nearDist * farDist / (farDist - nearDist);
10631
10632 for (; iter != depthSortedAnnotations.end() && iter->position.z > nearDist; iter++)
10633 {
10634 // Compute normalized device z
10635 float ndc_z = d1 + d2 / -iter->position.z;
10636 ndc_z = min(1.0f, max(-1.0f, ndc_z)); // Clamp to [-1,1]
10637
10638 // Offsets to left align label
10639 int labelHOffset = 0;
10640 int labelVOffset = 0;
10641
10642 glPushMatrix();
10643 if (iter->markerRep != NULL)
10644 {
10645 const MarkerRepresentation& markerRep = *iter->markerRep;
10646 float size = markerRep.size();
10647 if (iter->size > 0.0f)
10648 {
10649 size = iter->size;
10650 }
10651
10652 glTranslatef((GLfloat) (int) iter->position.x, (GLfloat) (int) iter->position.y, ndc_z);
10653 glColor(iter->color);
10654
10655 glDisable(GL_TEXTURE_2D);
10656 if (markerRep.symbol() == MarkerRepresentation::Crosshair)
10657 renderCrosshair(size, realTime);
10658 else
10659 markerRep.render(size);
10660 glEnable(GL_TEXTURE_2D);
10661
10662 if (!markerRep.label().empty())
10663 {
10664 int labelOffset = (int) markerRep.size() / 2;
10665 glTranslatef(labelOffset + PixelOffset, -labelOffset - font[fs]->getHeight() + PixelOffset, 0.0f);
10666 font[fs]->render(markerRep.label());
10667 }
10668 }
10669 else
10670 {
10671 glTranslatef((int) iter->position.x + PixelOffset + labelHOffset,
10672 (int) iter->position.y + PixelOffset + labelVOffset,
10673 ndc_z);
10674 glColor(iter->color);
10675 font[fs]->render(iter->labelText);
10676 }
10677 glPopMatrix();
10678 }
10679
10680 glPopMatrix();
10681 glMatrixMode(GL_PROJECTION);
10682 glPopMatrix();
10683 glMatrixMode(GL_MODELVIEW);
10684 glDisable(GL_DEPTH_TEST);
10685
10686 return iter;
10687 }
10688
10689
10690 vector<Renderer::Annotation>::iterator
renderAnnotations(vector<Annotation>::iterator startIter,vector<Annotation>::iterator endIter,float nearDist,float farDist,FontStyle fs)10691 Renderer::renderAnnotations(vector<Annotation>::iterator startIter,
10692 vector<Annotation>::iterator endIter,
10693 float nearDist,
10694 float farDist,
10695 FontStyle fs)
10696 {
10697 if (font[fs] == NULL)
10698 return endIter;
10699
10700 glEnable(GL_DEPTH_TEST);
10701 glEnable(GL_TEXTURE_2D);
10702 font[fs]->bind();
10703 glEnable(GL_BLEND);
10704 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
10705
10706 glMatrixMode(GL_PROJECTION);
10707 glPushMatrix();
10708 glLoadIdentity();
10709 gluOrtho2D(0, windowWidth, 0, windowHeight);
10710 glMatrixMode(GL_MODELVIEW);
10711 glPushMatrix();
10712 glLoadIdentity();
10713 glTranslatef(GLfloat((int) (windowWidth / 2)),
10714 GLfloat((int) (windowHeight / 2)), 0);
10715
10716 // Precompute values that will be used to generate the normalized device z value;
10717 // we're effectively just handling the projection instead of OpenGL. We use an orthographic
10718 // projection matrix in order to get the label text position exactly right but need to mimic
10719 // the depth coordinate generation of a perspective projection.
10720 float d1 = -(farDist + nearDist) / (farDist - nearDist);
10721 float d2 = -2.0f * nearDist * farDist / (farDist - nearDist);
10722
10723 vector<Annotation>::iterator iter = startIter;
10724 for (; iter != endIter && iter->position.z > nearDist; iter++)
10725 {
10726 // Compute normalized device z
10727 float ndc_z = d1 + d2 / -iter->position.z;
10728 ndc_z = min(1.0f, max(-1.0f, ndc_z)); // Clamp to [-1,1]
10729
10730 // Offsets to left align label
10731 int labelHOffset = 0;
10732 int labelVOffset = 0;
10733
10734 if (iter->markerRep != NULL)
10735 {
10736 glPushMatrix();
10737 const MarkerRepresentation& markerRep = *iter->markerRep;
10738 float size = markerRep.size();
10739 if (iter->size > 0.0f)
10740 {
10741 size = iter->size;
10742 }
10743
10744 glTranslatef((GLfloat) (int) iter->position.x, (GLfloat) (int) iter->position.y, ndc_z);
10745 glColor(iter->color);
10746
10747 glDisable(GL_TEXTURE_2D);
10748 if (markerRep.symbol() == MarkerRepresentation::Crosshair)
10749 renderCrosshair(size, realTime);
10750 else
10751 markerRep.render(size);
10752 glEnable(GL_TEXTURE_2D);
10753
10754 if (!markerRep.label().empty())
10755 {
10756 int labelOffset = (int) markerRep.size() / 2;
10757 glTranslatef(labelOffset + PixelOffset, -labelOffset - font[fs]->getHeight() + PixelOffset, 0.0f);
10758 font[fs]->render(markerRep.label());
10759 }
10760 glPopMatrix();
10761 }
10762
10763 if (iter->labelText[0] != '\0')
10764 {
10765 if (iter->markerRep != NULL)
10766 labelHOffset += (int) iter->markerRep->size() / 2 + 3;
10767
10768 glPushMatrix();
10769 glTranslatef((int) iter->position.x + PixelOffset + labelHOffset,
10770 (int) iter->position.y + PixelOffset + labelVOffset,
10771 ndc_z);
10772 glColor(iter->color);
10773 font[fs]->render(iter->labelText);
10774 glPopMatrix();
10775 }
10776 }
10777
10778 glPopMatrix();
10779 glMatrixMode(GL_PROJECTION);
10780 glPopMatrix();
10781 glMatrixMode(GL_MODELVIEW);
10782 glDisable(GL_DEPTH_TEST);
10783
10784 return iter;
10785 }
10786
10787
renderMarkers(const MarkerList & markers,const UniversalCoord & cameraPosition,const Quatd & cameraOrientation,double jd)10788 void Renderer::renderMarkers(const MarkerList& markers,
10789 const UniversalCoord& cameraPosition,
10790 const Quatd& cameraOrientation,
10791 double jd)
10792 {
10793 // Calculate the cosine of half the maximum field of view. We'll use this for
10794 // fast testing of marker visibility. The stored field of view is the
10795 // vertical field of view; we want the field of view as measured on the
10796 // diagonal between viewport corners.
10797 double h = tan(degToRad(fov / 2));
10798 double diag = sqrt(1.0 + square(h) + square(h * (double) windowWidth / (double) windowHeight));
10799 double cosFOV = 1.0 / diag;
10800
10801 Vec3d viewVector = Vec3d(0.0, 0.0, -1.0) * cameraOrientation.toMatrix3();
10802
10803 for (MarkerList::const_iterator iter = markers.begin(); iter != markers.end(); iter++)
10804 {
10805 UniversalCoord uc = iter->position(jd);
10806 Vec3d offset = (uc - cameraPosition) * astro::microLightYearsToKilometers(1.0);
10807
10808 // Only render those markers that lie withing the field of view.
10809 if ((offset * viewVector) > cosFOV * offset.length())
10810 {
10811 double distance = offset.length();
10812 float symbolSize = 0.0f;
10813 if (iter->sizing() == DistanceBasedSize)
10814 {
10815 symbolSize = (float) (iter->representation().size() / distance) / pixelSize;
10816 }
10817
10818 if (iter->occludable())
10819 {
10820 // If the marker is occludable, add it to the sorted annotation list if it's relatively
10821 // nearby, and to the background list if it's very distant.
10822 if (distance < astro::lightYearsToKilometers(1.0))
10823 {
10824 // Modify the marker position so that it is always in front of the marked object.
10825 double boundingRadius;
10826 if (iter->object().body() != NULL)
10827 boundingRadius = iter->object().body()->getBoundingRadius();
10828 else
10829 boundingRadius = iter->object().radius();
10830 offset *= (1.0 - boundingRadius * 1.01 / distance);
10831
10832 addSortedAnnotation(&(iter->representation()), EMPTY_STRING, iter->representation().color(),
10833 Point3f((float) offset.x, (float) offset.y, (float) offset.z),
10834 AlignLeft, VerticalAlignTop, symbolSize);
10835 }
10836 else
10837 {
10838 addAnnotation(backgroundAnnotations,
10839 &(iter->representation()), EMPTY_STRING, iter->representation().color(),
10840 Point3f((float) offset.x, (float) offset.y, (float) offset.z),
10841 AlignLeft, VerticalAlignTop, symbolSize);
10842 }
10843 }
10844 else
10845 {
10846 addAnnotation(foregroundAnnotations,
10847 &(iter->representation()), EMPTY_STRING, iter->representation().color(),
10848 Point3f((float) offset.x, (float) offset.y, (float) offset.z),
10849 AlignLeft, VerticalAlignTop, symbolSize);
10850 }
10851 }
10852 }
10853 }
10854
10855
setStarStyle(StarStyle style)10856 void Renderer::setStarStyle(StarStyle style)
10857 {
10858 starStyle = style;
10859 markSettingsChanged();
10860 }
10861
10862
getStarStyle() const10863 Renderer::StarStyle Renderer::getStarStyle() const
10864 {
10865 return starStyle;
10866 }
10867
10868
StarVertexBuffer(unsigned int _capacity)10869 Renderer::StarVertexBuffer::StarVertexBuffer(unsigned int _capacity) :
10870 capacity(_capacity),
10871 vertices(NULL),
10872 texCoords(NULL),
10873 colors(NULL)
10874 {
10875 nStars = 0;
10876 vertices = new float[capacity * 12];
10877 texCoords = new float[capacity * 8];
10878 colors = new unsigned char[capacity * 16];
10879
10880 // Fill the texture coordinate array now, since it will always have
10881 // the same contents.
10882 for (unsigned int i = 0; i < capacity; i++)
10883 {
10884 unsigned int n = i * 8;
10885 texCoords[n ] = 0; texCoords[n + 1] = 0;
10886 texCoords[n + 2] = 1; texCoords[n + 3] = 0;
10887 texCoords[n + 4] = 1; texCoords[n + 5] = 1;
10888 texCoords[n + 6] = 0; texCoords[n + 7] = 1;
10889 }
10890 }
10891
~StarVertexBuffer()10892 Renderer::StarVertexBuffer::~StarVertexBuffer()
10893 {
10894 if (vertices != NULL)
10895 delete[] vertices;
10896 if (colors != NULL)
10897 delete[] colors;
10898 if (texCoords != NULL)
10899 delete[] texCoords;
10900 }
10901
start()10902 void Renderer::StarVertexBuffer::start()
10903 {
10904 glEnableClientState(GL_VERTEX_ARRAY);
10905 glVertexPointer(3, GL_FLOAT, 0, vertices);
10906 glEnableClientState(GL_COLOR_ARRAY);
10907 glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
10908 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
10909 glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
10910 glDisableClientState(GL_NORMAL_ARRAY);
10911 }
10912
render()10913 void Renderer::StarVertexBuffer::render()
10914 {
10915 if (nStars != 0)
10916 {
10917 glDrawArrays(GL_QUADS, 0, nStars * 4);
10918 nStars = 0;
10919 }
10920 }
10921
finish()10922 void Renderer::StarVertexBuffer::finish()
10923 {
10924 render();
10925 glDisableClientState(GL_COLOR_ARRAY);
10926 glDisableClientState(GL_VERTEX_ARRAY);
10927 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
10928 }
10929
addStar(const Vec3f & pos,const Color & color,float size)10930 void Renderer::StarVertexBuffer::addStar(const Vec3f& pos,
10931 const Color& color,
10932 float size)
10933 {
10934 if (nStars < capacity)
10935 {
10936 int n = nStars * 12;
10937 vertices[n + 0] = pos.x + v0.x * size;
10938 vertices[n + 1] = pos.y + v0.y * size;
10939 vertices[n + 2] = pos.z + v0.z * size;
10940 vertices[n + 3] = pos.x + v1.x * size;
10941 vertices[n + 4] = pos.y + v1.y * size;
10942 vertices[n + 5] = pos.z + v1.z * size;
10943 vertices[n + 6] = pos.x + v2.x * size;
10944 vertices[n + 7] = pos.y + v2.y * size;
10945 vertices[n + 8] = pos.z + v2.z * size;
10946 vertices[n + 9] = pos.x + v3.x * size;
10947 vertices[n + 10] = pos.y + v3.y * size;
10948 vertices[n + 11] = pos.z + v3.z * size;
10949 n = nStars * 16;
10950 color.get(colors + n);
10951 color.get(colors + n + 4);
10952 color.get(colors + n + 8);
10953 color.get(colors + n + 12);
10954
10955 nStars++;
10956 }
10957
10958 if (nStars == capacity)
10959 {
10960 render();
10961 nStars = 0;
10962 }
10963 }
10964
setBillboardOrientation(const Quatf & q)10965 void Renderer::StarVertexBuffer::setBillboardOrientation(const Quatf& q)
10966 {
10967 Mat3f m = q.toMatrix3();
10968 v0 = Vec3f(-1, -1, 0) * m;
10969 v1 = Vec3f( 1, -1, 0) * m;
10970 v2 = Vec3f( 1, 1, 0) * m;
10971 v3 = Vec3f(-1, 1, 0) * m;
10972 }
10973
10974
PointStarVertexBuffer(unsigned int _capacity)10975 Renderer::PointStarVertexBuffer::PointStarVertexBuffer(unsigned int _capacity) :
10976 capacity(_capacity),
10977 nStars(0),
10978 vertices(NULL),
10979 context(NULL),
10980 useSprites(false),
10981 texture(NULL)
10982 {
10983 vertices = new StarVertex[capacity];
10984 }
10985
~PointStarVertexBuffer()10986 Renderer::PointStarVertexBuffer::~PointStarVertexBuffer()
10987 {
10988 if (vertices != NULL)
10989 delete[] vertices;
10990 }
10991
startSprites(const GLContext & _context)10992 void Renderer::PointStarVertexBuffer::startSprites(const GLContext& _context)
10993 {
10994 context = &_context;
10995 assert(context->getVertexProcessor() != NULL || !useSprites); // vertex shaders required for new star rendering
10996
10997 unsigned int stride = sizeof(StarVertex);
10998 glEnableClientState(GL_VERTEX_ARRAY);
10999 glVertexPointer(3, GL_FLOAT, stride, &vertices[0].position);
11000 glEnableClientState(GL_COLOR_ARRAY);
11001 glColorPointer(4, GL_UNSIGNED_BYTE, stride, &vertices[0].color);
11002
11003 VertexProcessor* vproc = context->getVertexProcessor();
11004 vproc->enable();
11005 vproc->use(vp::starDisc);
11006 vproc->enableAttribArray(6);
11007 vproc->attribArray(6, 1, GL_FLOAT, stride, &vertices[0].size);
11008
11009 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
11010 glDisableClientState(GL_NORMAL_ARRAY);
11011
11012 glEnable(GL_POINT_SPRITE_ARB);
11013 glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
11014
11015 useSprites = true;
11016 }
11017
startPoints(const GLContext & _context)11018 void Renderer::PointStarVertexBuffer::startPoints(const GLContext& _context)
11019 {
11020 context = &_context;
11021
11022 unsigned int stride = sizeof(StarVertex);
11023 glEnableClientState(GL_VERTEX_ARRAY);
11024 glVertexPointer(3, GL_FLOAT, stride, &vertices[0].position);
11025 glEnableClientState(GL_COLOR_ARRAY);
11026 glColorPointer(4, GL_UNSIGNED_BYTE, stride, &vertices[0].color);
11027
11028 // An option to control the size of the stars would be helpful.
11029 // Which size looks best depends a lot on the resolution and the
11030 // type of display device.
11031 // glPointSize(2.0f);
11032 // glEnable(GL_POINT_SMOOTH);
11033 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
11034 glDisable(GL_TEXTURE_2D);
11035
11036 glDisableClientState(GL_NORMAL_ARRAY);
11037
11038 useSprites = false;
11039 }
11040
render()11041 void Renderer::PointStarVertexBuffer::render()
11042 {
11043 if (nStars != 0)
11044 {
11045 unsigned int stride = sizeof(StarVertex);
11046 if (useSprites)
11047 {
11048 glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
11049 glEnable(GL_TEXTURE_2D);
11050 }
11051 else
11052 {
11053 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
11054 glDisable(GL_TEXTURE_2D);
11055 glPointSize(1.0f);
11056 }
11057 glVertexPointer(3, GL_FLOAT, stride, &vertices[0].position);
11058 glColorPointer(4, GL_UNSIGNED_BYTE, stride, &vertices[0].color);
11059
11060 if (useSprites)
11061 {
11062 VertexProcessor* vproc = context->getVertexProcessor();
11063 vproc->attribArray(6, 1, GL_FLOAT, stride, &vertices[0].size);
11064 }
11065
11066 if (texture != NULL)
11067 texture->bind();
11068 glDrawArrays(GL_POINTS, 0, nStars);
11069 nStars = 0;
11070 }
11071 }
11072
finish()11073 void Renderer::PointStarVertexBuffer::finish()
11074 {
11075 render();
11076 glDisableClientState(GL_COLOR_ARRAY);
11077 glDisableClientState(GL_VERTEX_ARRAY);
11078 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
11079
11080 if (useSprites)
11081 {
11082 VertexProcessor* vproc = context->getVertexProcessor();
11083 vproc->disableAttribArray(6);
11084 vproc->disable();
11085
11086 glDisable(GL_POINT_SPRITE_ARB);
11087 }
11088 else
11089 {
11090 glEnable(GL_TEXTURE_2D);
11091 }
11092 }
11093
addStar(const Vec3f & pos,const Color & color,float size)11094 void Renderer::PointStarVertexBuffer::addStar(const Vec3f& pos,
11095 const Color& color,
11096 float size)
11097 {
11098 if (nStars < capacity)
11099 {
11100 vertices[nStars].position = pos;
11101 vertices[nStars].size = size;
11102 color.get(vertices[nStars].color);
11103 nStars++;
11104 }
11105
11106 if (nStars == capacity)
11107 {
11108 render();
11109 nStars = 0;
11110 }
11111 }
11112
setTexture(Texture * _texture)11113 void Renderer::PointStarVertexBuffer::setTexture(Texture* _texture)
11114 {
11115 texture = _texture;
11116 }
11117
11118
loadTextures(Body * body)11119 void Renderer::loadTextures(Body* body)
11120 {
11121 Surface& surface = body->getSurface();
11122
11123 if (surface.baseTexture.tex[textureResolution] != InvalidResource)
11124 surface.baseTexture.find(textureResolution);
11125 if ((surface.appearanceFlags & Surface::ApplyBumpMap) != 0 &&
11126 context->bumpMappingSupported() &&
11127 surface.bumpTexture.tex[textureResolution] != InvalidResource)
11128 surface.bumpTexture.find(textureResolution);
11129 if ((surface.appearanceFlags & Surface::ApplyNightMap) != 0 &&
11130 (renderFlags & ShowNightMaps) != 0)
11131 surface.nightTexture.find(textureResolution);
11132 if ((surface.appearanceFlags & Surface::SeparateSpecularMap) != 0 &&
11133 surface.specularTexture.tex[textureResolution] != InvalidResource)
11134 surface.specularTexture.find(textureResolution);
11135
11136 if ((renderFlags & ShowCloudMaps) != 0 &&
11137 body->getAtmosphere() != NULL &&
11138 body->getAtmosphere()->cloudTexture.tex[textureResolution] != InvalidResource)
11139 {
11140 body->getAtmosphere()->cloudTexture.find(textureResolution);
11141 }
11142
11143 if (body->getRings() != NULL &&
11144 body->getRings()->texture.tex[textureResolution] != InvalidResource)
11145 {
11146 body->getRings()->texture.find(textureResolution);
11147 }
11148
11149 if (body->getGeometry() != InvalidResource)
11150 {
11151 GetGeometryManager()->find(body->getGeometry());
11152 }
11153 }
11154
11155
invalidateOrbitCache()11156 void Renderer::invalidateOrbitCache()
11157 {
11158 orbitCache.clear();
11159 }
11160
11161
settingsHaveChanged() const11162 bool Renderer::settingsHaveChanged() const
11163 {
11164 return settingsChanged;
11165 }
11166
11167
markSettingsChanged()11168 void Renderer::markSettingsChanged()
11169 {
11170 settingsChanged = true;
11171 notifyWatchers();
11172 }
11173
11174
addWatcher(RendererWatcher * watcher)11175 void Renderer::addWatcher(RendererWatcher* watcher)
11176 {
11177 assert(watcher != NULL);
11178 watchers.insert(watchers.end(), watcher);
11179 }
11180
removeWatcher(RendererWatcher * watcher)11181 void Renderer::removeWatcher(RendererWatcher* watcher)
11182 {
11183 list<RendererWatcher*>::iterator iter =
11184 find(watchers.begin(), watchers.end(), watcher);
11185 if (iter != watchers.end())
11186 watchers.erase(iter);
11187 }
11188
notifyWatchers() const11189 void Renderer::notifyWatchers() const
11190 {
11191 for (list<RendererWatcher*>::const_iterator iter = watchers.begin();
11192 iter != watchers.end(); iter++)
11193 {
11194 (*iter)->notifyRenderSettingsChanged(this);
11195 }
11196 }
11197