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

OpenGL

开发平台:

Visual C++

  1. // skygrid.cpp
  2. //
  3. // Celestial longitude/latitude grids.
  4. //
  5. // Copyright (C) 2008-2009, the Celestia Development Team
  6. // Initial version by Chris Laurel, <claurel@gmail.com>
  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 <cmath>
  13. #include <cstdlib>
  14. #include <sstream>
  15. #include <iomanip>
  16. #include <algorithm>
  17. #include "celutil/util.h"
  18. #include "render.h"
  19. #include "vecgl.h"
  20. #include "skygrid.h"
  21. using namespace std;
  22. // #define DEBUG_LABEL_PLACEMENT
  23. // The maximum number of parallels or meridians that will be visible
  24. const double MAX_VISIBLE_ARCS = 10.0;
  25. // Number of line segments used to approximate one arc of the celestial sphere
  26. const int ARC_SUBDIVISIONS = 100;
  27. // Size of the cross indicating the north and south poles
  28. const double POLAR_CROSS_SIZE = 0.01;
  29. // Grid line spacing tables
  30. static const int MSEC = 1;
  31. static const int SEC = 1000;
  32. static const int MIN = 60 * SEC;
  33. static const int DEG = 60 * MIN;
  34. static const int HR  = 60 * MIN;
  35. static const int HOUR_MIN_SEC_TOTAL = 24 * HR;
  36. static const int DEG_MIN_SEC_TOTAL  = 180 * DEG;
  37. static const int HOUR_MIN_SEC_SPACING[] =
  38. {
  39.     2*HR,
  40.     1*HR,
  41.     30*MIN,
  42.     15*MIN,
  43.     10*MIN,
  44.     5*MIN,
  45.     3*MIN,
  46.     2*MIN,
  47.     1*MIN,
  48.     30*SEC,
  49.     15*SEC,
  50.     10*SEC,
  51.     5*SEC,
  52.     3*SEC,
  53.     2*SEC,
  54.     1*SEC,
  55.     500*MSEC,
  56.     200*MSEC,
  57.     100*MSEC
  58. };
  59. static const int DEG_MIN_SEC_SPACING[]  =
  60. {
  61.     30*DEG,
  62.     15*DEG,
  63.     10*DEG,
  64.     5*DEG,
  65.     3*DEG,
  66.     2*DEG,
  67.     1*DEG,
  68.     30*MIN,
  69.     15*MIN,
  70.     10*MIN,
  71.     5*MIN,
  72.     3*MIN,
  73.     2*MIN,
  74.     1*MIN,
  75.     30*SEC,
  76.     15*SEC,
  77.     10*SEC,
  78.     5*SEC,
  79.     3*SEC,
  80.     2*SEC,
  81.     1*SEC,
  82.     500*MSEC,
  83.     200*MSEC,
  84.     100*MSEC
  85. };
  86.     
  87. // Alternate spacing tables
  88. #if 0
  89. // Max step between spacings is 5x; all spacings are
  90. // integer multiples of subsequent spacings.
  91. static const int HOUR_MIN_SEC_SPACING[] =
  92. {
  93.     2*HR,   1*HR,  30*MIN, 10*MIN,  5*MIN,
  94.     1*MIN, 30*SEC, 10*SEC,  5*SEC,  1*SEC,
  95.     500*MSEC, 100*MSEC
  96. }; 
  97. static const int DEG_MIN_SEC_SPACING[]  =
  98. {
  99.     30*DEG, 10*DEG,  5*DEG,  1*DEG, 30*MIN,
  100.     10*MIN,  5*MIN,  1*MIN, 30*SEC, 10*SEC,
  101.     5*SEC,  1*SEC, 500*MSEC, 100*MSEC
  102. };
  103. #endif
  104. #if 0
  105. // Max step between spacings is 3x
  106. static const int HOUR_MIN_SEC_STEPS[] =
  107. {
  108.     2*HR,   1*HR,  30*MIN, 10*MIN,  5*MIN, 2*MIN+30*SEC,
  109.     1*MIN, 30*SEC, 10*SEC,  5*SEC, 2*SEC+500*MSEC, 1*SEC,
  110.     500*MSEC, 200*MSEC, 100*MSEC, 50*MSEC, 20*MSEC, 10*MSEC
  111. }; 
  112. static const int DEG_MIN_SEC_STEPS[]  =
  113.     30*DEG, 10*DEG,  5*DEG, 2*DEG+30*MIN, 1*DEG, 30*MIN,
  114.     10*MIN,  5*MIN, 2*MIN+30*SEC, 1*MIN, 30*SEC, 10*SEC,
  115.     5*SEC, 2*SEC+500*MSEC, 1*SEC, 500*MSEC, 200*MSEC, 100*MSEC,
  116.     50*MSEC, 20*MSEC, 10*MSEC
  117. };
  118. #endif
  119. SkyGrid::SkyGrid() :
  120.     m_orientation(1.0),
  121.     m_lineColor(Color::White),
  122.     m_labelColor(Color::White),
  123.     m_longitudeUnits(LongitudeHours),
  124.     m_longitudeDirection(IncreasingCounterclockwise)
  125. {
  126. }
  127. SkyGrid::~SkyGrid()
  128. {
  129. }
  130. static Vec3d 
  131. toStandardCoords(const Vec3d& v)
  132. {
  133.     return Vec3d(v.x, -v.z, v.y);
  134. }
  135. // Compute the difference between two angles in [-PI, PI]
  136. template<class T> static T
  137. angleDiff(T a, T b)
  138. {
  139.     T diff = std::fabs(a - b);
  140.     if (diff > PI)
  141.         return (T) (2.0 * PI - diff);
  142.     else
  143.         return diff;
  144. }
  145. template<class T> static T
  146. min4(T a, T b, T c, T d)
  147. {
  148.     return std::min(a, std::min(b, std::min(c, d)));
  149. }
  150. static void updateAngleRange(double a, double b, double* maxDiff, double* minAngle, double* maxAngle)
  151. {
  152.     if (angleDiff(a, b) > *maxDiff)
  153.     {
  154.         *maxDiff = angleDiff(a, b);
  155.         *minAngle = a;
  156.         *maxAngle = b;
  157.     }
  158. }
  159. // Get the horizontal alignment for the coordinate label along the specified frustum plane
  160. static Renderer::LabelAlignment
  161. getCoordLabelHAlign(int planeIndex)
  162. {
  163.     switch (planeIndex)
  164.     {
  165.     case 2:
  166.         return Renderer::AlignLeft;
  167.     case 3:
  168.         return Renderer::AlignRight;
  169.     default:
  170.         return Renderer::AlignCenter;
  171.     }
  172. }
  173. // Get the vertical alignment for the coordinate label along the specified frustum plane
  174. static Renderer::LabelVerticalAlignment
  175. getCoordLabelVAlign(int planeIndex)
  176. {
  177.     if (planeIndex == 1)
  178.         return Renderer::VerticalAlignTop;
  179.     else
  180.         return Renderer::VerticalAlignBottom;
  181. }
  182. // Find the intersection of a circle and the plane with the specified normal and
  183. // containing the origin. The circle is defined parametrically by:
  184. // center + cos(t)*u + sin(t)*u
  185. // u and v are orthogonal vectors with magnitudes equal to the radius of the
  186. // circle.
  187. // Return true if there are two solutions.
  188. template<class T> static bool planeCircleIntersection(const Vector3<T>& planeNormal,
  189.                                                       const Vector3<T>& center,
  190.                                                       const Vector3<T>& u,
  191.                                                       const Vector3<T>& v,
  192.                                                       Vector3<T>* sol0,
  193.                                                       Vector3<T>* sol1)
  194. {
  195.     // Any point p on the plane must satisfy p*N = 0. Thus the intersection points
  196.     // satisfy (center + cos(t)U + sin(t)V)*N = 0
  197.     // This simplifies to an equation of the form:
  198.     // a*cos(t)+b*sin(t)+c = 0, with a=N*U, b=N*V, and c=N*center
  199.     T a = u * planeNormal;
  200.     T b = v * planeNormal;
  201.     T c = center * planeNormal;
  202.     
  203.     // The solution is +-acos((-ac +- sqrt(a^2+b^2-c^2))/(a^2+b^2))
  204.     T s = a * a + b * b;
  205.     if (s == 0.0)
  206.     {
  207.         // No solution; plane containing circle is parallel to test plane
  208.         return false;
  209.     }
  210.     
  211.     if (s - c * c <= 0)
  212.     {
  213.         // One or no solutions; no need to distinguish between these
  214.         // cases for our purposes.
  215.         return false;
  216.     }
  217.     
  218.     // No need to actually call acos to get the solution, since we're just
  219.     // going to plug it into sin and cos anyhow.
  220.     T r = b * std::sqrt(s - c * c);
  221.     T cosTheta0 = (-a * c + r) / s;
  222.     T cosTheta1 = (-a * c - r) / s;
  223.     T sinTheta0 = std::sqrt(1 - cosTheta0 * cosTheta0);
  224.     T sinTheta1 = std::sqrt(1 - cosTheta1 * cosTheta1);
  225.     
  226.     *sol0 = center + cosTheta0 * u + sinTheta0 * v;
  227.     *sol1 = center + cosTheta1 * u + sinTheta1 * v;
  228.     
  229.     // Check that we've chosen a solution that produces a point on the
  230.     // plane. If not, we need to use the -acos solution.
  231.     if (fabs(*sol0 * planeNormal) > 1.0e-8)
  232.     {
  233.         *sol0 = center + cosTheta0 * u - sinTheta0 * v;
  234.     }
  235.     
  236.     if (fabs(*sol1 * planeNormal) > 1.0e-8)
  237.     {
  238.         *sol1 = center + cosTheta1 * u - sinTheta1 * v;
  239.     }
  240.     
  241.     return true;
  242. }
  243. // Get the a string with a label for the specified latitude. Both
  244. // the latitude and latitudeStep are given in milliarcseconds.
  245. string
  246. SkyGrid::latitudeLabel(int latitude, int latitudeStep) const
  247. {
  248.     // Produce a sexigesimal string
  249.     ostringstream out;
  250.     if (latitude < 0)
  251.         out << '-';
  252.     out << std::abs(latitude / DEG) << UTF8_DEGREE_SIGN;
  253.     if (latitudeStep % DEG != 0)
  254.     {
  255.         out << ' ' << setw(2) << setfill('0') << std::abs((latitude / MIN) % 60) << ''';
  256.         if (latitudeStep % MIN != 0)
  257.         {
  258.             out << ' ' << setw(2) << setfill('0') << std::abs((latitude / SEC) % 60);
  259.             if (latitudeStep % SEC != 0)
  260.                 out << '.' << setw(3) << setfill('0') << latitude % SEC;
  261.             out << '"';
  262.         }
  263.     }
  264.     return out.str();
  265. }
  266. // Get the a string with a label for the specified longitude. Both
  267. // the longitude and longitude are given in milliarcseconds.
  268. string
  269. SkyGrid::longitudeLabel(int longitude, int longitudeStep) const
  270. {
  271.     int totalUnits = HOUR_MIN_SEC_TOTAL;
  272.     int baseUnit = HR;
  273.     const char* baseUnitSymbol = "h";
  274.     char minuteSymbol = 'm';
  275.     char secondSymbol = 's';
  276.     if (m_longitudeUnits == LongitudeDegrees)
  277.     {
  278.         totalUnits = DEG_MIN_SEC_TOTAL * 2;
  279.         baseUnit = DEG;
  280.         baseUnitSymbol = UTF8_DEGREE_SIGN;
  281.         minuteSymbol = ''';
  282.         secondSymbol = '"';
  283.     }
  284.     // Produce a sexigesimal string
  285.     ostringstream out;
  286.     if (longitude < 0)
  287.         longitude += totalUnits;
  288.     // Reverse the labels if the longitude increases clockwise (e.g. for
  289.     // horizontal coordinate grids, where azimuth is defined to increase
  290.     // eastward from due north.
  291.     if (m_longitudeDirection == IncreasingClockwise)
  292.         longitude = (totalUnits - longitude) % totalUnits;
  293.     
  294.     out << longitude / baseUnit << baseUnitSymbol;
  295.     if (longitudeStep % baseUnit != 0)
  296.     {
  297.         out << ' ' << setw(2) << setfill('0') << (longitude / MIN) % 60 << minuteSymbol;
  298.         if (longitudeStep % MIN != 0)
  299.         {
  300.             out << ' ' << setw(2) << setfill('0') << (longitude / SEC) % 60;
  301.             if (longitudeStep % SEC != 0)
  302.                 out << '.' << setw(3) << setfill('0') << longitude % SEC;
  303.             out << secondSymbol;
  304.         }
  305.     }
  306.     return out.str();
  307. }
  308. // Compute the angular step between parallels
  309. int 
  310. SkyGrid::parallelSpacing(double idealSpacing) const
  311. {
  312.     // We want to use parallels and meridian spacings that are nice multiples of hours, degrees,
  313.     // minutes, or seconds. Choose spacings from a table. We take the table entry that gives
  314.     // the spacing closest to but not less than the ideal spacing.
  315.     int spacing = DEG_MIN_SEC_TOTAL;
  316.     // Scan the tables to find the best spacings
  317.     unsigned int tableSize = sizeof(DEG_MIN_SEC_SPACING) / sizeof(DEG_MIN_SEC_SPACING[0]);
  318.     for (unsigned int i = 0; i < tableSize; i++)
  319.     {
  320.         if (PI * (double) DEG_MIN_SEC_SPACING[i] / (double) DEG_MIN_SEC_TOTAL < idealSpacing)
  321.             break;
  322.         spacing = DEG_MIN_SEC_SPACING[i];
  323.     }
  324.     
  325.     return spacing;
  326. }
  327. // Compute the angular step between meridians
  328. int
  329. SkyGrid::meridianSpacing(double idealSpacing) const
  330. {
  331.     const int* spacingTable = HOUR_MIN_SEC_SPACING;
  332.     unsigned int tableSize = sizeof(HOUR_MIN_SEC_SPACING) / sizeof(HOUR_MIN_SEC_SPACING[0]);
  333.     int totalUnits = HOUR_MIN_SEC_TOTAL;
  334.     // Use degree spacings if the latitude units are degrees instead of hours
  335.     if (m_longitudeUnits == LongitudeDegrees)
  336.     {
  337.         spacingTable = DEG_MIN_SEC_SPACING;
  338.         tableSize = sizeof(DEG_MIN_SEC_SPACING) / sizeof(DEG_MIN_SEC_SPACING[0]);
  339.         totalUnits = DEG_MIN_SEC_TOTAL * 2;
  340.     }
  341.     int spacing = totalUnits;
  342.     for (unsigned int i = 0; i < tableSize; i++)
  343.     {
  344.         if (2 * PI * (double) spacingTable[i] / (double) totalUnits < idealSpacing)
  345.             break;
  346.         spacing = spacingTable[i];
  347.     }
  348.     return spacing;
  349. }
  350. void
  351. SkyGrid::render(Renderer& renderer,
  352.                 const Observer& observer,
  353.                 int windowWidth,
  354.                 int windowHeight)
  355. {
  356.     // 90 degree rotation about the x-axis used to transform coordinates
  357.     // to Celestia's system.
  358.     Quatd xrot90 = Quatd::xrotation(-PI / 2.0);
  359.     double vfov = observer.getFOV();
  360.     double viewAspectRatio = (double) windowWidth / (double) windowHeight;
  361.     // Calculate the cosine of half the maximum field of view. We'll use this for
  362.     // fast testing of marker visibility. The stored field of view is the
  363.     // vertical field of view; we want the field of view as measured on the
  364.     // diagonal between viewport corners.
  365.     double h = tan(vfov / 2);
  366.     double w = h * viewAspectRatio;
  367.     double diag = sqrt(1.0 + square(h) + square(h * viewAspectRatio));
  368.     double cosHalfFov = 1.0 / diag;
  369.     double halfFov = acos(cosHalfFov);
  370.     
  371.     float polarCrossSize = (float) (POLAR_CROSS_SIZE * halfFov);
  372.     // We want to avoid drawing more of the grid than we have to. The following code
  373.     // determines the region of the grid intersected by the view frustum. We're
  374.     // interested in the minimum and maximum phi and theta of the visible patch
  375.     // of the celestial sphere.
  376.     // Find the minimum and maximum theta (longitude) by finding the smallest
  377.     // longitude range containing all corners of the view frustum.
  378.     // View frustum corners
  379.     Vec3d c0(-w, -h, -1.0);
  380.     Vec3d c1( w, -h, -1.0);
  381.     Vec3d c2(-w,  h, -1.0);
  382.     Vec3d c3( w,  h, -1.0);
  383.     Quatd cameraOrientation = observer.getOrientation();
  384.     Mat3d r = (cameraOrientation * xrot90 * ~m_orientation * ~xrot90).toMatrix3();
  385.     // Transform the frustum corners by the camera and grid
  386.     // rotations.
  387.     c0 = toStandardCoords(c0 * r);
  388.     c1 = toStandardCoords(c1 * r);
  389.     c2 = toStandardCoords(c2 * r);
  390.     c3 = toStandardCoords(c3 * r);
  391.     double thetaC0 = atan2(c0.y, c0.x);
  392.     double thetaC1 = atan2(c1.y, c1.x);
  393.     double thetaC2 = atan2(c2.y, c2.x);
  394.     double thetaC3 = atan2(c3.y, c3.x);
  395.     // Compute the minimum longitude range containing the corners; slightly
  396.     // tricky because of the wrapping at PI/-PI.
  397.     double minTheta = thetaC0;
  398.     double maxTheta = thetaC1;   
  399.     double maxDiff = 0.0;
  400.     updateAngleRange(thetaC0, thetaC1, &maxDiff, &minTheta, &maxTheta);
  401.     updateAngleRange(thetaC0, thetaC2, &maxDiff, &minTheta, &maxTheta);
  402.     updateAngleRange(thetaC0, thetaC3, &maxDiff, &minTheta, &maxTheta);
  403.     updateAngleRange(thetaC1, thetaC2, &maxDiff, &minTheta, &maxTheta);
  404.     updateAngleRange(thetaC1, thetaC3, &maxDiff, &minTheta, &maxTheta);
  405.     updateAngleRange(thetaC2, thetaC3, &maxDiff, &minTheta, &maxTheta);
  406.     if (std::fabs(maxTheta - minTheta) < PI)
  407.     {
  408.         if (minTheta > maxTheta)
  409.             std::swap(minTheta, maxTheta);
  410.     }
  411.     else
  412.     {
  413.         if (maxTheta > minTheta)
  414.             std::swap(minTheta, maxTheta);
  415.     }
  416.     maxTheta = minTheta + maxDiff;
  417.     
  418.     // Calculate the normals to the view frustum planes; we'll use these to
  419.     // when computing intersection points with the parallels and meridians of the
  420.     // grid. Coordinate labels will be drawn at the intersection points.
  421.     Vec3d frustumNormal[4];    
  422.     frustumNormal[0] = Vec3d( 0,  1, -h);
  423.     frustumNormal[1] = Vec3d( 0, -1, -h);
  424.     frustumNormal[2] = Vec3d( 1,  0, -w);
  425.     frustumNormal[3] = Vec3d(-1,  0, -w);
  426.     
  427.     {
  428.         for (int i = 0; i < 4; i++)
  429.         {
  430.             frustumNormal[i].normalize();
  431.             frustumNormal[i] = toStandardCoords(frustumNormal[i] * r);
  432.         }
  433.     }
  434.     Vec3d viewCenter(0.0, 0.0, -1.0);
  435.     viewCenter = toStandardCoords(viewCenter * r);
  436.     double centerDec;
  437.     if (fabs(viewCenter.z) < 1.0)
  438.         centerDec = std::asin(viewCenter.z);
  439.     else if (viewCenter.z < 0.0)
  440.         centerDec = -PI / 2.0;
  441.     else
  442.         centerDec = PI / 2.0;
  443.     
  444.     double minDec = centerDec - halfFov;
  445.     double maxDec = centerDec + halfFov;
  446.     if (maxDec >= PI / 2.0)
  447.     {
  448.         // view cone contains north pole
  449.         maxDec = PI / 2.0;
  450.         minTheta = -PI;
  451.         maxTheta = PI;
  452.     }
  453.     else if (minDec <= -PI / 2.0)
  454.     {
  455.         // view cone contains south pole
  456.         minDec = -PI / 2.0;
  457.         minTheta = -PI;
  458.         maxTheta = PI;
  459.     }
  460.     double idealParallelSpacing = 2.0 * halfFov / MAX_VISIBLE_ARCS;
  461.     double idealMeridianSpacing = idealParallelSpacing;
  462.     // Adjust the spacing between meridians based on how close the view direction
  463.     // is to the poles; the density of meridians increases as we approach the pole,
  464.     // so we want to increase the angular distance between meridians.
  465. #if 1
  466.     // Choose spacing based on the minimum declination (closest to zero)
  467.     double minAbsDec = std::min(std::fabs(minDec), std::fabs(maxDec));
  468.     if (minDec * maxDec <= 0.0f) // Check if min and max straddle the equator
  469.         minAbsDec = 0.0f;
  470.     idealMeridianSpacing /= cos(minAbsDec);
  471. #else
  472.     // Choose spacing based on the maximum declination (closest to pole)
  473.     double maxAbsDec = std::max(std::fabs(minDec), std::fabs(maxDec));
  474.     idealMeridianSpacing /= max(cos(PI / 2.0 - 5.0 * idealParallelSpacing), cos(maxAbsDec));
  475. #endif
  476.     int totalLongitudeUnits = HOUR_MIN_SEC_TOTAL;
  477.     if (m_longitudeUnits == LongitudeDegrees)
  478.         totalLongitudeUnits = DEG_MIN_SEC_TOTAL * 2;
  479.     int raIncrement  = meridianSpacing(idealMeridianSpacing);
  480.     int decIncrement = parallelSpacing(idealParallelSpacing);
  481.     int startRa  = (int) std::ceil (totalLongitudeUnits * (minTheta / (PI * 2.0f)) / (float) raIncrement) * raIncrement;
  482.     int endRa    = (int) std::floor(totalLongitudeUnits * (maxTheta / (PI * 2.0f)) / (float) raIncrement) * raIncrement;
  483.     int startDec = (int) std::ceil (DEG_MIN_SEC_TOTAL  * (minDec / PI) / (float) decIncrement) * decIncrement;
  484.     int endDec   = (int) std::floor(DEG_MIN_SEC_TOTAL  * (maxDec / PI) / (float) decIncrement) * decIncrement;
  485.     // Get the orientation at single precision
  486.     Quatd q = xrot90 * m_orientation * ~xrot90;
  487.     Quatf orientationf((float) q.w, (float) q.x, (float) q.y, (float) q.z);
  488.     glColor(m_lineColor);
  489.     // Render the parallels
  490.     glPushMatrix();
  491.     glRotate(xrot90 * ~m_orientation * ~xrot90);
  492.     // Radius of sphere is arbitrary, with the constraint that it shouldn't
  493.     // intersect the near or far plane of the view frustum.
  494.     glScalef(1000.0f, 1000.0f, 1000.0f);
  495.     double arcStep = (maxTheta - minTheta) / (double) ARC_SUBDIVISIONS;
  496.     double theta0 = minTheta;
  497.     for (int dec = startDec; dec <= endDec; dec += decIncrement)
  498.     {
  499.         double phi = PI * (double) dec / (double) DEG_MIN_SEC_TOTAL;
  500.         double cosPhi = cos(phi);
  501.         double sinPhi = sin(phi);
  502.         glBegin(GL_LINE_STRIP);
  503.         for (int j = 0; j <= ARC_SUBDIVISIONS; j++)
  504.         {
  505.             double theta = theta0 + j * arcStep;
  506.             float x = (float) (cosPhi * std::cos(theta));
  507.             float y = (float) (cosPhi * std::sin(theta));
  508.             float z = (float) sinPhi;
  509.             glVertex3f(x, z, -y);  // convert to Celestia coords
  510.         }
  511.         glEnd();
  512.         
  513.         // Place labels at the intersections of the view frustum planes
  514.         // and the parallels.
  515.         Vec3d center(0.0, 0.0, sinPhi);
  516.         Vec3d axis0(cosPhi, 0.0, 0.0);
  517.         Vec3d axis1(0.0, cosPhi, 0.0);
  518.         for (int k = 0; k < 4; k += 2)
  519.         {
  520.             Vec3d isect0(0.0, 0.0, 0.0);
  521.             Vec3d isect1(0.0, 0.0, 0.0);
  522.             Renderer::LabelAlignment hAlign = getCoordLabelHAlign(k);
  523.             Renderer::LabelVerticalAlignment vAlign = getCoordLabelVAlign(k);
  524.             if (planeCircleIntersection(frustumNormal[k], center, axis0, axis1,
  525.                                         &isect0, &isect1))
  526.             {
  527.                 string labelText = latitudeLabel(dec, decIncrement);
  528.                 Point3f p0((float) isect0.x, (float) isect0.z, (float) -isect0.y);
  529.                 Point3f p1((float) isect1.x, (float) isect1.z, (float) -isect1.y);
  530. #ifdef DEBUG_LABEL_PLACEMENT
  531.                 glPointSize(5.0);
  532.                 glBegin(GL_POINTS);
  533.                 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
  534.                 glVertex3f(p0.x, p0.y, p0.z);
  535.                 glVertex3f(p1.x, p1.y, p1.z);
  536.                 glColor(m_lineColor);
  537.                 glEnd();
  538. #endif
  539.                 
  540.                 Mat3f m = conjugate(observer.getOrientationf()).toMatrix3();
  541.                 p0 = p0 * orientationf.toMatrix3();
  542.                 p1 = p1 * orientationf.toMatrix3();
  543.                 
  544.                 if ((p0 * m).z < 0.0)
  545.                 {
  546.                     renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p0, hAlign, vAlign);
  547.                 }
  548.                 
  549.                 if ((p1 * m).z < 0.0)
  550.                 {
  551.                     renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p1, hAlign, vAlign);
  552.                 }                
  553.             }
  554.         }
  555.     }
  556.     // Draw the meridians
  557.     // Render meridians only to the last latitude circle; this looks better
  558.     // than spokes radiating from the pole.
  559.     double maxMeridianAngle = PI / 2.0 * (1.0 - 2.0 * (double) decIncrement / (double) DEG_MIN_SEC_TOTAL);
  560.     minDec = std::max(minDec, -maxMeridianAngle);
  561.     maxDec = std::min(maxDec,  maxMeridianAngle);
  562.     arcStep = (maxDec - minDec) / (double) ARC_SUBDIVISIONS;
  563.     double phi0 = minDec;
  564.     double cosMaxMeridianAngle = cos(maxMeridianAngle);
  565.     for (int ra = startRa; ra <= endRa; ra += raIncrement)
  566.     {
  567.         double theta = 2.0 * PI * (double) ra / (double) totalLongitudeUnits;
  568.         double cosTheta = cos(theta);
  569.         double sinTheta = sin(theta);
  570.         glBegin(GL_LINE_STRIP);
  571.         for (int j = 0; j <= ARC_SUBDIVISIONS; j++)
  572.         {
  573.             double phi = phi0 + j * arcStep;
  574.             float x = (float) (cos(phi) * cosTheta);
  575.             float y = (float) (cos(phi) * sinTheta);
  576.             float z = (float) sin(phi);
  577.             glVertex3f(x, z, -y);  // convert to Celestia coords
  578.         }
  579.         glEnd();
  580.         
  581.         // Place labels at the intersections of the view frustum planes
  582.         // and the meridians.
  583.         Vec3d center(0.0, 0.0, 0.0);
  584.         Vec3d axis0(cosTheta, sinTheta, 0.0);
  585.         Vec3d axis1(0.0, 0.0, 1.0);
  586.         for (int k = 1; k < 4; k += 2)
  587.         {
  588.             Vec3d isect0(0.0, 0.0, 0.0);
  589.             Vec3d isect1(0.0, 0.0, 0.0);
  590.             Renderer::LabelAlignment hAlign = getCoordLabelHAlign(k);
  591.             Renderer::LabelVerticalAlignment vAlign = getCoordLabelVAlign(k);
  592.             if (planeCircleIntersection(frustumNormal[k], center, axis0, axis1,
  593.                                         &isect0, &isect1))
  594.             {
  595.                 string labelText = longitudeLabel(ra, raIncrement);
  596.                 Point3f p0((float) isect0.x, (float) isect0.z, (float) -isect0.y);
  597.                 Point3f p1((float) isect1.x, (float) isect1.z, (float) -isect1.y);
  598. #ifdef DEBUG_LABEL_PLACEMENT
  599.                 glPointSize(5.0);
  600.                 glBegin(GL_POINTS);
  601.                 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
  602.                 glVertex3f(p0.x, p0.y, p0.z);
  603.                 glVertex3f(p1.x, p1.y, p1.z);
  604.                 glColor(m_lineColor);
  605.                 glEnd();
  606. #endif
  607.                 Mat3f m = conjugate(observer.getOrientationf()).toMatrix3();
  608.                 p0 = p0 * orientationf.toMatrix3();
  609.                 p1 = p1 * orientationf.toMatrix3();
  610.                 
  611.                 if ((p0 * m).z < 0.0 && axis0 * isect0 >= cosMaxMeridianAngle)
  612.                 {
  613.                     renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p0, hAlign, vAlign);
  614.                 }
  615.                 
  616.                 if ((p1 * m).z < 0.0 && axis0 * isect1 >= cosMaxMeridianAngle)
  617.                 {
  618.                     renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p1, hAlign, vAlign);
  619.                 }               
  620.             }
  621.         }        
  622.     }
  623.     
  624.     // Draw crosses indicating the north and south poles
  625.     glBegin(GL_LINES);
  626.     glVertex3f(-polarCrossSize, 1.0f,  0.0f);
  627.     glVertex3f( polarCrossSize, 1.0f,  0.0f);
  628.     glVertex3f(0.0f, 1.0f, -polarCrossSize);
  629.     glVertex3f(0.0f, 1.0f,  polarCrossSize);
  630.     glVertex3f(-polarCrossSize, -1.0f,  0.0f);
  631.     glVertex3f( polarCrossSize, -1.0f,  0.0f);
  632.     glVertex3f(0.0f, -1.0f, -polarCrossSize);
  633.     glVertex3f(0.0f, -1.0f,  polarCrossSize);
  634.     glEnd();
  635.     
  636.     glPopMatrix();
  637. }