void
MeshGeometry::render(RenderContext& rc,
                     double /* clock */) const
{
    if (!m_hwBuffersCurrent)
    {
        realize();
    }

    // Track the last used material in order to avoid redundant
    // material bindings.
    unsigned int lastMaterialIndex = Submesh::DefaultMaterialIndex;

    rc.pushModelView();
    rc.scaleModelView(m_meshScale);

    // Render all submeshes
    GLVertexBuffer* boundVertexBuffer = NULL;

    for (unsigned int i = 0; i < m_submeshes.size(); ++i)
    {
        const Submesh& submesh = *m_submeshes[i];
        if (i < m_submeshBuffers.size() && m_submeshBuffers[i])
        {
            boundVertexBuffer = m_submeshBuffers[i];
            rc.bindVertexBuffer(submesh.vertices()->vertexSpec(), m_submeshBuffers[i], submesh.vertices()->stride());
        }
        else
        {
            if (boundVertexBuffer)
            {
                boundVertexBuffer->unbind();
                boundVertexBuffer = false;
            }
            rc.bindVertexArray(submesh.vertices());
        }

        const vector<PrimitiveBatch*>& batches = submesh.primitiveBatches();
        const vector<unsigned int>& materials = submesh.materials();
        assert(batches.size() == materials.size());

        // Render all batches in the submesh
        for (unsigned int j = 0; j < batches.size(); j++)
        {
            // If we have a new material, bind it
            unsigned int materialIndex = materials[j];
            if (materialIndex != lastMaterialIndex)
            {
                if (materialIndex < m_materials.size())
                {
                    rc.bindMaterial(m_materials[materialIndex].ptr());
                }
                lastMaterialIndex = materialIndex;
            }

            rc.drawPrimitives(*batches[j]);
        }
    }

    if (boundVertexBuffer)
    {
        boundVertexBuffer->unbind();
    }

    rc.popModelView();
}
void
SimpleTrajectoryGeometry::render(RenderContext& rc, double clock) const
{
    double t0 = firstSampleTime();
    double t1 = lastSampleTime();

    double fadeRate = 0.0;
    double fadeStartTime = 0.0;
    double fadeStartValue = 1.0;

    // Only draw during the appropriate render pass
    if ((rc.pass() == RenderContext::OpaquePass && !isOpaque()) ||
        (rc.pass() == RenderContext::TranslucentPass && isOpaque()))
    {
        return;
    }
    
    if (displayedPortion() == TrajectoryGeometry::WindowBeforeCurrentTime)
    {
        t0 = clock + m_windowLead - m_windowDuration;
        t1 = clock + m_windowLead;

        fadeStartTime = t0;
        fadeStartValue = 0.0;
        double fadeEndTime = fadeStartTime + m_windowDuration * m_fadeFraction;
        fadeRate = 1.0 / (fadeEndTime - fadeStartTime);
    }

    // Nothing to be drawn
    if (t1 <= t0)
    {
        return;
    }

    // Basic opacity of the plot. It may be modified based on three things:
    //   - Approximate size in pixels of the trajectory (small trajectories will fade out)
    //   - Distance from the camera to the 'front' (usually the current position of the oribiting body)
    //   - 'Age' of the trajectory: typically the most recent portions are drawn more opaque than the
    //        older parts. This is handled by setting per-vertex colors.
    float opacity = 0.99f * m_opacity;

    const float sizeFadeStart = 30.0f;
    const float sizeFadeEnd = 15.0f;
    float pixelSize = boundingSphereRadius() / (rc.modelview().translation().norm() * rc.pixelSize());
    if (pixelSize < sizeFadeStart)
    {
        opacity *= std::max(0.0f, (pixelSize - sizeFadeEnd) / (sizeFadeStart - sizeFadeEnd));
    }

    if (opacity <= 0.0f)
    {
        // Complete fade out; no need to draw anything.
        return;
    }

    rc.pushModelView();
    if (m_frame.isValid())
    {
        rc.rotateModelView(m_frame->orientation(clock).cast<float>());
    }

    TrajectoryVertex vertex;
    vertex.color[0] = (unsigned char) (m_color.red() * 255.99f);
    vertex.color[1] = (unsigned char) (m_color.green() * 255.99f);
    vertex.color[2] = (unsigned char) (m_color.blue() * 255.99f);
    vertex.color[3] = 255;

    m_vertexData.clear();

    unsigned int sampleIndex = 0;
    while (sampleIndex < m_samples.size() && t0 > m_samples[sampleIndex].timeTag)
    {
        ++sampleIndex;
    }

    if (sampleIndex > 0 && sampleIndex < m_samples.size())
    {
        double dt = m_samples[sampleIndex].timeTag - m_samples[sampleIndex - 1].timeTag;
        double t = (t0 - m_samples[sampleIndex - 1].timeTag) / dt;
        Vector3d interpolated = interpolateSamples(t, dt, m_samples[sampleIndex - 1], m_samples[sampleIndex]);
        float alpha = std::max(0.0f, std::min(1.0f, float(fadeStartValue + (t0 - fadeStartTime) * fadeRate)));

        vertex.position = interpolated.cast<float>();
        vertex.color[3] = (unsigned char) (alpha * 255.99f);
        m_vertexData.push_back(vertex);
    }

    while (sampleIndex < m_samples.size() && t1 > m_samples[sampleIndex].timeTag)
    {
        float alpha = std::max(0.0f, std::min(1.0f, float(fadeStartValue + (m_samples[sampleIndex].timeTag - fadeStartTime) * fadeRate)));

        vertex.position = m_samples[sampleIndex].position.cast<float>();
        vertex.color[3] = (unsigned char) (alpha * 255.99f);
        m_vertexData.push_back(vertex);

        ++sampleIndex;
    }

    if (sampleIndex > 0 && sampleIndex < m_samples.size())
    {
        double dt = m_samples[sampleIndex].timeTag - m_samples[sampleIndex - 1].timeTag;
        double t = (t1 - m_samples[sampleIndex - 1].timeTag) / dt;
        Vector3d interpolated = interpolateSamples(t, dt, m_samples[sampleIndex - 1], m_samples[sampleIndex]);
        float alpha = std::max(0.0f, std::min(1.0f, float(fadeStartValue + (t1 - fadeStartTime) * fadeRate)));

        vertex.position = interpolated.cast<float>();
        vertex.color[3] = (unsigned char) (alpha * 255.99f);
        m_vertexData.push_back(vertex);
    }

    // Fade trajectory based on size
    Vector3f frontPosition = rc.modelview() * vertex.position;
    float frontDistance = frontPosition.norm();

    // Fade trajectory based on distance to front point. This is helpful because the simple trajectory model
    // is not precise, and fading hides the discrepancy between the plot and the body's current position.
    const float fadeStart = 0.04f;
    const float fadeFinish = 0.01f;
    if (frontDistance < fadeStart * boundingSphereRadius())
    {
        opacity *= std::max(0.0f, (frontDistance / boundingSphereRadius() - fadeFinish) / (fadeStart - fadeFinish));
    }

    Material material;
    material.setDiffuse(Spectrum::White());
    material.setOpacity(opacity);
    rc.bindMaterial(&material);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    if (m_vertexData.size() > 1 && opacity > 0.0f)
    {
        rc.bindVertexArray(VertexSpec::PositionColor, m_vertexData[0].position.data(), sizeof(TrajectoryVertex));
        rc.drawPrimitives(PrimitiveBatch(PrimitiveBatch::LineStrip, m_vertexData.size() - 1, 0));
        rc.unbindVertexArray();
    }

    glDisable(GL_BLEND);

    rc.popModelView();
}
void
MeshGeometry::renderShadow(RenderContext& rc,
                           double /* clock */) const
{
    if (!m_hwBuffersCurrent)
    {
        realize();
    }

    // Use an extremely basic material to avoid wasting time
    // with pixel shader calculations when we're just interested
    // in depth values.
    Material simpleMaterial;
    rc.bindMaterial(&simpleMaterial);

    rc.pushModelView();
    rc.scaleModelView(m_meshScale);

    // Render all submeshes
    GLVertexBuffer* boundVertexBuffer = NULL;
    for (unsigned int i = 0; i < m_submeshes.size(); ++i)
    {
        const Submesh& submesh = *m_submeshes[i];

        if (i < m_submeshBuffers.size() && m_submeshBuffers[i])
        {
            boundVertexBuffer = m_submeshBuffers[i];
            rc.bindVertexBuffer(submesh.vertices()->vertexSpec(), m_submeshBuffers[i], submesh.vertices()->stride());
        }
        else
        {
            if (boundVertexBuffer)
            {
                boundVertexBuffer->unbind();
                boundVertexBuffer = false;
            }
            rc.bindVertexArray(submesh.vertices());
        }

        const vector<PrimitiveBatch*>& batches = submesh.primitiveBatches();
        const vector<unsigned int>& materials = submesh.materials();
        assert(batches.size() == materials.size());

        // Render all batches in the submesh
        for (unsigned int j = 0; j < batches.size(); j++)
        {
            // Skip mostly transparent items when drawing into the shadow
            // buffer.
            // TODO: Textures with transparent parts aren't handled here
            unsigned int materialIndex = materials[j];
            if (materialIndex >= m_materials.size() || m_materials[materialIndex]->opacity() > 0.5f)
            {
                rc.drawPrimitives(*batches[j]);
            }
        }
    }

    if (boundVertexBuffer)
    {
        boundVertexBuffer->unbind();
    }

    rc.popModelView();
}