Index: stardb.cpp =================================================================== --- stardb.cpp (revision 4187) +++ stardb.cpp (working copy) @@ -36,7 +36,7 @@ static string TychoCatalogPrefix("TYC "); static string SAOCatalogPrefix("SAO "); -static const float STAR_OCTREE_ROOT_SIZE = 15000.0f; +static const float STAR_OCTREE_ROOT_SIZE = 1500000.0f; static const float STAR_OCTREE_MAGNITUDE = 6.0f; static const float STAR_EXTRA_ROOM = 0.01f; // Reserve 1% capacity for extra stars Index: render.h =================================================================== --- render.h (revision 4187) +++ render.h (working copy) @@ -43,6 +43,10 @@ RenderableStar, RenderableBody, RenderableCometTail, + RenderableRings, + RenderableCloudLayer, + RenderableHaloAtmosphere, + RenderableScatteringAtmosphere, RenderableBodyAxes, RenderableFrameAxes, RenderableSunDirection, @@ -292,7 +296,7 @@ void start(); void render(); void finish(); - void addStar(const Point3f&, const Color&, float); + void addStar(const Vec3f&, const Color&, float); void setBillboardOrientation(const Quatf&); private: @@ -314,13 +318,13 @@ void startSprites(const GLContext&); void render(); void finish(); - void addStar(const Point3f& f, const Color&, float); + void addStar(const Vec3f& f, const Color&, float); void setTexture(Texture*); private: struct StarVertex { - Point3f position; + Vec3f position; float size; unsigned char color[4]; float pad; @@ -441,7 +445,41 @@ Quatf orientation, double now, float, float); + + void renderPlanetaryRings(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float nearPlaneDistance, + float farPlaneDistance); + void renderCloudLayer(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float nearPlaneDistance, + float /* farPlaneDistance */); + + void renderHaloAtmosphere(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float nearPlaneDistance); + + void renderScatteringAtmosphere(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float nearPlaneDistance); + void renderAxes(Body& body, Point3f pos, float distance, Index: render.cpp =================================================================== --- render.cpp (revision 4187) +++ render.cpp (working copy) @@ -795,8 +795,10 @@ // Affected cards: ATI (various), etc // Renderer strings are not unique. usePointSprite = false; + clog << "disable point sprites\n"; } #endif + clog<< "point sprites: " << usePointSprite << endl; } // More ugly hacks; according to Matt Craighead at NVIDIA, an NVIDIA @@ -2089,6 +2091,50 @@ nearPlaneDistance, farPlaneDistance); break; + case RenderListEntry::RenderableRings: + assert(rle.body != NULL); + renderPlanetaryRings(*rle.body, + rle.position, + rle.distance, + observer, + cameraOrientation, + *rle.lightSourceList, + nearPlaneDistance, farPlaneDistance); + break; + + case RenderListEntry::RenderableCloudLayer: + assert(rle.body != NULL); + renderCloudLayer(*rle.body, + rle.position, + rle.distance, + observer, + cameraOrientation, + *rle.lightSourceList, + nearPlaneDistance, farPlaneDistance); + break; + + case RenderListEntry::RenderableHaloAtmosphere: + assert(rle.body != NULL); + renderHaloAtmosphere(*rle.body, + rle.position, + rle.distance, + observer, + cameraOrientation, + *rle.lightSourceList, + nearPlaneDistance); + break; + + case RenderListEntry::RenderableScatteringAtmosphere: + assert(rle.body != NULL); + renderScatteringAtmosphere(*rle.body, + rle.position, + rle.distance, + observer, + cameraOrientation, + *rle.lightSourceList, + nearPlaneDistance); + break; + case RenderListEntry::RenderableCometTail: assert(rle.body != NULL); renderCometTail(*rle.body, @@ -2395,7 +2441,6 @@ // Translate the camera before rendering the stars glPushMatrix(); - glTranslatef(-observerPosLY.x, -observerPosLY.y, -observerPosLY.z); // Render stars glBlendFunc(GL_SRC_ALPHA, GL_ONE); @@ -2407,6 +2452,8 @@ renderStars(*universe.getStarCatalog(), faintestMag, observer); } + glTranslatef(-observerPosLY.x, -observerPosLY.y, -observerPosLY.z); + // Render asterisms if ((renderFlags & ShowDiagrams) != 0 && universe.getAsterisms() != NULL) { @@ -2851,7 +2898,7 @@ glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); + glDepthMask(GL_TRUE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if ((renderFlags & ShowSmoothLines) != 0) { @@ -5785,6 +5832,66 @@ } +// Set the OpenGL light state +// +// TODO: Only very old and broken hardware/drivers have trouble with +// normal rescaling. We should be able to simplify the function by +// removing support for such configurations. +static void setupGLLightState(const LightingState& ls, + bool rescaleNormalSupported, + float radius) +{ + for (unsigned int i = 0; i < ls.nLights; i++) + { + const DirectionalLight& light = ls.lights[i]; + + glLightDirection(GL_LIGHT0 + i, ls.lights[i].direction_obj); + + // Ugly hacks for older graphics hardware. glScale is used to scale + // a unit sphere up to planet size. Since normals are transformed by the + // inverse transpose of the model matrix, this means they end up + // getting scaled by a factor of 1.0 / planet radius (in km). This + // has terrible effects on lighting: the planet appears almost + // completely dark. To get around this, the GL_rescale_normal + // extension was introduced and eventually incorporated into into the + // OpenGL 1.2 standard. Of course, not everyone implemented this + // incredibly simple and essential little extension. No 3dfx Voodoo 1/2/3 + // drivers seem to support this extension. + // + // The following code attempts to get around the problem by + // scaling the light brightness by the planet radius. According to the + // OpenGL spec, this should work fine, as clamping of colors to [0, 1] + // occurs *after* lighting. It works fine on my GeForce3 when I + // disable EXT_rescale_normal, but I'm not certain whether other + // drivers are as well behaved. + // + // Addendum: Unsurprisingly, using color values outside [0, 1] produces + // problems on Savage4 cards. + Vec3f lightColor = Vec3f(light.color.red(), + light.color.green(), + light.color.blue()) * light.irradiance; + if (rescaleNormalSupported) + { + glLightColor(GL_LIGHT0 + i, GL_DIFFUSE, lightColor); + glLightColor(GL_LIGHT0 + i, GL_SPECULAR, lightColor); + } + else + { + glLightColor(GL_LIGHT0 + i, GL_DIFFUSE, lightColor * radius); + } + glEnable(GL_LIGHT0 + i); + } +} + + +// Disable all light sources other than the first +static void restoreGLLightState(const LightingState& ls) +{ + for (unsigned i = 0; i < ls.nLights; i++) + glDisable(GL_LIGHT0 + i); +} + + void Renderer::renderObject(Point3f pos, float distance, double now, @@ -5876,6 +5983,8 @@ // See if the surface should be lit bool lit = (obj.surface->appearanceFlags & Surface::Emissive) == 0; + setupGLLightState(ls, useRescaleNormal, radius); +#if 0 // Set the OpenGL light state unsigned int i; for (i = 0; i < ls.nLights; i++) @@ -5920,7 +6029,7 @@ } glEnable(GL_LIGHT0 + i); } - +#endif // Compute the inverse model/view matrix Mat4f invMV = (cameraOrientation.toMatrix4() * Mat4f::translation(Point3f(-pos.x, -pos.y, -pos.z)) * @@ -5946,11 +6055,9 @@ // due to limited floating point precision frustumFarPlane = (float) sqrt(square(d) - square(eradius)) * 1.1f; } - else - { - // We're inside the bounding sphere; leave the far plane alone - } - + // ... else we're inside the bounding sphere; leave the far plane alone + +#if 0 if (obj.atmosphere != NULL) { float atmosphereHeight = max(obj.atmosphere->cloudHeight, @@ -5964,6 +6071,7 @@ square(eradius)); } } +#endif } // Transform the frustum into object coordinates using the @@ -6081,197 +6189,6 @@ } } - if (obj.rings != NULL && distance <= obj.rings->innerRadius) - { - if (context->getRenderPath() == GLContext::GLPath_GLSL) - { - renderRings_GLSL(*obj.rings, ri, ls, - radius, 1.0f - obj.semiAxes.y, - textureResolution, - (renderFlags & ShowRingShadows) != 0 && lit, - detailOptions.ringSystemSections); - } - else - { - renderRings(*obj.rings, ri, radius, 1.0f - obj.semiAxes.y, - textureResolution, - context->getMaxTextures() > 1 && - (renderFlags & ShowRingShadows) != 0 && lit, - *context, - detailOptions.ringSystemSections); - } - } - - if (obj.atmosphere != NULL) - { - Atmosphere* atmosphere = const_cast(obj.atmosphere); - - // Compute the apparent thickness in pixels of the atmosphere. - // If it's only one pixel thick, it can look quite unsightly - // due to aliasing. To avoid popping, we gradually fade in the - // atmosphere as it grows from two to three pixels thick. - float fade; - float thicknessInPixels = 0.0f; - if (distance - radius > 0.0f) - { - thicknessInPixels = atmosphere->height / - ((distance - radius) * pixelSize); - fade = clamp(thicknessInPixels - 2); - } - else - { - fade = 1.0f; - } - - if (fade > 0 && (renderFlags & ShowAtmospheres) != 0) - { - // Only use new atmosphere code in OpenGL 2.0 path when new style parameters are defined. - // TODO: convert old style atmopshere parameters - if (context->getRenderPath() == GLContext::GLPath_GLSL && - atmosphere->mieScaleHeight > 0.0f) - { - float atmScale = 1.0f + atmosphere->height / radius; - - renderAtmosphere_GLSL(ri, ls, - atmosphere, - radius * atmScale, - planetMat, - viewFrustum, - *context); - } - else - { - glPushMatrix(); - glLoadIdentity(); - glDisable(GL_LIGHTING); - glDisable(GL_TEXTURE_2D); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - glRotate(cameraOrientation); - renderEllipsoidAtmosphere(*atmosphere, - pos, - obj.orientation, - semiAxes, - ri.sunDir_eye, - ri.ambientColor, - thicknessInPixels, - lit); - glEnable(GL_TEXTURE_2D); - glPopMatrix(); - } - } - - // If there's a cloud layer, we'll render it now. - if (cloudTex != NULL) - { - glPushMatrix(); - - float cloudScale = 1.0f + atmosphere->cloudHeight / radius; - glScalef(cloudScale, cloudScale, cloudScale); - - // If we're beneath the cloud level, render the interior of - // the cloud sphere. - if (distance - radius < atmosphere->cloudHeight) - glFrontFace(GL_CW); - - if (atmosphere->cloudSpeed != 0.0f) - { - // Make the clouds appear to rotate above the surface of - // the planet. This is easier to do with the texture - // matrix than the model matrix because changing the - // texture matrix doesn't require us to compute a second - // set of model space rendering parameters. - glMatrixMode(GL_TEXTURE); - glTranslatef(cloudTexOffset, 0.0f, 0.0f); - glMatrixMode(GL_MODELVIEW); - } - - glEnable(GL_LIGHTING); - glDepthMask(GL_FALSE); - cloudTex->bind(); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(1, 1, 1, 1); - - // Cloud layers can be trouble for the depth buffer, since they tend - // to be very close to the surface of a planet relative to the radius - // of the planet. We'll help out by offsetting the cloud layer toward - // the viewer. - if (distance > radius * 1.1f) - { - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -1.0f); - } - - if (lit) - { - if (context->getRenderPath() == GLContext::GLPath_GLSL) - { - renderClouds_GLSL(ri, ls, - atmosphere, - cloudTex, - cloudNormalMap, - cloudTexOffset, - obj.rings, - radius, - textureResolution, - renderFlags, - planetMat, - viewFrustum, - *context); - } - else - { - VertexProcessor* vproc = context->getVertexProcessor(); - if (vproc != NULL) - { - vproc->enable(); - vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color); - vproc->parameter(vp::TextureTranslation, - cloudTexOffset, 0.0f, 0.0f, 0.0f); - if (ls.nLights > 1) - vproc->use(vp::diffuseTexOffset_2light); - else - vproc->use(vp::diffuseTexOffset); - setLightParameters_VP(*vproc, ls, ri.color, Color::Black); - } - - g_lodSphere->render(*context, - LODSphereMesh::Normals | LODSphereMesh::TexCoords0, - viewFrustum, - ri.pixWidth, - cloudTex); - - if (vproc != NULL) - vproc->disable(); - } - } - else - { - glDisable(GL_LIGHTING); - g_lodSphere->render(*context, - LODSphereMesh::Normals | LODSphereMesh::TexCoords0, - viewFrustum, - ri.pixWidth, - cloudTex); - glEnable(GL_LIGHTING); - } - - glDisable(GL_POLYGON_OFFSET_FILL); - - // Reset the texture matrix - glMatrixMode(GL_TEXTURE); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - - glDepthMask(GL_TRUE); - glFrontFace(GL_CCW); - - glPopMatrix(); - } - } - // No separate shadow rendering pass required for GLSL path if (ls.shadows[0] != NULL && ls.shadows[0]->size() != 0 && @@ -6324,32 +6241,13 @@ } } - if (obj.rings != NULL && distance > obj.rings->innerRadius) - { - glDepthMask(GL_FALSE); - if (context->getRenderPath() == GLContext::GLPath_GLSL) - { - renderRings_GLSL(*obj.rings, ri, ls, - radius, 1.0f - obj.semiAxes.y, - textureResolution, - (renderFlags & ShowRingShadows) != 0 && lit, - detailOptions.ringSystemSections); - } - else - { - renderRings(*obj.rings, ri, radius, 1.0f - obj.semiAxes.y, - textureResolution, - (context->hasMultitexture() && - (renderFlags & ShowRingShadows) != 0 && lit), - *context, - detailOptions.ringSystemSections); - } - } - +#if 0 // Disable all light sources other than the first for (i = 0; i < ls.nLights; i++) glDisable(GL_LIGHT0 + i); - +#endif + restoreGLLightState(ls); + glPopMatrix(); glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); @@ -6628,6 +6526,625 @@ } +void Renderer::renderPlanetaryRings(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float nearPlaneDistance, + float /* farPlaneDistance */) +{ + double now = observer.getTime(); + + float planetRadius = body.getRadius(); + Vec3f semiAxes = body.getSemiAxes(); + float planetOblateness = 1.0f - semiAxes.y / planetRadius; + + float altitude = distance - planetRadius; + float discSizeInPixels = planetRadius / (max(nearPlaneDistance, altitude) * pixelSize); + + // Compute the orientation of the planet + Quatd q = body.getEclipticToBodyFixed(now); + Quatf orientation = Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z); + + LightingState ls; + setupObjectLighting(lightSources, + body.getAstrocentricPosition(now), + orientation, + semiAxes, + pos, + ls); + assert(ls.nLights < MaxLights); + + ls.ambientColor = Vec3f(ambientColor.red(), + ambientColor.green(), + ambientColor.blue()); + + RenderInfo ri; + if (ls.nLights > 0) + { + ri.sunDir_eye = ls.lights[0].direction_eye; + ri.sunDir_obj = ls.lights[0].direction_obj; + ri.sunColor = ls.lights[0].color;// * ls.lights[0].intensity; + } + else + { + ri.sunDir_eye = Vec3f(0.0f, 1.0f, 0.0f); + ri.sunDir_obj = Vec3f(0.0f, 1.0f, 0.0f); + ri.sunColor = Color(0.0f, 0.0f, 0.0f); + } + + // Get the eye direction and position in model space + Mat3f planetMat = (~orientation).toMatrix3(); + ri.eyeDir_obj = (Point3f(0, 0, 0) - pos) * planetMat; + ri.eyeDir_obj.normalize(); + ri.eyePos_obj = Point3f(-pos.x / planetRadius, + -pos.y / planetRadius, + -pos.z / planetRadius) * planetMat; + + ri.orientation = cameraOrientation; + ri.pixWidth = discSizeInPixels; + + ri.ambientColor = ambientColor; + ri.useTexEnvCombine = context->getRenderPath() != GLContext::GLPath_Basic; + + bool lit = (body.getSurface().appearanceFlags & Surface::Emissive) == 0; + bool showRingShadows = lit && + context->hasMultitexture() && + (renderFlags & ShowRingShadows) != 0; + + // Set OpenGL light, transform, and depth buffer state + setupGLLightState(ls, useRescaleNormal, planetRadius); + + // Apply the model transform for the object + glPushMatrix(); + glTranslate(pos); + glRotate(~orientation); + glScalef(planetRadius, planetRadius, planetRadius); + + // Even though rings are transparent, the geometry will never overlap itself, + // so we can turn on depth writes. The motivation for doing this is to ensure + // that rings are rendered correctly around planets with atmospheres. When + // depth writes are disabled, the atmosphere will be incorrectly drawn + // over the rings when the viewer is outside the ring system bounding sphere. + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + + if (context->getRenderPath() == GLContext::GLPath_GLSL) + { + renderRings_GLSL(*body.getRings(), + ri, + ls, + planetRadius, + planetOblateness, + textureResolution, + showRingShadows, + detailOptions.ringSystemSections); + } + else + { + renderRings(*body.getRings(), + ri, + planetRadius, + planetOblateness, + textureResolution, + showRingShadows, + *context, + detailOptions.ringSystemSections); + } + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glPopMatrix(); + + restoreGLLightState(ls); +} + + +void Renderer::renderCloudLayer(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float nearPlaneDistance, + float /* farPlaneDistance */) +{ + double now = observer.getTime(); + + float planetRadius = body.getRadius(); + Vec3f semiAxes = body.getSemiAxes(); + + float altitude = distance - planetRadius; + float discSizeInPixels = planetRadius / (max(nearPlaneDistance, altitude) * pixelSize); + + Atmosphere* atmosphere = body.getAtmosphere(); + assert(atmosphere != NULL); + + // Get the cloud texture map. Don't render anything if there's not a valid texture map. + Texture* cloudTex = NULL; + if (atmosphere->cloudTexture.tex[textureResolution] != InvalidResource) + cloudTex = atmosphere->cloudTexture.find(textureResolution); + if (cloudTex == NULL) + return; + + Texture* cloudNormalMap = NULL; + if (atmosphere->cloudNormalMap.tex[textureResolution] != InvalidResource) + cloudNormalMap = atmosphere->cloudNormalMap.find(textureResolution); + float cloudTexOffset = 0.0f; + if (atmosphere->cloudSpeed != 0.0f) + cloudTexOffset = (float) (-pfmod(now * atmosphere->cloudSpeed / (2 * PI), 1.0)); + + // Compute the orientation of the planet + Quatd q = body.getEclipticToBodyFixed(now); + Quatf orientation = body.getOrientation() * Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z); + + LightingState ls; + setupObjectLighting(lightSources, + body.getAstrocentricPosition(now), + orientation, + semiAxes, + pos, + ls); + assert(ls.nLights < MaxLights); + + ls.ambientColor = Vec3f(ambientColor.red(), + ambientColor.green(), + ambientColor.blue()); + + RenderInfo ri; + if (ls.nLights > 0) + { + ri.sunDir_eye = ls.lights[0].direction_eye; + ri.sunDir_obj = ls.lights[0].direction_obj; + ri.sunColor = ls.lights[0].color;// * ls.lights[0].intensity; + } + else + { + ri.sunDir_eye = Vec3f(0.0f, 1.0f, 0.0f); + ri.sunDir_obj = Vec3f(0.0f, 1.0f, 0.0f); + ri.sunColor = Color(0.0f, 0.0f, 0.0f); + } + + // Get the eye direction and position in model space + Mat4f planetMat = (~orientation).toMatrix4(); + ri.eyeDir_obj = (Point3f(0, 0, 0) - pos) * planetMat; + ri.eyeDir_obj.normalize(); + ri.eyePos_obj = Point3f(-pos.x / planetRadius, + -pos.y / planetRadius, + -pos.z / planetRadius) * planetMat; + + ri.orientation = cameraOrientation; + ri.pixWidth = discSizeInPixels; + + ri.ambientColor = ambientColor; + ri.useTexEnvCombine = context->getRenderPath() != GLContext::GLPath_Basic; + + // cloudScale is the radius of the cloud shell relative planetRadius + float cloudScale = 1.0f + atmosphere->cloudHeight / planetRadius; + + bool insideCloudShell = Ellipsoid(semiAxes * cloudScale).contains(pos); + bool insidePlanet = Ellipsoid(semiAxes).contains(pos); + + // The sphere rendering code uses the view frustum to determine which + // patches are visible. In order to avoid rendering patches that can't + // be seen, make the far plane of the frustum as close to the viewer + // as possible. + float frustumFarPlane = 0.0f; + { + float d = pos.distanceFromOrigin(); + float minSemiAxis = min(semiAxes.x, min(semiAxes.y, semiAxes.z)); + float minCloudShellAxis = minSemiAxis * cloudScale; + + if (!insideCloudShell) + { + // Outside the cloud shell; compute the horizon distance and use that + // as the frustum far plane + + // Include a fudge factor to eliminate overaggressive clipping + // due to limited floating point precision + frustumFarPlane = (float) sqrt(square(d) - square(minCloudShellAxis)) * 1.1f; + } + else if (!insidePlanet) + { + // We're inside the cloud shell; compute the horizon distance for the + // planet, adjust for cloud distance from surface along the horizon + // tangent line. + if (d > minSemiAxis) + { + frustumFarPlane = (float) sqrt(square(d) - square(minSemiAxis)) * 1.1f; + frustumFarPlane += (float) sqrt(square(minCloudShellAxis) - square(minSemiAxis)); + } + } + else + { + // Inside the planet--the entire cloud shell is potentially visible + frustumFarPlane = planetRadius * cloudScale; + } + } + + // Transform the frustum into object coordinates using the + // inverse model/view matrix. + // TODO: should we scale by 1.0 / (planetRadius + cloudHeight)? + Mat4f invMV = (cameraOrientation.toMatrix4() * + Mat4f::translation(Point3f(-pos.x, -pos.y, -pos.z)) * + planetMat * + Mat4f::scaling(1.0f / planetRadius)); + + Frustum viewFrustum(degToRad(fov), + (float) windowWidth / (float) windowHeight, + nearPlaneDistance, frustumFarPlane); + viewFrustum.transform(invMV); + + bool lit = (body.getSurface().appearanceFlags & Surface::Emissive) == 0; + + // Apply the model transform for the object + glPushMatrix(); + glTranslate(pos); + glRotate(~orientation); + + glScale(semiAxes * cloudScale); + + // Set OpenGL light, transform, and depth buffer state + setupGLLightState(ls, useRescaleNormal, planetRadius); + + // Set depth buffer state to read-only, since clouds are not generally opaque + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + // If we're beneath the cloud level, render the interior of + // the cloud sphere. + if (insideCloudShell) + glFrontFace(GL_CW); + + if (atmosphere->cloudSpeed != 0.0f) + { + // Make the clouds appear to rotate above the surface of + // the planet. This is easier to do with the texture + // matrix than the model matrix because actually rotating + // the geometry is likely to cause z fighting artifacts. + glMatrixMode(GL_TEXTURE); + glTranslatef(cloudTexOffset, 0.0f, 0.0f); + glMatrixMode(GL_MODELVIEW); + } + + glEnable(GL_LIGHTING); + cloudTex->bind(); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(1, 1, 1, 1); + + // Cloud layers can be trouble for the depth buffer, since they tend + // to be very close to the surface of a planet relative to the radius + // of the planet. We'll help out by offsetting the cloud layer toward + // the viewer. + if (distance > planetRadius * 1.1f) + { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, -1.0f); + } + + if (lit) + { + if (context->getRenderPath() == GLContext::GLPath_GLSL) + { + renderClouds_GLSL(ri, ls, + atmosphere, + cloudTex, + cloudNormalMap, + cloudTexOffset, + body.getRings(), + planetRadius, + textureResolution, + renderFlags, + planetMat, + viewFrustum, + *context); + } + else + { + VertexProcessor* vproc = context->getVertexProcessor(); + if (vproc != NULL) + { + vproc->enable(); + vproc->parameter(vp::AmbientColor, ri.ambientColor * ri.color); + vproc->parameter(vp::TextureTranslation, + cloudTexOffset, 0.0f, 0.0f, 0.0f); + if (ls.nLights > 1) + vproc->use(vp::diffuseTexOffset_2light); + else + vproc->use(vp::diffuseTexOffset); + setLightParameters_VP(*vproc, ls, ri.color, Color::Black); + } + + g_lodSphere->render(*context, + LODSphereMesh::Normals | LODSphereMesh::TexCoords0, + viewFrustum, + ri.pixWidth, + cloudTex); + + if (vproc != NULL) + vproc->disable(); + } + } + else + { + glDisable(GL_LIGHTING); + g_lodSphere->render(*context, + LODSphereMesh::Normals | LODSphereMesh::TexCoords0, + viewFrustum, + ri.pixWidth, + cloudTex); + } + + glDisable(GL_POLYGON_OFFSET_FILL); + + // Reset the texture matrix + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + + glFrontFace(GL_CCW); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glPopMatrix(); + + restoreGLLightState(ls); + glDisable(GL_LIGHTING); +} + + +void Renderer::renderHaloAtmosphere(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float /* nearPlaneDistance */) +{ + double now = observer.getTime(); + + float planetRadius = body.getRadius(); + Vec3f semiAxes = body.getSemiAxes(); + + Atmosphere* atmosphere = body.getAtmosphere(); + assert(atmosphere != NULL); + + // Compute the orientation of the planet + Quatd q = body.getEclipticToBodyFixed(now); + Quatf orientation = body.getOrientation() * + Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z); + + LightingState ls; + setupObjectLighting(lightSources, + body.getAstrocentricPosition(now), + orientation, + semiAxes, + pos, + ls); + ls.ambientColor = Vec3f(ambientColor.red(), + ambientColor.green(), + ambientColor.blue()); + + Vec3f sunDir_eye = ls.lights[0].direction_eye; + + bool lit = (body.getSurface().appearanceFlags & Surface::Emissive) == 0; + + // Compute the apparent thickness in pixels of the atmosphere. + // If it's only one pixel thick, it can look quite unsightly + // due to aliasing. To avoid popping, we gradually fade in the + // atmosphere as it grows from two to three pixels thick. + float fade; + float thicknessInPixels = 0.0f; + if (distance - planetRadius > 0.0f) + { + thicknessInPixels = atmosphere->height / ((distance - planetRadius) * pixelSize); + fade = clamp(thicknessInPixels - 2); + } + else + { + fade = 1.0f; + } + + if (fade > 0 && (renderFlags & ShowAtmospheres) != 0) + { + // Atmosphere halo is drawn and lit in camera space + glPushMatrix(); + glLoadIdentity(); + glDisable(GL_LIGHTING); + + glDisable(GL_TEXTURE_2D); + + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // Set depth buffer state to read-only, since atmosphere halos are not opaque + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + glRotate(cameraOrientation); + renderEllipsoidAtmosphere(*atmosphere, + pos, + orientation, + semiAxes, + sunDir_eye, + ambientColor, + thicknessInPixels, + lit); + glEnable(GL_TEXTURE_2D); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glPopMatrix(); + } +} + + +void Renderer::renderScatteringAtmosphere(Body& body, + Point3f pos, + float distance, + const Observer& observer, + const Quatf& cameraOrientation, + const vector& lightSources, + float nearPlaneDistance) +{ + double now = observer.getTime(); + + float planetRadius = body.getRadius(); + Vec3f semiAxes = body.getSemiAxes(); + + float altitude = distance - planetRadius; + float discSizeInPixels = planetRadius / (max(nearPlaneDistance, altitude) * pixelSize); + + Atmosphere* atmosphere = body.getAtmosphere(); + assert(atmosphere != NULL); + + // Compute the orientation of the planet + Quatd q = body.getEclipticToBodyFixed(now); + Quatf orientation = body.getOrientation() * Quatf((float) q.w, (float) q.x, (float) q.y, (float) q.z); + + LightingState ls; + setupObjectLighting(lightSources, + body.getAstrocentricPosition(now), + orientation, + semiAxes, + pos, + ls); + assert(ls.nLights < MaxLights); + + ls.ambientColor = Vec3f(ambientColor.red(), + ambientColor.green(), + ambientColor.blue()); + + RenderInfo ri; + if (ls.nLights > 0) + { + ri.sunDir_eye = ls.lights[0].direction_eye; + ri.sunDir_obj = ls.lights[0].direction_obj; + ri.sunColor = ls.lights[0].color;// * ls.lights[0].intensity; + } + else + { + ri.sunDir_eye = Vec3f(0.0f, 1.0f, 0.0f); + ri.sunDir_obj = Vec3f(0.0f, 1.0f, 0.0f); + ri.sunColor = Color(0.0f, 0.0f, 0.0f); + } + + // Get the eye direction and position in model space + Mat4f planetMat = (~orientation).toMatrix4(); + ri.eyeDir_obj = (Point3f(0, 0, 0) - pos) * planetMat; + ri.eyeDir_obj.normalize(); + ri.eyePos_obj = Point3f(-pos.x / planetRadius, + -pos.y / planetRadius, + -pos.z / planetRadius) * planetMat; + + ri.orientation = cameraOrientation; + ri.pixWidth = discSizeInPixels; + + ri.ambientColor = ambientColor; + ri.useTexEnvCombine = context->getRenderPath() != GLContext::GLPath_Basic; + + float atmosphereShellHeight = atmosphere->mieScaleHeight * (float) -log(AtmosphereExtinctionThreshold); + float atmosphereScale = 1.0f + atmosphereShellHeight / planetRadius; + + bool insideAtmosphereShell = Ellipsoid(semiAxes * atmosphereScale).contains(pos); + bool insidePlanet = Ellipsoid(semiAxes).contains(pos); + + // The sphere rendering code uses the view frustum to determine which + // patches are visible. In order to avoid rendering patches that can't + // be seen, make the far plane of the frustum as close to the viewer + // as possible. + float frustumFarPlane = 0.0f; + { + float d = pos.distanceFromOrigin(); + float minSemiAxis = min(semiAxes.x, min(semiAxes.y, semiAxes.z)); + float minAtmosphereShellAxis = minSemiAxis * atmosphereScale; + + if (!insideAtmosphereShell) + { + // Outside the atmosphere shell; compute the horizon distance and use that + // as the frustum far plane + + // Include a fudge factor to eliminate overaggressive clipping + // due to limited floating point precision + frustumFarPlane = (float) sqrt(square(d) - square(minAtmosphereShellAxis)) * 1.1f; + } + else if (!insidePlanet) + { + // We're inside the atmosphere shell; compute the horizon distance for the + // planet, adjust for atmosphere distance from surface along the horizon + // tangent line. + if (d > minSemiAxis) + { + frustumFarPlane = (float) sqrt(square(d) - square(minSemiAxis)) * 1.1f; + frustumFarPlane += (float) sqrt(square(minAtmosphereShellAxis) - square(minSemiAxis)); + } + } + else + { + // Inside the planet--the entire cloud shell is potentially visible + frustumFarPlane = planetRadius * atmosphereScale; + } + } + + // Transform the frustum into object coordinates using the + // inverse model/view matrix. + // TODO: should we scale by 1.0 / (planetRadius + cloudHeight)? + Mat4f invMV = (cameraOrientation.toMatrix4() * + Mat4f::translation(Point3f(-pos.x, -pos.y, -pos.z)) * + planetMat * + Mat4f::scaling(1.0f / planetRadius)); + + Frustum viewFrustum(degToRad(fov), + (float) windowWidth / (float) windowHeight, + nearPlaneDistance, frustumFarPlane); + viewFrustum.transform(invMV); + + // Set OpenGL light, transform, and depth buffer state + setupGLLightState(ls, useRescaleNormal, planetRadius); + + // Apply the model transform for the object + glPushMatrix(); + glTranslate(pos); + glRotate(~orientation); + glScale(semiAxes); + + // Set depth buffer state to read-only, since atmosphere is potentially translucent + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + float atmScale = 1.0f + atmosphere->height / planetRadius; + renderAtmosphere_GLSL(ri, ls, + atmosphere, + planetRadius * atmScale, + planetMat, + viewFrustum, + *context); + + glDisable(GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + + glPopMatrix(); + + restoreGLLightState(ls); +} + + void Renderer::renderStar(const Star& star, Point3f pos, float distance, @@ -7318,6 +7835,85 @@ renderList.push_back(rle); } } + + if (body->getRings() != NULL) + { + discSize = (body->getRings()->outerRadius / (float) distanceFromObserver) / pixelSize; + + if (discSize > 1) + { + RenderListEntry rle; + rle.renderableType = RenderListEntry::RenderableRings; + rle.body = body; + rle.star = NULL; + rle.isOpaque = false; + rle.position = Point3f(pos.x, pos.y, pos.z); + rle.sun = Vec3f((float) -bodyPos.x, (float) -bodyPos.y, (float) -bodyPos.z); + rle.distance = (float) distanceFromObserver; + rle.centerZ = pos * viewMatZ; + rle.radius = body->getRings()->outerRadius; + rle.discSizeInPixels = discSize; + rle.appMag = appMag; + rle.lightSourceList = lightSourceList; + renderList.push_back(rle); + } + } + + if (body->getAtmosphere() != NULL && + (renderFlags & ShowCloudMaps) != 0 && + discSize > 1) + { + RenderListEntry rle; + rle.renderableType = RenderListEntry::RenderableCloudLayer; + rle.body = body; + rle.star = NULL; + rle.isOpaque = false; + rle.position = Point3f(pos.x, pos.y, pos.z); + rle.sun = Vec3f((float) -bodyPos.x, (float) -bodyPos.y, (float) -bodyPos.z); + rle.distance = (float) distanceFromObserver; + rle.centerZ = pos * viewMatZ; + rle.radius = body->getRadius() + body->getAtmosphere()->cloudHeight; + rle.discSizeInPixels = discSize; + rle.appMag = appMag; + rle.lightSourceList = lightSourceList; + renderList.push_back(rle); + } + + if (body->getAtmosphere() != NULL && + (renderFlags & ShowAtmospheres) != 0 && + discSize > 1) + { + // Select between the two types of supported atmospheres. If GLSL is enabled + // and the body has scattering parameters defined, use the new and more + // realisitic scattering atmospheres. Otherswise, fall back to halo + // atmospheres. + RenderListEntry rle; + if (context->getRenderPath() == GLContext::GLPath_GLSL && + body->getAtmosphere()->mieScaleHeight > 0.0f) + { + float atmShellHeight = body->getAtmosphere()->mieScaleHeight * + (float) -log(AtmosphereExtinctionThreshold); + rle.renderableType = RenderListEntry::RenderableScatteringAtmosphere; + rle.radius = body->getRadius() + atmShellHeight; + } + else + { + rle.renderableType = RenderListEntry::RenderableHaloAtmosphere; + rle.radius = body->getRadius() + body->getAtmosphere()->height; + } + + rle.body = body; + rle.star = NULL; + rle.isOpaque = false; + rle.position = Point3f(pos.x, pos.y, pos.z); + rle.sun = Vec3f((float) -bodyPos.x, (float) -bodyPos.y, (float) -bodyPos.z); + rle.distance = (float) distanceFromObserver; + rle.centerZ = pos * viewMatZ; + rle.discSizeInPixels = discSize; + rle.appMag = appMag; + rle.lightSourceList = lightSourceList; + renderList.push_back(rle); + } if (body->getVisibleReferenceMarks() != 0) { @@ -7731,7 +8327,7 @@ void process(const Star& star, float distance, float appMag); public: - Point3f obsPos; + Point3d obsPos; vector* glareParticles; vector* renderList; @@ -7767,7 +8363,9 @@ nProcessed++; Point3f starPos = star.getPosition(); - Vec3f relPos = starPos - obsPos; + Vec3f relPos((float) ((double) starPos.x - obsPos.x), + (float) ((double) starPos.y - obsPos.y), + (float) ((double) starPos.z - obsPos.z)); float orbitalRadius = star.getOrbitalRadius(); bool hasOrbit = orbitalRadius > 0.0f; @@ -7869,13 +8467,13 @@ if (starPrimitive == GL_POINTS) { - pointStarVertexBuffer->addStar(starPos, + pointStarVertexBuffer->addStar(relPos, Color(starColor, alpha), pointSize); } else { - starVertexBuffer->addStar(starPos, + starVertexBuffer->addStar(relPos, Color(starColor, alpha), pointSize * renderDistance); } @@ -7888,7 +8486,7 @@ if (appMag < saturationMag) { Renderer::Particle p; - p.center = starPos; + p.center = Point3f(relPos.x, relPos.y, relPos.z); p.size = size; p.color = Color(starColor, alpha); @@ -7947,7 +8545,7 @@ void process(const Star& star, float distance, float appMag); public: - Point3f obsPos; + Point3d obsPos; vector* renderList; Renderer::PointStarVertexBuffer* starVertexBuffer; @@ -7981,7 +8579,9 @@ nProcessed++; Point3f starPos = star.getPosition(); - Vec3f relPos = starPos - obsPos; + Vec3f relPos((float) ((double) starPos.x - obsPos.x), + (float) ((double) starPos.y - obsPos.y), + (float) ((double) starPos.z - obsPos.z)); float orbitalRadius = star.getOrbitalRadius(); bool hasOrbit = orbitalRadius > 0.0f; @@ -8081,11 +8681,11 @@ discSize *= discScale; float glareAlpha = min(0.5f, discScale / 4.0f); - glareVertexBuffer->addStar(starPos, Color(starColor, glareAlpha), discSize * 3.0f); + glareVertexBuffer->addStar(relPos, Color(starColor, glareAlpha), discSize * 3.0f); alpha = 1.0f; } - starVertexBuffer->addStar(starPos, Color(starColor, alpha), discSize); + starVertexBuffer->addStar(relPos, Color(starColor, alpha), discSize); } else { @@ -8097,9 +8697,9 @@ { float discScale = min(100.0f, satPoint - appMag + 2.0f); float glareAlpha = min(GlareOpacity, (discScale - 2.0f) / 4.0f); - glareVertexBuffer->addStar(starPos, Color(starColor, glareAlpha), 2.0f * discScale * size); + glareVertexBuffer->addStar(relPos, Color(starColor, glareAlpha), 2.0f * discScale * size); } - starVertexBuffer->addStar(starPos, Color(starColor, alpha), size); + starVertexBuffer->addStar(relPos, Color(starColor, alpha), size); } ++nRendered; @@ -8130,9 +8730,9 @@ } -static Point3f microLYToLY(const Point3f& p) +template static Point3 microLYToLY(const Point3& p) { - return Point3f(p.x * 1e-6f, p.y * 1e-6f, p.z * 1e-6f); + return Point3(p.x * (T) 1e-6, p.y * (T) 1e-6, p.z * (T) 1e-6); } @@ -8152,7 +8752,7 @@ const Observer& observer) { StarRenderer starRenderer; - Point3f obsPos = microLYToLY((Point3f) observer.getPosition()); + Point3d obsPos = microLYToLY((Point3d) observer.getPosition()); starRenderer.context = context; starRenderer.renderer = this; @@ -8225,7 +8825,7 @@ starRenderer.starVertexBuffer->start(); } starDB.findVisibleStars(starRenderer, - obsPos, + Point3f((float) obsPos.x, (float) obsPos.y, (float) obsPos.z), observer.getOrientationf(), degToRad(fov), (float) windowWidth / (float) windowHeight, @@ -8245,7 +8845,8 @@ float faintestMagNight, const Observer& observer) { - Point3f obsPos = microLYToLY((Point3f) observer.getPosition()); + clog << "renderPointStars\n"; + Point3d obsPos = microLYToLY((Point3d) observer.getPosition()); PointStarRenderer starRenderer; starRenderer.context = context; @@ -8299,7 +8900,7 @@ starRenderer.starVertexBuffer->startSprites(*context); starDB.findVisibleStars(starRenderer, - obsPos, + Point3f((float) obsPos.x, (float) obsPos.y, (float) obsPos.z), observer.getOrientationf(), degToRad(fov), (float) windowWidth / (float) windowHeight, @@ -9019,7 +9620,7 @@ glDisableClientState(GL_TEXTURE_COORD_ARRAY); } -void Renderer::StarVertexBuffer::addStar(const Point3f& pos, +void Renderer::StarVertexBuffer::addStar(const Vec3f& pos, const Color& color, float size) { @@ -9183,7 +9784,7 @@ } } -void Renderer::PointStarVertexBuffer::addStar(const Point3f& pos, +void Renderer::PointStarVertexBuffer::addStar(const Vec3f& pos, const Color& color, float size) {