celestia/src/celengine/body.cpp

1332 lines
33 KiB
C++

// body.cpp
//
// Copyright (C) 2001-2006 Chris Laurel <claurel@shatters.net>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#include <cstdlib>
#include <cassert>
#include <algorithm>
#include <celmath/mathlib.h>
#include <celutil/util.h>
#include <celutil/utf8.h>
#include "geometry.h"
#include "meshmanager.h"
#include "body.h"
#include "atmosphere.h"
#include "frame.h"
#include "timeline.h"
#include "timelinephase.h"
#include "frametree.h"
#include "referencemark.h"
using namespace Eigen;
using namespace std;
Body::Body(PlanetarySystem* _system, const string& _name) :
names(1),
localizedNameIndex(0),
system(_system),
satellites(NULL),
timeline(NULL),
frameTree(NULL),
radius(1.0f),
semiAxes(1.0f, 1.0f, 1.0f),
mass(0.0f),
albedo(0.5f),
geometryOrientation(Quaternionf::Identity()),
cullingRadius(0.0f),
geometry(InvalidResource),
geometryScale(1.0f),
surface(Color(1.0f, 1.0f, 1.0f)),
atmosphere(NULL),
rings(NULL),
classification(Unknown),
altSurfaces(NULL),
locations(NULL),
locationsComputed(false),
referenceMarks(NULL),
visible(1),
clickable(1),
visibleAsPoint(1),
overrideOrbitColor(0),
orbitVisibility(UseClassVisibility),
secondaryIlluminator(true)
{
setName(_name);
recomputeCullingRadius();
system->addBody(this);
}
Body::~Body()
{
if (system != NULL)
system->removeBody(this);
// Remove from frame hierarchy
// Clean up the reference mark list
if (referenceMarks != NULL)
{
for (list<ReferenceMark*>::iterator iter = referenceMarks->begin(); iter != referenceMarks->end(); ++iter)
delete *iter;
delete referenceMarks;
}
delete timeline;
delete satellites;
delete frameTree;
}
/*! Reset body attributes to their default values. The object hierarchy is left untouched,
* i.e. child objects are not removed. Alternate surfaces and locations are not removed
* either.
*/
void Body::setDefaultProperties()
{
radius = 1.0f;
semiAxes = Vector3f::Ones();
mass = 0.0f;
albedo = 0.5f;
geometryOrientation = Quaternionf::Identity();
geometry = InvalidResource;
surface = Surface(Color::White);
delete atmosphere;
atmosphere = NULL;
delete rings;
rings = NULL;
classification = Unknown;
visible = 1;
clickable = 1;
visibleAsPoint = 1;
overrideOrbitColor = 0;
orbitVisibility = UseClassVisibility;
recomputeCullingRadius();
}
/*! Return the list of all names (non-localized) by which this
* body is known.
*/
const vector<string>& Body::getNames() const
{
return names;
}
/*! Return the primary name for the body; if i18n, return the
* localized name of the body.
*/
string Body::getName(bool i18n) const
{
if (!i18n)
return names[0];
else
return names[localizedNameIndex];
}
/*! Get the localized name for the body. If no localized name
* has been set, the primary name is returned.
*/
string Body::getLocalizedName() const
{
return names[localizedNameIndex];
}
bool Body::hasLocalizedName() const
{
return localizedNameIndex != 0;
}
/*! Set the primary name of the body. The localized name is updated
* automatically as well.
* Note: setName() is private, and only called from the Body constructor.
* It shouldn't be called elsewhere.
*/
void Body::setName(const string& name)
{
names[0] = name;
string localizedName = _(name.c_str());
if (name == localizedName)
{
// No localized name; set the localized name index to zero to
// indicate this.
localizedNameIndex = 0;
}
else
{
names.push_back(localizedName);
localizedNameIndex = names.size() - 1;
}
}
/*! Add a new name for this body. Aliases are non localized.
*/
void Body::addAlias(const string& alias)
{
names.push_back(alias);
system->addAlias(this, alias);
}
PlanetarySystem* Body::getSystem() const
{
return system;
}
FrameTree* Body::getFrameTree() const
{
return frameTree;
}
FrameTree* Body::getOrCreateFrameTree()
{
if (frameTree == NULL)
frameTree = new FrameTree(this);
return frameTree;
}
const Timeline* Body::getTimeline() const
{
return timeline;
}
void Body::setTimeline(Timeline* newTimeline)
{
if (timeline != newTimeline)
{
delete timeline;
timeline = newTimeline;
markChanged();
}
}
void Body::markChanged()
{
if (timeline != NULL)
timeline->markChanged();
}
void Body::markUpdated()
{
if (frameTree != NULL)
frameTree->markUpdated();
}
const ReferenceFrame* Body::getOrbitFrame(double tdb) const
{
return timeline->findPhase(tdb)->orbitFrame();
}
const Orbit* Body::getOrbit(double tdb) const
{
return timeline->findPhase(tdb)->orbit();
}
const ReferenceFrame* Body::getBodyFrame(double tdb) const
{
return timeline->findPhase(tdb)->bodyFrame();
}
const RotationModel* Body::getRotationModel(double tdb) const
{
return timeline->findPhase(tdb)->rotationModel();
}
/*! Get the radius of a sphere large enough to contain the primary
* geometry of the object: either a mesh or an ellipsoid.
* For an irregular (mesh) object, the radius is defined to be
* the largest semi-axis of the axis-aligned bounding box. The
* radius of the smallest sphere containing the object is potentially
* larger by a factor of sqrt(3).
*
* This method does not consider additional object features
* such as rings, atmospheres, or reference marks; use
* getCullingRadius() for that.
*/
float Body::getBoundingRadius() const
{
if (geometry == InvalidResource)
return radius;
else
return radius * 1.7320508f; // sqrt(3)
}
/*! Return the radius of sphere large enough to contain any geometry
* associated with this object: the primary geometry, comet tail,
* rings, atmosphere shell, cloud layers, or reference marks.
*/
float Body::getCullingRadius() const
{
return cullingRadius;
}
float Body::getMass() const
{
return mass;
}
void Body::setMass(float _mass)
{
mass = _mass;
}
float Body::getAlbedo() const
{
return albedo;
}
void Body::setAlbedo(float _albedo)
{
albedo = _albedo;
}
Quaternionf Body::getGeometryOrientation() const
{
return geometryOrientation;
}
void Body::setGeometryOrientation(const Quaternionf& orientation)
{
geometryOrientation = orientation;
}
/*! Set the semiaxes of a body.
*/
void Body::setSemiAxes(const Vector3f& _semiAxes)
{
semiAxes = _semiAxes;
// Radius will always be the largest of the three semi axes
radius = semiAxes.maxCoeff();
recomputeCullingRadius();
}
/*! Retrieve the body's semiaxes
*/
Vector3f Body::getSemiAxes() const
{
return semiAxes;
}
/*! Get the radius of the body. For a spherical body, this is simply
* the sphere's radius. For an ellipsoidal body, the radius is the
* largest of the three semiaxes. For irregular bodies (with a shape
* represented by a mesh), the radius is the largest semiaxis of the
* mesh's axis aligned bounding axis. Note that this means some portions
* of the mesh may extend outside the sphere of the retrieved radius.
* To obtain the radius of a sphere that will definitely enclose the
* body, call getBoundingRadius() instead.
*/
float Body::getRadius() const
{
return radius;
}
/*! Return true if the body is a perfect sphere.
*/
bool Body::isSphere() const
{
return (geometry == InvalidResource) &&
(semiAxes.x() == semiAxes.y()) &&
(semiAxes.x() == semiAxes.z());
}
/*! Return true if the body is ellipsoidal, with geometry determined
* completely by its semiaxes rather than a triangle based model.
*/
bool Body::isEllipsoid() const
{
return geometry == InvalidResource;
}
const Surface& Body::getSurface() const
{
return surface;
}
Surface& Body::getSurface()
{
return surface;
}
void Body::setSurface(const Surface& surf)
{
surface = surf;
}
void Body::setGeometry(ResourceHandle _geometry)
{
geometry = _geometry;
}
/*! Set the scale factor for geometry; this is only used with unnormalized meshes.
* When a mesh is normalized, the effective scale factor is the radius.
*/
void Body::setGeometryScale(float scale)
{
geometryScale = scale;
}
PlanetarySystem* Body::getSatellites() const
{
return satellites;
}
void Body::setSatellites(PlanetarySystem* ssys)
{
satellites = ssys;
}
RingSystem* Body::getRings() const
{
return rings;
}
void Body::setRings(const RingSystem& _rings)
{
if (rings == NULL)
rings = new RingSystem(_rings);
else
*rings = _rings;
recomputeCullingRadius();
}
const Atmosphere* Body::getAtmosphere() const
{
return atmosphere;
}
Atmosphere* Body::getAtmosphere()
{
return atmosphere;
}
void Body::setAtmosphere(const Atmosphere& _atmosphere)
{
if (atmosphere == NULL)
atmosphere = new Atmosphere();
*atmosphere = _atmosphere;
recomputeCullingRadius();
}
// The following four functions are used to get the state of the body
// in universal coordinates:
// * getPosition
// * getOrientation
// * getVelocity
// * getAngularVelocity
/*! Get the position of the body in the universal coordinate system.
* This method uses high-precision coordinates and is thus
* slower relative to getAstrocentricPosition(), which works strictly
* with standard double precision. For most purposes,
* getAstrocentricPosition() should be used instead of the more
* general getPosition().
*/
UniversalCoord Body::getPosition(double tdb) const
{
Vector3d position = Vector3d::Zero();
const TimelinePhase* phase = timeline->findPhase(tdb);
Vector3d p = phase->orbit()->positionAtTime(tdb);
ReferenceFrame* frame = phase->orbitFrame();
while (frame->getCenter().getType() == Selection::Type_Body)
{
phase = frame->getCenter().body()->timeline->findPhase(tdb);
position += frame->getOrientation(tdb).conjugate() * p;
p = phase->orbit()->positionAtTime(tdb);
frame = phase->orbitFrame();
}
position += frame->getOrientation(tdb).conjugate() * p;
if (frame->getCenter().star())
return frame->getCenter().star()->getPosition(tdb).offsetKm(position);
else
return frame->getCenter().getPosition(tdb).offsetKm(position);
}
/*! Get the orientation of the body in the universal coordinate system.
*/
Quaterniond Body::getOrientation(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
return phase->rotationModel()->orientationAtTime(tdb) * phase->bodyFrame()->getOrientation(tdb);
}
/*! Get the velocity of the body in the universal frame.
*/
Vector3d Body::getVelocity(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
ReferenceFrame* orbitFrame = phase->orbitFrame();
Vector3d v = phase->orbit()->velocityAtTime(tdb);
v = orbitFrame->getOrientation(tdb).conjugate() * v + orbitFrame->getCenter().getVelocity(tdb);
if (!orbitFrame->isInertial())
{
Vector3d r = Selection(const_cast<Body*>(this)).getPosition(tdb).offsetFromKm(orbitFrame->getCenter().getPosition(tdb));
v += orbitFrame->getAngularVelocity(tdb).cross(r);
}
return v;
}
/*! Get the angular velocity of the body in the universal frame.
*/
Vector3d Body::getAngularVelocity(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
Vector3d v = phase->rotationModel()->angularVelocityAtTime(tdb);
ReferenceFrame* bodyFrame = phase->bodyFrame();
v = bodyFrame->getOrientation(tdb).conjugate() * v;
if (!bodyFrame->isInertial())
{
v += bodyFrame->getAngularVelocity(tdb);
}
return v;
}
/*! Get the transformation which converts body coordinates into
* astrocentric coordinates. Some clarification on the meaning
* of 'astrocentric': the position of every solar system body
* is ultimately defined with respect to some star or star
* system barycenter.
*/
Matrix4d Body::getLocalToAstrocentric(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
Vector3d p = phase->orbitFrame()->convertToAstrocentric(phase->orbit()->positionAtTime(tdb), tdb);
return Eigen::Transform<double, 3, Affine>(Translation3d(p)).matrix();
}
/*! Get the position of the center of the body in astrocentric ecliptic coordinates.
*/
Vector3d Body::getAstrocentricPosition(double tdb) const
{
// TODO: Switch the iterative method used in getPosition
const TimelinePhase* phase = timeline->findPhase(tdb);
return phase->orbitFrame()->convertToAstrocentric(phase->orbit()->positionAtTime(tdb), tdb);
}
/*! Get a rotation that converts from the ecliptic frame to the body frame.
*/
Quaterniond Body::getEclipticToFrame(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
return phase->bodyFrame()->getOrientation(tdb);
}
/*! Get a rotation that converts from the ecliptic frame to the body's
* mean equatorial frame.
*/
Quaterniond Body::getEclipticToEquatorial(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
return phase->rotationModel()->equatorOrientationAtTime(tdb) * phase->bodyFrame()->getOrientation(tdb);
}
/*! Get a rotation that converts from the ecliptic frame to this
* objects's body fixed frame.
*/
Quaterniond Body::getEclipticToBodyFixed(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
return phase->rotationModel()->orientationAtTime(tdb) * phase->bodyFrame()->getOrientation(tdb);
}
// The body-fixed coordinate system has an origin at the center of the
// body, y-axis parallel to the rotation axis, x-axis through the prime
// meridian, and z-axis at a right angle the xy plane.
Quaterniond Body::getEquatorialToBodyFixed(double tdb) const
{
const TimelinePhase* phase = timeline->findPhase(tdb);
return phase->rotationModel()->spin(tdb);
}
/*! Get a transformation to convert from the object's body fixed frame
* to the astrocentric ecliptic frame.
*/
Matrix4d Body::getBodyFixedToAstrocentric(double tdb) const
{
//return getEquatorialToBodyFixed(tdb).toMatrix4() * getLocalToAstrocentric(tdb);
Matrix4d m = Eigen::Transform<double, 3, Affine>(getEquatorialToBodyFixed(tdb)).matrix();
return m * getLocalToAstrocentric(tdb);
}
Vector3d Body::planetocentricToCartesian(double lon, double lat, double alt) const
{
double phi = -degToRad(lat) + PI / 2;
double theta = degToRad(lon) - PI;
Vector3d pos(cos(theta) * sin(phi),
cos(phi),
-sin(theta) * sin(phi));
return pos * (getRadius() + alt);
}
Vector3d Body::planetocentricToCartesian(const Vector3d& lonLatAlt) const
{
return planetocentricToCartesian(lonLatAlt.x(), lonLatAlt.y(), lonLatAlt.z());
}
/*! Convert cartesian body-fixed coordinates to spherical planetocentric
* coordinates.
*/
Vector3d Body::cartesianToPlanetocentric(const Vector3d& v) const
{
Vector3d w = v.normalized();
double lat = PI / 2.0 - acos(w.y());
double lon = atan2(w.z(), -w.x());
return Vector3d(lon, lat, v.norm() - getRadius());
}
/*! Convert body-centered ecliptic coordinates to spherical planetocentric
* coordinates.
*/
Vector3d Body::eclipticToPlanetocentric(const Vector3d& ecl, double tdb) const
{
Vector3d bf = getEclipticToBodyFixed(tdb) * ecl;
return cartesianToPlanetocentric(bf);
}
bool Body::extant(double t) const
{
return timeline->includes(t);
}
void Body::getLifespan(double& begin, double& end) const
{
begin = timeline->startTime();
end = timeline->endTime();
}
float Body::getLuminosity(const Star& sun,
float distanceFromSun) const
{
return getLuminosity(sun.getLuminosity(), distanceFromSun);
}
float Body::getLuminosity(float sunLuminosity,
float distanceFromSun) const
{
// Compute the total power of the star in Watts
double power = astro::SOLAR_POWER * sunLuminosity;
// Compute the irradiance at a distance of 1au from the star in W/m^2
// double irradiance = power / sphereArea(astro::AUtoKilometers(1.0) * 1000);
// Compute the irradiance at the body's distance from the star
double satIrradiance = power / sphereArea(distanceFromSun * 1000);
// Compute the total energy hitting the planet
double incidentEnergy = satIrradiance * circleArea(radius * 1000);
double reflectedEnergy = incidentEnergy * albedo;
// Compute the luminosity (i.e. power relative to solar power)
return (float) (reflectedEnergy / astro::SOLAR_POWER);
}
/*! Get the apparent magnitude of the body, neglecting the phase (as if
* the body was at opposition.
*/
float Body::getApparentMagnitude(const Star& sun,
float distanceFromSun,
float distanceFromViewer) const
{
return astro::lumToAppMag(getLuminosity(sun, distanceFromSun),
astro::kilometersToLightYears(distanceFromViewer));
}
/*! Get the apparent magnitude of the body, neglecting the phase (as if
* the body was at opposition.
*/
float Body::getApparentMagnitude(float sunLuminosity,
float distanceFromSun,
float distanceFromViewer) const
{
return astro::lumToAppMag(getLuminosity(sunLuminosity, distanceFromSun),
astro::kilometersToLightYears(distanceFromViewer));
}
/*! Get the apparent magnitude of the body, corrected for its phase.
*/
float Body::getApparentMagnitude(const Star& sun,
const Vector3d& sunPosition,
const Vector3d& viewerPosition) const
{
return getApparentMagnitude(sun.getLuminosity(),
sunPosition,
viewerPosition);
}
/*! Get the apparent magnitude of the body, corrected for its phase.
*/
float Body::getApparentMagnitude(float sunLuminosity,
const Vector3d& sunPosition,
const Vector3d& viewerPosition) const
{
double distanceToViewer = viewerPosition.norm();
double distanceToSun = sunPosition.norm();
float illuminatedFraction = (float) (1.0 + (viewerPosition / distanceToViewer).dot(sunPosition / distanceToSun)) / 2.0f;
return astro::lumToAppMag(getLuminosity(sunLuminosity, (float) distanceToSun) * illuminatedFraction, (float) astro::kilometersToLightYears(distanceToViewer));
}
int Body::getClassification() const
{
return classification;
}
void Body::setClassification(int _classification)
{
classification = _classification;
recomputeCullingRadius();
markChanged();
}
/*! Return the effective classification of this body used when rendering
* orbits. Normally, this is just the classification of the object, but
* invisible objects are treated specially: they behave as if they have
* the classification of their child objects. This fixes annoyances when
* planets are defined with orbits relative to their system barycenters.
* For example, Pluto's orbit can seen in a solar system scale view, even
* though its orbit is defined relative to the Pluto-Charon barycenter
* and is this just a few hundred kilometers in size.
*/
int Body::getOrbitClassification() const
{
if (classification != Invisible || frameTree == NULL)
{
return classification;
}
else
{
int orbitClass = frameTree->childClassMask();
if (orbitClass & Planet)
return Planet;
else if (orbitClass & DwarfPlanet)
return DwarfPlanet;
else if (orbitClass & Moon)
return Moon;
else if (orbitClass & MinorMoon)
return MinorMoon;
else if (orbitClass & Asteroid)
return Asteroid;
else if (orbitClass & Spacecraft)
return Spacecraft;
else
return Invisible;
}
}
string Body::getInfoURL() const
{
return infoURL;
}
void Body::setInfoURL(const string& _infoURL)
{
infoURL = _infoURL;
}
Surface* Body::getAlternateSurface(const string& name) const
{
if (altSurfaces == NULL)
return NULL;
AltSurfaceTable::iterator iter = altSurfaces->find(name);
if (iter == altSurfaces->end())
return NULL;
else
return iter->second;
}
void Body::addAlternateSurface(const string& name, Surface* surface)
{
if (altSurfaces == NULL)
altSurfaces = new AltSurfaceTable();
//altSurfaces->insert(AltSurfaceTable::value_type(name, surface));
(*altSurfaces)[name] = surface;
}
vector<string>* Body::getAlternateSurfaceNames() const
{
vector<string>* names = new vector<string>();
if (altSurfaces != NULL)
{
for (AltSurfaceTable::const_iterator iter = altSurfaces->begin();
iter != altSurfaces->end(); iter++)
{
names->insert(names->end(), iter->first);
}
}
return names;
}
void Body::addLocation(Location* loc)
{
assert(loc != NULL);
if (loc == NULL)
return;
if (locations == NULL)
locations = new vector<Location*>();
locations->insert(locations->end(), loc);
loc->setParentBody(this);
}
vector<Location*>* Body::getLocations() const
{
return locations;
}
Location* Body::findLocation(const string& name, bool i18n) const
{
if (locations == NULL)
return NULL;
for (vector<Location*>::const_iterator iter = locations->begin();
iter != locations->end(); iter++)
{
if (!UTF8StringCompare(name, (*iter)->getName(i18n)))
return *iter;
}
return NULL;
}
// Compute the positions of locations on an irregular object using ray-mesh
// intersections. This is not automatically done when a location is added
// because it would force the loading of all meshes for objects with
// defined locations; on-demand (i.e. when the object becomes visible to
// a user) loading of meshes is preferred.
void Body::computeLocations()
{
if (locationsComputed)
return;
locationsComputed = true;
// No work to do if there's no mesh, or if the mesh cannot be loaded
if (geometry == InvalidResource)
return;
Geometry* g = GetGeometryManager()->find(geometry);
if (g == NULL)
return;
// TODO: Implement separate radius and bounding radius so that this hack is
// not necessary.
double boundingRadius = 2.0;
for (vector<Location*>::const_iterator iter = locations->begin();
iter != locations->end(); iter++)
{
Vector3f v = (*iter)->getPosition();
float alt = v.norm() - radius;
if (alt != -radius)
v.normalize();
v *= (float) boundingRadius;
Ray3d ray(v.cast<double>(), -v.cast<double>());
double t = 0.0;
if (g->pick(ray, t))
{
v *= (float) ((1.0 - t) * radius + alt);
(*iter)->setPosition(v);
}
}
}
/*! Add a new reference mark.
*/
void
Body::addReferenceMark(ReferenceMark* refMark)
{
if (referenceMarks == NULL)
referenceMarks = new list<ReferenceMark*>();
referenceMarks->push_back(refMark);
recomputeCullingRadius();
}
/*! Remove the first reference mark with the specified tag.
*/
void
Body::removeReferenceMark(const string& tag)
{
if (referenceMarks != NULL)
{
ReferenceMark* refMark = findReferenceMark(tag);
if (refMark != NULL)
{
referenceMarks->remove(refMark);
delete refMark;
recomputeCullingRadius();
}
}
}
/*! Find the first reference mark with the specified tag. If the body has
* no reference marks with the specified tag, this method will return
* NULL.
*/
ReferenceMark*
Body::findReferenceMark(const string& tag) const
{
if (referenceMarks != NULL)
{
for (list<ReferenceMark*>::iterator iter = referenceMarks->begin(); iter != referenceMarks->end(); ++iter)
{
if ((*iter)->getTag() == tag)
return *iter;
}
}
return NULL;
}
/*! Get the list of reference marks associated with this body. May return
* NULL if there are no reference marks.
*/
const list<ReferenceMark*>*
Body::getReferenceMarks() const
{
return referenceMarks;
}
/*! Sets whether or not the object is visible.
*/
void Body::setVisible(bool _visible)
{
visible = _visible ? 1 : 0;
}
/*! Sets whether or not the object can be selected by clicking on
* it. If set to false, the object is completely ignored when the
* user clicks it, making it possible to select background objects.
*/
void Body::setClickable(bool _clickable)
{
clickable = _clickable ? 1 : 0;
}
/*! Set whether or not the object is visible as a starlike point
* when it occupies less than a pixel onscreen. This is appropriate
* for planets and moons, but generally not desireable for buildings
* or spacecraft components.
*/
void Body::setVisibleAsPoint(bool _visibleAsPoint)
{
visibleAsPoint = _visibleAsPoint ? 1 : 0;
}
/*! The orbitColorOverride flag is set to true if an alternate orbit
* color should be used (specified via setOrbitColor) instead of the
* default class orbit color.
*/
void Body::setOrbitColorOverridden(bool override)
{
overrideOrbitColor = override ? 1 : 0;
}
/*! Set the visibility policy for the orbit of this object:
* - NeverVisible: Never show the orbit of this object.
* - UseClassVisibility: (Default) Show the orbit of this object
* its class is enabled in the orbit mask.
* - AlwaysVisible: Always show the orbit of this object whenever
* orbit paths are enabled.
*/
void Body::setOrbitVisibility(VisibilityPolicy _orbitVisibility)
{
orbitVisibility = _orbitVisibility;
}
/*! Set the color used when rendering the orbit. This is only used
* when the orbitColorOverride flag is set to true; otherwise, the
* standard orbit color for all objects of the class is used.
*/
void Body::setOrbitColor(const Color& c)
{
orbitColor = c;
}
/*! Set whether or not the object should be considered when calculating
* secondary illumination (e.g. planetshine.)
*/
void Body::setSecondaryIlluminator(bool enable)
{
if (enable != secondaryIlluminator)
{
markChanged();
secondaryIlluminator = enable;
}
}
void Body::recomputeCullingRadius()
{
float r = getBoundingRadius();
if (rings != NULL)
r = max(r, rings->outerRadius);
if (atmosphere != NULL)
{
r = max(r, atmosphere->height);
r = max(r, atmosphere->cloudHeight);
}
if (referenceMarks != NULL)
{
for (std::list<ReferenceMark*>::const_iterator iter = referenceMarks->begin();
iter != referenceMarks->end(); iter++)
{
r = max(r, (*iter)->boundingSphereRadius());
}
}
if (classification == Body::Comet)
r = max(r, astro::AUtoKilometers(1.0f));
if (r != cullingRadius)
{
cullingRadius = r;
markChanged();
}
}
/**** Implementation of PlanetarySystem ****/
/*! Return the equatorial frame for this object. This frame is used as
* the default frame for objects in SSC files that orbit non-stellar bodies.
* In order to avoid wasting memory, it is created until the first time it
* is requested.
*/
PlanetarySystem::PlanetarySystem(Body* _primary) :
star(NULL),
primary(_primary)
{
if (primary != NULL && primary->getSystem() != NULL)
star = primary->getSystem()->getStar();
}
PlanetarySystem::PlanetarySystem(Star* _star) :
star(_star),
primary(NULL)
{
}
PlanetarySystem::~PlanetarySystem()
{
}
/*! Add a new alias for an object. If an object with the specified
* alias already exists in the planetary system, the old entry will
* be replaced.
*/
void PlanetarySystem::addAlias(Body* body, const string& alias)
{
assert(body->getSystem() == this);
objectIndex.insert(make_pair(alias, body));
}
/*! Remove the an alias for an object. This method does nothing
* if the alias is not present in the index, or if the alias
* refers to a different object.
*/
void PlanetarySystem::removeAlias(const Body* body, const string& alias)
{
assert(body->getSystem() == this);
ObjectIndex::iterator iter = objectIndex.find(alias);
if (iter != objectIndex.end())
{
if (iter->second == body)
objectIndex.erase(iter);
}
}
void PlanetarySystem::addBody(Body* body)
{
satellites.insert(satellites.end(), body);
addBodyToNameIndex(body);
}
// Add all aliases for the body to the name index
void PlanetarySystem::addBodyToNameIndex(Body* body)
{
const vector<string>& names = body->getNames();
for (vector<string>::const_iterator iter = names.begin(); iter != names.end(); iter++)
{
objectIndex.insert(make_pair(*iter, body));
}
}
// Remove all references to the body in the name index.
void PlanetarySystem::removeBodyFromNameIndex(const Body* body)
{
assert(body->getSystem() == this);
// Erase the object from the object indices
const vector<string>& names = body->getNames();
for (vector<string>::const_iterator iter = names.begin(); iter != names.end(); iter++)
{
removeAlias(body, *iter);
}
}
void PlanetarySystem::removeBody(Body* body)
{
for (vector<Body*>::iterator iter = satellites.begin();
iter != satellites.end(); iter++)
{
if (*iter == body)
{
satellites.erase(iter);
break;
}
}
removeBodyFromNameIndex(body);
}
void PlanetarySystem::replaceBody(Body* oldBody, Body* newBody)
{
for (vector<Body*>::iterator iter = satellites.begin();
iter != satellites.end(); iter++)
{
if (*iter == oldBody)
{
*iter = newBody;
break;
}
}
removeBodyFromNameIndex(oldBody);
addBodyToNameIndex(newBody);
}
/*! Find a body with the specified name within a planetary system.
*
* deepSearch: if true, recursively search the systems of child objects
* i18n: if true, allow matching of localized body names. When responding
* to a user query, this flag should be true. In other cases--such
* as resolving an object name in an ssc file--it should be false. Otherwise,
* object lookup will behave differently based on the locale.
*/
Body* PlanetarySystem::find(const string& _name, bool deepSearch, bool i18n) const
{
ObjectIndex::const_iterator firstMatch = objectIndex.find(_name);
if (firstMatch != objectIndex.end())
{
Body* matchedBody = firstMatch->second;
if (i18n)
{
return matchedBody;
}
else
{
// Ignore localized names
if (!matchedBody->hasLocalizedName() || _name != matchedBody->getLocalizedName())
return matchedBody;
}
}
if (deepSearch)
{
for (vector<Body*>::const_iterator iter = satellites.begin();
iter != satellites.end(); iter++)
{
if (UTF8StringCompare((*iter)->getName(i18n), _name) == 0)
{
return *iter;
}
else if (deepSearch && (*iter)->getSatellites() != NULL)
{
Body* body = (*iter)->getSatellites()->find(_name, deepSearch, i18n);
if (body != NULL)
return body;
}
}
}
return NULL;
}
bool PlanetarySystem::traverse(TraversalFunc func, void* info) const
{
for (int i = 0; i < getSystemSize(); i++)
{
Body* body = getBody(i);
// assert(body != NULL);
if (!func(body, info))
return false;
if (body->getSatellites() != NULL)
{
if (!body->getSatellites()->traverse(func, info))
return false;
}
}
return true;
}
std::vector<std::string> PlanetarySystem::getCompletion(const std::string& _name, bool deepSearch) const
{
std::vector<std::string> completion;
int _name_length = UTF8Length(_name);
// Search through all names in this planetary system.
for (ObjectIndex::const_iterator iter = objectIndex.begin();
iter != objectIndex.end(); iter++)
{
const string& alias = iter->first;
if (UTF8StringCompare(alias, _name, _name_length) == 0)
{
completion.push_back(alias);
}
}
// Scan child objects
if (deepSearch)
{
for (vector<Body*>::const_iterator iter = satellites.begin();
iter != satellites.end(); iter++)
{
if ((*iter)->getSatellites() != NULL)
{
vector<string> bodies = (*iter)->getSatellites()->getCompletion(_name);
completion.insert(completion.end(), bodies.begin(), bodies.end());
}
}
}
return completion;
}
/*! Get the order of the object in the list of children. Returns -1 if the
* specified body is not a child object.
*/
int PlanetarySystem::getOrder(const Body* body) const
{
vector<Body*>::const_iterator iter = std::find(satellites.begin(), satellites.end(), body);
if (iter == satellites.end())
return -1;
else
return iter - satellites.begin();
}