parseobject.cpp
上传用户:center1979
上传日期:2022-07-26
资源大小:50633k
文件大小:55k
源码类别:

OpenGL

开发平台:

Visual C++

  1. // parseobject.cpp
  2. //
  3. // Copyright (C) 2004-2008 Chris Laurel <claurel@gmail.com>
  4. //
  5. // Functions for parsing objects common to star, solar system, and
  6. // deep sky catalogs.
  7. //
  8. // This program is free software; you can redistribute it and/or
  9. // modify it under the terms of the GNU General Public License
  10. // as published by the Free Software Foundation; either version 2
  11. // of the License, or (at your option) any later version.
  12. #include <cassert>
  13. #include <celutil/debug.h>
  14. #include "parseobject.h"
  15. #include "customorbit.h"
  16. #include "customrotation.h"
  17. #include "spiceorbit.h"
  18. #include "spicerotation.h"
  19. #include "scriptorbit.h"
  20. #include "scriptrotation.h"
  21. #include "frame.h"
  22. #include "trajmanager.h"
  23. #include "rotationmanager.h"
  24. #include "universe.h"
  25. using namespace std;
  26. bool
  27. ParseDate(Hash* hash, const string& name, double& jd)
  28. {
  29.     // Check first for a number value representing a Julian date
  30.     if (hash->getNumber(name, jd))
  31.         return true;
  32.     string dateString;
  33.     if (hash->getString(name, dateString))
  34.     {
  35.         astro::Date date(1, 1, 1);
  36.         if (astro::parseDate(dateString, date))
  37.         {
  38.             jd = (double) date;
  39.             return true;
  40.         }
  41.     }
  42.     return false;
  43. }
  44. /*!
  45.  * Create a new Keplerian orbit from an ssc property table:
  46.  *
  47.  * EllipticalOrbit
  48.  * {
  49.  *     # One of the following is required to specify orbit size:
  50.  *     SemiMajorAxis <number>
  51.  *     PericenterDistance <number>
  52.  *
  53.  *     # Required
  54.  *     Period <number>
  55.  *     
  56.  *     Eccentricity <number>   (default: 0.0)
  57.  *     Inclination <degrees>   (default: 0.0)
  58.  *     AscendingNode <degrees> (default: 0.0)
  59.  *
  60.  *     # One or none of the following:
  61.  *     ArgOfPericenter <degrees>  (default: 0.0)
  62.  *     LongOfPericenter <degrees> (default: 0.0)
  63.  *
  64.  *     Epoch <date> (default J2000.0)
  65.  *
  66.  *     # One or none of the following:
  67.  *     MeanAnomaly <degrees>     (default: 0.0)
  68.  *     MeanLongitude <degrees>   (default: 0.0)
  69.  * }
  70.  *
  71.  * If usePlanetUnits is true:
  72.  *     Period is in Julian years
  73.  *     SemiMajorAxis or PericenterDistance is in AU
  74.  * Otherwise:
  75.  *     Period is in Julian days
  76.  *     SemiMajorAxis or PericenterDistance is in kilometers.    
  77.  */
  78. static EllipticalOrbit*
  79. CreateEllipticalOrbit(Hash* orbitData,
  80.                       bool usePlanetUnits)
  81. {
  82.     // SemiMajorAxis and Period are absolutely required; everything
  83.     // else has a reasonable default.
  84.     double pericenterDistance = 0.0;
  85.     double semiMajorAxis = 0.0;
  86.     if (!orbitData->getNumber("SemiMajorAxis", semiMajorAxis))
  87.     {
  88.         if (!orbitData->getNumber("PericenterDistance", pericenterDistance))
  89.         {
  90.             clog << "SemiMajorAxis/PericenterDistance missing!  Skipping planet . . .n";
  91.             return NULL;
  92.         }
  93.     }
  94.     double period = 0.0;
  95.     if (!orbitData->getNumber("Period", period))
  96.     {
  97.         clog << "Period missing!  Skipping planet . . .n";
  98.         return NULL;
  99.     }
  100.     double eccentricity = 0.0;
  101.     orbitData->getNumber("Eccentricity", eccentricity);
  102.     double inclination = 0.0;
  103.     orbitData->getNumber("Inclination", inclination);
  104.     double ascendingNode = 0.0;
  105.     orbitData->getNumber("AscendingNode", ascendingNode);
  106.     double argOfPericenter = 0.0;
  107.     if (!orbitData->getNumber("ArgOfPericenter", argOfPericenter))
  108.     {
  109.         double longOfPericenter = 0.0;
  110.         if (orbitData->getNumber("LongOfPericenter", longOfPericenter))
  111.             argOfPericenter = longOfPericenter - ascendingNode;
  112.     }
  113.     double epoch = astro::J2000;
  114.     ParseDate(orbitData, "Epoch", epoch);
  115.     // Accept either the mean anomaly or mean longitude--use mean anomaly
  116.     // if both are specified.
  117.     double anomalyAtEpoch = 0.0;
  118.     if (!orbitData->getNumber("MeanAnomaly", anomalyAtEpoch))
  119.     {
  120.         double longAtEpoch = 0.0;
  121.         if (orbitData->getNumber("MeanLongitude", longAtEpoch))
  122.             anomalyAtEpoch = longAtEpoch - (argOfPericenter + ascendingNode);
  123.     }
  124.     if (usePlanetUnits)
  125.     {
  126.         semiMajorAxis = astro::AUtoKilometers(semiMajorAxis);
  127.         pericenterDistance = astro::AUtoKilometers(pericenterDistance);
  128.         period = period * 365.25;
  129.     }
  130.     // If we read the semi-major axis, use it to compute the pericenter
  131.     // distance.
  132.     if (semiMajorAxis != 0.0)
  133.         pericenterDistance = semiMajorAxis * (1.0 - eccentricity);
  134.     return new EllipticalOrbit(pericenterDistance,
  135.                                eccentricity,
  136.                                degToRad(inclination),
  137.                                degToRad(ascendingNode),
  138.                                degToRad(argOfPericenter),
  139.                                degToRad(anomalyAtEpoch),
  140.                                period,
  141.                                epoch);
  142. }
  143. /*!
  144.  * Create a new sampled orbit from an ssc property table:
  145.  *
  146.  * SampledTrajectory
  147.  * {
  148.  *     Source <string>
  149.  *     Interpolation "Cubic" | "Linear"
  150.  *     DoublePrecision <boolean>
  151.  * }
  152.  *
  153.  * Source is the only required field. Interpolation defaults to cubic, and
  154.  * DoublePrecision defaults to true.
  155.  */
  156. static Orbit*
  157. CreateSampledTrajectory(Hash* trajData, const string& path)
  158. {
  159.     string sourceName;
  160.     if (!trajData->getString("Source", sourceName))
  161.     {
  162.         clog << "SampledTrajectory is missing a source.n";
  163.         return NULL;
  164.     }
  165.     // Read interpolation type; string value must be either "Linear" or "Cubic"
  166.     // Default interpolation type is cubic.
  167.     string interpolationString;
  168.     TrajectoryInterpolation interpolation = TrajectoryInterpolationCubic;
  169.     if (trajData->getString("Interpolation", interpolationString))
  170.     {
  171.         if (!compareIgnoringCase(interpolationString, "linear"))
  172.             interpolation = TrajectoryInterpolationLinear;
  173.         else if (!compareIgnoringCase(interpolationString, "cubic"))
  174.             interpolation = TrajectoryInterpolationCubic;
  175.         else
  176.             clog << "Unknown interpolation type " << interpolationString << endl; // non-fatal error
  177.     }
  178.     // Double precision is true by default
  179.     bool useDoublePrecision = true;
  180.     trajData->getBoolean("DoublePrecision", useDoublePrecision);
  181.     TrajectoryPrecision precision = useDoublePrecision ? TrajectoryPrecisionDouble : TrajectoryPrecisionSingle;
  182.     DPRINTF(1, "Attempting to load sampled trajectory from source '%s'n", sourceName.c_str());
  183.     ResourceHandle orbitHandle = GetTrajectoryManager()->getHandle(TrajectoryInfo(sourceName, path, interpolation, precision));
  184.     Orbit* orbit = GetTrajectoryManager()->find(orbitHandle);
  185.     if (orbit == NULL)
  186.     {
  187.         clog << "Could not load sampled trajectory from '" << sourceName << "'n";
  188.     }
  189.     return orbit;
  190. }
  191. // Create a new FixedPosition trajectory. A FixedPosition is a property list
  192. // with one of the following 3-vector properties:
  193. //     Rectangular
  194. //     Planetographic
  195. //     Planetocentric
  196. // Planetographic and planetocentric coordinates are given in the order longitude,
  197. // latitude, altitude. Units of altitude are kilometers. Planetographic and
  198. // and planetocentric coordinates are only practical when the coordinate system
  199. // is BodyFixed.
  200. static Orbit*
  201. CreateFixedPosition(Hash* trajData, const Selection& centralObject, bool usePlanetUnits)
  202. {
  203.     Vec3d position(0.0, 0.0, 0.0);
  204.     Vec3d v(0.0, 0.0, 0.0);
  205.     if (trajData->getVector("Rectangular", v))
  206.     {       
  207.         if (usePlanetUnits)
  208.             v = v * astro::AUtoKilometers(1.0);
  209.         // Convert to Celestia's coordinate system
  210.         position = Vec3d(v.x, v.z, -v.y);
  211.     }
  212.     else if (trajData->getVector("Planetographic", v))
  213.     {
  214.         if (centralObject.getType() != Selection::Type_Body)
  215.         {
  216.             clog << "FixedPosition planetographic coordinates aren't valid for stars.n";
  217.             return NULL;
  218.         }
  219.         // TODO: Need function to calculate planetographic coordinates
  220.         // TODO: Change planetocentricToCartesian so that 180 degree offset isn't required
  221.         position = centralObject.body()->planetocentricToCartesian(180.0 + v.x, v.y, v.z);
  222.     }
  223.     else if (trajData->getVector("Planetocentric", v))
  224.     {
  225.         if (centralObject.getType() != Selection::Type_Body)
  226.         {
  227.             clog << "FixedPosition planetocentric coordinates aren't valid for stars.n";
  228.             return NULL;
  229.         }
  230.         // TODO: Change planetocentricToCartesian so that 180 degree offset isn't required
  231.         position = centralObject.body()->planetocentricToCartesian(180.0 + v.x, v.y, v.z);
  232.     }
  233.     else
  234.     {
  235.         clog << "Missing coordinates for FixedPositionn";
  236.         return NULL;
  237.     }
  238.     return new FixedOrbit(Point3d(position.x, position.y, position.z));
  239. }
  240. // Parse a string list--either a single string or an array of strings is permitted.
  241. static bool
  242. ParseStringList(Hash* table,
  243. const string& propertyName,
  244. list<string>& stringList)
  245. {
  246. Value* v = table->getValue(propertyName);
  247. if (v == NULL)
  248. return NULL;
  249. // Check for a single string first.
  250. if (v->getType() == Value::StringType)
  251. {
  252. stringList.push_back(v->getString());
  253. return true;
  254. }
  255. else if (v->getType() == Value::ArrayType)
  256. {
  257. Array* array = v->getArray();
  258. Array::const_iterator iter;
  259. // Verify that all array entries are strings
  260. for (iter = array->begin(); iter != array->end(); iter++)
  261. {
  262. if ((*iter)->getType() != Value::StringType)
  263. return false;
  264. }
  265. // Add strings to stringList
  266. for (iter = array->begin(); iter != array->end(); iter++)
  267.  stringList.push_back((*iter)->getString());
  268. return true;
  269. }
  270. else
  271. {
  272. return false;
  273. }
  274. }
  275. #ifdef USE_SPICE
  276. /*! Create a new SPICE orbit. This is just a Celestia wrapper for a trajectory specified
  277.  *  in a SPICE SPK file.
  278.  *
  279.  *  SpiceOrbit
  280.  *  {
  281.  *      Kernel <string|string array>   # optional
  282.  *      Target <string>
  283.  *      Origin <string>
  284.  *      BoundingRadius <number>
  285.  *      Period <number>                # optional
  286.  *      Beginning <number>             # optional
  287.  *      Ending <number>                # optional
  288.  *  }
  289.  *
  290.  *  The Kernel property specifies one or more SPK files that must be loaded. Any 
  291.  *  already loaded kernels will also be used if they contain trajectories for
  292.  *  the target or origin.
  293.  *  Target and origin are strings that give NAIF IDs for the target and origin
  294.  *  objects. Either names or integer IDs are valid, but integer IDs still must
  295.  *  be quoted.
  296.  *  BoundingRadius gives a conservative estimate of the maximum distance between
  297.  *  the target and origin objects. It is required by Celestia for visibility
  298.  *  culling when rendering.
  299.  *  Beginning and Ending specify the valid time range of the SPICE orbit. It is
  300.  *  an error to specify Beginning without Ending, and vice versa. If neither is
  301.  *  specified, the valid range is computed from the coverage window in the SPICE
  302.  *  kernel pool. If the coverage window is noncontiguous, the first interval is
  303.  *  used.
  304.  */
  305. static SpiceOrbit*
  306. CreateSpiceOrbit(Hash* orbitData,
  307.                  const string& path,
  308.                  bool usePlanetUnits)
  309. {
  310.     string targetBodyName;
  311.     string originName;
  312. list<string> kernelList;
  313. if (orbitData->getValue("Kernel") != NULL)
  314. {
  315. // Kernel list is optional; a SPICE orbit may rely on kernels already loaded into
  316. // the kernel pool.
  317. if (!ParseStringList(orbitData, "Kernel", kernelList))
  318. {
  319. clog << "Kernel list for SPICE orbit is neither a string nor array of stringsn";
  320. return NULL;
  321. }
  322. }
  323.     if (!orbitData->getString("Target", targetBodyName))
  324.     {
  325.         clog << "Target name missing from SPICE orbitn";
  326.         return NULL;
  327.     }
  328.     if (!orbitData->getString("Origin", originName))
  329.     {
  330.         clog << "Origin name missing from SPICE orbitn";
  331.         return NULL;
  332.     }
  333.     // A bounding radius for culling is required for SPICE orbits
  334.     double boundingRadius = 0.0;
  335.     if (!orbitData->getNumber("BoundingRadius", boundingRadius))
  336.     {
  337.         clog << "Bounding Radius missing from SPICE orbitn";
  338.         return NULL;
  339.     }
  340.     // The period of the orbit may be specified if appropriate; a value
  341.     // of zero for the period (the default), means that the orbit will
  342.     // be considered aperiodic.
  343.     double period = 0.0;
  344.     orbitData->getNumber("Period", period);
  345.     if (usePlanetUnits)
  346.     {
  347.         boundingRadius = astro::AUtoKilometers(boundingRadius);
  348.         period = period * 365.25;
  349.     }
  350. // Either a complete time interval must be specified with Beginning/Ending, or
  351. // else neither field can be present.
  352. Value* beginningDate = orbitData->getValue("Beginning");
  353. Value* endingDate = orbitData->getValue("Ending");
  354. if (beginningDate != NULL && endingDate == NULL)
  355. {
  356. clog << "Beginning specified for SPICE orbit, but ending is missing.n";
  357. return NULL;
  358. }
  359. if (endingDate != NULL && beginningDate == NULL)
  360. {
  361. clog << "Ending specified for SPICE orbit, but beginning is missing.n";
  362. return NULL;
  363. }
  364. SpiceOrbit* orbit = NULL;
  365. if (beginningDate != NULL && endingDate != NULL)
  366. {
  367. double beginningTDBJD = 0.0;
  368. if (!ParseDate(orbitData, "Beginning", beginningTDBJD))
  369. {
  370. clog << "Invalid beginning date specified for SPICE orbit.n";
  371. return NULL;
  372. }
  373. double endingTDBJD = 0.0;
  374. if (!ParseDate(orbitData, "Ending", endingTDBJD))
  375. {
  376. clog << "Invalid ending date specified for SPICE orbit.n";
  377. return NULL;
  378. }
  379. orbit = new SpiceOrbit(targetBodyName,
  380.    originName,
  381.    period,
  382.    boundingRadius,
  383.    beginningTDBJD,
  384.    endingTDBJD);
  385. }
  386. else
  387. {
  388. // No time interval given; we'll use whatever coverage window is given
  389. // in the SPICE kernel.
  390. orbit = new SpiceOrbit(targetBodyName,
  391.    originName,
  392.    period,
  393.    boundingRadius);
  394. }
  395.     if (!orbit->init(path, &kernelList))
  396.     {
  397.         // Error using SPICE library; destroy the orbit; hopefully a
  398.         // fallback is defined in the SSC file.
  399.         delete orbit;
  400.         orbit = NULL;
  401.     }
  402.     return orbit;
  403. }
  404. /*! Create a new rotation model based on a SPICE frame.
  405.  *
  406.  *  SpiceRotation
  407.  *  {
  408.  *      Kernel <string|string array>   # optional
  409.  *      Frame <string>
  410.  *      BaseFrame <string>             # optional (defaults to ecliptic)
  411.  *      Period <number>                # optional (units are hours)
  412.  *      Beginning <number>             # optional
  413.  *      Ending <number>                # optional
  414.  *  }
  415.  *
  416.  *  The Kernel property specifies one or more SPICE kernel files that must be
  417.  *  loaded in order for the frame to be defined over the required range. Any 
  418.  *  already loaded kernels will be used if they contain information relevant
  419.  *  for defining the frame.
  420.  *  Frame and base name are strings that give SPICE names for the frames. The
  421.  *  orientation of the SpiceRotation is the orientation of the frame relative to
  422.  *  the base frame. By default, the base frame is eclipj2000.
  423.  *  Beginning and Ending specify the valid time range of the SPICE rotation.
  424.  *  If the Beginning and Ending are omitted, the rotation model is assumed to
  425.  *  be valid at any time. It is an error to specify Beginning without Ending,
  426.  *  and vice versa.
  427.  *  Period specifies the principal rotation period; it defaults to 0 indicating
  428.  *  that the rotation is aperiodic. It is not essential to provide the rotation
  429.  *  period; it is only used by Celestia for displaying object information such
  430.  *  as sidereal day length.
  431.  */
  432. static SpiceRotation*
  433. CreateSpiceRotation(Hash* rotationData,
  434.                     const string& path)
  435. {
  436.     string frameName;
  437.     string baseFrameName = "eclipj2000";
  438. list<string> kernelList;
  439. if (rotationData->getValue("Kernel") != NULL)
  440. {
  441. // Kernel list is optional; a SPICE rotation may rely on kernels already loaded into
  442. // the kernel pool.
  443. if (!ParseStringList(rotationData, "Kernel", kernelList))
  444. {
  445. clog << "Kernel list for SPICE rotation is neither a string nor array of stringsn";
  446. return NULL;
  447. }
  448. }
  449.     if (!rotationData->getString("Frame", frameName))
  450.     {
  451.         clog << "Frame name missing from SPICE rotationn";
  452.         return NULL;
  453.     }
  454.     rotationData->getString("BaseFrame", baseFrameName);
  455.     // The period of the rotation may be specified if appropriate; a value
  456.     // of zero for the period (the default), means that the rotation will
  457.     // be considered aperiodic.
  458.     double period = 0.0;
  459.     rotationData->getNumber("Period", period);
  460.     period = period / 24.0;
  461. // Either a complete time interval must be specified with Beginning/Ending, or
  462. // else neither field can be present.
  463. Value* beginningDate = rotationData->getValue("Beginning");
  464. Value* endingDate = rotationData->getValue("Ending");
  465. if (beginningDate != NULL && endingDate == NULL)
  466. {
  467. clog << "Beginning specified for SPICE rotation, but ending is missing.n";
  468. return NULL;
  469. }
  470. if (endingDate != NULL && beginningDate == NULL)
  471. {
  472. clog << "Ending specified for SPICE rotation, but beginning is missing.n";
  473. return NULL;
  474. }
  475. SpiceRotation* rotation = NULL;
  476. if (beginningDate != NULL && endingDate != NULL)
  477. {
  478. double beginningTDBJD = 0.0;
  479. if (!ParseDate(rotationData, "Beginning", beginningTDBJD))
  480. {
  481. clog << "Invalid beginning date specified for SPICE rotation.n";
  482. return NULL;
  483. }
  484. double endingTDBJD = 0.0;
  485. if (!ParseDate(rotationData, "Ending", endingTDBJD))
  486. {
  487. clog << "Invalid ending date specified for SPICE rotation.n";
  488. return NULL;
  489. }
  490. rotation = new SpiceRotation(frameName,
  491.             baseFrameName,
  492.          period,
  493.          beginningTDBJD,
  494.          endingTDBJD);
  495. }
  496. else
  497. {
  498. // No time interval given; rotation is valid at any time.
  499. rotation = new SpiceRotation(frameName,
  500.                                      baseFrameName,
  501.                                      period);
  502. }
  503.     if (!rotation->init(path, &kernelList))
  504.     {
  505.         // Error using SPICE library; destroy the rotation.
  506.         delete rotation;
  507.         rotation = NULL;
  508.     }
  509.     return rotation;
  510. }
  511. #endif
  512. static ScriptedOrbit*
  513. CreateScriptedOrbit(Hash* orbitData,
  514.                     const string& path)
  515. {
  516. #if !defined(CELX)
  517.     clog << "ScriptedOrbit not usable without scripting support.n";
  518.     return NULL;
  519. #else
  520.     // Function name is required
  521.     string funcName;
  522.     if (!orbitData->getString("Function", funcName))
  523.     {
  524.         clog << "Function name missing from script orbit definition.n";
  525.         return NULL;
  526.     }
  527.     // Module name is optional
  528.     string moduleName;
  529.     orbitData->getString("Module", moduleName);
  530.     string* pathCopy = new string(path);
  531.     Value* pathValue = new Value(*pathCopy);
  532.     orbitData->addValue("AddonPath", *pathValue);
  533.     ScriptedOrbit* scriptedOrbit = new ScriptedOrbit();
  534.     if (scriptedOrbit != NULL)
  535.     {
  536.         if (!scriptedOrbit->initialize(moduleName, funcName, orbitData))
  537.         {
  538.             delete scriptedOrbit;
  539.             scriptedOrbit = NULL;
  540.         }
  541.     }
  542.     return scriptedOrbit;
  543. #endif
  544. }
  545. Orbit*
  546. CreateOrbit(const Selection& centralObject,
  547.             Hash* planetData,
  548.             const string& path,
  549.             bool usePlanetUnits)
  550. {
  551.     Orbit* orbit = NULL;
  552.     string customOrbitName;
  553.     if (planetData->getString("CustomOrbit", customOrbitName))
  554.     {
  555.         orbit = GetCustomOrbit(customOrbitName);
  556.         if (orbit != NULL)
  557.         {
  558.             return orbit;
  559.         }
  560.         clog << "Could not find custom orbit named '" << customOrbitName <<
  561.             "'n";
  562.     }
  563. #ifdef USE_SPICE
  564.     Value* spiceOrbitDataValue = planetData->getValue("SpiceOrbit");
  565.     if (spiceOrbitDataValue != NULL)
  566.     {
  567.         if (spiceOrbitDataValue->getType() != Value::HashType)
  568.         {
  569.             clog << "Object has incorrect spice orbit syntax.n";
  570.             return NULL;
  571.         }
  572.         else
  573.         {
  574.             orbit = CreateSpiceOrbit(spiceOrbitDataValue->getHash(), path, usePlanetUnits);
  575.             if (orbit != NULL)
  576.             {
  577.                 return orbit;
  578.             }
  579.             clog << "Bad spice orbitn";
  580.             DPRINTF(0, "Could not load SPICE orbitn");
  581.         }
  582.     }
  583. #endif
  584.     // Trajectory calculated by Lua script
  585.     Value* scriptedOrbitValue = planetData->getValue("ScriptedOrbit");
  586.     if (scriptedOrbitValue != NULL)
  587.     {
  588.         if (scriptedOrbitValue->getType() != Value::HashType)
  589.         {
  590.             clog << "Object has incorrect scripted orbit syntax.n";
  591.             return NULL;
  592.         }
  593.         else
  594.         {
  595.             orbit = CreateScriptedOrbit(scriptedOrbitValue->getHash(), path);
  596.             if (orbit != NULL)
  597.                 return orbit;
  598.         }
  599.     }
  600.     // New 1.5.0 style for sampled trajectories. Permits specification of
  601.     // precision and interpolation type.
  602.     Value* sampledTrajDataValue = planetData->getValue("SampledTrajectory");
  603.     if (sampledTrajDataValue != NULL)
  604.     {
  605.         if (sampledTrajDataValue->getType() != Value::HashType)
  606.         {
  607.             clog << "Object has incorrect syntax for SampledTrajectory.n";
  608.             return NULL;
  609.         }
  610.         else
  611.         {
  612.             return CreateSampledTrajectory(sampledTrajDataValue->getHash(), path);
  613.         }
  614.     }
  615.     // Old style for sampled trajectories. Assumes cubic interpolation and
  616.     // single precision.
  617.     string sampOrbitFile;
  618.     if (planetData->getString("SampledOrbit", sampOrbitFile))
  619.     {
  620.         DPRINTF(1, "Attempting to load sampled orbit file '%s'n",
  621.                 sampOrbitFile.c_str());
  622.         ResourceHandle orbitHandle =
  623.             GetTrajectoryManager()->getHandle(TrajectoryInfo(sampOrbitFile,
  624.                                                              path,
  625.                                                              TrajectoryInterpolationCubic,
  626.                                                              TrajectoryPrecisionSingle));
  627.         orbit = GetTrajectoryManager()->find(orbitHandle);
  628.         if (orbit != NULL)
  629.         {
  630.             return orbit;
  631.         }
  632.         clog << "Could not load sampled orbit file '" << sampOrbitFile << "'n";
  633.     }
  634.     Value* orbitDataValue = planetData->getValue("EllipticalOrbit");
  635.     if (orbitDataValue != NULL)
  636.     {
  637.         if (orbitDataValue->getType() != Value::HashType)
  638.         {
  639.             clog << "Object has incorrect elliptical orbit syntax.n";
  640.             return NULL;
  641.         }
  642.         else
  643.         {
  644.             return CreateEllipticalOrbit(orbitDataValue->getHash(),
  645.                                          usePlanetUnits);
  646.         }
  647.     }
  648.     // Create an 'orbit' that places the object at a fixed point in its
  649.     // reference frame. There are two forms for FixedPosition: a simple
  650.     // form with an 3-vector value, and complex form with a properlist
  651.     // value. The simple form:
  652.     //
  653.     // FixedPosition [ x y z ]
  654.     //
  655.     // is a shorthand for:
  656.     //
  657.     // FixedPosition { Rectangular [ x y z ] }
  658.     //
  659.     // In addition to Rectangular, other coordinate types for fixed position are
  660.     // Planetographic and Planetocentric.
  661.     Value* fixedPositionValue = planetData->getValue("FixedPosition");
  662.     if (fixedPositionValue != NULL)
  663.     {
  664.         Vec3d fixedPosition(0.0, 0.0, 0.0);
  665.         if (planetData->getVector("FixedPosition", fixedPosition))
  666.         {
  667.             // Convert to Celestia's coordinate system
  668.             fixedPosition = Vec3d(fixedPosition.x,
  669.                                   fixedPosition.z,
  670.                                   -fixedPosition.y);
  671.             if (usePlanetUnits)
  672.                 fixedPosition = fixedPosition * astro::AUtoKilometers(1.0);
  673.             return new FixedOrbit(Point3d(0.0, 0.0, 0.0) + fixedPosition);
  674.         }
  675.         else if (fixedPositionValue->getType() == Value::HashType)
  676.         {
  677.             return CreateFixedPosition(fixedPositionValue->getHash(), centralObject, usePlanetUnits);
  678.         }
  679.         else
  680.         {
  681.             clog << "Object has incorrect FixedPosition syntax.n";
  682.         }
  683.     }
  684.     // LongLat will make an object fixed relative to the surface of its center
  685.     // object. This is done by creating an orbit with a period equal to the
  686.     // rotation rate of the parent object. A body-fixed reference frame is a
  687.     // much better way to accomplish this.
  688.     Vec3d longlat(0.0, 0.0, 0.0);
  689.     if (planetData->getVector("LongLat", longlat))
  690.     {
  691.         Body* centralBody = centralObject.body();
  692.         if (centralBody != NULL)
  693.         {
  694.             Vec3d pos = centralBody->planetocentricToCartesian(longlat.x, longlat.y, longlat.z);
  695.             return new SynchronousOrbit(*centralBody, Point3d(pos.x, pos.y, pos.z));
  696.         }
  697.         else
  698.         {
  699.             // TODO: Allow fixing objects to the surface of stars.
  700.         }
  701.         return NULL;
  702.     }
  703.     return NULL;
  704. }
  705. static ConstantOrientation*
  706. CreateFixedRotationModel(double offset,
  707.                          double inclination,
  708.                          double ascendingNode)
  709. {
  710.     Quatd q = Quatd::yrotation(-PI - offset) *
  711.               Quatd::xrotation(-inclination) *
  712.               Quatd::yrotation(-ascendingNode);
  713.     return new ConstantOrientation(q);
  714. }
  715. static RotationModel*
  716. CreateUniformRotationModel(Hash* rotationData,
  717.                            double syncRotationPeriod)
  718. {
  719.     // Default to synchronous rotation
  720.     double period = syncRotationPeriod;
  721.     if (rotationData->getNumber("Period", period))
  722.     {
  723.         period = period / 24.0;
  724.     }
  725.     float offset = 0.0f;
  726.     if (rotationData->getNumber("MeridianAngle", offset))
  727.     {
  728.         offset = degToRad(offset);
  729.     }
  730.     double epoch = astro::J2000;
  731.     ParseDate(rotationData, "Epoch", epoch);
  732.     float inclination = 0.0f;
  733.     if (rotationData->getNumber("Inclination", inclination))
  734.     {
  735.         inclination = degToRad(inclination);
  736.     }
  737.     float ascendingNode = 0.0f;
  738.     if (rotationData->getNumber("AscendingNode", ascendingNode))
  739.     {
  740.         ascendingNode = degToRad(ascendingNode);
  741.     }
  742.     // No period was specified, and the default synchronous
  743.     // rotation period is zero, indicating that the object
  744.     // doesn't have a periodic orbit. Default to a constant
  745.     // orientation instead.
  746.     if (period == 0.0)
  747.     {
  748.         return CreateFixedRotationModel(offset, inclination, ascendingNode);
  749.     }
  750.     else
  751.     {
  752.         return new UniformRotationModel(period,
  753.                                         offset,
  754.                                         epoch,
  755.                                         inclination,
  756.                                         ascendingNode);
  757.     }
  758. }
  759. static ConstantOrientation*
  760. CreateFixedRotationModel(Hash* rotationData)
  761. {
  762.     double offset = 0.0;
  763.     if (rotationData->getNumber("MeridianAngle", offset))
  764.     {
  765.         offset = degToRad(offset);
  766.     }
  767.     double inclination = 0.0;
  768.     if (rotationData->getNumber("Inclination", inclination))
  769.     {
  770.         inclination = degToRad(inclination);
  771.     }
  772.     double ascendingNode = 0.0;
  773.     if (rotationData->getNumber("AscendingNode", ascendingNode))
  774.     {
  775.         ascendingNode = degToRad(ascendingNode);
  776.     }
  777.     Quatd q = Quatd::yrotation(-PI - offset) *
  778.               Quatd::xrotation(-inclination) *
  779.               Quatd::yrotation(-ascendingNode);
  780.     return new ConstantOrientation(q);
  781. }
  782. static ConstantOrientation*
  783. CreateFixedAttitudeRotationModel(Hash* rotationData)
  784. {
  785.     double heading = 0.0;
  786.     if (rotationData->getNumber("Heading", heading))
  787.     {
  788.         heading = degToRad(heading);
  789.     }
  790.     
  791.     double tilt = 0.0;
  792.     if (rotationData->getNumber("Tilt", tilt))
  793.     {
  794.         tilt = degToRad(tilt);
  795.     }
  796.     
  797.     double roll = 0.0;
  798.     if (rotationData->getNumber("Roll", roll))
  799.     {
  800.         roll = degToRad(roll);
  801.     }
  802.     
  803.     Quatd q = Quatd::yrotation(-PI - heading) *
  804.               Quatd::xrotation(-tilt) *
  805.               Quatd::zrotation(-roll);
  806.     
  807.     return new ConstantOrientation(q);
  808. }
  809. static RotationModel*
  810. CreatePrecessingRotationModel(Hash* rotationData,
  811.                               double syncRotationPeriod)
  812. {
  813.     // Default to synchronous rotation
  814.     double period = syncRotationPeriod;
  815.     if (rotationData->getNumber("Period", period))
  816.     {
  817.         period = period / 24.0;
  818.     }
  819.     float offset = 0.0f;
  820.     if (rotationData->getNumber("MeridianAngle", offset))
  821.     {
  822.         offset = degToRad(offset);
  823.     }
  824.     double epoch = astro::J2000;
  825.     ParseDate(rotationData, "Epoch", epoch);
  826.     float inclination = 0.0f;
  827.     if (rotationData->getNumber("Inclination", inclination))
  828.     {
  829.         inclination = degToRad(inclination);
  830.     }
  831.     float ascendingNode = 0.0f;
  832.     if (rotationData->getNumber("AscendingNode", ascendingNode))
  833.     {
  834.         ascendingNode = degToRad(ascendingNode);
  835.     }
  836.     // The default value of 0 is handled specially, interpreted to indicate
  837.     // that there's no precession.
  838.     double precessionPeriod = 0.0;
  839.     if (rotationData->getNumber("PrecessionPeriod", precessionPeriod))
  840.     {
  841.         // The precession period is specified in the ssc file in units
  842.         // of years, but internally Celestia uses days.
  843.         precessionPeriod = precessionPeriod * 365.25;
  844.     }
  845.     // No period was specified, and the default synchronous
  846.     // rotation period is zero, indicating that the object
  847.     // doesn't have a periodic orbit. Default to a constant
  848.     // orientation instead.
  849.     if (period == 0.0)
  850.     {
  851.         return CreateFixedRotationModel(offset, inclination, ascendingNode);
  852.     }
  853.     else
  854.     {
  855.         return new PrecessingRotationModel(period,
  856.                                            offset,
  857.                                            epoch,
  858.                                            inclination,
  859.                                            ascendingNode,
  860.                                            precessionPeriod);
  861.     }
  862. }
  863. static ScriptedRotation*
  864. CreateScriptedRotation(Hash* rotationData,
  865.                        const string& path)
  866. {
  867. #if !defined(CELX)
  868.     clog << "ScriptedRotation not usable without scripting support.n";
  869.     return NULL;
  870. #else
  871.     // Function name is required
  872.     string funcName;
  873.     if (!rotationData->getString("Function", funcName))
  874.     {
  875.         clog << "Function name missing from scripted rotation definition.n";
  876.         return NULL;
  877.     }
  878.     // Module name is optional
  879.     string moduleName;
  880.     rotationData->getString("Module", moduleName);
  881.     string* pathCopy = new string(path);
  882.     Value* pathValue = new Value(*pathCopy);
  883.     rotationData->addValue("AddonPath", *pathValue);
  884.     ScriptedRotation* scriptedRotation = new ScriptedRotation();
  885.     if (scriptedRotation != NULL)
  886.     {
  887.         if (!scriptedRotation->initialize(moduleName, funcName, rotationData))
  888.         {
  889.             delete scriptedRotation;
  890.             scriptedRotation = NULL;
  891.         }
  892.     }
  893.     return scriptedRotation;
  894. #endif
  895. }
  896. // Parse rotation information. Unfortunately, Celestia didn't originally have
  897. // RotationModel objects, so information about the rotation of the object isn't
  898. // grouped into a single subobject--the ssc fields relevant for rotation just
  899. // appear in the top level structure.
  900. RotationModel*
  901. CreateRotationModel(Hash* planetData,
  902.                     const string& path,
  903.                     double syncRotationPeriod)
  904. {
  905.     RotationModel* rotationModel = NULL;
  906.     // If more than one rotation model is specified, the following precedence
  907.     // is used to determine which one should be used:
  908.     //   CustomRotation
  909.     //   SPICE C-Kernel
  910.     //   SampledOrientation
  911.     //   PrecessingRotation
  912.     //   UniformRotation
  913.     //   legacy rotation parameters
  914.     string customRotationModelName;
  915.     if (planetData->getString("CustomRotation", customRotationModelName))
  916.     {
  917.         rotationModel = GetCustomRotationModel(customRotationModelName);
  918.         if (rotationModel != NULL)
  919.         {
  920.             return rotationModel;
  921.         }
  922.         clog << "Could not find custom rotation model named '" <<
  923.             customRotationModelName << "'n";
  924.     }
  925. #ifdef USE_SPICE
  926.     Value* spiceRotationDataValue = planetData->getValue("SpiceRotation");
  927.     if (spiceRotationDataValue != NULL)
  928.     {
  929.         if (spiceRotationDataValue->getType() != Value::HashType)
  930.         {
  931.             clog << "Object has incorrect spice rotation syntax.n";
  932.             return NULL;
  933.         }
  934.         else
  935.         {
  936.             rotationModel = CreateSpiceRotation(spiceRotationDataValue->getHash(), path);
  937.             if (rotationModel != NULL)
  938.             {
  939.                 return rotationModel;
  940.             }
  941.             clog << "Bad spice rotation modeln";
  942.             DPRINTF(0, "Could not load SPICE rotation modeln");
  943.         }
  944.     }
  945. #endif
  946.     Value* scriptedRotationValue = planetData->getValue("ScriptedRotation");
  947.     if (scriptedRotationValue != NULL)
  948.     {
  949.         if (scriptedRotationValue->getType() != Value::HashType)
  950.         {
  951.             clog << "Object has incorrect scripted rotation syntax.n";
  952.             return NULL;
  953.         }
  954.         else
  955.         {
  956.             rotationModel = CreateScriptedRotation(scriptedRotationValue->getHash(), path);
  957.             if (rotationModel != NULL)
  958.                 return rotationModel;
  959.         }
  960.     }
  961.     string sampOrientationFile;
  962.     if (planetData->getString("SampledOrientation", sampOrientationFile))
  963.     {
  964.         DPRINTF(1, "Attempting to load orientation file '%s'n",
  965.                 sampOrientationFile.c_str());
  966.         ResourceHandle orientationHandle =
  967.             GetRotationModelManager()->getHandle(RotationModelInfo(sampOrientationFile, path));
  968.         rotationModel = GetRotationModelManager()->find(orientationHandle);
  969.         if (rotationModel != NULL)
  970.         {
  971.             return rotationModel;
  972.         }
  973.         clog << "Could not load rotation model file '" <<
  974.             sampOrientationFile << "'n";
  975.     }
  976.     Value* precessingRotationValue = planetData->getValue("PrecessingRotation");
  977.     if (precessingRotationValue != NULL)
  978.     {
  979.         if (precessingRotationValue->getType() != Value::HashType)
  980.         {
  981.             clog << "Object has incorrect syntax for precessing rotation.n";
  982.             return NULL;
  983.         }
  984.         else
  985.         {
  986.             return CreatePrecessingRotationModel(precessingRotationValue->getHash(),
  987.                                                  syncRotationPeriod);
  988.         }
  989.     }
  990.     Value* uniformRotationValue = planetData->getValue("UniformRotation");
  991.     if (uniformRotationValue != NULL)
  992.     {
  993.         if (uniformRotationValue->getType() != Value::HashType)
  994.         {
  995.             clog << "Object has incorrect UniformRotation syntax.n";
  996.             return NULL;
  997.         }
  998.         else
  999.         {
  1000.             return CreateUniformRotationModel(uniformRotationValue->getHash(),
  1001.                                               syncRotationPeriod);
  1002.         }
  1003.     }
  1004.     Value* fixedRotationValue = planetData->getValue("FixedRotation");
  1005.     if (fixedRotationValue != NULL)
  1006.     {
  1007.         if (fixedRotationValue->getType() != Value::HashType)
  1008.         {
  1009.             clog << "Object has incorrect FixedRotation syntax.n";
  1010.             return NULL;
  1011.         }
  1012.         else
  1013.         {
  1014.             return CreateFixedRotationModel(fixedRotationValue->getHash());
  1015.         }
  1016.     }
  1017.     
  1018.     Value* fixedAttitudeValue = planetData->getValue("FixedAttitude");
  1019.     if (fixedAttitudeValue != NULL)
  1020.     {
  1021.         if (fixedAttitudeValue->getType() != Value::HashType)
  1022.         {
  1023.             clog << "Object has incorrect FixedAttitude syntax.n";
  1024.             return NULL;
  1025.         }
  1026.         else
  1027.         {
  1028.             return CreateFixedAttitudeRotationModel(fixedAttitudeValue->getHash());
  1029.         }
  1030.     }
  1031.     // For backward compatibility we need to support rotation parameters
  1032.     // that appear in the main block of the object definition.
  1033.     // Default to synchronous rotation
  1034.     bool specified = false;
  1035.     double period = syncRotationPeriod;
  1036.     if (planetData->getNumber("RotationPeriod", period))
  1037.     {
  1038.         specified = true;
  1039.         period = period / 24.0f;
  1040.     }
  1041.     float offset = 0.0f;
  1042.     if (planetData->getNumber("RotationOffset", offset))
  1043.     {
  1044.         specified = true;
  1045.         offset = degToRad(offset);
  1046.     }
  1047.     double epoch = astro::J2000;
  1048.     if (ParseDate(planetData, "RotationEpoch", epoch))
  1049.     {
  1050.         specified = true;
  1051.     }
  1052.     float inclination = 0.0f;
  1053.     if (planetData->getNumber("Obliquity", inclination))
  1054.     {
  1055.         specified = true;
  1056.         inclination = degToRad(inclination);
  1057.     }
  1058.     float ascendingNode = 0.0f;
  1059.     if (planetData->getNumber("EquatorAscendingNode", ascendingNode))
  1060.     {
  1061.         specified = true;
  1062.         ascendingNode = degToRad(ascendingNode);
  1063.     }
  1064.     double precessionRate = 0.0f;
  1065.     if (planetData->getNumber("PrecessionRate", precessionRate))
  1066.     {
  1067.         specified = true;
  1068.     }
  1069.     if (specified)
  1070.     {
  1071.         RotationModel* rm = NULL;
  1072.         if (period == 0.0)
  1073.         {
  1074.             // No period was specified, and the default synchronous
  1075.             // rotation period is zero, indicating that the object
  1076.             // doesn't have a periodic orbit. Default to a constant
  1077.             // orientation instead.
  1078.             rm = CreateFixedRotationModel(offset, inclination, ascendingNode);
  1079.         }
  1080.         else if (precessionRate == 0.0)
  1081.         {
  1082.             rm = new UniformRotationModel(period,
  1083.                                           offset,
  1084.                                           epoch,
  1085.                                           inclination,
  1086.                                           ascendingNode);
  1087.         }
  1088.         else
  1089.         {
  1090.             rm = new PrecessingRotationModel(period,
  1091.                                              offset,
  1092.                                              epoch,
  1093.                                              inclination,
  1094.                                              ascendingNode,
  1095.                                              -360.0 / precessionRate);
  1096.         }
  1097.         return rm;
  1098.     }
  1099.     else
  1100.     {
  1101.         // No rotation fields specified
  1102.         return NULL;
  1103.     }
  1104. }
  1105. RotationModel* CreateDefaultRotationModel(double syncRotationPeriod)
  1106. {
  1107.     if (syncRotationPeriod == 0.0)
  1108.     {
  1109.         // If syncRotationPeriod is 0, the orbit of the object is
  1110.         // aperiodic and we'll just return a FixedRotation.
  1111.         return new ConstantOrientation(Quatd(1.0));
  1112.     }
  1113.     else
  1114.     {
  1115.         return new UniformRotationModel(syncRotationPeriod,
  1116.                                         0.0f,
  1117.                                         astro::J2000,
  1118.                                         0.0f,
  1119.                                         0.0f);
  1120.     }
  1121. }
  1122. // Get the center object of a frame definition. Return an empty selection
  1123. // if it's missing or refers to an object that doesn't exist.
  1124. static Selection
  1125. getFrameCenter(const Universe& universe, Hash* frameData, const Selection& defaultCenter)
  1126. {
  1127.     string centerName;
  1128.     if (!frameData->getString("Center", centerName))
  1129.     {
  1130.         if (defaultCenter.empty())
  1131.             cerr << "No center specified for reference frame.n";
  1132.         return defaultCenter;
  1133.     }
  1134.     Selection centerObject = universe.findPath(centerName, NULL, 0);
  1135.     if (centerObject.empty())
  1136.     {
  1137.         cerr << "Center object '" << centerName << "' of reference frame not found.n";
  1138.         return Selection();
  1139.     }
  1140.     // Should verify that center object is a star or planet, and
  1141.     // that it is a member of the same star system as the body in which
  1142.     // the frame will be used.
  1143.     return centerObject;
  1144. }
  1145. static BodyFixedFrame*
  1146. CreateBodyFixedFrame(const Universe& universe,
  1147.                      Hash* frameData,
  1148.                      const Selection& defaultCenter)
  1149. {
  1150.     Selection center = getFrameCenter(universe, frameData, defaultCenter);
  1151.     if (center.empty())
  1152.         return NULL;
  1153.     else
  1154.         return new BodyFixedFrame(center, center);
  1155. }
  1156. static BodyMeanEquatorFrame*
  1157. CreateMeanEquatorFrame(const Universe& universe,
  1158.                        Hash* frameData,
  1159.                        const Selection& defaultCenter)
  1160. {
  1161.     Selection center = getFrameCenter(universe, frameData, defaultCenter);
  1162.     if (center.empty())
  1163.         return NULL;
  1164.     Selection obj = center;
  1165.     string objName;
  1166.     if (frameData->getString("Object", objName))
  1167.     {
  1168.         obj = universe.findPath(objName, NULL, 0);
  1169.         if (obj.empty())
  1170.         {
  1171.             clog << "Object '" << objName << "' for mean equator frame not found.n";
  1172.             return NULL;
  1173.         }
  1174.     }
  1175.     clog << "CreateMeanEquatorFrame " << center.getName() << ", " << obj.getName() << "n";
  1176.     double freezeEpoch = 0.0;
  1177.     if (ParseDate(frameData, "Freeze", freezeEpoch))
  1178.     {
  1179.         return new BodyMeanEquatorFrame(center, obj, freezeEpoch);
  1180.     }
  1181.     else
  1182.     {
  1183.         return new BodyMeanEquatorFrame(center, obj);
  1184.     }
  1185. }
  1186. // Convert a string to an axis label. Permitted axis labels are
  1187. // x, y, z, -x, -y, and -z. +x, +y, and +z are allowed as synonyms for
  1188. // x, y, z. Case is ignored.
  1189. static int
  1190. parseAxisLabel(const std::string& label)
  1191. {
  1192.     if (compareIgnoringCase(label, "x") == 0 ||
  1193.         compareIgnoringCase(label, "+x") == 0)
  1194.     {
  1195.         return 1;
  1196.     }
  1197.     if (compareIgnoringCase(label, "y") == 0 ||
  1198.         compareIgnoringCase(label, "+y") == 0)
  1199.     {
  1200.         return 2;
  1201.     }
  1202.     if (compareIgnoringCase(label, "z") == 0 ||
  1203.         compareIgnoringCase(label, "+z") == 0)
  1204.     {
  1205.         return 3;
  1206.     }
  1207.     if (compareIgnoringCase(label, "-x") == 0)
  1208.     {
  1209.         return -1;
  1210.     }
  1211.     if (compareIgnoringCase(label, "-y") == 0)
  1212.     {
  1213.         return -2;
  1214.     }
  1215.     if (compareIgnoringCase(label, "-z") == 0)
  1216.     {
  1217.         return -3;
  1218.     }
  1219.     return 0;
  1220. }
  1221. static int
  1222. getAxis(Hash* vectorData)
  1223. {
  1224.     string axisLabel;
  1225.     if (!vectorData->getString("Axis", axisLabel))
  1226.     {
  1227.         DPRINTF(0, "Bad two-vector frame: missing axis label for vector.n");
  1228.         return 0;
  1229.     }
  1230.     int axis = parseAxisLabel(axisLabel);
  1231.     if (axis == 0)
  1232.     {
  1233.         DPRINTF(0, "Bad two-vector frame: vector has invalid axis label.n");
  1234.     }
  1235.     // Permute axis labels to match non-standard Celestia coordinate
  1236.     // conventions: y <- z, z <- -y
  1237.     switch (axis)
  1238.     {
  1239.     case 2:
  1240.         return -3;
  1241.     case -2:
  1242.         return 3;
  1243.     case 3:
  1244.         return 2;
  1245.     case -3:
  1246.         return -2;
  1247.     default:
  1248.         return axis;
  1249.     }
  1250.     return axis;
  1251. }
  1252. // Get the target object of a direction vector definition. Return an
  1253. // empty selection if it's missing or refers to an object that doesn't exist.
  1254. static Selection
  1255. getVectorTarget(const Universe& universe, Hash* vectorData)
  1256. {
  1257.     string targetName;
  1258.     if (!vectorData->getString("Target", targetName))
  1259.     {
  1260.         clog << "Bad two-vector frame: no target specified for vector.n";
  1261.         return Selection();
  1262.     }
  1263.     Selection targetObject = universe.findPath(targetName, NULL, 0);
  1264.     if (targetObject.empty())
  1265.     {
  1266.         clog << "Bad two-vector frame: target object '" << targetName << "' of vector not found.n";
  1267.         return Selection();
  1268.     }
  1269.     return targetObject;
  1270. }
  1271. // Get the observer object of a direction vector definition. Return an
  1272. // empty selection if it's missing or refers to an object that doesn't exist.
  1273. static Selection
  1274. getVectorObserver(const Universe& universe, Hash* vectorData)
  1275. {
  1276.     string obsName;
  1277.     if (!vectorData->getString("Observer", obsName))
  1278.     {
  1279.         // Omission of observer is permitted; it will default to the
  1280.         // frame center.
  1281.         return Selection();
  1282.     }
  1283.     Selection obsObject = universe.findPath(obsName, NULL, 0);
  1284.     if (obsObject.empty())
  1285.     {
  1286.         clog << "Bad two-vector frame: observer object '" << obsObject.getName() << "' of vector not found.n";
  1287.         return Selection();
  1288.     }
  1289.     return obsObject;
  1290. }
  1291. static FrameVector*
  1292. CreateFrameVector(const Universe& universe,
  1293.                   const Selection& center,
  1294.                   Hash* vectorData)
  1295. {
  1296.     Value* value = NULL;
  1297.     value = vectorData->getValue("RelativePosition");
  1298.     if (value != NULL && value->getHash() != NULL)
  1299.     {
  1300.         Hash* relPosData = value->getHash();
  1301.         Selection observer = getVectorObserver(universe, relPosData);
  1302.         Selection target = getVectorTarget(universe, relPosData);
  1303.         // Default observer is the frame center
  1304.         if (observer.empty())
  1305.             observer = center;
  1306.         if (observer.empty() || target.empty())
  1307.             return NULL;
  1308.         else
  1309.             return new FrameVector(FrameVector::createRelativePositionVector(observer, target));
  1310.     }
  1311.     value = vectorData->getValue("RelativeVelocity");
  1312.     if (value != NULL && value->getHash() != NULL)
  1313.     {
  1314.         Hash* relVData = value->getHash();
  1315.         Selection observer = getVectorObserver(universe, relVData);
  1316.         Selection target = getVectorTarget(universe, relVData);
  1317.         // Default observer is the frame center
  1318.         if (observer.empty())
  1319.             observer = center;
  1320.         if (observer.empty() || target.empty())
  1321.             return NULL;
  1322.         else
  1323.             return new FrameVector(FrameVector::createRelativeVelocityVector(observer, target));
  1324.     }
  1325.     value = vectorData->getValue("ConstantVector");
  1326.     if (value != NULL && value->getHash() != NULL)
  1327.     {
  1328.         Hash* constVecData = value->getHash();
  1329.         Vec3d vec(0.0, 0.0, 1.0);
  1330.         constVecData->getVector("Vector", vec);
  1331.         if (vec.length() == 0.0)
  1332.         {
  1333.             clog << "Bad two-vector frame: constant vector has length zeron";
  1334.             return NULL;
  1335.         }
  1336.         vec.normalize();
  1337.         vec = Vec3d(vec.x, vec.z, -vec.y);
  1338.         // The frame for the vector is optional; a NULL frame indicates
  1339.         // J2000 ecliptic.
  1340.         ReferenceFrame* f = NULL;
  1341.         Value* frameValue = constVecData->getValue("Frame");
  1342.         if (frameValue != NULL)
  1343.         {
  1344.             f = CreateReferenceFrame(universe, frameValue, center, NULL);
  1345.             if (f == NULL)
  1346.                 return NULL;
  1347.         }
  1348.         return new FrameVector(FrameVector::createConstantVector(vec, f));
  1349.     }
  1350.     else
  1351.     {
  1352.         clog << "Bad two-vector frame: unknown vector typen";
  1353.         return NULL;
  1354.     }
  1355. }
  1356. static TwoVectorFrame*
  1357. CreateTwoVectorFrame(const Universe& universe,
  1358.                      Hash* frameData,
  1359.                      const Selection& defaultCenter)
  1360. {
  1361.     Selection center = getFrameCenter(universe, frameData, defaultCenter);
  1362.     if (center.empty())
  1363.         return NULL;
  1364.     // Primary and secondary vector definitions are required
  1365.     Value* primaryValue = frameData->getValue("Primary");
  1366.     if (!primaryValue)
  1367.     {
  1368.         clog << "Primary axis missing from two-vector frame.n";
  1369.         return NULL;
  1370.     }
  1371.     Hash* primaryData = primaryValue->getHash();
  1372.     if (!primaryData)
  1373.     {
  1374.         clog << "Bad syntax for primary axis of two-vector frame.n";
  1375.         return NULL;
  1376.     }
  1377.     Value* secondaryValue = frameData->getValue("Secondary");
  1378.     if (!secondaryValue)
  1379.     {
  1380.         clog << "Secondary axis missing from two-vector frame.n";
  1381.         return NULL;
  1382.     }
  1383.     Hash* secondaryData = secondaryValue->getHash();
  1384.     if (!secondaryData)
  1385.     {
  1386.         clog << "Bad syntax for secondary axis of two-vector frame.n";
  1387.         return NULL;
  1388.     }
  1389.     // Get and validate the axes for the direction vectors
  1390.     int primaryAxis = getAxis(primaryData);
  1391.     int secondaryAxis = getAxis(secondaryData);
  1392.     assert(abs(primaryAxis) <= 3);
  1393.     assert(abs(secondaryAxis) <= 3);
  1394.     if (primaryAxis == 0 || secondaryAxis == 0)
  1395.     {
  1396.         return NULL;
  1397.     }
  1398.     if (abs(primaryAxis) == abs(secondaryAxis))
  1399.     {
  1400.         clog << "Bad two-vector frame: axes for vectors are collinear.n";
  1401.         return NULL;
  1402.     }
  1403.     FrameVector* primaryVector = CreateFrameVector(universe,
  1404.                                                    center,
  1405.                                                    primaryData);
  1406.     FrameVector* secondaryVector = CreateFrameVector(universe,
  1407.                                                      center,
  1408.                                                      secondaryData);
  1409.     TwoVectorFrame* frame = NULL;
  1410.     if (primaryVector != NULL && secondaryVector != NULL)
  1411.     {
  1412.         frame = new TwoVectorFrame(center,
  1413.                                    *primaryVector, primaryAxis,
  1414.                                    *secondaryVector, secondaryAxis);
  1415.     }
  1416.     delete primaryVector;
  1417.     delete secondaryVector;
  1418.     return frame;
  1419. }
  1420. static J2000EclipticFrame*
  1421. CreateJ2000EclipticFrame(const Universe& universe,
  1422.                          Hash* frameData,
  1423.                          const Selection& defaultCenter)
  1424. {
  1425.     Selection center = getFrameCenter(universe, frameData, defaultCenter);
  1426.     
  1427.     if (center.empty())
  1428.         return NULL;
  1429.     else
  1430.         return new J2000EclipticFrame(center);
  1431. }
  1432. static J2000EquatorFrame*
  1433. CreateJ2000EquatorFrame(const Universe& universe,
  1434.                         Hash* frameData,
  1435.                         const Selection& defaultCenter)
  1436. {
  1437.     Selection center = getFrameCenter(universe, frameData, defaultCenter);
  1438.     
  1439.     if (center.empty())
  1440.         return NULL;
  1441.     else
  1442.         return new J2000EquatorFrame(center);
  1443. }
  1444. /* Helper function for CreateTopocentricFrame().
  1445.  * Creates a two-vector frame with the specified center, target, and observer.
  1446.  */
  1447. TwoVectorFrame*
  1448. CreateTopocentricFrame(const Selection& center,
  1449.                        const Selection& target,
  1450.                        const Selection& observer)
  1451. {
  1452.     BodyMeanEquatorFrame* eqFrame = new BodyMeanEquatorFrame(target, target);
  1453.     FrameVector north = FrameVector::createConstantVector(Vec3d(0.0, 1.0, 0.0), eqFrame);
  1454.     FrameVector up = FrameVector::createRelativePositionVector(observer, target);
  1455.     
  1456.     return new TwoVectorFrame(center, up, -2, north, -3);    
  1457. }
  1458. /* Create a new Topocentric frame. The topocentric frame is designed to make it easy
  1459.  * to place objects on the surface of a planet or moon. The z-axis will point toward
  1460.  * the observer's zenith (which here is the direction away from the center of the
  1461.  * planet.) The x-axis will point in the local north direction. The equivalent
  1462.  * two-vector frame is:
  1463.  *
  1464.  * TwoVector
  1465.  * {
  1466.  *    Center <center>
  1467.  *    Primary   
  1468.  *    {
  1469.  *       Axis "z"
  1470.  *       RelativePosition { Target <target> Observer <observer> }
  1471.  *    }
  1472.  *    Secondary   
  1473.  *    {
  1474.  *       Axis "x"
  1475.  *       ConstantVector   
  1476.  *       {
  1477.  *          Vector [ 0 0 1]
  1478.  *          Frame { BodyFixed { Center <target> } }
  1479.  *       }
  1480.  *    }
  1481.  * }
  1482.  *
  1483.  * Typically, the topocentric frame is used as a BodyFrame to orient an
  1484.  * object on the surface of a planet. In this situation, the observer is
  1485.  * object itself and the target object is the planet. In fact, these are
  1486.  * the defaults: when no target, observer, or center is specified, the
  1487.  * observer and center are both 'self' and the target is the parent
  1488.  * object. Thus, for a Mars rover, using a topocentric frame is as simple
  1489.  * as:
  1490.  *
  1491.  * "Rover" "Sol/Mars"
  1492.  * {
  1493.  *     BodyFrame { Topocentric { } }
  1494.  *     ...
  1495.  * }
  1496.  */
  1497. static TwoVectorFrame*
  1498. CreateTopocentricFrame(const Universe& universe,
  1499.                        Hash* frameData,
  1500.                        const Selection& defaultTarget,
  1501.                        const Selection& defaultObserver)
  1502. {
  1503.     Selection target;
  1504.     Selection observer;
  1505.     Selection center;
  1506.     string centerName;
  1507.     if (frameData->getString("Center", centerName))
  1508.     {
  1509.         // If a center is provided, the default observer is the center and
  1510.         // the default target is the center's parent. This gives sensible results
  1511.         // when a topocentric frame is used as an orbit frame.
  1512.         center = universe.findPath(centerName, NULL, 0);
  1513.         if (center.empty())
  1514.         {
  1515.             cerr << "Center object '" << centerName << "' for topocentric frame not found.n";
  1516.             return NULL;
  1517.        }
  1518.        observer = center;
  1519.        target = center.parent();
  1520.     }  
  1521.     else
  1522.     {
  1523.         // When no center is provided, use the default observer as the center. This
  1524.         // is typical when a topocentric frame is the body frame. The default observer
  1525.         // is usually the object itself.
  1526.         target = defaultTarget;
  1527.         observer = defaultObserver;
  1528.         center = defaultObserver;
  1529.     }
  1530.     string targetName;
  1531.     if (!frameData->getString("Target", targetName))
  1532.     {
  1533.         if (target.empty())
  1534.         {
  1535.             cerr << "No target specified for topocentric frame.n";
  1536.             return NULL;
  1537.         }
  1538.     }
  1539.     else
  1540.     {
  1541.         target = universe.findPath(targetName, NULL, 0);
  1542.         if (target.empty())
  1543.         {
  1544.             cerr << "Target object '" << targetName << "' for topocentric frame not found.n";
  1545.             return NULL;
  1546.         }
  1547.         // Should verify that center object is a star or planet, and
  1548.         // that it is a member of the same star system as the body in which
  1549.         // the frame will be used.
  1550.     }
  1551.     string observerName;
  1552.     if (!frameData->getString("Observer", observerName))
  1553.     {
  1554.         if (observer.empty())
  1555.         {
  1556.             cerr << "No observer specified for topocentric frame.n";
  1557.             return NULL;
  1558.         }
  1559.     }
  1560.     else
  1561.     {
  1562.         observer = universe.findPath(observerName, NULL, 0);
  1563.         if (observer.empty())
  1564.         {
  1565.             cerr << "Observer object '" << observerName << "' for topocentric frame not found.n";
  1566.             return NULL;
  1567.         }
  1568.     }
  1569.     return CreateTopocentricFrame(center, target, observer);
  1570. }
  1571. static ReferenceFrame*
  1572. CreateComplexFrame(const Universe& universe, Hash* frameData, const Selection& defaultCenter, Body* defaultObserver)
  1573. {
  1574.     Value* value = frameData->getValue("BodyFixed");
  1575.     if (value != NULL)
  1576.     {
  1577.         if (value->getType() != Value::HashType)
  1578.         {
  1579.             clog << "Object has incorrect body-fixed frame syntax.n";
  1580.             return NULL;
  1581.         }
  1582.         else
  1583.         {
  1584.             return CreateBodyFixedFrame(universe, value->getHash(), defaultCenter);
  1585.         }
  1586.     }
  1587.     value = frameData->getValue("MeanEquator");
  1588.     if (value != NULL)
  1589.     {
  1590.         if (value->getType() != Value::HashType)
  1591.         {
  1592.             clog << "Object has incorrect mean equator frame syntax.n";
  1593.             return NULL;
  1594.         }
  1595.         else
  1596.         {
  1597.             return CreateMeanEquatorFrame(universe, value->getHash(), defaultCenter);
  1598.         }
  1599.     }
  1600.     value = frameData->getValue("TwoVector");
  1601.     if (value != NULL)
  1602.     {
  1603.         if (value->getType() != Value::HashType)
  1604.         {
  1605.             clog << "Object has incorrect two-vector frame syntax.n";
  1606.             return NULL;
  1607.         }
  1608.         else
  1609.         {
  1610.             return CreateTwoVectorFrame(universe, value->getHash(), defaultCenter);
  1611.         }
  1612.     }
  1613.     value = frameData->getValue("Topocentric");
  1614.     if (value != NULL)
  1615.     {
  1616.         if (value->getType() != Value::HashType)
  1617.         {
  1618.             clog << "Object has incorrect topocentric frame syntax.n";
  1619.             return NULL;
  1620.         }
  1621.         else
  1622.         {
  1623.             return CreateTopocentricFrame(universe, value->getHash(), defaultCenter, Selection(defaultObserver));
  1624.         }
  1625.     }
  1626.     value = frameData->getValue("EclipticJ2000");
  1627.     if (value != NULL)
  1628.     {
  1629.         if (value->getType() != Value::HashType)
  1630.         {
  1631.             clog << "Object has incorrect J2000 ecliptic frame syntax.n";
  1632.             return NULL;
  1633.         }
  1634.         else
  1635.         {
  1636.             return CreateJ2000EclipticFrame(universe, value->getHash(), defaultCenter);
  1637.         }
  1638.     }
  1639.     value = frameData->getValue("EquatorJ2000");
  1640.     if (value != NULL)
  1641.     {
  1642.         if (value->getType() != Value::HashType)
  1643.         {
  1644.             clog << "Object has incorrect J2000 equator frame syntax.n";
  1645.             return NULL;
  1646.         }
  1647.         else
  1648.         {
  1649.             return CreateJ2000EquatorFrame(universe, value->getHash(), defaultCenter);
  1650.         }
  1651.     }
  1652.     clog << "Frame definition does not have a valid frame type.n";
  1653.     return NULL;
  1654. }
  1655. ReferenceFrame* CreateReferenceFrame(const Universe& universe,
  1656.                                      Value* frameValue,
  1657.                                      const Selection& defaultCenter,
  1658.                                      Body* defaultObserver)
  1659. {
  1660.     if (frameValue->getType() == Value::StringType)
  1661.     {
  1662.         // TODO: handle named frames
  1663.         clog << "Invalid syntax for frame definition.n";
  1664.         return NULL;
  1665.     }
  1666.     else if (frameValue->getType() == Value::HashType)
  1667.     {
  1668.         return CreateComplexFrame(universe, frameValue->getHash(), defaultCenter, defaultObserver);
  1669.     }
  1670.     else
  1671.     {
  1672.         clog << "Invalid syntax for frame definition.n";
  1673.         return NULL;
  1674.     }
  1675. }