Index: celengine/body.cpp =================================================================== --- celengine/body.cpp (revision 4077) +++ celengine/body.cpp (working copy) @@ -48,8 +48,11 @@ locations(NULL), locationsComputed(false), referenceMarks(0), + visible(1), clickable(1), visibleAsPoint(1), + overrideOrbitColor(0), + orbitVisibility(UseClassVisibility), frameRefStar(NULL) { system = _system; @@ -874,18 +877,69 @@ } +/*! 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: + * - NeverVisibile: Never show the orbit of this object. + * - UseClassVisibility: (Default) Show the orbit of this object + * its class is enabled in the orbit mask. + * - AlwaysVisibile: 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; +} + + + /**** Implementation of PlanetarySystem ****/ PlanetarySystem::PlanetarySystem(Body* _primary) : primary(_primary) Index: celengine/body.h =================================================================== --- celengine/body.h (revision 4077) +++ celengine/body.h (working copy) @@ -1,295 +1,319 @@ -// body.h -// -// Copyright (C) 2001-2006 Chris Laurel -// -// 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. - -#ifndef _CELENGINE_BODY_H_ -#define _CELENGINE_BODY_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class ReferenceFrame; -class Body; - -class PlanetarySystem -{ - public: - PlanetarySystem(Body* _primary); - PlanetarySystem(Star* _star); - - Star* getStar() const { return star; }; - Body* getPrimaryBody() const { return primary; }; - int getSystemSize() const { return satellites.size(); }; - Body* getBody(int i) const { return satellites[i]; }; - void addBody(Body* body); - void removeBody(Body* body); - void replaceBody(Body* oldBody, Body* newBody); - - enum TraversalResult - { - ContinueTraversal = 0, - StopTraversal = 1 - }; - - typedef bool (*TraversalFunc)(Body*, void*); - - bool traverse(TraversalFunc, void*) const; - Body* find(const std::string&, bool deepSearch = false, bool i18n = false) const; - std::vector getCompletion(const std::string& _name, bool rec = true) const; - - private: - typedef std::map ObjectIndex; - - private: - Star* star; - Body* primary; - std::vector satellites; - ObjectIndex objectIndex; // index of bodies by name - ObjectIndex i18nObjectIndex; -}; - - -class RingSystem -{ - public: - float innerRadius; - float outerRadius; - Color color; - MultiResTexture texture; - - RingSystem(float inner, float outer) : - innerRadius(inner), outerRadius(outer), color(1.0f, 1.0f, 1.0f), texture() - { }; - RingSystem(float inner, float outer, Color _color, int _loTexture = -1, int _texture = -1) : - innerRadius(inner), outerRadius(outer), color(_color), texture(_loTexture, _texture) - { }; - RingSystem(float inner, float outer, Color _color, const MultiResTexture& _texture) : - innerRadius(inner), outerRadius(outer), color(_color), texture(_texture) - { }; -}; - - -class Body -{ - public: - Body(PlanetarySystem*); - ~Body(); - - enum - { - Planet = 0x01, - Moon = 0x02, - Asteroid = 0x04, - Comet = 0x08, - Spacecraft = 0x10, - Invisible = 0x20, - Barycenter = 0x40, - SmallBody = 0x80, - DwarfPlanet = 0x100, - Stellar = 0x200, // only used for orbit mask - Unknown = 0x10000, - }; - - PlanetarySystem* getSystem() const; - std::string getName(bool i18n = false) const; - void setName(const std::string); - Orbit* getOrbit() const; - void setOrbit(Orbit*); - const Body* getOrbitBarycenter() const; - void setOrbitBarycenter(const Body*); - - const ReferenceFrame* getOrbitFrame() const; - void setOrbitFrame(const ReferenceFrame* f); - const ReferenceFrame* getBodyFrame() const; - void setBodyFrame(const ReferenceFrame* f); - - const RotationModel* getRotationModel() const; - void setRotationModel(const RotationModel*); - - // Size methods - void setSemiAxes(const Vec3f&); - Vec3f getSemiAxes() const; - float getRadius() const; - bool isSphere() const; - bool isEllipsoid() const; - - float getMass() const; - void setMass(float); - float getAlbedo() const; - void setAlbedo(float); - Quatf getOrientation() const; - void setOrientation(const Quatf&); - int getClassification() const; - void setClassification(int); - std::string getInfoURL() const; - void setInfoURL(const std::string&); - - PlanetarySystem* getSatellites() const; - void setSatellites(PlanetarySystem*); - - float getBoundingRadius() const; - - RingSystem* getRings() const; - void setRings(const RingSystem&); - const Atmosphere* getAtmosphere() const; - Atmosphere* getAtmosphere(); - void setAtmosphere(const Atmosphere&); - - void setModel(ResourceHandle); - ResourceHandle getModel() const; - void setSurface(const Surface&); - const Surface& getSurface() const; - Surface& getSurface(); - - float getLuminosity(const Star& sun, - float distanceFromSun) const; - float getLuminosity(float sunLuminosity, - float distanceFromSun) const; - - /*! Get the apparent magnitude of the body, neglecting the phase (as if - * the body was at opposition. - */ - float getApparentMagnitude(const Star& sun, - float distanceFromSun, - float distanceFromViewer) const; - - /*! Get the apparent magnitude of the body, neglecting the phase (as if - * the body was at opposition. - */ - float getApparentMagnitude(float sunLuminosity, - float distanceFromSun, - float distanceFromViewer) const; - - /*! Get the apparent magnitude of the body, corrected for its phase. - */ - float getApparentMagnitude(const Star& sun, - const Vec3d& sunPosition, - const Vec3d& viewerPosition) const; - - /*! Get the apparent magnitude of the body, corrected for the phase. - */ - float getApparentMagnitude(float sunLuminosity, - const Vec3d& sunPosition, - const Vec3d& viewerPosition) const; - - /*! Get the transformation which converts body coordinates into - * heliocentric coordinates. Some clarification on the meaning - * of 'heliocentric': the position of every solar system body - * is ultimately defined with respect to some star or star - * system barycenter. Currently, this star is the root of the - * name hierarchy containing the body. In future (post-1.5.0) - * versions of Celestia, this will be changed so that the - * reference star is the root of the frame hierarchy. - */ - Mat4d getLocalToHeliocentric(double) const; - Point3d getHeliocentricPosition(double) const; - Quatd getEquatorialToBodyFixed(double) const; - Quatd getEclipticalToFrame(double) const; - Quatd getEclipticalToEquatorial(double) const; - Quatd getEclipticalToBodyFixed(double) const; - Mat4d getBodyFixedToHeliocentric(double) const; - - Vec3d planetocentricToCartesian(double lon, double lat, double alt) const; - Vec3d planetocentricToCartesian(const Vec3d& lonLatAlt) const; - Vec3d cartesianToPlanetocentric(const Vec3d& v) const; - - Vec3d eclipticToPlanetocentric(const Vec3d& ecl, double tdb) const; - - - bool extant(double) const; - void setLifespan(double, double); - void getLifespan(double&, double&) const; - - Surface* getAlternateSurface(const std::string&) const; - void addAlternateSurface(const std::string&, Surface*); - std::vector* getAlternateSurfaceNames() const; - - std::vector* getLocations() const; - void addLocation(Location*); - Location* findLocation(const std::string&, bool i18n = false) const; - void computeLocations(); - - bool isClickable() const { return clickable == 1; } - void setClickable(bool _clickable); - bool isVisibleAsPoint() const { return visibleAsPoint == 1; } - void setVisibleAsPoint(bool _visibleAsPoint); - - enum - { - BodyAxes = 0x01, - FrameAxes = 0x02, - LongLatGrid = 0x04, - SunDirection = 0x08, - VelocityVector = 0x10, - }; - - bool referenceMarkVisible(uint32) const; - uint32 getVisibleReferenceMarks() const; - void setVisibleReferenceMarks(uint32); - - Star* getReferenceStar() const; - Star* getFrameReferenceStar() const; - - private: - std::string name; - std::string i18nName; - - PlanetarySystem* system; - - Orbit* orbit; - const Body* orbitBarycenter; - const ReferenceFrame* orbitFrame; - const ReferenceFrame* bodyFrame; - - const RotationModel* rotationModel; - - float radius; - Vec3f semiAxes; - float mass; - float albedo; - Quatf orientation; - - double protos; - double eschatos; - - ResourceHandle model; - Surface surface; - - Atmosphere* atmosphere; - RingSystem* rings; - - PlanetarySystem* satellites; - - int classification; - - std::string infoURL; - - typedef std::map AltSurfaceTable; - AltSurfaceTable *altSurfaces; - - std::vector* locations; - mutable bool locationsComputed; - - uint32 referenceMarks; - unsigned int clickable : 1; - unsigned int visibleAsPoint : 1; - - // Only necessary until we switch to using frame hierarchy - Star* frameRefStar; -}; - -#endif // _CELENGINE_BODY_H_ +// body.h +// +// Copyright (C) 2001-2006 Chris Laurel +// +// 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. + +#ifndef _CELENGINE_BODY_H_ +#define _CELENGINE_BODY_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ReferenceFrame; +class Body; + +class PlanetarySystem +{ + public: + PlanetarySystem(Body* _primary); + PlanetarySystem(Star* _star); + + Star* getStar() const { return star; }; + Body* getPrimaryBody() const { return primary; }; + int getSystemSize() const { return satellites.size(); }; + Body* getBody(int i) const { return satellites[i]; }; + void addBody(Body* body); + void removeBody(Body* body); + void replaceBody(Body* oldBody, Body* newBody); + + enum TraversalResult + { + ContinueTraversal = 0, + StopTraversal = 1 + }; + + typedef bool (*TraversalFunc)(Body*, void*); + + bool traverse(TraversalFunc, void*) const; + Body* find(const std::string&, bool deepSearch = false, bool i18n = false) const; + std::vector getCompletion(const std::string& _name, bool rec = true) const; + + private: + typedef std::map ObjectIndex; + + private: + Star* star; + Body* primary; + std::vector satellites; + ObjectIndex objectIndex; // index of bodies by name + ObjectIndex i18nObjectIndex; +}; + + +class RingSystem +{ + public: + float innerRadius; + float outerRadius; + Color color; + MultiResTexture texture; + + RingSystem(float inner, float outer) : + innerRadius(inner), outerRadius(outer), color(1.0f, 1.0f, 1.0f), texture() + { }; + RingSystem(float inner, float outer, Color _color, int _loTexture = -1, int _texture = -1) : + innerRadius(inner), outerRadius(outer), color(_color), texture(_loTexture, _texture) + { }; + RingSystem(float inner, float outer, Color _color, const MultiResTexture& _texture) : + innerRadius(inner), outerRadius(outer), color(_color), texture(_texture) + { }; +}; + + +class Body +{ + public: + Body(PlanetarySystem*); + ~Body(); + + enum + { + Planet = 0x01, + Moon = 0x02, + Asteroid = 0x04, + Comet = 0x08, + Spacecraft = 0x10, + Invisible = 0x20, + Barycenter = 0x40, + SmallBody = 0x80, + DwarfPlanet = 0x100, + Stellar = 0x200, // only used for orbit mask + SurfaceFeature = 0x400, + Component = 0x800, + Unknown = 0x10000, + }; + + enum VisibilityPolicy + { + NeverVisibile = 0, + UseClassVisibility = 1, + AlwaysVisible = 2, + }; + + PlanetarySystem* getSystem() const; + std::string getName(bool i18n = false) const; + void setName(const std::string); + Orbit* getOrbit() const; + void setOrbit(Orbit*); + const Body* getOrbitBarycenter() const; + void setOrbitBarycenter(const Body*); + + const ReferenceFrame* getOrbitFrame() const; + void setOrbitFrame(const ReferenceFrame* f); + const ReferenceFrame* getBodyFrame() const; + void setBodyFrame(const ReferenceFrame* f); + + const RotationModel* getRotationModel() const; + void setRotationModel(const RotationModel*); + + // Size methods + void setSemiAxes(const Vec3f&); + Vec3f getSemiAxes() const; + float getRadius() const; + bool isSphere() const; + bool isEllipsoid() const; + + float getMass() const; + void setMass(float); + float getAlbedo() const; + void setAlbedo(float); + Quatf getOrientation() const; + void setOrientation(const Quatf&); + int getClassification() const; + void setClassification(int); + std::string getInfoURL() const; + void setInfoURL(const std::string&); + + PlanetarySystem* getSatellites() const; + void setSatellites(PlanetarySystem*); + + float getBoundingRadius() const; + + RingSystem* getRings() const; + void setRings(const RingSystem&); + const Atmosphere* getAtmosphere() const; + Atmosphere* getAtmosphere(); + void setAtmosphere(const Atmosphere&); + + void setModel(ResourceHandle); + ResourceHandle getModel() const; + void setSurface(const Surface&); + const Surface& getSurface() const; + Surface& getSurface(); + + float getLuminosity(const Star& sun, + float distanceFromSun) const; + float getLuminosity(float sunLuminosity, + float distanceFromSun) const; + + /*! Get the apparent magnitude of the body, neglecting the phase (as if + * the body was at opposition. + */ + float getApparentMagnitude(const Star& sun, + float distanceFromSun, + float distanceFromViewer) const; + + /*! Get the apparent magnitude of the body, neglecting the phase (as if + * the body was at opposition. + */ + float getApparentMagnitude(float sunLuminosity, + float distanceFromSun, + float distanceFromViewer) const; + + /*! Get the apparent magnitude of the body, corrected for its phase. + */ + float getApparentMagnitude(const Star& sun, + const Vec3d& sunPosition, + const Vec3d& viewerPosition) const; + + /*! Get the apparent magnitude of the body, corrected for the phase. + */ + float getApparentMagnitude(float sunLuminosity, + const Vec3d& sunPosition, + const Vec3d& viewerPosition) const; + + /*! Get the transformation which converts body coordinates into + * heliocentric coordinates. Some clarification on the meaning + * of 'heliocentric': the position of every solar system body + * is ultimately defined with respect to some star or star + * system barycenter. Currently, this star is the root of the + * name hierarchy containing the body. In future (post-1.5.0) + * versions of Celestia, this will be changed so that the + * reference star is the root of the frame hierarchy. + */ + Mat4d getLocalToHeliocentric(double) const; + Point3d getHeliocentricPosition(double) const; + Quatd getEquatorialToBodyFixed(double) const; + Quatd getEclipticalToFrame(double) const; + Quatd getEclipticalToEquatorial(double) const; + Quatd getEclipticalToBodyFixed(double) const; + Mat4d getBodyFixedToHeliocentric(double) const; + + Vec3d planetocentricToCartesian(double lon, double lat, double alt) const; + Vec3d planetocentricToCartesian(const Vec3d& lonLatAlt) const; + Vec3d cartesianToPlanetocentric(const Vec3d& v) const; + + Vec3d eclipticToPlanetocentric(const Vec3d& ecl, double tdb) const; + + + bool extant(double) const; + void setLifespan(double, double); + void getLifespan(double&, double&) const; + + Surface* getAlternateSurface(const std::string&) const; + void addAlternateSurface(const std::string&, Surface*); + std::vector* getAlternateSurfaceNames() const; + + std::vector* getLocations() const; + void addLocation(Location*); + Location* findLocation(const std::string&, bool i18n = false) const; + void computeLocations(); + + bool isVisible() const { return visible == 1; } + void setVisible(bool _visible); + bool isClickable() const { return clickable == 1; } + void setClickable(bool _clickable); + bool isVisibleAsPoint() const { return visibleAsPoint == 1; } + void setVisibleAsPoint(bool _visibleAsPoint); + bool isOrbitColorOverridden() const { return overrideOrbitColor == 1; } + void setOrbitColorOverridden(bool override); + VisibilityPolicy getOrbitVisibility() const { return orbitVisibility; } + void setOrbitVisibility(VisibilityPolicy _orbitVisibility); + + Color getOrbitColor() const { return orbitColor; } + void setOrbitColor(const Color&); + + enum + { + BodyAxes = 0x01, + FrameAxes = 0x02, + LongLatGrid = 0x04, + SunDirection = 0x08, + VelocityVector = 0x10, + }; + + bool referenceMarkVisible(uint32) const; + uint32 getVisibleReferenceMarks() const; + void setVisibleReferenceMarks(uint32); + + Star* getReferenceStar() const; + Star* getFrameReferenceStar() const; + + private: + std::string name; + std::string i18nName; + + PlanetarySystem* system; + + Orbit* orbit; + const Body* orbitBarycenter; + const ReferenceFrame* orbitFrame; + const ReferenceFrame* bodyFrame; + + const RotationModel* rotationModel; + + float radius; + Vec3f semiAxes; + float mass; + float albedo; + Quatf orientation; + + double protos; + double eschatos; + + ResourceHandle model; + Surface surface; + + Atmosphere* atmosphere; + RingSystem* rings; + + PlanetarySystem* satellites; + + int classification; + + std::string infoURL; + + typedef std::map AltSurfaceTable; + AltSurfaceTable *altSurfaces; + + std::vector* locations; + mutable bool locationsComputed; + + uint32 referenceMarks; + + Color orbitColor; + + unsigned int visible : 1; + unsigned int clickable : 1; + unsigned int visibleAsPoint : 1; + unsigned int overrideOrbitColor : 1; + VisibilityPolicy orbitVisibility : 2; + + // Only necessary until we switch to using frame hierarchy + Star* frameRefStar; +}; + +#endif // _CELENGINE_BODY_H_ Index: celengine/render.cpp =================================================================== --- celengine/render.cpp (revision 4077) +++ celengine/render.cpp (working copy) @@ -416,6 +416,9 @@ } +#if 0 +// Not used yet. + // The RectToSpherical map converts XYZ coordinates to UV coordinates // via a cube map lookup. However, a lot of GPUs don't support linear // interpolation of textures with > 8 bits per component, which is @@ -446,6 +449,7 @@ pixel[2] = ba >> 8; pixel[3] = ba & 0xff; } +#endif static void BuildGaussianDiscMipLevel(unsigned char* mipPixels, @@ -1161,7 +1165,7 @@ }; -void renderOrbitColor(int classification, bool selected, float opacity) +void renderOrbitColor(const Body *body, bool selected, float opacity) { Color orbitColor; @@ -1170,8 +1174,18 @@ // Highlight the orbit of the selected object in red orbitColor = Renderer::SelectionOrbitColor; } + else if (body != NULL && body->isOrbitColorOverridden()) + { + orbitColor = body->getOrbitColor(); + } else { + int classification; + if (body != NULL) + classification = body->getClassification(); + else + classification = Body::Stellar; + switch (classification) { case Body::Moon: @@ -1356,6 +1370,7 @@ } +#if 0 // Not yet used static Point3d renderOrbitSplineAdaptive(const Renderer::OrbitSample& p0, const Renderer::OrbitSample& p1, @@ -1436,6 +1451,7 @@ return lastP; } +#endif static Point3d renderOrbitSection(const Orbit& orbit, @@ -1743,7 +1759,7 @@ highlight = highlightObject.body() == body; else highlight = highlightObject.star() == orbitPath.star; - renderOrbitColor(body != NULL ? body->getClassification() : Body::Stellar, highlight, orbitPath.opacity); + renderOrbitColor(body, highlight, orbitPath.opacity); if ((renderFlags & ShowPartialTrajectories) == 0 || orbit->isPeriodic()) { @@ -6302,7 +6318,7 @@ // ignore eclipses where the caster is not an ellipsoid, since we can't // generate correct shadows in this case. if (caster.getRadius() >= receiver.getRadius() * MinRelativeOccluderRadius && - caster.getClassification() != Body::Invisible && + caster.isVisible() && caster.extant(now) && caster.getModel() == InvalidResource) { @@ -6447,7 +6463,7 @@ // Calculate eclipse circumstances if ((renderFlags & ShowEclipseShadows) != 0 && - body.getClassification() != Body::Invisible && + body.isVisible() && body.getSystem() != NULL) { PlanetarySystem* system = body.getSystem(); @@ -6532,28 +6548,31 @@ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); - if (useNewStarRendering) + if (body.isVisibleAsPoint()) { - renderObjectAsPoint(pos, - body.getRadius(), - appMag, - faintestMag, - discSizeInPixels, - body.getSurface().color, - cameraOrientation, - false, false); + if (useNewStarRendering) + { + renderObjectAsPoint(pos, + body.getRadius(), + appMag, + faintestMag, + discSizeInPixels, + body.getSurface().color, + cameraOrientation, + false, false); + } + else + { + renderBodyAsParticle(pos, + appMag, + faintestMag, + discSizeInPixels, + body.getSurface().color, + cameraOrientation, + (nearPlaneDistance + farPlaneDistance) / 2.0f, + false); + } } - else - { - renderBodyAsParticle(pos, - appMag, - faintestMag, - discSizeInPixels, - body.getSurface().color, - cameraOrientation, - (nearPlaneDistance + farPlaneDistance) / 2.0f, - false); - } } @@ -7175,9 +7194,9 @@ } Vec3f pos((float) posd.x, (float) posd.y, (float) posd.z); + bool visibleAsPoint = appMag < faintestPlanetMag && body->isVisibleAsPoint(); - if ((discSize > 1 || appMag < faintestPlanetMag) && - body->getClassification() != Body::Invisible) + if ((discSize > 1 || visibleAsPoint) && body->isVisible()) { RenderListEntry rle; rle.renderableType = RenderListEntry::RenderableBody; Index: celengine/selection.cpp =================================================================== --- celengine/selection.cpp (revision 4073) +++ celengine/selection.cpp (working copy) @@ -175,3 +175,19 @@ return Selection(); } } + + +/*! Return true if the selection's visibility flag is set. */ +bool Selection::isVisible() const +{ + switch (type) + { + case Type_Body: + return body()->isVisible(); + case Type_Star: + case Type_DeepSky: + return true; + default: + return false; + } +} Index: celengine/selection.h =================================================================== --- celengine/selection.h (revision 4073) +++ celengine/selection.h (working copy) @@ -43,6 +43,8 @@ std::string getName(bool i18n = false) const; Selection parent() const; + bool isVisible() const; + Star* star() const { return type == Type_Star ? static_cast(obj) : NULL; Index: celengine/solarsys.cpp =================================================================== --- celengine/solarsys.cpp (revision 4077) +++ celengine/solarsys.cpp (working copy) @@ -1,927 +1,946 @@ -// solarsys.cpp -// -// Copyright (C) 2001-2006 Chris Laurel -// -// Solar system catalog parser. -// -// 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 -// #include -#include - -#ifndef _WIN32 -#ifndef TARGET_OS_MAC -#include -#endif /* ! TARGET_OS_MAC */ -#endif /* ! _WIN32 */ - -#include -#include -#include -#include -#include "astro.h" -#include "parser.h" -#include "texmanager.h" -#include "meshmanager.h" -#include "universe.h" -#include "multitexture.h" -#include "parseobject.h" - -using namespace std; - - -enum Disposition -{ - AddObject, - ReplaceObject, - ModifyObject, -}; - - -/*! - Solar system catalog (.ssc) files contain items of three different types: - bodies, locations, and alternate surfaces. Bodies planets, moons, asteroids, - comets, and spacecraft. Locations are points on the surfaces of bodies which - may be labelled but aren't rendered. Alternate surfaces are additional - surface definitions for bodies. - - An ssc file contains zero or more definitions of this form: - - \code - [disposition] [item type] "name" "parent name" - { - ...object info fields... - } - \endcode - - The disposition of the object determines what happens if an item with the - same parent and same name already exists. It may be one of the following: - - Add - Default if none is specified. Add the item even if one of the - same name already exists. - - Replace - Replace an existing item with the new one - - Modify - Modify the existing item, changing the fields that appear - in the new definition. - - All dispositions are equivalent to add if no item of the same name - already exists. - - The item type is one of Body, Location, or AltSurface, defaulting to - Body when no type is given. - - The name and parent name are both mandatory. -*/ - -static void errorMessagePrelude(const Tokenizer& tok) -{ - cerr << _("Error in .ssc file (line ") << tok.getLineNumber() << "): "; -} - -static void sscError(const Tokenizer& tok, - const string& msg) -{ - errorMessagePrelude(tok); - cerr << msg << '\n'; -} - - -//! Maximum depth permitted for nested frames. -static unsigned int MaxFrameDepth = 50; - -static bool isFrameCircular(const ReferenceFrame& frame, ReferenceFrame::FrameType frameType) -{ - return frame.nestingDepth(MaxFrameDepth, frameType) > MaxFrameDepth; -} - - - -static Location* CreateLocation(Hash* locationData, - Body* body) -{ - Location* location = new Location(); - - Vec3d longlat(0.0, 0.0, 0.0); - locationData->getVector("LongLat", longlat); - - Vec3d position = body->planetocentricToCartesian(longlat.x, - longlat.y, - longlat.z); - location->setPosition(Vec3f((float) position.x, (float) position.y, (float) position.z)); - - double size = 1.0; - locationData->getNumber("Size", size); - location->setSize((float) size); - - double importance = -1.0; - locationData->getNumber("Importance", importance); - location->setImportance((float) importance); - - string featureTypeName; - if (locationData->getString("Type", featureTypeName)) - location->setFeatureType(Location::parseFeatureType(featureTypeName)); - - return location; -} - - -static void FillinSurface(Hash* surfaceData, - Surface* surface, - const std::string& path) -{ - surfaceData->getColor("Color", surface->color); - - // Haze is deprecated; used only in pre-OpenGL 2.0 render paths - Color hazeColor = surface->hazeColor; - float hazeDensity = hazeColor.alpha(); - if (surfaceData->getColor("HazeColor", hazeColor) | surfaceData->getNumber("HazeDensity", hazeDensity)) - { - surface->hazeColor = Color(hazeColor.red(), hazeColor.green(), - hazeColor.blue(), hazeDensity); - } - - surfaceData->getColor("SpecularColor", surface->specularColor); - surfaceData->getNumber("SpecularPower", surface->specularPower); - - surfaceData->getNumber("LunarLambert", surface->lunarLambert); - - string baseTexture; - string bumpTexture; - string nightTexture; - string specularTexture; - string normalTexture; - string overlayTexture; - bool applyBaseTexture = surfaceData->getString("Texture", baseTexture); - bool applyBumpMap = surfaceData->getString("BumpMap", bumpTexture); - bool applyNightMap = surfaceData->getString("NightTexture", nightTexture); - bool separateSpecular = surfaceData->getString("SpecularTexture", - specularTexture); - bool applyNormalMap = surfaceData->getString("NormalMap", normalTexture); - bool applyOverlay = surfaceData->getString("OverlayTexture", - overlayTexture); - - unsigned int baseFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; - unsigned int bumpFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; - unsigned int nightFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; - unsigned int specularFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; - - float bumpHeight = 2.5f; - surfaceData->getNumber("BumpHeight", bumpHeight); - - bool blendTexture = false; - surfaceData->getBoolean("BlendTexture", blendTexture); - - bool emissive = false; - surfaceData->getBoolean("Emissive", emissive); - - bool compressTexture = false; - surfaceData->getBoolean("CompressTexture", compressTexture); - if (compressTexture) - baseFlags |= TextureInfo::CompressTexture; - - if (blendTexture) - surface->appearanceFlags |= Surface::BlendTexture; - if (emissive) - surface->appearanceFlags |= Surface::Emissive; - if (applyBaseTexture) - surface->appearanceFlags |= Surface::ApplyBaseTexture; - if (applyBumpMap || applyNormalMap) - surface->appearanceFlags |= Surface::ApplyBumpMap; - if (applyNightMap) - surface->appearanceFlags |= Surface::ApplyNightMap; - if (separateSpecular) - surface->appearanceFlags |= Surface::SeparateSpecularMap; - if (applyOverlay) - surface->appearanceFlags |= Surface::ApplyOverlay; - if (surface->specularColor != Color(0.0f, 0.0f, 0.0f)) - surface->appearanceFlags |= Surface::SpecularReflection; - - if (applyBaseTexture) - surface->baseTexture.setTexture(baseTexture, path, baseFlags); - if (applyNightMap) - surface->nightTexture.setTexture(nightTexture, path, nightFlags); - if (separateSpecular) - surface->specularTexture.setTexture(specularTexture, path, specularFlags); - - // If both are present, NormalMap overrides BumpMap - if (applyNormalMap) - surface->bumpTexture.setTexture(normalTexture, path, bumpFlags); - else if (applyBumpMap) - surface->bumpTexture.setTexture(bumpTexture, path, bumpHeight, bumpFlags); - - if (applyOverlay) - surface->overlayTexture.setTexture(overlayTexture, path, baseFlags); -} - - -// Set up the orbit barycenter for a body. By default, it is the parent of the -// object -static Selection GetOrbitBarycenter(const string& name, - PlanetarySystem* system) -{ - Selection orbitBarycenter; - Body* primary = system->getPrimaryBody(); - if (primary != NULL) - orbitBarycenter = Selection(primary); - else - orbitBarycenter = Selection(system->getStar()); - - // The barycenter must be in the same star system as the object we're creating - if (orbitBarycenter.body()) - { - if (system->getStar() != orbitBarycenter.body()->getSystem()->getStar()) - { - cerr << "OrbitBarycenter" << _(" of ") << name << _(" must be in same star system\n"); - return Selection(); - } - } - else if (orbitBarycenter.star()) - { - if (system->getStar() != orbitBarycenter.star()) - { - cerr << "OrbitBarycenter" << _(" of ") << name << _(" must be in same star system\n"); - return Selection(); - } - } - - return orbitBarycenter; -} - - -// Create a body (planet or moon) using the values from a hash -// The usePlanetsUnits flags specifies whether period and semi-major axis -// are in years and AU rather than days and kilometers -static Body* CreatePlanet(const string& name, - PlanetarySystem* system, - Universe& universe, - Body* existingBody, - Hash* planetData, - const string& path, - Disposition disposition) -{ - Body* body = NULL; - - if (disposition == ModifyObject) - { - body = existingBody; - } - - if (body == NULL) - { - body = new Body(system); - } - - Selection orbitBarycenter = GetOrbitBarycenter(name, system); - bool orbitsPlanet = false; - if (orbitBarycenter.body()) - { - body->setOrbitBarycenter(orbitBarycenter.body()); - orbitsPlanet = true; - } - else if (orbitBarycenter.star()) - { - body->setOrbitBarycenter(NULL); - } - else - { - // Bad orbit barycenter specified - if (body != existingBody) - delete body; - return NULL; - } - - // Set the reference frame of the orbit - Value* frameValue = planetData->getValue("OrbitFrame"); - if (frameValue != NULL) - { - ReferenceFrame* frame = CreateReferenceFrame(universe, frameValue); - if (frame != NULL) - { - body->setOrbitFrame(frame); - - // If the center of the is a star, orbital element units are - // in AU; otherwise, use kilometers. - if (frame->getCenter().star() != NULL) - orbitsPlanet = false; - else - orbitsPlanet = true; - } - } - - Value* bodyFrameValue = planetData->getValue("BodyFrame"); - if (bodyFrameValue != NULL) - { - ReferenceFrame* frame = CreateReferenceFrame(universe, bodyFrameValue); - if (frame != NULL) - { - body->setBodyFrame(frame); - } - } - - if (body->getOrbitFrame() != NULL) - { - if (isFrameCircular(*body->getOrbitFrame(), ReferenceFrame::PositionFrame)) - { - clog << "Orbit frame for " << body->getName() << " is nested too deep (probably circular)\n"; - body->setOrbitFrame(NULL); - } - } - - if (body->getBodyFrame() != NULL) - { - if (isFrameCircular(*body->getBodyFrame(), ReferenceFrame::OrientationFrame)) - { - clog << "Body frame for " << body->getName() << " is nested too deep (probably circular)\n"; - body->setBodyFrame(NULL); - } - } - - - Orbit* orbit = CreateOrbit(system, planetData, path, !orbitsPlanet); - if (orbit != NULL) - { - body->setOrbit(orbit); - } - - if (body->getOrbit() == NULL) - { - DPRINTF(0, "No valid orbit specified for object '%s'; skipping . . .\n", - body->getName().c_str()); - if (body != existingBody) - delete body; - return NULL; - } - - // Three values control the shape and size of an ellipsoidal object: - // semiAxes, radius, and oblateness. It is an error if neither the - // radius nor semiaxes are set. If both are set, the radius is - // multipled by each of the specified semiaxis to give the shape of - // the body ellipsoid. Oblateness is ignored if semiaxes are provided; - // otherwise, the ellipsoid has semiaxes: ( radius, radius, 1-radius ). - // These rather complex rules exist to maintain backward compatibility. - // - // If the body also has a mesh, it is always scaled in x, y, and z by - // the maximum semiaxis, never anisotropically. - - double radius = (double) body->getRadius(); - bool radiusSpecified = false; - if (planetData->getNumber("Radius", radius)) - { - body->setSemiAxes(Vec3f((float) radius, (float) radius, (float) radius)); - radiusSpecified = true; - } - - Vec3d semiAxes; - if (planetData->getVector("SemiAxes", semiAxes)) - { - if (radiusSpecified) - semiAxes *= radius; - // Swap y and z to match internal coordinate system - body->setSemiAxes(Vec3f((float) semiAxes.x, (float) semiAxes.z, (float) semiAxes.y)); - } - else - { - double oblateness = 0.0; - if (planetData->getNumber("Oblateness", oblateness)) - { - body->setSemiAxes((float) body->getRadius() * Vec3f(1.0f, 1.0f - (float) oblateness, 1.0f)); - } - } - - - int classification = body->getClassification(); - string classificationName; - if (planetData->getString("Class", classificationName)) - { - if (compareIgnoringCase(classificationName, "planet") == 0) - classification = Body::Planet; - else if (compareIgnoringCase(classificationName, "moon") == 0) - classification = Body::Moon; - else if (compareIgnoringCase(classificationName, "comet") == 0) - classification = Body::Comet; - else if (compareIgnoringCase(classificationName, "asteroid") == 0) - classification = Body::Asteroid; - else if (compareIgnoringCase(classificationName, "spacecraft") == 0) - classification = Body::Spacecraft; - else if (compareIgnoringCase(classificationName, "invisible") == 0) - classification = Body::Invisible; - } - - if (classification == Body::Unknown) - { - // Try to guess the type - if (system->getPrimaryBody() != NULL) - { - if(radius > 0.1) - classification = Body::Moon; - else - classification = Body::Spacecraft; - } - else - { - if (radius < 1000.0) - classification = Body::Asteroid; - else - classification = Body::Planet; - } - } - body->setClassification(classification); - - // g++ is missing limits header, so we can use this - // double beginning = -numeric_limits::infinity(); - // double ending = numeric_limits::infinity(); - double beginning = -1.0e+50; - double ending = 1.0e+50; - body->getLifespan(beginning, ending); - ParseDate(planetData, "Beginning", beginning); - ParseDate(planetData, "Ending", ending); - body->setLifespan(beginning, ending); - - string infoURL; - if (planetData->getString("InfoURL", infoURL)) - { - if (infoURL.find(':') == string::npos) - { - // Relative URL, the base directory is the current one, - // not the main installation directory - if (path[1] == ':') - // Absolute Windows path, file:/// is required - infoURL = "file:///" + path + "/" + infoURL; - else if (!path.empty()) - infoURL = path + "/" + infoURL; - } - body->setInfoURL(infoURL); - } - - double albedo = 0.5; - if (planetData->getNumber("Albedo", albedo)) - body->setAlbedo((float) albedo); - - double mass = 0.0; - if (planetData->getNumber("Mass", mass)) - body->setMass((float) mass); - - Quatf orientation; - if (planetData->getRotation("Orientation", orientation)) - body->setOrientation(orientation); - - // Get the rotation model for this body - double syncRotationPeriod = body->getOrbit()->getPeriod(); - RotationModel* rm = CreateRotationModel(planetData, path, - syncRotationPeriod); - if (rm != NULL) - { - // TODO: Free old rotation model and replace it with the new one; - // should reference count rotation model objects. - body->setRotationModel(rm); - } - else - { - // If no rotation model is provided, use a default rotation model-- - // a uniform rotation that's synchronous with the orbit (appropriate - // for nearly all natural satellites in the solar system.) If the - // disposition is modify, we do not want to replace the existing - // rotation model with a default one. - if (disposition != ModifyObject) - { - body->setRotationModel(CreateDefaultRotationModel(syncRotationPeriod)); - } - } - - Surface surface; - if (disposition == ModifyObject) - { - surface = body->getSurface(); - } - else - { - surface.color = Color(1.0f, 1.0f, 1.0f); - surface.hazeColor = Color(0.0f, 0.0f, 0.0f, 0.0f); - } - FillinSurface(planetData, &surface, path); - body->setSurface(surface); - - { - string model(""); - if (planetData->getString("Mesh", model)) - { - Vec3f modelCenter(0.0f, 0.0f, 0.0f); - if (planetData->getVector("MeshCenter", modelCenter)) - { - // TODO: Adjust bounding radius if model center isn't - // (0.0f, 0.0f, 0.0f) - } - - ResourceHandle modelHandle = GetModelManager()->getHandle(ModelInfo(model, path, modelCenter)); - body->setModel(modelHandle); - - } - } - - // Read the atmosphere - { - Value* atmosDataValue = planetData->getValue("Atmosphere"); - if (atmosDataValue != NULL) - { - if (atmosDataValue->getType() != Value::HashType) - { - cout << "ReadSolarSystem: Atmosphere must be an assoc array.\n"; - } - else - { - Hash* atmosData = atmosDataValue->getHash(); - assert(atmosData != NULL); - - Atmosphere* atmosphere = NULL; - if (disposition == ModifyObject) - { - atmosphere = body->getAtmosphere(); - if (atmosphere == NULL) - { - Atmosphere atm; - body->setAtmosphere(atm); - atmosphere = body->getAtmosphere(); - } - } - else - { - atmosphere = new Atmosphere(); - } - atmosData->getNumber("Height", atmosphere->height); - atmosData->getColor("Lower", atmosphere->lowerColor); - atmosData->getColor("Upper", atmosphere->upperColor); - atmosData->getColor("Sky", atmosphere->skyColor); - atmosData->getColor("Sunset", atmosphere->sunsetColor); - - atmosData->getNumber("Mie", atmosphere->mieCoeff); - atmosData->getNumber("MieScaleHeight", atmosphere->mieScaleHeight); - atmosData->getNumber("MieAsymmetry", atmosphere->miePhaseAsymmetry); - atmosData->getVector("Rayleigh", atmosphere->rayleighCoeff); - //atmosData->getNumber("RayleighScaleHeight", atmosphere->rayleighScaleHeight); - atmosData->getVector("Absorption", atmosphere->absorptionCoeff); - - // Get the cloud map settings - atmosData->getNumber("CloudHeight", atmosphere->cloudHeight); - if (atmosData->getNumber("CloudSpeed", atmosphere->cloudSpeed)) - atmosphere->cloudSpeed = degToRad(atmosphere->cloudSpeed); - - string cloudTexture; - if (atmosData->getString("CloudMap", cloudTexture)) - { - atmosphere->cloudTexture.setTexture(cloudTexture, - path, - TextureInfo::WrapTexture); - } - - string cloudNormalMap; - if (atmosData->getString("CloudNormalMap", cloudNormalMap)) - { - atmosphere->cloudNormalMap.setTexture(cloudNormalMap, - path, - TextureInfo::WrapTexture); - } - - body->setAtmosphere(*atmosphere); - if (disposition != ModifyObject) - delete atmosphere; - } - } - } - - // Read the ring system - { - Value* ringsDataValue = planetData->getValue("Rings"); - if (ringsDataValue != NULL) - { - if (ringsDataValue->getType() != Value::HashType) - { - cout << "ReadSolarSystem: Rings must be an assoc array.\n"; - } - else - { - Hash* ringsData = ringsDataValue->getHash(); - // ASSERT(ringsData != NULL); - - RingSystem rings(0.0f, 0.0f); - if (body->getRings() != NULL) - rings = *body->getRings(); - - double inner = 0.0, outer = 0.0; - if (ringsData->getNumber("Inner", inner)) - rings.innerRadius = (float) inner; - if (ringsData->getNumber("Outer", outer)) - rings.outerRadius = (float) outer; - - Color color(1.0f, 1.0f, 1.0f); - if (ringsData->getColor("Color", color)) - rings.color = color; - - string textureName; - if (ringsData->getString("Texture", textureName)) - rings.texture = MultiResTexture(textureName, path); - - body->setRings(rings); - } - } - } - - bool clickable = true; - if (planetData->getBoolean("Clickable", clickable)) - { - body->setClickable(clickable); - } - - return body; -} - - -// Create a barycenter object using the values from a hash -static Body* CreateReferencePoint(const string& name, - PlanetarySystem* system, - Universe& universe, - Body* existingBody, - Hash* refPointData, - const string& path, - Disposition disposition) -{ - Body* body = NULL; - - if (disposition == ModifyObject) - { - body = existingBody; - } - - if (body == NULL) - { - body = new Body(system); - } - - body->setSemiAxes(Vec3f(1.0f, 1.0f, 1.0f)); - body->setClassification(Body::Invisible); - - Selection orbitBarycenter = GetOrbitBarycenter(name, system); - bool orbitsPlanet = false; - if (orbitBarycenter.body()) - { - body->setOrbitBarycenter(orbitBarycenter.body()); - orbitsPlanet = true; - } - else if (orbitBarycenter.star()) - { - body->setOrbitBarycenter(NULL); - } - else - { - // Bad orbit barycenter specified - if (body != existingBody) - delete body; - return NULL; - } - - // Set the reference frame of the orbit - Value* frameValue = refPointData->getValue("OrbitFrame"); - if (frameValue != NULL) - { - ReferenceFrame* frame = CreateReferenceFrame(universe, frameValue); - if (frame != NULL) - { - body->setOrbitFrame(frame); - - // If the center of the is a star, orbital element units are - // in AU; otherwise, use kilometers. - if (frame->getCenter().star() != NULL) - orbitsPlanet = false; - else - orbitsPlanet = true; - } - } - - if (body->getOrbitFrame() != NULL) - { - if (isFrameCircular(*body->getOrbitFrame(), ReferenceFrame::PositionFrame)) - { - clog << "Orbit frame for " << body->getName() << " is nested too deep (probably circular)\n"; - body->setOrbitFrame(NULL); - } - } - - Orbit* orbit = CreateOrbit(system, refPointData, path, !orbitsPlanet); - if (orbit != NULL) - { - body->setOrbit(orbit); - } - - if (body->getOrbit() == NULL) - { - DPRINTF(0, "No valid orbit specified for barycenter '%s'; skipping . . .\n", - name.c_str()); - if (body != existingBody) - delete body; - return NULL; - } - - body->setRotationModel(new ConstantOrientation(Quatd(1.0))); - - return body; -} - - -bool LoadSolarSystemObjects(istream& in, - Universe& universe, - const std::string& directory) -{ - Tokenizer tokenizer(&in); - Parser parser(&tokenizer); - - while (tokenizer.nextToken() != Tokenizer::TokenEnd) - { - // Read the disposition; if none is specified, the default is Add. - Disposition disposition = AddObject; - if (tokenizer.getTokenType() == Tokenizer::TokenName) - { - if (tokenizer.getNameValue() == "Add") - { - disposition = AddObject; - tokenizer.nextToken(); - } - else if (tokenizer.getNameValue() == "Replace") - { - disposition = ReplaceObject; - tokenizer.nextToken(); - } - else if (tokenizer.getNameValue() == "Modify") - { - disposition = ModifyObject; - tokenizer.nextToken(); - } - } - - // Read the item type; if none is specified the default is Body - string itemType("Body"); - if (tokenizer.getTokenType() == Tokenizer::TokenName) - { - itemType = tokenizer.getNameValue(); - tokenizer.nextToken(); - } - - if (tokenizer.getTokenType() != Tokenizer::TokenString) - { - sscError(tokenizer, "object name expected"); - return false; - } - string name = tokenizer.getStringValue().c_str(); - - if (tokenizer.nextToken() != Tokenizer::TokenString) - { - sscError(tokenizer, "bad parent object name"); - return false; - } - string parentName = tokenizer.getStringValue().c_str(); - - Value* objectDataValue = parser.readValue(); - if (objectDataValue == NULL) - { - sscError(tokenizer, "bad object definition"); - return false; - } - - if (objectDataValue->getType() != Value::HashType) - { - sscError(tokenizer, "{ expected"); - delete objectDataValue; - return false; - } - Hash* objectData = objectDataValue->getHash(); - - Selection parent = universe.findPath(parentName, NULL, 0); - PlanetarySystem* parentSystem = NULL; - - if (itemType == "Body" || itemType == "ReferencePoint") - { - //bool orbitsPlanet = false; - if (parent.star() != NULL) - { - SolarSystem* solarSystem = universe.getSolarSystem(parent.star()); - if (solarSystem == NULL) - { - // No solar system defined for this star yet, so we need - // to create it. - solarSystem = universe.createSolarSystem(parent.star()); - } - parentSystem = solarSystem->getPlanets(); - } - else if (parent.body() != NULL) - { - // Parent is a planet or moon - parentSystem = parent.body()->getSatellites(); - if (parentSystem == NULL) - { - // If the planet doesn't already have any satellites, we - // have to create a new planetary system for it. - parentSystem = new PlanetarySystem(parent.body()); - parent.body()->setSatellites(parentSystem); - } - //orbitsPlanet = true; - } - else - { - errorMessagePrelude(tokenizer); - cerr << _("parent body '") << parentName << _("' of '") << name << _("' not found.\n"); - } - - if (parentSystem != NULL) - { - Body* existingBody = parentSystem->find(name); - if (existingBody && disposition == AddObject) - { - errorMessagePrelude(tokenizer); - cerr << _("warning duplicate definition of ") << - parentName << " " << name << '\n'; - } - - Body* body; - if (itemType == "ReferencePoint") - body = CreateReferencePoint(name, parentSystem, universe, existingBody, objectData, directory, disposition); - else - body = CreatePlanet(name, parentSystem, universe, existingBody, objectData, directory, disposition); - - if (body != NULL) - { - body->setName(name); - if (disposition == ReplaceObject) - { - parentSystem->replaceBody(existingBody, body); - delete existingBody; - } - else if (disposition == AddObject) - { - parentSystem->addBody(body); - } - } - } - } - else if (itemType == "AltSurface") - { - Surface* surface = new Surface(); - surface->color = Color(1.0f, 1.0f, 1.0f); - surface->hazeColor = Color(0.0f, 0.0f, 0.0f, 0.0f); - FillinSurface(objectData, surface, directory); - if (surface != NULL && parent.body() != NULL) - parent.body()->addAlternateSurface(name, surface); - else - sscError(tokenizer, _("bad alternate surface")); - } - else if (itemType == "Location") - { - if (parent.body() != NULL) - { - Location* location = CreateLocation(objectData, parent.body()); - if (location != NULL) - { - location->setName(name); - parent.body()->addLocation(location); - } - else - { - sscError(tokenizer, _("bad location")); - } - } - else - { - errorMessagePrelude(tokenizer); - cerr << _("parent body '") << parentName << _("' of '") << name << _("' not found.\n"); - } - } - delete objectDataValue; - } - - // TODO: Return some notification if there's an error parsing the file - return true; -} - - -SolarSystem::SolarSystem(Star* _star) : star(_star) -{ - planets = new PlanetarySystem(_star); -} - - -Star* SolarSystem::getStar() const -{ - return star; -} - -Point3f SolarSystem::getCenter() const -{ - // TODO: This is a very simple method at the moment, but it will get - // more complex when planets around multistar systems are supported - // where the planets may orbit the center of mass of two stars. - return star->getPosition(); -} - -PlanetarySystem* SolarSystem::getPlanets() const -{ - return planets; -} +// solarsys.cpp +// +// Copyright (C) 2001-2006 Chris Laurel +// +// Solar system catalog parser. +// +// 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 +// #include +#include + +#ifndef _WIN32 +#ifndef TARGET_OS_MAC +#include +#endif /* ! TARGET_OS_MAC */ +#endif /* ! _WIN32 */ + +#include +#include +#include +#include +#include "astro.h" +#include "parser.h" +#include "texmanager.h" +#include "meshmanager.h" +#include "universe.h" +#include "multitexture.h" +#include "parseobject.h" + +using namespace std; + + +enum Disposition +{ + AddObject, + ReplaceObject, + ModifyObject, +}; + + +/*! + Solar system catalog (.ssc) files contain items of three different types: + bodies, locations, and alternate surfaces. Bodies planets, moons, asteroids, + comets, and spacecraft. Locations are points on the surfaces of bodies which + may be labelled but aren't rendered. Alternate surfaces are additional + surface definitions for bodies. + + An ssc file contains zero or more definitions of this form: + + \code + [disposition] [item type] "name" "parent name" + { + ...object info fields... + } + \endcode + + The disposition of the object determines what happens if an item with the + same parent and same name already exists. It may be one of the following: + - Add - Default if none is specified. Add the item even if one of the + same name already exists. + - Replace - Replace an existing item with the new one + - Modify - Modify the existing item, changing the fields that appear + in the new definition. + + All dispositions are equivalent to add if no item of the same name + already exists. + + The item type is one of Body, Location, or AltSurface, defaulting to + Body when no type is given. + + The name and parent name are both mandatory. +*/ + +static void errorMessagePrelude(const Tokenizer& tok) +{ + cerr << _("Error in .ssc file (line ") << tok.getLineNumber() << "): "; +} + +static void sscError(const Tokenizer& tok, + const string& msg) +{ + errorMessagePrelude(tok); + cerr << msg << '\n'; +} + + +//! Maximum depth permitted for nested frames. +static unsigned int MaxFrameDepth = 50; + +static bool isFrameCircular(const ReferenceFrame& frame, ReferenceFrame::FrameType frameType) +{ + return frame.nestingDepth(MaxFrameDepth, frameType) > MaxFrameDepth; +} + + + +static Location* CreateLocation(Hash* locationData, + Body* body) +{ + Location* location = new Location(); + + Vec3d longlat(0.0, 0.0, 0.0); + locationData->getVector("LongLat", longlat); + + Vec3d position = body->planetocentricToCartesian(longlat.x, + longlat.y, + longlat.z); + location->setPosition(Vec3f((float) position.x, (float) position.y, (float) position.z)); + + double size = 1.0; + locationData->getNumber("Size", size); + location->setSize((float) size); + + double importance = -1.0; + locationData->getNumber("Importance", importance); + location->setImportance((float) importance); + + string featureTypeName; + if (locationData->getString("Type", featureTypeName)) + location->setFeatureType(Location::parseFeatureType(featureTypeName)); + + return location; +} + + +static void FillinSurface(Hash* surfaceData, + Surface* surface, + const std::string& path) +{ + surfaceData->getColor("Color", surface->color); + + // Haze is deprecated; used only in pre-OpenGL 2.0 render paths + Color hazeColor = surface->hazeColor; + float hazeDensity = hazeColor.alpha(); + if (surfaceData->getColor("HazeColor", hazeColor) | surfaceData->getNumber("HazeDensity", hazeDensity)) + { + surface->hazeColor = Color(hazeColor.red(), hazeColor.green(), + hazeColor.blue(), hazeDensity); + } + + surfaceData->getColor("SpecularColor", surface->specularColor); + surfaceData->getNumber("SpecularPower", surface->specularPower); + + surfaceData->getNumber("LunarLambert", surface->lunarLambert); + + string baseTexture; + string bumpTexture; + string nightTexture; + string specularTexture; + string normalTexture; + string overlayTexture; + bool applyBaseTexture = surfaceData->getString("Texture", baseTexture); + bool applyBumpMap = surfaceData->getString("BumpMap", bumpTexture); + bool applyNightMap = surfaceData->getString("NightTexture", nightTexture); + bool separateSpecular = surfaceData->getString("SpecularTexture", + specularTexture); + bool applyNormalMap = surfaceData->getString("NormalMap", normalTexture); + bool applyOverlay = surfaceData->getString("OverlayTexture", + overlayTexture); + + unsigned int baseFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; + unsigned int bumpFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; + unsigned int nightFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; + unsigned int specularFlags = TextureInfo::WrapTexture | TextureInfo::AllowSplitting; + + float bumpHeight = 2.5f; + surfaceData->getNumber("BumpHeight", bumpHeight); + + bool blendTexture = false; + surfaceData->getBoolean("BlendTexture", blendTexture); + + bool emissive = false; + surfaceData->getBoolean("Emissive", emissive); + + bool compressTexture = false; + surfaceData->getBoolean("CompressTexture", compressTexture); + if (compressTexture) + baseFlags |= TextureInfo::CompressTexture; + + if (blendTexture) + surface->appearanceFlags |= Surface::BlendTexture; + if (emissive) + surface->appearanceFlags |= Surface::Emissive; + if (applyBaseTexture) + surface->appearanceFlags |= Surface::ApplyBaseTexture; + if (applyBumpMap || applyNormalMap) + surface->appearanceFlags |= Surface::ApplyBumpMap; + if (applyNightMap) + surface->appearanceFlags |= Surface::ApplyNightMap; + if (separateSpecular) + surface->appearanceFlags |= Surface::SeparateSpecularMap; + if (applyOverlay) + surface->appearanceFlags |= Surface::ApplyOverlay; + if (surface->specularColor != Color(0.0f, 0.0f, 0.0f)) + surface->appearanceFlags |= Surface::SpecularReflection; + + if (applyBaseTexture) + surface->baseTexture.setTexture(baseTexture, path, baseFlags); + if (applyNightMap) + surface->nightTexture.setTexture(nightTexture, path, nightFlags); + if (separateSpecular) + surface->specularTexture.setTexture(specularTexture, path, specularFlags); + + // If both are present, NormalMap overrides BumpMap + if (applyNormalMap) + surface->bumpTexture.setTexture(normalTexture, path, bumpFlags); + else if (applyBumpMap) + surface->bumpTexture.setTexture(bumpTexture, path, bumpHeight, bumpFlags); + + if (applyOverlay) + surface->overlayTexture.setTexture(overlayTexture, path, baseFlags); +} + + +// Set up the orbit barycenter for a body. By default, it is the parent of the +// object +static Selection GetOrbitBarycenter(const string& name, + PlanetarySystem* system) +{ + Selection orbitBarycenter; + Body* primary = system->getPrimaryBody(); + if (primary != NULL) + orbitBarycenter = Selection(primary); + else + orbitBarycenter = Selection(system->getStar()); + + // The barycenter must be in the same star system as the object we're creating + if (orbitBarycenter.body()) + { + if (system->getStar() != orbitBarycenter.body()->getSystem()->getStar()) + { + cerr << "OrbitBarycenter" << _(" of ") << name << _(" must be in same star system\n"); + return Selection(); + } + } + else if (orbitBarycenter.star()) + { + if (system->getStar() != orbitBarycenter.star()) + { + cerr << "OrbitBarycenter" << _(" of ") << name << _(" must be in same star system\n"); + return Selection(); + } + } + + return orbitBarycenter; +} + + +// Create a body (planet or moon) using the values from a hash +// The usePlanetsUnits flags specifies whether period and semi-major axis +// are in years and AU rather than days and kilometers +static Body* CreatePlanet(const string& name, + PlanetarySystem* system, + Universe& universe, + Body* existingBody, + Hash* planetData, + const string& path, + Disposition disposition) +{ + Body* body = NULL; + + if (disposition == ModifyObject) + { + body = existingBody; + } + + if (body == NULL) + { + body = new Body(system); + } + + Selection orbitBarycenter = GetOrbitBarycenter(name, system); + bool orbitsPlanet = false; + if (orbitBarycenter.body()) + { + body->setOrbitBarycenter(orbitBarycenter.body()); + orbitsPlanet = true; + } + else if (orbitBarycenter.star()) + { + body->setOrbitBarycenter(NULL); + } + else + { + // Bad orbit barycenter specified + if (body != existingBody) + delete body; + return NULL; + } + + // Set the reference frame of the orbit + Value* frameValue = planetData->getValue("OrbitFrame"); + if (frameValue != NULL) + { + ReferenceFrame* frame = CreateReferenceFrame(universe, frameValue); + if (frame != NULL) + { + body->setOrbitFrame(frame); + + // If the center of the is a star, orbital element units are + // in AU; otherwise, use kilometers. + if (frame->getCenter().star() != NULL) + orbitsPlanet = false; + else + orbitsPlanet = true; + } + } + + Value* bodyFrameValue = planetData->getValue("BodyFrame"); + if (bodyFrameValue != NULL) + { + ReferenceFrame* frame = CreateReferenceFrame(universe, bodyFrameValue); + if (frame != NULL) + { + body->setBodyFrame(frame); + } + } + + if (body->getOrbitFrame() != NULL) + { + if (isFrameCircular(*body->getOrbitFrame(), ReferenceFrame::PositionFrame)) + { + clog << "Orbit frame for " << body->getName() << " is nested too deep (probably circular)\n"; + body->setOrbitFrame(NULL); + } + } + + if (body->getBodyFrame() != NULL) + { + if (isFrameCircular(*body->getBodyFrame(), ReferenceFrame::OrientationFrame)) + { + clog << "Body frame for " << body->getName() << " is nested too deep (probably circular)\n"; + body->setBodyFrame(NULL); + } + } + + + Orbit* orbit = CreateOrbit(system, planetData, path, !orbitsPlanet); + if (orbit != NULL) + { + body->setOrbit(orbit); + } + + if (body->getOrbit() == NULL) + { + DPRINTF(0, "No valid orbit specified for object '%s'; skipping . . .\n", + body->getName().c_str()); + if (body != existingBody) + delete body; + return NULL; + } + + // Three values control the shape and size of an ellipsoidal object: + // semiAxes, radius, and oblateness. It is an error if neither the + // radius nor semiaxes are set. If both are set, the radius is + // multipled by each of the specified semiaxis to give the shape of + // the body ellipsoid. Oblateness is ignored if semiaxes are provided; + // otherwise, the ellipsoid has semiaxes: ( radius, radius, 1-radius ). + // These rather complex rules exist to maintain backward compatibility. + // + // If the body also has a mesh, it is always scaled in x, y, and z by + // the maximum semiaxis, never anisotropically. + + double radius = (double) body->getRadius(); + bool radiusSpecified = false; + if (planetData->getNumber("Radius", radius)) + { + body->setSemiAxes(Vec3f((float) radius, (float) radius, (float) radius)); + radiusSpecified = true; + } + + Vec3d semiAxes; + if (planetData->getVector("SemiAxes", semiAxes)) + { + if (radiusSpecified) + semiAxes *= radius; + // Swap y and z to match internal coordinate system + body->setSemiAxes(Vec3f((float) semiAxes.x, (float) semiAxes.z, (float) semiAxes.y)); + } + else + { + double oblateness = 0.0; + if (planetData->getNumber("Oblateness", oblateness)) + { + body->setSemiAxes((float) body->getRadius() * Vec3f(1.0f, 1.0f - (float) oblateness, 1.0f)); + } + } + + + int classification = body->getClassification(); + string classificationName; + if (planetData->getString("Class", classificationName)) + { + if (compareIgnoringCase(classificationName, "planet") == 0) + classification = Body::Planet; + else if (compareIgnoringCase(classificationName, "moon") == 0) + classification = Body::Moon; + else if (compareIgnoringCase(classificationName, "comet") == 0) + classification = Body::Comet; + else if (compareIgnoringCase(classificationName, "asteroid") == 0) + classification = Body::Asteroid; + else if (compareIgnoringCase(classificationName, "spacecraft") == 0) + classification = Body::Spacecraft; + else if (compareIgnoringCase(classificationName, "invisible") == 0) + classification = Body::Invisible; + else if (compareIgnoringCase(classificationName, "surfacefeature") == 0) + classification = Body::SurfaceFeature; + else if (compareIgnoringCase(classificationName, "component") == 0) + classification = Body::Component; + } + + if (classification == Body::Unknown) + { + // Try to guess the type + if (system->getPrimaryBody() != NULL) + { + if(radius > 0.1) + classification = Body::Moon; + else + classification = Body::Spacecraft; + } + else + { + if (radius < 1000.0) + classification = Body::Asteroid; + else + classification = Body::Planet; + } + } + body->setClassification(classification); + + if (classification == Body::Invisible) + body->setVisible(false); + + // Surface features and component objects are by default not + // visible as points at a distance. + if (classification == Body::Invisible || + classification == Body::SurfaceFeature || + classification == Body::Component) + { + body->setVisibleAsPoint(false); + } + + // g++ is missing limits header, so we can use this + // double beginning = -numeric_limits::infinity(); + // double ending = numeric_limits::infinity(); + double beginning = -1.0e+50; + double ending = 1.0e+50; + body->getLifespan(beginning, ending); + ParseDate(planetData, "Beginning", beginning); + ParseDate(planetData, "Ending", ending); + body->setLifespan(beginning, ending); + + string infoURL; + if (planetData->getString("InfoURL", infoURL)) + { + if (infoURL.find(':') == string::npos) + { + // Relative URL, the base directory is the current one, + // not the main installation directory + if (path[1] == ':') + // Absolute Windows path, file:/// is required + infoURL = "file:///" + path + "/" + infoURL; + else if (!path.empty()) + infoURL = path + "/" + infoURL; + } + body->setInfoURL(infoURL); + } + + double albedo = 0.5; + if (planetData->getNumber("Albedo", albedo)) + body->setAlbedo((float) albedo); + + double mass = 0.0; + if (planetData->getNumber("Mass", mass)) + body->setMass((float) mass); + + Quatf orientation; + if (planetData->getRotation("Orientation", orientation)) + body->setOrientation(orientation); + + // Get the rotation model for this body + double syncRotationPeriod = body->getOrbit()->getPeriod(); + RotationModel* rm = CreateRotationModel(planetData, path, + syncRotationPeriod); + if (rm != NULL) + { + // TODO: Free old rotation model and replace it with the new one; + // should reference count rotation model objects. + body->setRotationModel(rm); + } + else + { + // If no rotation model is provided, use a default rotation model-- + // a uniform rotation that's synchronous with the orbit (appropriate + // for nearly all natural satellites in the solar system.) If the + // disposition is modify, we do not want to replace the existing + // rotation model with a default one. + if (disposition != ModifyObject) + { + body->setRotationModel(CreateDefaultRotationModel(syncRotationPeriod)); + } + } + + Surface surface; + if (disposition == ModifyObject) + { + surface = body->getSurface(); + } + else + { + surface.color = Color(1.0f, 1.0f, 1.0f); + surface.hazeColor = Color(0.0f, 0.0f, 0.0f, 0.0f); + } + FillinSurface(planetData, &surface, path); + body->setSurface(surface); + + { + string model(""); + if (planetData->getString("Mesh", model)) + { + Vec3f modelCenter(0.0f, 0.0f, 0.0f); + if (planetData->getVector("MeshCenter", modelCenter)) + { + // TODO: Adjust bounding radius if model center isn't + // (0.0f, 0.0f, 0.0f) + } + + ResourceHandle modelHandle = GetModelManager()->getHandle(ModelInfo(model, path, modelCenter)); + body->setModel(modelHandle); + + } + } + + // Read the atmosphere + { + Value* atmosDataValue = planetData->getValue("Atmosphere"); + if (atmosDataValue != NULL) + { + if (atmosDataValue->getType() != Value::HashType) + { + cout << "ReadSolarSystem: Atmosphere must be an assoc array.\n"; + } + else + { + Hash* atmosData = atmosDataValue->getHash(); + assert(atmosData != NULL); + + Atmosphere* atmosphere = NULL; + if (disposition == ModifyObject) + { + atmosphere = body->getAtmosphere(); + if (atmosphere == NULL) + { + Atmosphere atm; + body->setAtmosphere(atm); + atmosphere = body->getAtmosphere(); + } + } + else + { + atmosphere = new Atmosphere(); + } + atmosData->getNumber("Height", atmosphere->height); + atmosData->getColor("Lower", atmosphere->lowerColor); + atmosData->getColor("Upper", atmosphere->upperColor); + atmosData->getColor("Sky", atmosphere->skyColor); + atmosData->getColor("Sunset", atmosphere->sunsetColor); + + atmosData->getNumber("Mie", atmosphere->mieCoeff); + atmosData->getNumber("MieScaleHeight", atmosphere->mieScaleHeight); + atmosData->getNumber("MieAsymmetry", atmosphere->miePhaseAsymmetry); + atmosData->getVector("Rayleigh", atmosphere->rayleighCoeff); + //atmosData->getNumber("RayleighScaleHeight", atmosphere->rayleighScaleHeight); + atmosData->getVector("Absorption", atmosphere->absorptionCoeff); + + // Get the cloud map settings + atmosData->getNumber("CloudHeight", atmosphere->cloudHeight); + if (atmosData->getNumber("CloudSpeed", atmosphere->cloudSpeed)) + atmosphere->cloudSpeed = degToRad(atmosphere->cloudSpeed); + + string cloudTexture; + if (atmosData->getString("CloudMap", cloudTexture)) + { + atmosphere->cloudTexture.setTexture(cloudTexture, + path, + TextureInfo::WrapTexture); + } + + string cloudNormalMap; + if (atmosData->getString("CloudNormalMap", cloudNormalMap)) + { + atmosphere->cloudNormalMap.setTexture(cloudNormalMap, + path, + TextureInfo::WrapTexture); + } + + body->setAtmosphere(*atmosphere); + if (disposition != ModifyObject) + delete atmosphere; + } + } + } + + // Read the ring system + { + Value* ringsDataValue = planetData->getValue("Rings"); + if (ringsDataValue != NULL) + { + if (ringsDataValue->getType() != Value::HashType) + { + cout << "ReadSolarSystem: Rings must be an assoc array.\n"; + } + else + { + Hash* ringsData = ringsDataValue->getHash(); + // ASSERT(ringsData != NULL); + + RingSystem rings(0.0f, 0.0f); + if (body->getRings() != NULL) + rings = *body->getRings(); + + double inner = 0.0, outer = 0.0; + if (ringsData->getNumber("Inner", inner)) + rings.innerRadius = (float) inner; + if (ringsData->getNumber("Outer", outer)) + rings.outerRadius = (float) outer; + + Color color(1.0f, 1.0f, 1.0f); + if (ringsData->getColor("Color", color)) + rings.color = color; + + string textureName; + if (ringsData->getString("Texture", textureName)) + rings.texture = MultiResTexture(textureName, path); + + body->setRings(rings); + } + } + } + + bool clickable = true; + if (planetData->getBoolean("Clickable", clickable)) + { + body->setClickable(clickable); + } + + return body; +} + + +// Create a barycenter object using the values from a hash +static Body* CreateReferencePoint(const string& name, + PlanetarySystem* system, + Universe& universe, + Body* existingBody, + Hash* refPointData, + const string& path, + Disposition disposition) +{ + Body* body = NULL; + + if (disposition == ModifyObject) + { + body = existingBody; + } + + if (body == NULL) + { + body = new Body(system); + } + + body->setSemiAxes(Vec3f(1.0f, 1.0f, 1.0f)); + body->setClassification(Body::Invisible); + body->setVisible(false); + body->setVisibleAsPoint(false); + body->setClickable(false); + + Selection orbitBarycenter = GetOrbitBarycenter(name, system); + bool orbitsPlanet = false; + if (orbitBarycenter.body()) + { + body->setOrbitBarycenter(orbitBarycenter.body()); + orbitsPlanet = true; + } + else if (orbitBarycenter.star()) + { + body->setOrbitBarycenter(NULL); + } + else + { + // Bad orbit barycenter specified + if (body != existingBody) + delete body; + return NULL; + } + + // Set the reference frame of the orbit + Value* frameValue = refPointData->getValue("OrbitFrame"); + if (frameValue != NULL) + { + ReferenceFrame* frame = CreateReferenceFrame(universe, frameValue); + if (frame != NULL) + { + body->setOrbitFrame(frame); + + // If the center of the is a star, orbital element units are + // in AU; otherwise, use kilometers. + if (frame->getCenter().star() != NULL) + orbitsPlanet = false; + else + orbitsPlanet = true; + } + } + + if (body->getOrbitFrame() != NULL) + { + if (isFrameCircular(*body->getOrbitFrame(), ReferenceFrame::PositionFrame)) + { + clog << "Orbit frame for " << body->getName() << " is nested too deep (probably circular)\n"; + body->setOrbitFrame(NULL); + } + } + + Orbit* orbit = CreateOrbit(system, refPointData, path, !orbitsPlanet); + if (orbit != NULL) + { + body->setOrbit(orbit); + } + + if (body->getOrbit() == NULL) + { + DPRINTF(0, "No valid orbit specified for barycenter '%s'; skipping . . .\n", + name.c_str()); + if (body != existingBody) + delete body; + return NULL; + } + + body->setRotationModel(new ConstantOrientation(Quatd(1.0))); + + return body; +} + + +bool LoadSolarSystemObjects(istream& in, + Universe& universe, + const std::string& directory) +{ + Tokenizer tokenizer(&in); + Parser parser(&tokenizer); + + while (tokenizer.nextToken() != Tokenizer::TokenEnd) + { + // Read the disposition; if none is specified, the default is Add. + Disposition disposition = AddObject; + if (tokenizer.getTokenType() == Tokenizer::TokenName) + { + if (tokenizer.getNameValue() == "Add") + { + disposition = AddObject; + tokenizer.nextToken(); + } + else if (tokenizer.getNameValue() == "Replace") + { + disposition = ReplaceObject; + tokenizer.nextToken(); + } + else if (tokenizer.getNameValue() == "Modify") + { + disposition = ModifyObject; + tokenizer.nextToken(); + } + } + + // Read the item type; if none is specified the default is Body + string itemType("Body"); + if (tokenizer.getTokenType() == Tokenizer::TokenName) + { + itemType = tokenizer.getNameValue(); + tokenizer.nextToken(); + } + + if (tokenizer.getTokenType() != Tokenizer::TokenString) + { + sscError(tokenizer, "object name expected"); + return false; + } + string name = tokenizer.getStringValue().c_str(); + + if (tokenizer.nextToken() != Tokenizer::TokenString) + { + sscError(tokenizer, "bad parent object name"); + return false; + } + string parentName = tokenizer.getStringValue().c_str(); + + Value* objectDataValue = parser.readValue(); + if (objectDataValue == NULL) + { + sscError(tokenizer, "bad object definition"); + return false; + } + + if (objectDataValue->getType() != Value::HashType) + { + sscError(tokenizer, "{ expected"); + delete objectDataValue; + return false; + } + Hash* objectData = objectDataValue->getHash(); + + Selection parent = universe.findPath(parentName, NULL, 0); + PlanetarySystem* parentSystem = NULL; + + if (itemType == "Body" || itemType == "ReferencePoint") + { + //bool orbitsPlanet = false; + if (parent.star() != NULL) + { + SolarSystem* solarSystem = universe.getSolarSystem(parent.star()); + if (solarSystem == NULL) + { + // No solar system defined for this star yet, so we need + // to create it. + solarSystem = universe.createSolarSystem(parent.star()); + } + parentSystem = solarSystem->getPlanets(); + } + else if (parent.body() != NULL) + { + // Parent is a planet or moon + parentSystem = parent.body()->getSatellites(); + if (parentSystem == NULL) + { + // If the planet doesn't already have any satellites, we + // have to create a new planetary system for it. + parentSystem = new PlanetarySystem(parent.body()); + parent.body()->setSatellites(parentSystem); + } + //orbitsPlanet = true; + } + else + { + errorMessagePrelude(tokenizer); + cerr << _("parent body '") << parentName << _("' of '") << name << _("' not found.\n"); + } + + if (parentSystem != NULL) + { + Body* existingBody = parentSystem->find(name); + if (existingBody && disposition == AddObject) + { + errorMessagePrelude(tokenizer); + cerr << _("warning duplicate definition of ") << + parentName << " " << name << '\n'; + } + + Body* body; + if (itemType == "ReferencePoint") + body = CreateReferencePoint(name, parentSystem, universe, existingBody, objectData, directory, disposition); + else + body = CreatePlanet(name, parentSystem, universe, existingBody, objectData, directory, disposition); + + if (body != NULL) + { + body->setName(name); + if (disposition == ReplaceObject) + { + parentSystem->replaceBody(existingBody, body); + delete existingBody; + } + else if (disposition == AddObject) + { + parentSystem->addBody(body); + } + } + } + } + else if (itemType == "AltSurface") + { + Surface* surface = new Surface(); + surface->color = Color(1.0f, 1.0f, 1.0f); + surface->hazeColor = Color(0.0f, 0.0f, 0.0f, 0.0f); + FillinSurface(objectData, surface, directory); + if (surface != NULL && parent.body() != NULL) + parent.body()->addAlternateSurface(name, surface); + else + sscError(tokenizer, _("bad alternate surface")); + } + else if (itemType == "Location") + { + if (parent.body() != NULL) + { + Location* location = CreateLocation(objectData, parent.body()); + if (location != NULL) + { + location->setName(name); + parent.body()->addLocation(location); + } + else + { + sscError(tokenizer, _("bad location")); + } + } + else + { + errorMessagePrelude(tokenizer); + cerr << _("parent body '") << parentName << _("' of '") << name << _("' not found.\n"); + } + } + delete objectDataValue; + } + + // TODO: Return some notification if there's an error parsing the file + return true; +} + + +SolarSystem::SolarSystem(Star* _star) : star(_star) +{ + planets = new PlanetarySystem(_star); +} + + +Star* SolarSystem::getStar() const +{ + return star; +} + +Point3f SolarSystem::getCenter() const +{ + // TODO: This is a very simple method at the moment, but it will get + // more complex when planets around multistar systems are supported + // where the planets may orbit the center of mass of two stars. + return star->getPosition(); +} + +PlanetarySystem* SolarSystem::getPlanets() const +{ + return planets; +} Index: celengine/universe.cpp =================================================================== --- celengine/universe.cpp (revision 4077) +++ celengine/universe.cpp (working copy) @@ -317,7 +317,7 @@ PlanetPickInfo* pickInfo = (PlanetPickInfo*) info; // Reject invisible bodies and bodies that don't exist at the current time - if (body->getClassification() == Body::Invisible || !body->extant(pickInfo->jd) || !body->isClickable()) + if (!body->isVisible() || !body->extant(pickInfo->jd) || !body->isClickable()) return true; Point3d bpos = body->getHeliocentricPosition(pickInfo->jd); @@ -361,7 +361,7 @@ double distance = -1.0; // Test for intersection with the bounding sphere - if (body->getClassification() != Body::Invisible && + if (body->isVisible() && body->extant(pickInfo->jd) && body->isClickable() && testIntersection(pickInfo->pickRay, Sphered(bpos, radius), distance)) @@ -1208,7 +1208,6 @@ if (sel.empty()) { - cerr << "nothing found" << endl; return completion; } @@ -1220,7 +1219,7 @@ PlanetarySystem* worlds = NULL; if (sel.getType() == Selection::Type_Body) - {cerr << "body found" << endl; + { worlds = sel.body()->getSatellites(); vector* locations = sel.body()->getLocations(); if (locations != NULL && withLocations) Index: celestia/celx.cpp =================================================================== --- celestia/celx.cpp (revision 4077) +++ celestia/celx.cpp (working copy) @@ -1327,7 +1327,43 @@ return lua_tonumber(l, index); } +// Safe wrapper for lua_tobool, c.f. safeGetString +// Non-fata errors will return defaultValue +static bool safeGetBoolean(lua_State* l, + int index, + FatalErrors fatalErrors = AllErrors, + const char* errorMsg = "Boolean argument expected", + bool defaultValue = false) +{ + int argc = lua_gettop(l); + if (index < 1 || index > argc) + { + if (fatalErrors & WrongArgc) + { + doError(l, errorMsg); + } + else + { + return defaultValue; + } + } + if (!lua_isboolean(l, index)) + { + if (fatalErrors & WrongType) + { + doError(l, errorMsg); + } + else + { + return defaultValue; + } + } + + return lua_toboolean(l, index) != 0; +} + + // Add a field to the table on top of the stack static void setTable(lua_State* l, const char* field, lua_Number value) { @@ -2340,6 +2376,87 @@ return 1; } + +// Return true if the object is visible, false if not. +static int object_visible(lua_State* l) +{ + checkArgs(l, 1, 1, "No arguments expected to function object:visible"); + + Selection* sel = this_object(l); + lua_pushboolean(l, sel->isVisible()); + + return 1; +} + + +// Set the object visibility flag. +static int object_setvisible(lua_State* l) +{ + checkArgs(l, 2, 2, "One argument expected to object:setvisible()"); + + Selection* sel = this_object(l); + bool visible = safeGetBoolean(l, 2, AllErrors, "Argument to object:setvisible() must be a boolean"); + if (sel->body() != NULL) + { + sel->body()->setVisible(visible); + } + + return 0; +} + + +static int object_setorbitcolor(lua_State* l) +{ + checkArgs(l, 4, 4, "Red, green, and blue color values exepected for object:setorbitcolor()"); + + Selection* sel = this_object(l); + float r = (float) safeGetNumber(l, 2, WrongType, "Argument 1 to object:setorbitcolor() must be a number", 0.0); + float g = (float) safeGetNumber(l, 3, WrongType, "Argument 2 to object:setorbitcolor() must be a number", 0.0); + float b = (float) safeGetNumber(l, 4, WrongType, "Argument 3 to object:setorbitcolor() must be a number", 0.0); + Color orbitColor(r, g, b); + + if (sel->body() != NULL) + { + sel->body()->setOrbitColor(orbitColor); + } + + return 0; +} + + +static int object_orbitcoloroverridden(lua_State* l) +{ + checkArgs(l, 1, 1, "No arguments expected to object:orbitcoloroverridden"); + + bool isOverridden = false; + Selection* sel = this_object(l); + if (sel->body() != NULL) + { + isOverridden = sel->body()->isOrbitColorOverridden(); + } + + lua_pushboolean(l, isOverridden); + + return 1; +} + + +static int object_setorbitcoloroverridden(lua_State* l) +{ + checkArgs(l, 2, 2, "One arguments expected to object:setorbitcoloroverridden"); + + Selection* sel = this_object(l); + bool override = safeGetBoolean(l, 2, AllErrors, "Argument to object:setorbitcoloroverridden() must be a boolean"); + + if (sel->body() != NULL) + { + sel->body()->setOrbitColorOverridden(override); + } + + return 0; +} + + static int object_radius(lua_State* l) { checkArgs(l, 1, 1, "No arguments expected to function object:radius"); @@ -2798,6 +2915,11 @@ CreateClassMetatable(l, _Object); RegisterMethod(l, "__tostring", object_tostring); + RegisterMethod(l, "visible", object_visible); + RegisterMethod(l, "setvisible", object_setvisible); + RegisterMethod(l, "orbitcoloroverridden", object_orbitcoloroverridden); + RegisterMethod(l, "setorbitcoloroverridden", object_setorbitcoloroverridden); + RegisterMethod(l, "setorbitcolor", object_setorbitcolor); RegisterMethod(l, "radius", object_radius); RegisterMethod(l, "setradius", object_setradius); RegisterMethod(l, "type", object_type);