Celestia
1// visibleregion.cpp
2//
3// Visible region reference mark for ellipsoidal bodies.
4//
5// Copyright (C) 2008-present, the Celestia Development Team
6// Initial version by Chris Laurel, claurel@gmail.com
7//
8// This program is free software; you can redistribute it and/or
9// modify it under the terms of the GNU General Public License
10// as published by the Free Software Foundation; either version 2
11// of the License, or (at your option) any later version.
12
13#include <cmath>14#include <Eigen/Geometry>15#include <celcompat/numbers.h>16#include <celmath/intersect.h>17#include <celrender/linerenderer.h>18#include "body.h"19#include "render.h"20#include "selection.h"21#include "visibleregion.h"22
23using namespace std;24using namespace Eigen;25using celestia::render::LineRenderer;26namespace math = celestia::math;27
28
29/*! Construct a new reference mark that shows the outline of the
30* region on the surface of a body in which the target object is
31* visible. The following are assumed:
32* - target is a point
33* - the body is an ellipsoid
34*
35* This reference mark is useful in a few situations. When the
36* body is a planet or moon and target is the sun, the outline of
37* the visible region is the terminator. If target is a satellite,
38* the outline is its circle of visibility.
39*/
40VisibleRegion::VisibleRegion(const Body& body, const Selection& target) :41m_body(body),42m_target(target),43m_color(1.0f, 1.0f, 0.0f),44m_opacity(1.0f)45{
46setTag("visible region");47}
48
49
50Color
51VisibleRegion::color() const52{
53return m_color;54}
55
56
57void
58VisibleRegion::setColor(Color color)59{
60m_color = color;61}
62
63
64float
65VisibleRegion::opacity() const66{
67return m_opacity;68}
69
70
71void
72VisibleRegion::setOpacity(float opacity)73{
74m_opacity = opacity;75}
76
77
78constexpr const unsigned maxSections = 360;79
80void
81VisibleRegion::render(Renderer* renderer,82const Vector3f& position,83float discSizeInPixels,84double tdb,85const Matrices& m) const86{
87// Proper terminator calculation requires double precision floats in GLSL88// which were introduced in ARB_gpu_shader_fp64 unavailable with GL2.1.89// Because of this we make calculations on a CPU and stream results to GPU.90
91// Don't render anything if the current time is not within the92// target object's time window.93if (m_target.body() != nullptr)94{95if (!m_target.body()->extant(tdb))96return;97}98
99// Fade in the terminator when the planet is small100const float minDiscSize = 5.0f;101const float fullOpacityDiscSize = 10.0f;102float opacity = (discSizeInPixels - minDiscSize) / (fullOpacityDiscSize - minDiscSize);103
104// Don't render the terminator if the it's smaller than the minimum size105if (opacity <= 0.0f)106return;107opacity = min(opacity, 1.0f) * m_opacity;108
109// Base the amount of subdivision on the apparent size110auto nSections = (unsigned int) (30.0f + discSizeInPixels * 0.5f);111nSections = min(nSections, maxSections);112
113Quaterniond q = m_body.getEclipticToBodyFixed(tdb);114Quaternionf qf = q.cast<float>();115
116// The outline can't be rendered exactly on the planet sphere, or117// there will be z-fighting problems. Render it at a height above the118// planet that will place it about one pixel away from the planet.119float scale = (discSizeInPixels + 1) / discSizeInPixels;120scale = max(scale, 1.0001f);121
122Vector3f semiAxes = m_body.getSemiAxes();123double maxSemiAxis = m_body.getRadius();124
125// In order to avoid precision problems and extremely large values, scale126// the target position and semiaxes such that the largest semiaxis is 1.0.127Vector3d lightDir = m_body.getPosition(tdb).offsetFromKm(m_target.getPosition(tdb));128lightDir = lightDir / maxSemiAxis;129lightDir = q * lightDir;130
131// Another measure to prevent precision problems: if the distance to the132// object is much greater than the largest semi axis, clamp it to 1e4 times133// the radius, as body-to-target rays at that distance are nearly parallel anyhow.134if (lightDir.norm() > 10000.0)135lightDir *= (10000.0 / lightDir.norm());136
137// Pick two orthogonal axes both normal to the light direction138Vector3d lightDirNorm = lightDir.normalized();139
140Vector3d uAxis = lightDirNorm.unitOrthogonal();141Vector3d vAxis = uAxis.cross(lightDirNorm);142
143Vector3d recipSemiAxes = maxSemiAxis * semiAxes.cast<double>().cwiseInverse();144Vector3d e = -lightDir;145Vector3d e_ = e.cwiseProduct(recipSemiAxes);146double ee = e_.squaredNorm();147
148LineRenderer lr(*renderer, 1.0f, LineRenderer::PrimType::LineStrip);149lr.startUpdate();150
151for (unsigned i = 0; i <= nSections + 1; i++)152{153double theta = (double) i / (double) (nSections) * 2.0 * celestia::numbers::pi;154Vector3d w = cos(theta) * uAxis + sin(theta) * vAxis;155
156Vector3d toCenter = math::ellipsoidTangent(recipSemiAxes, w, e, e_, ee);157toCenter *= maxSemiAxis * scale;158lr.addVertex(Vector3f(toCenter.cast<float>()));159}160
161Affine3f transform = Translation3f(position) * qf.conjugate();162Matrix4f modelView = (*m.modelview) * transform.matrix();163
164Renderer::PipelineState ps;165ps.blending = true;166ps.blendFunc = {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA};167ps.depthMask = true;168ps.depthTest = true;169ps.smoothLines = true;170renderer->setPipelineState(ps);171
172lr.render({ m.projection, &modelView }, Color(m_color, opacity), nSections+1, 0);173lr.finish();174}
175
176
177float
178VisibleRegion::boundingSphereRadius() const179{
180return m_body.getRadius();181}
182