- Use adaptive sampling to calculate cubic splines for drawing orbits and trajectories

- Added capability to show fading orbit 'trails' (disabled by default)
- Added more parameters to control the appearance of orbits
sensor-dev
Chris Laurel 2010-01-28 02:24:14 +00:00
parent 6b6bdc4316
commit bfa2f5fb09
5 changed files with 333 additions and 139 deletions

View File

@ -10,6 +10,7 @@
#define DEBUG_COALESCE 0
#define DEBUG_SECONDARY_ILLUMINATION 0
#define DEBUG_ORBIT_CACHE 0
//#define DEBUG_HDR
#ifdef DEBUG_HDR
@ -33,6 +34,8 @@ std::ofstream hdrlog;
//#define USE_BLOOM_LISTS
#endif
// #define ENABLE_SELF_SHADOW
#ifndef _WIN32
#ifndef TARGET_OS_MAC
#include <config.h>
@ -239,6 +242,11 @@ Color Renderer::EclipticColor (0.5f, 0.1f, 0.1f);
Color Renderer::SelectionCursorColor (1.0f, 0.0f, 0.0f);
#if ENABLE_SELF_SHADOW
static FramebufferObject* shadowFbo = NULL;
#endif
// Some useful unit conversions
inline float mmToInches(float mm)
{
@ -1133,6 +1141,18 @@ bool Renderer::init(GLContext* _context,
genSceneTexture();
genBlurTextures();
#endif
#if ENABLE_SELF_SHADOW
if (GLEW_EXT_framebuffer_object)
{
shadowFbo = new FramebufferObject(1024, 1024, FramebufferObject::DepthAttachment);
if (!shadowFbo->isValid())
{
clog << "Error creating shadow FBO.\n";
}
}
#endif
commonDataInitialized = true;
}
@ -1737,21 +1757,40 @@ static void disableSmoothLines()
class OrbitSampler : public OrbitSampleProc
{
public:
CurvePlot* m_orbitPath;
vector<CurvePlotSample> samples;
OrbitSampler()
{
}
OrbitSampler(CurvePlot* orbitPath) : m_orbitPath(orbitPath) {};
void sample(double t, const Vector3d& position, const Vector3d& velocity)
{
CurvePlotSample samp;
samp.t = t;
samp.position = position;
samp.velocity = velocity;
samp.t = t;
m_orbitPath->addSample(samp);
};
samples.push_back(samp);
}
void insertForward(CurvePlot* plot)
{
for (vector<CurvePlotSample>::const_iterator iter = samples.begin(); iter != samples.end(); ++iter)
{
plot->addSample(*iter);
}
}
void insertBackward(CurvePlot* plot)
{
for (vector<CurvePlotSample>::const_reverse_iterator iter = samples.rbegin(); iter != samples.rend(); ++iter)
{
plot->addSample(*iter);
}
}
};
void renderOrbitColor(const Body *body, bool selected, float opacity)
Vector4f renderOrbitColor(const Body *body, bool selected, float opacity)
{
Color orbitColor;
@ -1803,9 +1842,9 @@ void renderOrbitColor(const Body *body, bool selected, float opacity)
}
#ifdef USE_HDR
glColor(orbitColor, 1.f - opacity * orbitColor.alpha());
return Vector4f(orbitColor.red(), orbitColor.green(), orbitColor.blue(), 1.0f - opacity * orbitColor.alpha());
#else
glColor(orbitColor, opacity * orbitColor.alpha());
return Vector4f(orbitColor.red(), orbitColor.green(), orbitColor.blue(), opacity * orbitColor.alpha());
#endif
}
@ -1872,17 +1911,17 @@ void Renderer::renderOrbit(const OrbitPathListEntry& orbitPath,
}
else
{
startTime = t - orbit->getPeriod() / 2.0;
startTime = t - orbit->getPeriod();
}
cachedOrbit = new CurvePlot();
cachedOrbit->setLastUsed(frameCount);
OrbitSampler sampler(cachedOrbit);
OrbitSampler sampler;
orbit->sample(startTime,
orbit->getPeriod(),
nSamples,
startTime + orbit->getPeriod(),
sampler);
sampler.insertForward(cachedOrbit);
// If the orbit cache is full, first try and eliminate some old orbits
if (orbitCache.size() > OrbitCacheCullThreshold)
@ -1909,42 +1948,73 @@ void Renderer::renderOrbit(const OrbitPathListEntry& orbitPath,
if (cachedOrbit->empty())
return;
//*** Orbit rendering parameters
// The 'window' is the interval of time for which the orbit will be drawn.
// End of the orbit window relative to the current simulation time. Units
// are orbital periods.
const double OrbitWindowEnd = 0.5;
// Number of orbit periods shown. The orbit window is:
// [ t + (OrbitWindowEnd - OrbitPeriodsShown) * T, t + OrbitWindowEnd * T ]
// where t is the current simulation time and T is the orbital period.
const double OrbitPeriodsShown = 1.0;
// Fraction of the window over which the orbit fades from opaque to transparent.
// Fading is disabled when this value is zero.
const double LinearFadeFraction = 0.0;
// Extra size of the internal sample cache.
const double WindowSlack = 0.2;
//***
// 'Periodic' orbits are generally not strictly periodic because of perturbations
// from other bodies. Here we update the trajectory samples to make sure that the
// orbit covers a time range centered at the current time and covering a full revolution.
if (orbit->isPeriodic())
{
double startTime = t - orbit->getPeriod() / 2.0;
double endTime = t + orbit->getPeriod() / 2.0;
double dt = orbit->getPeriod() / detailOptions.orbitPathSamplePoints;
double period = orbit->getPeriod();
double endTime = t + period * OrbitWindowEnd;
double startTime = endTime - period * OrbitPeriodsShown;
if (startTime < cachedOrbit->startTime())
double currentWindowStart = cachedOrbit->startTime();
double currentWindowEnd = cachedOrbit->endTime();
double newWindowStart = startTime - period * WindowSlack;
double newWindowEnd = endTime + period * WindowSlack;
if (startTime < currentWindowStart)
{
cachedOrbit->removeSamplesAfter(endTime + dt);
// Remove samples at the end of the time window
cachedOrbit->removeSamplesAfter(newWindowEnd);
double orbitStartTime = cachedOrbit->empty() ? endTime : cachedOrbit->startTime() - dt;
do {
CurvePlotSample sample;
sample.t = orbitStartTime;
sample.position = orbit->positionAtTime(orbitStartTime);
sample.velocity = orbit->velocityAtTime(orbitStartTime);
cachedOrbit->addSample(sample);
orbitStartTime -= dt;
} while (orbitStartTime > startTime);
// Trim the first sample (because it will be duplicated when we sample the orbit.)
cachedOrbit->removeSamplesBefore(cachedOrbit->startTime() * (1.0 + 1.0e-15));
// Add the new samples
OrbitSampler sampler;
orbit->sample(newWindowStart, min(currentWindowStart, newWindowEnd), sampler);
sampler.insertBackward(cachedOrbit);
#if DEBUG_ORBIT_CACHE
clog << "new sample count: " << cachedOrbit->sampleCount() << endl;
#endif
}
else if (endTime > cachedOrbit->endTime())
else if (endTime > currentWindowEnd)
{
cachedOrbit->removeSamplesBefore(startTime - dt);
// Remove samples at the beginning of the time window
cachedOrbit->removeSamplesBefore(newWindowStart);
double orbitEndTime = cachedOrbit->empty() ? startTime : cachedOrbit->endTime() + dt;
do {
CurvePlotSample sample;
sample.t = orbitEndTime;
sample.position = orbit->positionAtTime(orbitEndTime);
sample.velocity = orbit->velocityAtTime(orbitEndTime);
cachedOrbit->addSample(sample);
orbitEndTime += dt;
} while (orbitEndTime < endTime);
// Trim the last sample (because it will be duplicated when we sample the orbit.)
cachedOrbit->removeSamplesAfter(cachedOrbit->endTime() * (1.0 - 1.0e-15));
// Add the new samples
OrbitSampler sampler;
orbit->sample(max(currentWindowEnd, newWindowStart), newWindowEnd, sampler);
sampler.insertForward(cachedOrbit);
#if DEBUG_ORBIT_CACHE
clog << "new sample count: " << cachedOrbit->sampleCount() << endl;
#endif
}
}
@ -1970,7 +2040,8 @@ void Renderer::renderOrbit(const OrbitPathListEntry& orbitPath,
highlight = highlightObject.body() == body;
else
highlight = highlightObject.star() == orbitPath.star;
renderOrbitColor(body, highlight, orbitPath.opacity);
Vector4f orbitColor = renderOrbitColor(body, highlight, orbitPath.opacity);
glColor4fv(orbitColor.data());
#ifdef STIPPLED_LINES
glLineStipple(3, 0x5555);
@ -1987,10 +2058,28 @@ void Renderer::renderOrbit(const OrbitPathListEntry& orbitPath,
if (orbit->isPeriodic())
{
cachedOrbit->render(modelview,
nearZ, farZ, viewFrustumPlaneNormals,
subdivisionThreshold,
t - orbit->getPeriod() / 2.0, t + orbit->getPeriod() / 2.0);
double period = orbit->getPeriod();
double windowEnd = t + period * OrbitWindowEnd;
double windowStart = windowEnd - period * OrbitPeriodsShown;
double windowDuration = windowEnd - windowStart;
if (LinearFadeFraction == 0.0f)
{
cachedOrbit->render(modelview,
nearZ, farZ, viewFrustumPlaneNormals,
subdivisionThreshold,
windowStart, windowEnd);
}
else
{
cachedOrbit->renderFaded(modelview,
nearZ, farZ, viewFrustumPlaneNormals,
subdivisionThreshold,
windowStart, windowEnd,
orbitColor,
windowStart,
windowEnd - windowDuration * (1.0 - LinearFadeFraction));
}
}
else
{

View File

@ -27,6 +27,152 @@ using namespace std;
static const double ORBITAL_VELOCITY_DIFF_DELTA = 1.0 / 1440.0;
static Vector3d cubicInterpolate(const Vector3d& p0, const Vector3d& v0,
const Vector3d& p1, const Vector3d& v1,
double t)
{
return p0 + (((2.0 * (p0 - p1) + v1 + v0) * (t * t * t)) +
((3.0 * (p1 - p0) - 2.0 * v0 - v1) * (t * t)) +
(v0 * t));
}
/** Sample the orbit over the time range [ startTime, endTime ] using the
* default sampling parameters for the orbit type.
*
* Subclasses of orbit should override this method as necessary. The default
* implementation uses an adaptive sampling scheme with the following defaults:
* tolerance: 1 km
* start step: T / 1e5
* min step: T / 1e7
* max step: T / 100
*
* Where T is either the mean orbital period for periodic orbits or the valid
* time span for aperiodic trajectories.
*/
void Orbit::sample(double startTime, double endTime, OrbitSampleProc& proc) const
{
double span = 0.0;
if (isPeriodic())
{
span = getPeriod();
}
else
{
double startValidInterval = 0.0;
double endValidInterval = 0.0;
getValidRange(startValidInterval, endValidInterval);
if (startValidInterval == endValidInterval)
{
span = endValidInterval - startValidInterval;
}
else
{
span = endTime - startTime;
}
}
AdaptiveSamplingParameters samplingParams;
samplingParams.tolerance = 1.0; // kilometers
samplingParams.maxStep = span / 100.0;
samplingParams.minStep = span / 1.0e7;
samplingParams.startStep = span / 1.0e5;
adaptiveSample(startTime, endTime, proc, samplingParams);
}
/** Adaptively sample the orbit over the range [ startTime, endTime ].
*/
void Orbit::adaptiveSample(double startTime, double endTime, OrbitSampleProc& proc, const AdaptiveSamplingParameters& samplingParams) const
{
double startStepSize = samplingParams.startStep;
double maxStepSize = samplingParams.maxStep;
double minStepSize = samplingParams.minStep;
double tolerance = samplingParams.tolerance;
double t = startTime;
const double stepFactor = 1.25;
Vector3d lastP = positionAtTime(t);
Vector3d lastV = velocityAtTime(t);
proc.sample(t, lastP, lastV);
int sampCount = 0;
int nTests = 0;
while (t < endTime)
{
// Make sure that we don't go past the end of the sample interval
maxStepSize = min(maxStepSize, endTime - t);
double dt = min(maxStepSize, startStepSize * 2.0);
Vector3d p1 = positionAtTime(t + dt);
Vector3d v1 = velocityAtTime(t + dt);
double tmid = t + dt / 2.0;
Vector3d pTest = positionAtTime(tmid);
Vector3d pInterp = cubicInterpolate(lastP, lastV * dt,
p1, v1 * dt,
0.5);
nTests++;
double positionError = (pInterp - pTest).norm();
// Error is greater than tolerance; decrease the step until the
// error is within the tolerance.
if (positionError > tolerance)
{
while (positionError > tolerance && dt > minStepSize)
{
dt /= stepFactor;
p1 = positionAtTime(t + dt);
v1 = velocityAtTime(t + dt);
tmid = t + dt / 2.0;
pTest = positionAtTime(tmid);
pInterp = cubicInterpolate(lastP, lastV * dt,
p1, v1 * dt,
0.5);
nTests++;
positionError = (pInterp - pTest).norm();
}
}
else
{
// Error is less than the tolerance; increase the step size until the
// tolerance is just exceeded.
while (positionError < tolerance && dt < maxStepSize)
{
dt *= stepFactor;
p1 = positionAtTime(t + dt);
v1 = velocityAtTime(t + dt);
tmid = t + dt / 2.0;
pTest = positionAtTime(tmid);
pInterp = cubicInterpolate(lastP, lastV * dt,
p1, v1 * dt,
0.5);
nTests++;
positionError = (pInterp - pTest).norm();
}
}
t = t + dt;
lastP = p1;
lastV = v1;
proc.sample(t, lastP, lastV);
sampCount++;
}
// Statistics for debugging
// clog << "Orbit samples: " << sampCount << ", nTests: " << nTests << endl;
}
EllipticalOrbit::EllipticalOrbit(double _pericenterDistance,
double _eccentricity,
double _inclination,
@ -286,66 +432,6 @@ double EllipticalOrbit::getBoundingRadius() const
}
void EllipticalOrbit::sample(double startTime, double duration, int nSamples,
OrbitSampleProc& proc) const
{
#if 0
{
// Sample uniformly in time
for (int i = 0; i < nSamples; i++)
{
double tsamp = startTime + duration / nSamples;
proc.sample(tsamp, positionAtTime(tsamp), velocityAtTime(tsamp));
}
}
#endif
if (eccentricity >= 1.0 || 1)
{
// Sample uniformly in eccentric anomaly
double t = startTime - epoch;
double meanMotion = 2.0 * PI / period;
double M0 = meanAnomalyAtEpoch + t * meanMotion;
double E0 = eccentricAnomaly(M0);
double dE = 2 * PI / (double) nSamples;
for (int i = 0; i < nSamples; i++)
{
// Compute the time tag for this sample
double E = E0 + dE * i;
double M = E - eccentricity * sin(E); // Mean anomaly from ecc anomaly
double tsamp = startTime + (M - M0) * period / (2 * PI); // Time from mean anomaly
proc.sample(tsamp, positionAtE(E), velocityAtE(E));
}
}
else
{
// Adaptive sampling of the orbit; more samples in regions of high curvature.
// nSamples is the number of samples that will be used for a perfectly circular
// orbit. Elliptical orbits will have regions of higher curvature thar require
// additional sample points.
double E = 0.0;
double dE = 2 * PI / (double) nSamples;
double w = (1 - square(eccentricity));
double M0 = E - eccentricity * sin(E);
while (E < 2 * PI)
{
// Compute the time tag for this sample
double M = E - eccentricity * sin(E); // Mean anomaly from ecc anomaly
double tsamp = startTime + (M - M0) * period / (2 * PI); // Time from mean anomaly
proc.sample(tsamp, positionAtE(E), velocityAtE(E));
// Compute the curvature
double k = w * pow(square(sin(E)) + w * w * square(cos(E)), -1.5);
// Step amount based on curvature--constrain it so that we don't end up
// taking too many samples anywhere. Clamping the curvature to 20 effectively
// limits the numbers of samples to 3*nSamples
E += dE / max(min(k, 20.0), 1.0);
}
}
}
CachingOrbit::CachingOrbit() :
@ -418,15 +504,6 @@ Vector3d CachingOrbit::computeVelocity(double jd) const
}
void CachingOrbit::sample(double start, double t, int nSamples,
OrbitSampleProc& proc) const
{
double dt = t / (double) nSamples;
for (int i = 0; i < nSamples; i++)
proc.sample(start + dt * i, positionAtTime(start + dt * i), velocityAtTime(start + dt * i));
}
static EllipticalOrbit* StateVectorToOrbit(const Vector3d& position,
const Vector3d& v,
double mass,
@ -556,17 +633,16 @@ double MixedOrbit::getBoundingRadius() const
}
void MixedOrbit::sample(double t0, double t1, int nSamples,
OrbitSampleProc& proc) const
void MixedOrbit::sample(double startTime, double endTime, OrbitSampleProc& proc) const
{
Orbit* o;
if (t0 < begin)
if (startTime < begin)
o = beforeApprox;
else if (t0 < end)
else if (startTime < end)
o = primary;
else
o = afterApprox;
o->sample(t0, t1, nSamples, proc);
o->sample(startTime, endTime, proc);
}
@ -612,8 +688,10 @@ FixedOrbit::getBoundingRadius() const
void
FixedOrbit::sample(double, double, int, OrbitSampleProc&) const
FixedOrbit::sample(double /* startTime */, double /* endTime */, OrbitSampleProc&) const
{
// Don't add any samples. This will prevent a fixed trajectory from
// every being drawn when orbit visualization is enabled.
}
@ -650,7 +728,7 @@ double SynchronousOrbit::getBoundingRadius() const
}
void SynchronousOrbit::sample(double, double, int, OrbitSampleProc&) const
void SynchronousOrbit::sample(double /* startTime */, double /* endTime */, OrbitSampleProc&) const
{
// Empty method--we never want to show a synchronous orbit.
}

View File

@ -34,14 +34,25 @@ class Orbit
virtual double getPeriod() const = 0;
virtual double getBoundingRadius() const = 0;
virtual void sample(double start, double t,
int nSamples, OrbitSampleProc& proc) const = 0;
virtual void sample(double startTime, double endTime, OrbitSampleProc& proc) const;
virtual bool isPeriodic() const { return true; };
// Return the time range over which the orbit is valid; if the orbit
// is always valid, begin and end should be equal.
virtual void getValidRange(double& begin, double& end) const
{ begin = 0.0; end = 0.0; };
struct AdaptiveSamplingParameters
{
double tolerance;
double startStep;
double minStep;
double maxStep;
};
void adaptiveSample(double startTime, double endTime, OrbitSampleProc& proc, const AdaptiveSamplingParameters& samplingParameters) const;
};
@ -57,7 +68,6 @@ class EllipticalOrbit : public Orbit
virtual Eigen::Vector3d velocityAtTime(double) const;
double getPeriod() const;
double getBoundingRadius() const;
virtual void sample(double, double, int, OrbitSampleProc&) const;
private:
double eccentricAnomaly(double) const;
@ -108,8 +118,6 @@ class CachingOrbit : public Orbit
Eigen::Vector3d positionAtTime(double jd) const;
Eigen::Vector3d velocityAtTime(double jd) const;
virtual void sample(double, double, int, OrbitSampleProc& proc) const;
private:
mutable Eigen::Vector3d lastPosition;
mutable Eigen::Vector3d lastVelocity;
@ -135,8 +143,7 @@ class MixedOrbit : public Orbit
virtual Eigen::Vector3d velocityAtTime(double jd) const;
virtual double getPeriod() const;
virtual double getBoundingRadius() const;
virtual void sample(double t0, double t1,
int nSamples, OrbitSampleProc& proc) const;
virtual void sample(double startTime, double endTime, OrbitSampleProc& proc) const;
private:
Orbit* primary;
@ -165,7 +172,7 @@ class SynchronousOrbit : public Orbit
virtual Eigen::Vector3d positionAtTime(double jd) const;
virtual double getPeriod() const;
virtual double getBoundingRadius() const;
virtual void sample(double, double, int, OrbitSampleProc& proc) const;
virtual void sample(double, double, OrbitSampleProc& proc) const;
private:
const Body& body;
@ -187,7 +194,7 @@ class FixedOrbit : public Orbit
virtual double getPeriod() const;
virtual bool isPeriodic() const;
virtual double getBoundingRadius() const;
virtual void sample(double, double, int, OrbitSampleProc&) const;
virtual void sample(double, double, OrbitSampleProc&) const;
private:
Eigen::Vector3d position;

View File

@ -21,6 +21,7 @@
#include <iostream>
#include <fstream>
#include <limits>
#include <iomanip>
using namespace Eigen;
using namespace std;
@ -76,7 +77,7 @@ public:
bool isPeriodic() const;
void getValidRange(double& begin, double& end) const;
virtual void sample(double, double, int, OrbitSampleProc& proc) const;
virtual void sample(double startTime, double endTime, OrbitSampleProc& proc) const;
private:
vector<Sample<T> > samples;
@ -392,7 +393,7 @@ template <typename T> Vector3d SampledOrbit<T>::computeVelocity(double jd) const
}
template <typename T> void SampledOrbit<T>::sample(double /* start */, double /* t */, int,
template <typename T> void SampledOrbit<T>::sample(double /* startTime */, double /* endTime */,
OrbitSampleProc& proc) const
{
for (unsigned int i = 0; i < samples.size(); i++)
@ -446,7 +447,7 @@ public:
bool isPeriodic() const;
void getValidRange(double& begin, double& end) const;
virtual void sample(double, double, int, OrbitSampleProc& proc) const;
virtual void sample(double startTime, double endTime, OrbitSampleProc& proc) const;
private:
vector<SampleXYZV<T> > samples;
@ -492,7 +493,10 @@ template <typename T> void SampledOrbitXYZV<T>::addSample(double t, const Vector
template <typename T> double SampledOrbitXYZV<T>::getPeriod() const
{
return samples[samples.size() - 1].t - samples[0].t;
if (samples.empty())
return 0.0;
else
return samples[samples.size() - 1].t - samples[0].t;
}
@ -654,7 +658,7 @@ template <typename T> Vector3d SampledOrbitXYZV<T>::computeVelocity(double jd) c
}
template <typename T> void SampledOrbitXYZV<T>::sample(double /* start */, double /* t */, int,
template <typename T> void SampledOrbitXYZV<T>::sample(double /* startTime */, double /* endTime */,
OrbitSampleProc& proc) const
{
for (typename vector<SampleXYZV<T> >::const_iterator iter = samples.begin();

View File

@ -11084,13 +11084,7 @@ class VSOP87Orbit : public CachingOrbit
r += SumSeries(vsR[i], t) * T;
T = t * T;
}
#if 0
// l and b are in units of 1e+8 radians
l *= 1.0e-8;
b *= 1.0e-8;
// r is in units of 1e-8 AU
r *= (KM_PER_AU / 100000000.0);
#endif
r *= KM_PER_AU;
// Corrections for internal coordinate system
@ -11101,6 +11095,28 @@ class VSOP87Orbit : public CachingOrbit
cos(b) * r,
-sin(l) * sin(b) * r);
}
/** Custom implementation of sample() for VSOP87 orbits. The default
* implementation runs too slowly and produces too many samples.
*/
void sample(double startTime, double endTime, OrbitSampleProc& proc) const
{
double span = getPeriod();
AdaptiveSamplingParameters samplingParams;
samplingParams.tolerance = 1.0; // kilometers
// startStep, minStep, and maxStep are all set to identical values,
// so the adaptive sampling function actuall behaves like uniform
// sampling.
samplingParams.startStep = span / 150.0;
samplingParams.minStep = samplingParams.startStep;
samplingParams.maxStep = samplingParams.startStep;
adaptiveSample(startTime, endTime, proc, samplingParams);
}
};