Exemple #1
0
void
SkyGrid::render(Renderer& renderer,
                const Observer& observer,
                int windowWidth,
                int windowHeight)
{
    // 90 degree rotation about the x-axis used to transform coordinates
    // to Celestia's system.
    Quatd xrot90 = Quatd::xrotation(-PI / 2.0);

    double vfov = observer.getFOV();
    double viewAspectRatio = (double) windowWidth / (double) windowHeight;

    // Calculate the cosine of half the maximum field of view. We'll use this for
    // fast testing of marker visibility. The stored field of view is the
    // vertical field of view; we want the field of view as measured on the
    // diagonal between viewport corners.
    double h = tan(vfov / 2);
    double w = h * viewAspectRatio;
    double diag = sqrt(1.0 + square(h) + square(h * viewAspectRatio));
    double cosHalfFov = 1.0 / diag;
    double halfFov = acos(cosHalfFov);
    
    float polarCrossSize = (float) (POLAR_CROSS_SIZE * halfFov);

    // We want to avoid drawing more of the grid than we have to. The following code
    // determines the region of the grid intersected by the view frustum. We're
    // interested in the minimum and maximum phi and theta of the visible patch
    // of the celestial sphere.

    // Find the minimum and maximum theta (longitude) by finding the smallest
    // longitude range containing all corners of the view frustum.

    // View frustum corners
    Vec3d c0(-w, -h, -1.0);
    Vec3d c1( w, -h, -1.0);
    Vec3d c2(-w,  h, -1.0);
    Vec3d c3( w,  h, -1.0);

    Quatd cameraOrientation = observer.getOrientation();
    Mat3d r = (cameraOrientation * xrot90 * ~m_orientation * ~xrot90).toMatrix3();

    // Transform the frustum corners by the camera and grid
    // rotations.
    c0 = toStandardCoords(c0 * r);
    c1 = toStandardCoords(c1 * r);
    c2 = toStandardCoords(c2 * r);
    c3 = toStandardCoords(c3 * r);

    double thetaC0 = atan2(c0.y, c0.x);
    double thetaC1 = atan2(c1.y, c1.x);
    double thetaC2 = atan2(c2.y, c2.x);
    double thetaC3 = atan2(c3.y, c3.x);

    // Compute the minimum longitude range containing the corners; slightly
    // tricky because of the wrapping at PI/-PI.
    double minTheta = thetaC0;
    double maxTheta = thetaC1;   
    double maxDiff = 0.0;
    updateAngleRange(thetaC0, thetaC1, &maxDiff, &minTheta, &maxTheta);
    updateAngleRange(thetaC0, thetaC2, &maxDiff, &minTheta, &maxTheta);
    updateAngleRange(thetaC0, thetaC3, &maxDiff, &minTheta, &maxTheta);
    updateAngleRange(thetaC1, thetaC2, &maxDiff, &minTheta, &maxTheta);
    updateAngleRange(thetaC1, thetaC3, &maxDiff, &minTheta, &maxTheta);
    updateAngleRange(thetaC2, thetaC3, &maxDiff, &minTheta, &maxTheta);

    if (std::fabs(maxTheta - minTheta) < PI)
    {
        if (minTheta > maxTheta)
            std::swap(minTheta, maxTheta);
    }
    else
    {
        if (maxTheta > minTheta)
            std::swap(minTheta, maxTheta);
    }
    maxTheta = minTheta + maxDiff;
    
    // Calculate the normals to the view frustum planes; we'll use these to
    // when computing intersection points with the parallels and meridians of the
    // grid. Coordinate labels will be drawn at the intersection points.
    Vec3d frustumNormal[4];    
    frustumNormal[0] = Vec3d( 0,  1, -h);
    frustumNormal[1] = Vec3d( 0, -1, -h);
    frustumNormal[2] = Vec3d( 1,  0, -w);
    frustumNormal[3] = Vec3d(-1,  0, -w);
    
    {
        for (int i = 0; i < 4; i++)
        {
            frustumNormal[i].normalize();
            frustumNormal[i] = toStandardCoords(frustumNormal[i] * r);
        }
    }

    Vec3d viewCenter(0.0, 0.0, -1.0);
    viewCenter = toStandardCoords(viewCenter * r);

    double centerDec;
    if (fabs(viewCenter.z) < 1.0)
        centerDec = std::asin(viewCenter.z);
    else if (viewCenter.z < 0.0)
        centerDec = -PI / 2.0;
    else
        centerDec = PI / 2.0;
    
    double minDec = centerDec - halfFov;
    double maxDec = centerDec + halfFov;

    if (maxDec >= PI / 2.0)
    {
        // view cone contains north pole
        maxDec = PI / 2.0;
        minTheta = -PI;
        maxTheta = PI;
    }
    else if (minDec <= -PI / 2.0)
    {
        // view cone contains south pole
        minDec = -PI / 2.0;
        minTheta = -PI;
        maxTheta = PI;
    }

    double idealParallelSpacing = 2.0 * halfFov / MAX_VISIBLE_ARCS;
    double idealMeridianSpacing = idealParallelSpacing;

    // Adjust the spacing between meridians based on how close the view direction
    // is to the poles; the density of meridians increases as we approach the pole,
    // so we want to increase the angular distance between meridians.
#if 1
    // Choose spacing based on the minimum declination (closest to zero)
    double minAbsDec = std::min(std::fabs(minDec), std::fabs(maxDec));
    if (minDec * maxDec <= 0.0f) // Check if min and max straddle the equator
        minAbsDec = 0.0f;
    idealMeridianSpacing /= cos(minAbsDec);
#else
    // Choose spacing based on the maximum declination (closest to pole)
    double maxAbsDec = std::max(std::fabs(minDec), std::fabs(maxDec));
    idealMeridianSpacing /= max(cos(PI / 2.0 - 5.0 * idealParallelSpacing), cos(maxAbsDec));
#endif

    int totalLongitudeUnits = HOUR_MIN_SEC_TOTAL;
    if (m_longitudeUnits == LongitudeDegrees)
        totalLongitudeUnits = DEG_MIN_SEC_TOTAL * 2;

    int raIncrement  = meridianSpacing(idealMeridianSpacing);
    int decIncrement = parallelSpacing(idealParallelSpacing);

    int startRa  = (int) std::ceil (totalLongitudeUnits * (minTheta / (PI * 2.0f)) / (float) raIncrement) * raIncrement;
    int endRa    = (int) std::floor(totalLongitudeUnits * (maxTheta / (PI * 2.0f)) / (float) raIncrement) * raIncrement;
    int startDec = (int) std::ceil (DEG_MIN_SEC_TOTAL  * (minDec / PI) / (float) decIncrement) * decIncrement;
    int endDec   = (int) std::floor(DEG_MIN_SEC_TOTAL  * (maxDec / PI) / (float) decIncrement) * decIncrement;

    // Get the orientation at single precision
    Quatd q = xrot90 * m_orientation * ~xrot90;
    Quatf orientationf((float) q.w, (float) q.x, (float) q.y, (float) q.z);

    glColor(m_lineColor);

    // Render the parallels
    glPushMatrix();
    glRotate(xrot90 * ~m_orientation * ~xrot90);

    // Radius of sphere is arbitrary, with the constraint that it shouldn't
    // intersect the near or far plane of the view frustum.
    glScalef(1000.0f, 1000.0f, 1000.0f);

    double arcStep = (maxTheta - minTheta) / (double) ARC_SUBDIVISIONS;
    double theta0 = minTheta;
    for (int dec = startDec; dec <= endDec; dec += decIncrement)
    {
        double phi = PI * (double) dec / (double) DEG_MIN_SEC_TOTAL;
        double cosPhi = cos(phi);
        double sinPhi = sin(phi);

        glBegin(GL_LINE_STRIP);
        for (int j = 0; j <= ARC_SUBDIVISIONS; j++)
        {
            double theta = theta0 + j * arcStep;
            float x = (float) (cosPhi * std::cos(theta));
            float y = (float) (cosPhi * std::sin(theta));
            float z = (float) sinPhi;
            glVertex3f(x, z, -y);  // convert to Celestia coords
        }
        glEnd();
        
        // Place labels at the intersections of the view frustum planes
        // and the parallels.
        Vec3d center(0.0, 0.0, sinPhi);
        Vec3d axis0(cosPhi, 0.0, 0.0);
        Vec3d axis1(0.0, cosPhi, 0.0);
        for (int k = 0; k < 4; k += 2)
        {
            Vec3d isect0(0.0, 0.0, 0.0);
            Vec3d isect1(0.0, 0.0, 0.0);
            Renderer::LabelAlignment hAlign = getCoordLabelHAlign(k);
            Renderer::LabelVerticalAlignment vAlign = getCoordLabelVAlign(k);

            if (planeCircleIntersection(frustumNormal[k], center, axis0, axis1,
                                        &isect0, &isect1))
            {
                string labelText = latitudeLabel(dec, decIncrement);

                Point3f p0((float) isect0.x, (float) isect0.z, (float) -isect0.y);
                Point3f p1((float) isect1.x, (float) isect1.z, (float) -isect1.y);

#ifdef DEBUG_LABEL_PLACEMENT
                glPointSize(5.0);
                glBegin(GL_POINTS);
                glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
                glVertex3f(p0.x, p0.y, p0.z);
                glVertex3f(p1.x, p1.y, p1.z);
                glColor(m_lineColor);
                glEnd();
#endif
                
                Mat3f m = conjugate(observer.getOrientationf()).toMatrix3();
                p0 = p0 * orientationf.toMatrix3();
                p1 = p1 * orientationf.toMatrix3();
                
                if ((p0 * m).z < 0.0)
                {
                    renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p0, hAlign, vAlign);
                }
                
                if ((p1 * m).z < 0.0)
                {
                    renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p1, hAlign, vAlign);
                }                
            }
        }
    }

    // Draw the meridians

    // Render meridians only to the last latitude circle; this looks better
    // than spokes radiating from the pole.
    double maxMeridianAngle = PI / 2.0 * (1.0 - 2.0 * (double) decIncrement / (double) DEG_MIN_SEC_TOTAL);
    minDec = std::max(minDec, -maxMeridianAngle);
    maxDec = std::min(maxDec,  maxMeridianAngle);
    arcStep = (maxDec - minDec) / (double) ARC_SUBDIVISIONS;
    double phi0 = minDec;

    double cosMaxMeridianAngle = cos(maxMeridianAngle);

    for (int ra = startRa; ra <= endRa; ra += raIncrement)
    {

        double theta = 2.0 * PI * (double) ra / (double) totalLongitudeUnits;
        double cosTheta = cos(theta);
        double sinTheta = sin(theta);

        glBegin(GL_LINE_STRIP);
        for (int j = 0; j <= ARC_SUBDIVISIONS; j++)
        {
            double phi = phi0 + j * arcStep;
            float x = (float) (cos(phi) * cosTheta);
            float y = (float) (cos(phi) * sinTheta);
            float z = (float) sin(phi);
            glVertex3f(x, z, -y);  // convert to Celestia coords
        }
        glEnd();
        
        // Place labels at the intersections of the view frustum planes
        // and the meridians.
        Vec3d center(0.0, 0.0, 0.0);
        Vec3d axis0(cosTheta, sinTheta, 0.0);
        Vec3d axis1(0.0, 0.0, 1.0);
        for (int k = 1; k < 4; k += 2)
        {
            Vec3d isect0(0.0, 0.0, 0.0);
            Vec3d isect1(0.0, 0.0, 0.0);
            Renderer::LabelAlignment hAlign = getCoordLabelHAlign(k);
            Renderer::LabelVerticalAlignment vAlign = getCoordLabelVAlign(k);

            if (planeCircleIntersection(frustumNormal[k], center, axis0, axis1,
                                        &isect0, &isect1))
            {
                string labelText = longitudeLabel(ra, raIncrement);

                Point3f p0((float) isect0.x, (float) isect0.z, (float) -isect0.y);
                Point3f p1((float) isect1.x, (float) isect1.z, (float) -isect1.y);

#ifdef DEBUG_LABEL_PLACEMENT
                glPointSize(5.0);
                glBegin(GL_POINTS);
                glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
                glVertex3f(p0.x, p0.y, p0.z);
                glVertex3f(p1.x, p1.y, p1.z);
                glColor(m_lineColor);
                glEnd();
#endif

                Mat3f m = conjugate(observer.getOrientationf()).toMatrix3();
                p0 = p0 * orientationf.toMatrix3();
                p1 = p1 * orientationf.toMatrix3();
                
                if ((p0 * m).z < 0.0 && axis0 * isect0 >= cosMaxMeridianAngle)
                {
                    renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p0, hAlign, vAlign);
                }
                
                if ((p1 * m).z < 0.0 && axis0 * isect1 >= cosMaxMeridianAngle)
                {
                    renderer.addBackgroundAnnotation(NULL, labelText, m_labelColor, p1, hAlign, vAlign);
                }               
            }
        }        
    }
    
    // Draw crosses indicating the north and south poles
    glBegin(GL_LINES);
    glVertex3f(-polarCrossSize, 1.0f,  0.0f);
    glVertex3f( polarCrossSize, 1.0f,  0.0f);
    glVertex3f(0.0f, 1.0f, -polarCrossSize);
    glVertex3f(0.0f, 1.0f,  polarCrossSize);
    glVertex3f(-polarCrossSize, -1.0f,  0.0f);
    glVertex3f( polarCrossSize, -1.0f,  0.0f);
    glVertex3f(0.0f, -1.0f, -polarCrossSize);
    glVertex3f(0.0f, -1.0f,  polarCrossSize);
    glEnd();
    
    glPopMatrix();
}