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

OpenGL

开发平台:

Visual C++

  1. // universe.cpp
  2. //
  3. // Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
  4. //
  5. // A container for catalogs of galaxies, stars, and planets.
  6. //
  7. // This program is free software; you can redistribute it and/or
  8. // modify it under the terms of the GNU General Public License
  9. // as published by the Free Software Foundation; either version 2
  10. // of the License, or (at your option) any later version.
  11. #include <celmath/mathlib.h>
  12. #include <celmath/intersect.h>
  13. #include <celutil/utf8.h>
  14. #include <cassert>
  15. #include "astro.h"
  16. #include "3dsmesh.h"
  17. #include "meshmanager.h"
  18. #include "render.h"
  19. #include "universe.h"
  20. #include "timelinephase.h"
  21. #include "frametree.h"
  22. #define ANGULAR_RES 3.5e-6
  23. using namespace std;
  24. Universe::Universe() :
  25.     starCatalog(NULL),
  26.     dsoCatalog(NULL),
  27.     solarSystemCatalog(NULL),
  28.     asterisms(NULL),
  29.     /*boundaries(NULL),*/
  30.     markers(NULL)
  31. {
  32.     markers = new MarkerList();
  33. }
  34. Universe::~Universe()
  35. {
  36.     delete markers;
  37.     // TODO: Clean up!
  38. }
  39. StarDatabase* Universe::getStarCatalog() const
  40. {
  41.     return starCatalog;
  42. }
  43. void Universe::setStarCatalog(StarDatabase* catalog)
  44. {
  45.     starCatalog = catalog;
  46. }
  47. SolarSystemCatalog* Universe::getSolarSystemCatalog() const
  48. {
  49.     return solarSystemCatalog;
  50. }
  51. void Universe::setSolarSystemCatalog(SolarSystemCatalog* catalog)
  52. {
  53.     solarSystemCatalog = catalog;
  54. }
  55. DSODatabase* Universe::getDSOCatalog() const
  56. {
  57.     return dsoCatalog;
  58. }
  59. void Universe::setDSOCatalog(DSODatabase* catalog)
  60. {
  61.     dsoCatalog = catalog;
  62. }
  63. AsterismList* Universe::getAsterisms() const
  64. {
  65.     return asterisms;
  66. }
  67. void Universe::setAsterisms(AsterismList* _asterisms)
  68. {
  69.     asterisms = _asterisms;
  70. }
  71. ConstellationBoundaries* Universe::getBoundaries() const
  72. {
  73.     return boundaries;
  74. }
  75. void Universe::setBoundaries(ConstellationBoundaries* _boundaries)
  76. {
  77.     boundaries = _boundaries;
  78. }
  79. // Return the planetary system of a star, or NULL if it has no planets.
  80. SolarSystem* Universe::getSolarSystem(const Star* star) const
  81. {
  82.     if (star == NULL)
  83.         return NULL;
  84.     uint32 starNum = star->getCatalogNumber();
  85.     SolarSystemCatalog::iterator iter = solarSystemCatalog->find(starNum);
  86.     if (iter == solarSystemCatalog->end())
  87.         return NULL;
  88.     else
  89.         return iter->second;
  90. }
  91. // A more general version of the method above--return the solar system
  92. // that contains an object, or NULL if there is no solar sytstem.
  93. SolarSystem* Universe::getSolarSystem(const Selection& sel) const
  94. {
  95.     switch (sel.getType())
  96.     {
  97.     case Selection::Type_Star:
  98.         return getSolarSystem(sel.star());
  99.     case Selection::Type_Body:
  100.         {
  101.             PlanetarySystem* system = sel.body()->getSystem();
  102.             while (system != NULL)
  103.             {
  104.                 Body* parent = system->getPrimaryBody();
  105.                 if (parent != NULL)
  106.                     system = parent->getSystem();
  107.                 else
  108.                     return getSolarSystem(Selection(system->getStar()));
  109.             }
  110.             return NULL;
  111.         }
  112.     case Selection::Type_Location:
  113.         return getSolarSystem(Selection(sel.location()->getParentBody()));
  114.     default:
  115.         return NULL;
  116.     }
  117. }
  118. // Create a new solar system for a star and return a pointer to it; if it
  119. // already has a solar system, just return a pointer to the existing one.
  120. SolarSystem* Universe::createSolarSystem(Star* star) const
  121. {
  122.     SolarSystem* solarSystem = getSolarSystem(star);
  123.     if (solarSystem != NULL)
  124.         return solarSystem;
  125.     solarSystem = new SolarSystem(star);
  126.     solarSystemCatalog->insert(SolarSystemCatalog::
  127.                                value_type(star->getCatalogNumber(),
  128.                                           solarSystem));
  129.     return solarSystem;
  130. }
  131. MarkerList* Universe::getMarkers() const
  132. {
  133.     return markers;
  134. }
  135. void Universe::markObject(const Selection& sel,
  136.                           const MarkerRepresentation& rep,
  137.                           int priority,
  138.                           bool occludable,
  139.                           MarkerSizing sizing)
  140. {
  141.     for (MarkerList::iterator iter = markers->begin();
  142.          iter != markers->end(); iter++)
  143.     {
  144.         if (iter->object() == sel)
  145.         {
  146.             // Handle the case when the object is already marked.  If the
  147.             // priority is higher than the existing marker, replace it.
  148.             // Otherwise, do nothing.
  149.             if (priority > iter->priority())
  150.             {
  151.                 markers->erase(iter);
  152.                 break;
  153.             }
  154.             else
  155.             {
  156.                 return;
  157.             }
  158.         }
  159.     }
  160.     Marker marker(sel);
  161.     marker.setRepresentation(rep);
  162.     marker.setPriority(priority);
  163.     marker.setOccludable(occludable);
  164.     marker.setSizing(sizing);
  165.     markers->insert(markers->end(), marker);
  166. }
  167. void Universe::unmarkObject(const Selection& sel, int priority)
  168. {
  169.     for (MarkerList::iterator iter = markers->begin();
  170.          iter != markers->end(); iter++)
  171.     {
  172.         if (iter->object() == sel)
  173.         {
  174.             if (priority >= iter->priority())
  175.                 markers->erase(iter);
  176.             break;
  177.         }
  178.     }
  179. }
  180. void Universe::unmarkAll()
  181. {
  182.     markers->erase(markers->begin(), markers->end());
  183. }
  184. bool Universe::isMarked(const Selection& sel, int priority) const
  185. {
  186.     for (MarkerList::iterator iter = markers->begin();
  187.          iter != markers->end(); iter++)
  188.     {
  189.         if (iter->object() == sel)
  190.             return iter->priority() >= priority;
  191.     }
  192.     return false;
  193. }
  194. class ClosestStarFinder : public StarHandler
  195. {
  196. public:
  197.     ClosestStarFinder(float _maxDistance, const Universe* _universe);
  198.     ~ClosestStarFinder() {};
  199.     void process(const Star& star, float distance, float appMag);
  200. public:
  201.     float maxDistance;
  202.     float closestDistance;
  203.     Star* closestStar;
  204.     const Universe* universe;
  205.     bool withPlanets;
  206. };
  207. ClosestStarFinder::ClosestStarFinder(float _maxDistance,
  208.                                      const Universe* _universe) :
  209.     maxDistance(_maxDistance),
  210.     closestDistance(_maxDistance),
  211.     closestStar(NULL),
  212.     universe(_universe),
  213.     withPlanets(false)
  214. {
  215. }
  216. void ClosestStarFinder::process(const Star& star, float distance, float)
  217. {
  218.     if (distance < closestDistance)
  219.     {
  220.         if (!withPlanets || universe->getSolarSystem(&star))
  221.         {
  222.             closestStar = const_cast<Star*>(&star);
  223.             closestDistance = distance;
  224.         }
  225.     }
  226. }
  227. class NearStarFinder : public StarHandler
  228. {
  229. public:
  230.     NearStarFinder(float _maxDistance, vector<const Star*>& nearStars);
  231.     ~NearStarFinder() {};
  232.     void process(const Star& star, float distance, float appMag);
  233. private:
  234.     float maxDistance;
  235.     vector<const Star*>& nearStars;
  236. };
  237. NearStarFinder::NearStarFinder(float _maxDistance,
  238.                                vector<const Star*>& _nearStars) :
  239.     maxDistance(_maxDistance),
  240.     nearStars(_nearStars)
  241. {
  242. }
  243. void NearStarFinder::process(const Star& star, float distance, float)
  244. {
  245.     if (distance < maxDistance)
  246.         nearStars.push_back(&star);
  247. }
  248. struct PlanetPickInfo
  249. {
  250.     double sinAngle2Closest;
  251.     double closestDistance;
  252.     double closestApproxDistance;
  253.     Body* closestBody;
  254.     Ray3d pickRay;
  255.     double jd;
  256.     float atanTolerance;
  257. };
  258. static bool ApproxPlanetPickTraversal(Body* body, void* info)
  259. {
  260.     PlanetPickInfo* pickInfo = (PlanetPickInfo*) info;
  261.     // Reject invisible bodies and bodies that don't exist at the current time
  262.     if (!body->isVisible() || !body->extant(pickInfo->jd) || !body->isClickable())
  263.         return true;
  264.     Point3d bpos = body->getAstrocentricPosition(pickInfo->jd);
  265.     Vec3d bodyDir = bpos - pickInfo->pickRay.origin;
  266.     double distance = bodyDir.length();
  267.     // Check the apparent radius of the orbit against our tolerance factor.
  268.     // This check exists to make sure than when picking a distant, we select
  269.     // the planet rather than one of its satellites.
  270.     float appOrbitRadius = (float) (body->getOrbit(pickInfo->jd)->getBoundingRadius() /
  271.                                     distance);
  272.     if ((pickInfo->atanTolerance > ANGULAR_RES ? pickInfo->atanTolerance:
  273.         ANGULAR_RES) > appOrbitRadius)
  274.     {
  275.         return true;
  276.     }
  277.     bodyDir.normalize();
  278.     Vec3d bodyMiss = bodyDir - pickInfo->pickRay.direction;
  279.     double sinAngle2 = sqrt(bodyMiss * bodyMiss)/2.0;
  280.     if (sinAngle2 <= pickInfo->sinAngle2Closest)
  281.     {
  282.         pickInfo->sinAngle2Closest = sinAngle2 > ANGULAR_RES ? sinAngle2 :
  283.                                          ANGULAR_RES;
  284.         pickInfo->closestBody = body;
  285.         pickInfo->closestApproxDistance = distance;
  286.     }
  287.     return true;
  288. }
  289. // Perform an intersection test between the pick ray and a body
  290. static bool ExactPlanetPickTraversal(Body* body, void* info)
  291. {
  292.     PlanetPickInfo* pickInfo = reinterpret_cast<PlanetPickInfo*>(info);
  293.     Point3d bpos = body->getAstrocentricPosition(pickInfo->jd);
  294.     float radius = body->getRadius();
  295.     double distance = -1.0;
  296.     // Test for intersection with the bounding sphere
  297.     if (body->isVisible() &&
  298.         body->extant(pickInfo->jd) &&
  299.         body->isClickable() &&
  300.         testIntersection(pickInfo->pickRay, Sphered(bpos, radius), distance))
  301.     {
  302.         if (body->getGeometry() == InvalidResource)
  303.         {
  304.             // There's no mesh, so the object is an ellipsoid.  If it's
  305.             // spherical, we've already done all the work we need to. Otherwise,
  306.             // we need to perform a ray-ellipsoid intersection test.
  307.             if (!body->isSphere())
  308.             {
  309.                 Vec3f semiAxes = body->getSemiAxes();
  310.                 Vec3d ellipsoidAxes(semiAxes.x, semiAxes.y, semiAxes.z);
  311.                 // Transform rotate the pick ray into object coordinates
  312.                 Mat3d m = conjugate(body->getEclipticToEquatorial(pickInfo->jd)).toMatrix3();
  313.                 Ray3d r(Point3d(0, 0, 0) + (pickInfo->pickRay.origin - bpos),
  314.                         pickInfo->pickRay.direction);
  315.                 r = r * m;
  316.                 if (!testIntersection(r, Ellipsoidd(ellipsoidAxes), distance))
  317.                     distance = -1.0;
  318.             }
  319.         }
  320.         else
  321.         {
  322.             // Transform rotate the pick ray into object coordinates
  323.             Quatf qf = body->getOrientation();
  324.             Quatd qd(qf.w, qf.x, qf.y, qf.z);
  325.             Mat3d m = conjugate(qd * body->getEclipticToBodyFixed(pickInfo->jd)).toMatrix3();
  326.             Ray3d r(Point3d(0, 0, 0) + (pickInfo->pickRay.origin - bpos),
  327.                     pickInfo->pickRay.direction);
  328.             r = r * m;
  329.             Geometry* geometry = GetGeometryManager()->find(body->getGeometry());
  330.             float scaleFactor = body->getGeometryScale();
  331.             if (geometry != NULL && geometry->isNormalized())
  332.                 scaleFactor = radius;
  333.             // The mesh vertices are normalized, then multiplied by a scale
  334.             // factor.  Thus, the ray needs to be multiplied by the inverse of
  335.             // the mesh scale factor.
  336.             double is = 1.0 / scaleFactor;
  337.             r.origin.x *= is;
  338.             r.origin.y *= is;
  339.             r.origin.z *= is;
  340.             r.direction *= is;
  341.             if (geometry != NULL)
  342.             {
  343.                 if (!geometry->pick(r, distance))
  344.                     distance = -1.0;
  345.             }
  346.         }
  347.         // Make also sure that the pickRay does not intersect the body in the
  348.         // opposite hemisphere! Hence, need again the "bodyMiss" angle
  349.         Vec3d bodyDir = bpos - pickInfo->pickRay.origin;
  350.         bodyDir.normalize();
  351.         Vec3d bodyMiss = bodyDir - pickInfo->pickRay.direction;
  352.         double sinAngle2 = sqrt(bodyMiss * bodyMiss)/2.0;
  353.         if (sinAngle2 < sin(PI/4.0) && distance > 0.0 &&
  354.             distance  <= pickInfo->closestDistance)
  355.         {
  356.             pickInfo->closestDistance = distance;
  357.             pickInfo->closestBody = body;
  358.         }
  359.     }
  360.     return true;
  361. }
  362. // Recursively traverse a frame tree; call the specified callback function for each
  363. // body in the tree. The callback function returns a boolean indicating whether
  364. // traversal should continue.
  365. //
  366. // TODO: This function works, but could use some cleanup:
  367. //   * Make it a member of the frame tree class
  368. //   * Combine info and func into a traversal callback class
  369. static bool traverseFrameTree(FrameTree* frameTree,
  370.                               double tdb,
  371.                               PlanetarySystem::TraversalFunc func,
  372.                               void* info)
  373. {
  374.     for (unsigned int i = 0; i < frameTree->childCount(); i++)
  375.     {
  376.         TimelinePhase* phase = frameTree->getChild(i);
  377.         if (phase->includes(tdb))
  378.         {
  379.             Body* body = phase->body();
  380.             if (!func(body, info))
  381.                 return false;
  382.             if (body->getFrameTree() != NULL)
  383.             {
  384.                 if (!traverseFrameTree(body->getFrameTree(), tdb, func, info))
  385.                     return false;
  386.             }
  387.         }
  388.     }
  389.     return true;
  390. }
  391. Selection Universe::pickPlanet(SolarSystem& solarSystem,
  392.                                const UniversalCoord& origin,
  393.                                const Vec3f& direction,
  394.                                double when,
  395.                                float /*faintestMag*/,
  396.                                float tolerance)
  397. {
  398.     double sinTol2 = (sin(tolerance/2.0) >  ANGULAR_RES ?
  399.               sin(tolerance/2.0) : ANGULAR_RES);
  400.     PlanetPickInfo pickInfo;
  401.     Star* star = solarSystem.getStar();
  402.     assert(star != NULL);
  403.     // Transform the pick ray origin into astrocentric coordinates
  404.     UniversalCoord starPos = star->getPosition(when);
  405.     Vec3d v = origin - starPos;
  406.     Point3d astrocentricOrigin(astro::microLightYearsToKilometers(v.x),
  407.                                astro::microLightYearsToKilometers(v.y),
  408.                                astro::microLightYearsToKilometers(v.z));
  409.     pickInfo.pickRay = Ray3d(astrocentricOrigin,
  410.                              Vec3d(direction.x, direction.y, direction.z));
  411.     pickInfo.sinAngle2Closest = 1.0;
  412.     pickInfo.closestDistance = 1.0e50;
  413.     pickInfo.closestApproxDistance = 1.0e50;
  414.     pickInfo.closestBody = NULL;
  415.     pickInfo.jd = when;
  416.     pickInfo.atanTolerance = (float) atan(tolerance);
  417.     // First see if there's a planet|moon that the pick ray intersects.
  418.     // Select the closest planet|moon intersected.
  419.     traverseFrameTree(solarSystem.getFrameTree(), when, ExactPlanetPickTraversal, (void*) &pickInfo);
  420.     if (pickInfo.closestBody != NULL)
  421.     {
  422.         // Retain that body
  423.         Body* closestBody = pickInfo.closestBody;
  424.     // Check if there is a satellite in front of the primary body that is
  425.     // sufficiently close to the pickRay
  426.         traverseFrameTree(solarSystem.getFrameTree(), when, ApproxPlanetPickTraversal, (void*) &pickInfo);
  427.         if (pickInfo.closestBody == closestBody)
  428.         return  Selection(closestBody);
  429.         // Nothing else around, select the body and return
  430.         // Are we close enough to the satellite and is it in front of the body?
  431.      if ((pickInfo.sinAngle2Closest <= sinTol2) &&
  432.             (pickInfo.closestDistance > pickInfo.closestApproxDistance))
  433.             return Selection(pickInfo.closestBody);
  434.             // Yes, select the satellite
  435.     else
  436.         return  Selection(closestBody);
  437.            //  No, select the primary body
  438.     }
  439.     // If no planet was intersected by the pick ray, choose the planet|moon
  440.     // with the smallest angular separation from the pick ray.  Very distant
  441.     // planets are likley to fail the intersection test even if the user
  442.     // clicks on a pixel where the planet's disc has been rendered--in order
  443.     // to make distant planets visible on the screen at all, their apparent
  444.     // size has to be greater than their actual disc size.
  445.     traverseFrameTree(solarSystem.getFrameTree(), when, ApproxPlanetPickTraversal, (void*) &pickInfo);
  446.     if (pickInfo.sinAngle2Closest <= sinTol2)
  447.         return Selection(pickInfo.closestBody);
  448.     else
  449.         return Selection();
  450. }
  451. // StarPicker is a callback class for StarDatabase::findVisibleStars
  452. class StarPicker : public StarHandler
  453. {
  454. public:
  455.     StarPicker(const Point3f&, const Vec3f&, double, float);
  456.     ~StarPicker() {};
  457.     void process(const Star&, float, float);
  458. public:
  459.     const Star* pickedStar;
  460.     Point3f pickOrigin;
  461.     Vec3f pickRay;
  462.     double sinAngle2Closest;
  463.     double when;
  464. };
  465. StarPicker::StarPicker(const Point3f& _pickOrigin,
  466.                        const Vec3f& _pickRay,
  467.                        double _when,
  468.                        float angle) :
  469.     pickedStar(NULL),
  470.     pickOrigin(_pickOrigin),
  471.     pickRay(_pickRay),
  472.     sinAngle2Closest(sin(angle/2.0) > ANGULAR_RES ? sin(angle/2.0) :
  473.                                                     ANGULAR_RES ),
  474.     when(_when)
  475. {
  476. }
  477. void StarPicker::process(const Star& star, float, float)
  478. {
  479.     Vec3f relativeStarPos = star.getPosition() - pickOrigin;
  480.     Vec3f starDir = relativeStarPos;
  481.     starDir.normalize();
  482.     double sinAngle2 = 0.0;
  483.     // Stars with orbits need special handling
  484.     float orbitalRadius = star.getOrbitalRadius();
  485.     if (orbitalRadius != 0.0f)
  486.     {
  487.         float distance;
  488.         // Check for an intersection with orbital bounding sphere; if there's
  489.         // no intersection, then just use normal calculation.  We actually test
  490.         // intersection with a larger sphere to make sure we don't miss a star
  491.         // right on the edge of the sphere.
  492.         if (testIntersection(Ray3f(Point3f(0.0f, 0.0f, 0.0f), pickRay),
  493.                              Spheref(Point3f(0.0f, 0.0f, 0.0f) + relativeStarPos,
  494.                                      orbitalRadius * 2.0f),
  495.                              distance))
  496.         {
  497.             Point3d starPos = star.getPosition(when);
  498.             starDir = Vec3f((float) (starPos.x * 1.0e-6 - pickOrigin.x),
  499.                             (float) (starPos.y * 1.0e-6 - pickOrigin.y),
  500.                             (float) (starPos.z * 1.0e-6 - pickOrigin.z));
  501.             starDir.normalize();
  502.         }
  503.     }
  504.     Vec3f starMiss = starDir - pickRay;
  505.     Vec3d sMd = Vec3d(starMiss.x, starMiss.y, starMiss.z);
  506.     sinAngle2 = sqrt(sMd * sMd)/2.0;
  507.     if (sinAngle2 <= sinAngle2Closest)
  508.     {
  509.         sinAngle2Closest = sinAngle2 > ANGULAR_RES ? sinAngle2 : ANGULAR_RES;
  510.         pickedStar = &star;
  511.         if (pickedStar->getOrbitBarycenter() != NULL)
  512.             pickedStar = pickedStar->getOrbitBarycenter();
  513.     }
  514. }
  515. class CloseStarPicker : public StarHandler
  516. {
  517. public:
  518.     CloseStarPicker(const UniversalCoord& pos,
  519.                     const Vec3f& dir,
  520.                     double t,
  521.                     float _maxDistance,
  522.                     float angle);
  523.     ~CloseStarPicker() {};
  524.     void process(const Star& star, float distance, float appMag);
  525. public:
  526.     UniversalCoord pickOrigin;
  527.     Vec3f pickDir;
  528.     double now;
  529.     float maxDistance;
  530.     const Star* closestStar;
  531.     float closestDistance;
  532.     double sinAngle2Closest;
  533. };
  534. CloseStarPicker::CloseStarPicker(const UniversalCoord& pos,
  535.                                  const Vec3f& dir,
  536.                                  double t,
  537.                                  float _maxDistance,
  538.                                  float angle) :
  539.     pickOrigin(pos),
  540.     pickDir(dir),
  541.     now(t),
  542.     maxDistance(_maxDistance),
  543.     closestStar(NULL),
  544.     closestDistance(0.0f),
  545.     sinAngle2Closest(sin(angle/2.0) > ANGULAR_RES ? sin(angle/2.0) :
  546.                                       ANGULAR_RES )
  547. {
  548. }
  549. void CloseStarPicker::process(const Star& star,
  550.                               float lowPrecDistance,
  551.                               float)
  552. {
  553.     if (lowPrecDistance > maxDistance)
  554.         return;
  555.     Vec3d hPos = (star.getPosition(now) - pickOrigin) *
  556.         astro::microLightYearsToKilometers(1.0);
  557.     Vec3f starDir((float) hPos.x, (float) hPos.y, (float) hPos.z);
  558.     float distance = 0.0f;
  559.      if (testIntersection(Ray3f(Point3f(0, 0, 0), pickDir),
  560.                          Spheref(Point3f(starDir.x, starDir.y, starDir.z),
  561.                                  star.getRadius()), distance))
  562.     {
  563.         if (distance > 0.0f)
  564.         {
  565.             if (closestStar == NULL || distance < closestDistance)
  566.             {
  567.                 closestStar = &star;
  568.                 closestDistance = starDir.length();
  569.                 sinAngle2Closest = ANGULAR_RES;
  570.                 // An exact hit--set the angle to "zero"
  571.             }
  572.         }
  573.     }
  574.     else
  575.     {
  576.         // We don't have an exact hit; check to see if we're close enough
  577.         float distance = starDir.length();
  578.         starDir.normalize();
  579.         Vec3f starMiss = starDir - pickDir;
  580.         Vec3d sMd = Vec3d(starMiss.x, starMiss.y, starMiss.z );
  581.         double sinAngle2 = sqrt(sMd * sMd)/2.0;
  582.         if (sinAngle2 <= sinAngle2Closest &&
  583.             (closestStar == NULL || distance < closestDistance))
  584.         {
  585.             closestStar = &star;
  586.             closestDistance = distance;
  587.             sinAngle2Closest = sinAngle2 > ANGULAR_RES ? sinAngle2 : ANGULAR_RES;
  588.         }
  589.     }
  590. }
  591. Selection Universe::pickStar(const UniversalCoord& origin,
  592.                              const Vec3f& direction,
  593.                              double when,
  594.                              float faintestMag,
  595.                              float tolerance)
  596. {
  597.     Point3f o = (Point3f) origin;
  598.     o.x *= 1e-6f;
  599.     o.y *= 1e-6f;
  600.     o.z *= 1e-6f;
  601.     // Use a high precision pick test for any stars that are close to the
  602.     // observer.  If this test fails, use a low precision pick test for stars
  603.     // which are further away.  All this work is necessary because the low
  604.     // precision pick test isn't reliable close to a star and the high
  605.     // precision test isn't nearly fast enough to use on our database of
  606.     // over 100k stars.
  607.     CloseStarPicker closePicker(origin, direction, when, 1.0f, tolerance);
  608.     starCatalog->findCloseStars(closePicker, o, 1.0f);
  609.     if (closePicker.closestStar != NULL)
  610.         return Selection(const_cast<Star*>(closePicker.closestStar));
  611.     // Find visible stars expects an orientation, but we just have a direction
  612.     // vector.  Convert the direction vector into an orientation by computing
  613.     // the rotation required to map (0, 0, -1) to the direction.
  614.     Quatf rotation;
  615.     Vec3f n(0, 0, -1);
  616.     Vec3f Missf = n - direction;
  617.     Vec3d Miss = Vec3d(Missf.x, Missf.y, Missf.z);
  618.     double sinAngle2 = sqrt(Miss * Miss)/2.0;
  619.     if (sinAngle2 <= ANGULAR_RES)
  620.     {
  621.         rotation.setAxisAngle(Vec3f(1, 0, 0), 0);
  622.     }
  623.     else if (sinAngle2 >= 1.0 - 0.5 * ANGULAR_RES * ANGULAR_RES)
  624.     {
  625.         rotation.setAxisAngle(Vec3f(1, 0, 0), (float) PI);
  626.     }
  627.     else
  628.     {
  629.         Vec3f axis = direction ^ n;
  630.         axis.normalize();
  631.         rotation.setAxisAngle(axis, (float) (2.0 * asin(sinAngle2)));
  632.     }
  633.     StarPicker picker(o, direction, when, tolerance);
  634.     starCatalog->findVisibleStars(picker,
  635.                                   o,
  636.                                   rotation,
  637.                                   tolerance, 1.0f,
  638.                                   faintestMag);
  639.     if (picker.pickedStar != NULL)
  640.         return Selection(const_cast<Star*>(picker.pickedStar));
  641.     else
  642.         return Selection();
  643. }
  644. class DSOPicker : public DSOHandler
  645. {
  646. public:
  647.     DSOPicker(const Point3d&, const Vec3d&, int, float);
  648.     ~DSOPicker() {};
  649.     void process(DeepSkyObject* const &, double, float);
  650. public:
  651.     Point3d pickOrigin;
  652.     Vec3d   pickDir;
  653.     int     renderFlags;
  654.     const DeepSkyObject* pickedDSO;
  655.     double  sinAngle2Closest;
  656. };
  657. DSOPicker::DSOPicker(const Point3d& pickOrigin,
  658.                      const Vec3d&   pickDir,
  659.                      int   renderFlags,
  660.                      float angle) :
  661.     pickOrigin      (pickOrigin),
  662.     pickDir         (pickDir),
  663.     renderFlags     (renderFlags),
  664.     pickedDSO       (NULL),
  665.     sinAngle2Closest(sin(angle/2.0) > ANGULAR_RES ? sin(angle/2.0) :
  666.                                                     ANGULAR_RES )
  667. {
  668. }
  669. void DSOPicker::process(DeepSkyObject* const & dso, double, float)
  670. {
  671.     if (!(dso->getRenderMask() & renderFlags) || !dso->isVisible() || !dso->isClickable())
  672.         return;
  673.     Vec3d relativeDSOPos = dso->getPosition() - pickOrigin;
  674.     Vec3d dsoDir = relativeDSOPos;
  675.     double distance2;
  676.     if (testIntersection(Ray3d(Point3d(0.0, 0.0, 0.0), pickDir),
  677.                          Sphered(Point3d(0.0, 0.0, 0.0) + relativeDSOPos, (double) dso->getRadius()), distance2))
  678.     {
  679.         Point3d dsoPos = dso->getPosition();
  680.         dsoDir         = Vec3d(dsoPos.x * 1.0e-6 - pickOrigin.x,
  681.                                dsoPos.y * 1.0e-6 - pickOrigin.y,
  682.                                dsoPos.z * 1.0e-6 - pickOrigin.z);
  683.     }
  684.     dsoDir.normalize();
  685.     Vec3d dsoMissd   = dsoDir - Vec3d(pickDir.x, pickDir.y, pickDir.z);
  686.     double sinAngle2 = sqrt(dsoMissd * dsoMissd)/2.0;
  687.     if (sinAngle2 <= sinAngle2Closest)
  688.     {
  689.         sinAngle2Closest = sinAngle2 > ANGULAR_RES ? sinAngle2 : ANGULAR_RES;
  690.         pickedDSO        = dso;
  691.     }
  692. }
  693. class CloseDSOPicker : public DSOHandler
  694. {
  695. public:
  696.     CloseDSOPicker(const Point3d& pos,
  697.                    const Vec3d& dir,
  698.                    int    renderFlags,
  699.                    double maxDistance,
  700.                    float);
  701.     ~CloseDSOPicker() {};
  702.     void process(DeepSkyObject* const & dso, double distance, float appMag);
  703. public:
  704.     Point3d pickOrigin;
  705.     Vec3d   pickDir;
  706.     int     renderFlags;
  707.     double  maxDistance;
  708.     const DeepSkyObject* closestDSO;
  709.     double largestCosAngle;
  710. };
  711. CloseDSOPicker::CloseDSOPicker(const Point3d& pos,
  712.                                const Vec3d& dir,
  713.                                int    renderFlags,
  714.                                double maxDistance,
  715.                                float) :
  716.     pickOrigin     (pos),
  717.     pickDir        (dir),
  718.     renderFlags    (renderFlags),
  719.     maxDistance    (maxDistance),
  720.     closestDSO     (NULL),
  721.     largestCosAngle(-2.0)
  722. {
  723. }
  724. void CloseDSOPicker::process(DeepSkyObject* const & dso,
  725.                              double distance,
  726.                              float)
  727. {
  728.     if (distance > maxDistance || !(dso->getRenderMask() & renderFlags) || !dso->isVisible() || !dso->isClickable())
  729.         return;
  730.     double  distanceToPicker       = 0.0;
  731.     double  cosAngleToBoundCenter  = 0.0;
  732.     if (dso->pick(Ray3d(pickOrigin, pickDir), distanceToPicker, cosAngleToBoundCenter))
  733.     {
  734.         // Don't select the object the observer is currently in:
  735.         if (pickOrigin.distanceTo(dso->getPosition()) > dso->getRadius() &&
  736.             cosAngleToBoundCenter > largestCosAngle)
  737.         {
  738.             closestDSO      = dso;
  739.             largestCosAngle = cosAngleToBoundCenter;
  740.         }
  741.     }
  742. }
  743. Selection Universe::pickDeepSkyObject(const UniversalCoord& origin,
  744.                                       const Vec3f& direction,
  745.                                       int   renderFlags,
  746.                                       float faintestMag,
  747.                                       float tolerance)
  748. {
  749.     Point3d orig = (Point3d) origin;
  750.     orig.x *= 1e-6;
  751.     orig.y *= 1e-6;
  752.     orig.z *= 1e-6;
  753.     Vec3d dir   = Vec3d(direction.x, direction.y, direction.z);
  754.     CloseDSOPicker closePicker(orig, dir, renderFlags, 1e9, tolerance);
  755.     dsoCatalog->findCloseDSOs(closePicker, orig, 1e9);
  756.     if (closePicker.closestDSO != NULL)
  757.     {
  758.         return Selection(const_cast<DeepSkyObject*>(closePicker.closestDSO));
  759.     }
  760.     Quatf rotation;
  761.     Vec3d n(0, 0, -1);
  762.     Vec3d Miss       = n - dir;
  763.     double sinAngle2 = sqrt(Miss * Miss)/2.0;
  764.     if (sinAngle2 <= ANGULAR_RES)
  765.     {
  766.         rotation.setAxisAngle(Vec3f(1, 0, 0), 0);
  767.     }
  768.     else if (sinAngle2 >= 1.0 - 0.5 * ANGULAR_RES * ANGULAR_RES)
  769.     {
  770.         rotation.setAxisAngle(Vec3f(1, 0, 0), (float) PI);
  771.     }
  772.     else
  773.     {
  774.         Vec3f axis = direction ^ Vec3f((float)n.x, (float)n.y, (float)n.z);
  775.         axis.normalize();
  776.         rotation.setAxisAngle(axis, (float) (2.0 * asin(sinAngle2)));
  777.     }
  778.     DSOPicker picker(orig, dir, renderFlags, tolerance);
  779.     dsoCatalog->findVisibleDSOs(picker,
  780.                                 orig,
  781.                                 rotation,
  782.                                 tolerance,
  783.                                 1.0f,
  784.                                 faintestMag);
  785.     if (picker.pickedDSO != NULL)
  786.         return Selection(const_cast<DeepSkyObject*>(picker.pickedDSO));
  787.     else
  788.         return Selection();
  789. }
  790. Selection Universe::pick(const UniversalCoord& origin,
  791.                          const Vec3f& direction,
  792.                          double when,
  793.                          int    renderFlags,
  794.                          float  faintestMag,
  795.                          float  tolerance)
  796. {
  797.     Selection sel;
  798.     if (renderFlags & Renderer::ShowPlanets)
  799.     {
  800.         closeStars.clear();
  801.         getNearStars(origin, 1.0f, closeStars);
  802.         for (vector<const Star*>::const_iterator iter = closeStars.begin();
  803.             iter != closeStars.end(); iter++)
  804.         {
  805.             SolarSystem* solarSystem = getSolarSystem(*iter);
  806.             if (solarSystem != NULL)
  807.             {
  808.                 sel = pickPlanet(*solarSystem,
  809.                                 origin, direction,
  810.                                 when,
  811.                                 faintestMag,
  812.                                 tolerance);
  813.                 if (!sel.empty())
  814.                     break;
  815.             }
  816.         }
  817.     }
  818. #if 0
  819.     SolarSystem* closestSolarSystem = getNearestSolarSystem(origin);
  820.     if (closestSolarSystem != NULL)
  821.     {
  822.         sel = pickPlanet(*closestSolarSystem,
  823.                          origin, direction,
  824.                          when,
  825.                          faintestMag,
  826.                          tolerance);
  827.     }
  828. #endif
  829.     if (sel.empty() && (renderFlags & Renderer::ShowStars))
  830.         sel = pickStar(origin, direction, when, faintestMag, tolerance);
  831.     if (sel.empty())
  832.         sel = pickDeepSkyObject(origin, direction, renderFlags, faintestMag, tolerance);
  833.     return sel;
  834. }
  835. // Search by name for an immediate child of the specified object.
  836. Selection Universe::findChildObject(const Selection& sel,
  837.                                     const string& name,
  838.                                     bool i18n) const
  839. {
  840.     switch (sel.getType())
  841.     {
  842.     case Selection::Type_Star:
  843.         {
  844.             SolarSystem* sys = getSolarSystem(sel.star());
  845.             if (sys != NULL)
  846.             {
  847.                 PlanetarySystem* planets = sys->getPlanets();
  848.                 if (planets != NULL)
  849.                     return Selection(planets->find(name, false, i18n));
  850.             }
  851.         }
  852.         break;
  853.     case Selection::Type_Body:
  854.         {
  855.             // First, search for a satellite
  856.             PlanetarySystem* sats = sel.body()->getSatellites();
  857.             if (sats != NULL)
  858.             {
  859.                 Body* body = sats->find(name, false, i18n);
  860.                 if (body != NULL)
  861.                     return Selection(body);
  862.             }
  863.             // If a satellite wasn't found, check this object's locations
  864.             Location* loc = sel.body()->findLocation(name, i18n);
  865.             if (loc != NULL)
  866.                 return Selection(loc);
  867.         }
  868.         break;
  869.     case Selection::Type_Location:
  870.         // Locations have no children
  871.         break;
  872.     case Selection::Type_DeepSky:
  873.         // Deep sky objects have no children
  874.         break;
  875.     default:
  876.         break;
  877.     }
  878.     return Selection();
  879. }
  880. // Search for a name within an object's context.  For stars, planets (bodies),
  881. // and locations, the context includes all bodies in the associated solar
  882. // system.  For locations and planets, the context additionally includes
  883. // sibling or child locations, respectively.
  884. Selection Universe::findObjectInContext(const Selection& sel,
  885.                                         const string& name,
  886.                                         bool i18n) const
  887. {
  888.     Body* contextBody = NULL;
  889.     switch (sel.getType())
  890.     {
  891.     case Selection::Type_Body:
  892.         contextBody = sel.body();
  893.         break;
  894.     case Selection::Type_Location:
  895.         contextBody = sel.location()->getParentBody();
  896.         break;
  897.     default:
  898.         break;
  899.     }
  900.     // First, search for bodies . . .
  901.     SolarSystem* sys = getSolarSystem(sel);
  902.     if (sys != NULL)
  903.     {
  904.         PlanetarySystem* planets = sys->getPlanets();
  905.         if (planets != NULL)
  906.         {
  907.             Body* body = planets->find(name, true, i18n);
  908.             if (body != NULL)
  909.                 return Selection(body);
  910.         }
  911.     }
  912.     // . . . and then locations.
  913.     if (contextBody != NULL)
  914.     {
  915.         Location* loc = contextBody->findLocation(name, i18n);
  916.         if (loc != NULL)
  917.             return Selection(loc);
  918.     }
  919.     return Selection();
  920. }
  921. // Select an object by name, with the following priority:
  922. //   1. Try to look up the name in the star catalog
  923. //   2. Search the deep sky catalog for a matching name.
  924. //   3. Check the solar systems for planet names; we don't make any decisions
  925. //      about which solar systems are relevant, and let the caller pass them
  926. //      to us to search.
  927. Selection Universe::find(const string& s,
  928.                          Selection* contexts,
  929.                          int nContexts,
  930.                          bool i18n) const
  931. {
  932.     if (starCatalog != NULL)
  933.     {
  934.     Star* star = starCatalog->find(s);
  935.     if (star != NULL)
  936.         return Selection(star);
  937.     }
  938.     if (dsoCatalog != NULL)
  939.     {
  940.         DeepSkyObject* dso = dsoCatalog->find(s);
  941.         if (dso != NULL)
  942.             return Selection(dso);
  943.     }
  944.     for (int i=0; i<nContexts; ++i)
  945.     {
  946.         Selection sel = findObjectInContext(contexts[i], s, i18n);
  947.         if (!sel.empty())
  948.             return sel;
  949.     }
  950.     return Selection();
  951. }
  952. // Find an object from a path, for example Sol/Earth/Moon or Upsilon And/b
  953. // Currently, 'absolute' paths starting with a / are not supported nor are
  954. // paths that contain galaxies.  The caller may pass in a list of solar systems
  955. // to search for objects--this is roughly analgous to the PATH environment
  956. // variable in Unix and Windows.  Typically, the solar system will be one
  957. // in which the user is currently located.
  958. Selection Universe::findPath(const string& s,
  959.                              Selection contexts[],
  960.                              int nContexts,
  961.                              bool i18n) const
  962. {
  963.     string::size_type pos = s.find('/', 0);
  964.     // No delimiter found--just do a normal find.
  965.     if (pos == string::npos)
  966.         return find(s, contexts, nContexts, i18n);
  967.     // Find the base object
  968.     string base(s, 0, pos);
  969.     Selection sel = find(base, contexts, nContexts, i18n);
  970.     while (!sel.empty() && pos != string::npos)
  971.     {
  972.         string::size_type nextPos = s.find('/', pos + 1);
  973.         string::size_type len;
  974.         if (nextPos == string::npos)
  975.             len = s.size() - pos - 1;
  976.         else
  977.             len = nextPos - pos - 1;
  978.         string name = string(s, pos + 1, len);
  979.         sel = findChildObject(sel, name, i18n);
  980.         pos = nextPos;
  981.     }
  982.     return sel;
  983. }
  984. vector<string> Universe::getCompletion(const string& s,
  985.                                                  Selection* contexts,
  986.                                                  int nContexts,
  987.                                                  bool withLocations)
  988. {
  989.     vector<string> completion;
  990.     int s_length = UTF8Length(s);
  991.     // Solar bodies first:
  992.     for (int i = 0; i < nContexts; i++)
  993.     {
  994.         if (withLocations && contexts[i].getType() == Selection::Type_Body)
  995.         {
  996.             vector<Location*>* locations = contexts[i].body()->getLocations();
  997.             if (locations != NULL)
  998.             {
  999.                 for (vector<Location*>::const_iterator iter = locations->begin();
  1000.                      iter != locations->end(); iter++)
  1001.                 {
  1002.                     if (!UTF8StringCompare(s, (*iter)->getName(true), s_length))
  1003.                         completion.push_back((*iter)->getName(true));
  1004.                 }
  1005.             }
  1006.         }
  1007.         SolarSystem* sys = getSolarSystem(contexts[i]);
  1008.         if (sys != NULL)
  1009.         {
  1010.             PlanetarySystem* planets = sys->getPlanets();
  1011.             if (planets != NULL)
  1012.             {
  1013.                 vector<string> bodies = planets->getCompletion(s);
  1014.                 completion.insert(completion.end(),
  1015.                                   bodies.begin(), bodies.end());
  1016.             }
  1017.         }
  1018.     }
  1019.     // Deep sky objects:
  1020.     if (dsoCatalog != NULL)
  1021.         {
  1022.         vector<string> dsos  = dsoCatalog->getCompletion(s);
  1023.         completion.insert(completion.end(), dsos.begin(), dsos.end());
  1024.     }
  1025.     // and finally stars;
  1026.     if (starCatalog != NULL)
  1027.     {
  1028.         vector<string> stars  = starCatalog->getCompletion(s);
  1029.     completion.insert(completion.end(), stars.begin(), stars.end());
  1030.     }
  1031.     return completion;
  1032. }
  1033. vector<string> Universe::getCompletionPath(const string& s,
  1034.                                                      Selection* contexts,
  1035.                                                      int nContexts,
  1036.                                                      bool withLocations)
  1037. {
  1038.     vector<string> completion;
  1039.     vector<string> locationCompletion;
  1040.     string::size_type pos = s.rfind('/', s.length());
  1041.     if (pos == string::npos)
  1042.         return getCompletion(s, contexts, nContexts, withLocations);
  1043.     string base(s, 0, pos);
  1044.     Selection sel = findPath(base, contexts, nContexts, true);
  1045.     if (sel.empty())
  1046.     {
  1047.         return completion;
  1048.     }
  1049.     if (sel.getType() == Selection::Type_DeepSky)
  1050.     {
  1051.         completion.push_back(dsoCatalog->getDSOName(sel.deepsky()));
  1052.         return completion;
  1053.     }
  1054.     PlanetarySystem* worlds = NULL;
  1055.     if (sel.getType() == Selection::Type_Body)
  1056.     {
  1057.         worlds = sel.body()->getSatellites();
  1058.         vector<Location*>* locations = sel.body()->getLocations();
  1059.         if (locations != NULL && withLocations)
  1060.         {
  1061.             string search = s.substr(pos + 1);
  1062.             for (vector<Location*>::const_iterator iter = locations->begin();
  1063.                  iter != locations->end(); iter++)
  1064.             {
  1065.                 if (!UTF8StringCompare(search, (*iter)->getName(true),
  1066.                                        search.length()))
  1067.                 {
  1068.                     locationCompletion.push_back((*iter)->getName(true));
  1069.                 }
  1070.             }
  1071.         }
  1072.     }
  1073.     else if (sel.getType() == Selection::Type_Star)
  1074.     {
  1075.         SolarSystem* ssys = getSolarSystem(sel.star());
  1076.         if (ssys != NULL)
  1077.             worlds = ssys->getPlanets();
  1078.     }
  1079.     if (worlds != NULL)
  1080.         completion = worlds->getCompletion(s.substr(pos + 1), false);
  1081.     completion.insert(completion.end(), locationCompletion.begin(), locationCompletion.end());
  1082.     return completion;
  1083. }
  1084. // Return the closest solar system to position, or NULL if there are no planets
  1085. // with in one light year.
  1086. SolarSystem* Universe::getNearestSolarSystem(const UniversalCoord& position) const
  1087. {
  1088.     Point3f pos = (Point3f) position;
  1089.     Point3f lyPos(pos.x * 1.0e-6f, pos.y * 1.0e-6f, pos.z * 1.0e-6f);
  1090.     ClosestStarFinder closestFinder(1.0f, this);
  1091.     closestFinder.withPlanets = true;
  1092.     starCatalog->findCloseStars(closestFinder, lyPos, 1.0f);
  1093.     return getSolarSystem(closestFinder.closestStar);
  1094. }
  1095. void
  1096. Universe::getNearStars(const UniversalCoord& position,
  1097.                        float maxDistance,
  1098.                        vector<const Star*>& nearStars) const
  1099. {
  1100.     Point3f pos = (Point3f) position;
  1101.     Point3f lyPos(pos.x * 1.0e-6f, pos.y * 1.0e-6f, pos.z * 1.0e-6f);
  1102.     NearStarFinder finder(1.0f, nearStars);
  1103.     starCatalog->findCloseStars(finder, lyPos, maxDistance);
  1104. }