// curveplot.cpp // // Copyright (C) 2009-2010 Chris Laurel . // // curveplot is a module for rendering curves in OpenGL at high precision. A // plot is a series of cubic curves. The curves are transformed // to camera space in software because double precision is absolutely // required. The cubics are adaptively subdivided based on distance from // the camera position. // // curveplot is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // Alternatively, you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of // the License, or (at your option) any later version. // // curveplot is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the // GNU General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License and a copy of the GNU General Public License along with // CurvePlot. If not, see . #define DEBUG_ADAPTIVE_SPLINE 0 #if DEBUG_ADAPTIVE_SPLINE #define USE_VERTEX_BUFFER 0 #else #define USE_VERTEX_BUFFER 1 #endif #include "glsupport.h" #include "curveplot.h" #include "shadermanager.h" #include #include using namespace std; using namespace Eigen; static const unsigned int SubdivisionFactor = 8; static const double InvSubdivisionFactor = 1.0 / (double) SubdivisionFactor; #if DEBUG_ADAPTIVE_SPLINE static float SplineColors[10][3] = { { 0, 0, 1 }, { 0, 1, 1 }, { 0, 1, 0 }, { 1, 1, 0 }, { 1, 0, 0 }, { 1, 0, 1 }, { 0.5f, 0.5f, 1.0f }, { 0.5f, 1.0f, 1.0f }, { 0.5f, 1.0f, 0.5f }, { 1.0f, 1.0f, 0.5f }, }; static unsigned int SegmentCounts[32]; #endif #ifndef EIGEN_VECTORIZE // Vectorization should be enabled for improved performance. #endif // Convert a 3-vector to a 4-vector by adding a zero static inline Vector4d zeroExtend(const Vector3d& v) { return Vector4d(v.x(), v.y(), v.z(), 0.0); } class HighPrec_Frustum { public: HighPrec_Frustum(double nearZ, double farZ, const Vector3d planeNormals[]) : m_nearZ(nearZ), m_farZ(farZ) { for (unsigned int i = 0; i < 4; i++) { m_planeNormals[i] = zeroExtend(planeNormals[i]); } } inline bool cullSphere(const Vector3d& center, double radius) const { return (center.z() - radius > m_nearZ || center.z() + radius < m_farZ || center.dot(m_planeNormals[0].head(3)) < -radius || center.dot(m_planeNormals[1].head(3)) < -radius || center.dot(m_planeNormals[2].head(3)) < -radius || center.dot(m_planeNormals[3].head(3)) < -radius); } inline bool cullSphere(const Vector4d& center, double radius) const { return (center.z() - radius > m_nearZ || center.z() + radius < m_farZ || center.dot(m_planeNormals[0]) < -radius || center.dot(m_planeNormals[1]) < -radius || center.dot(m_planeNormals[2]) < -radius || center.dot(m_planeNormals[3]) < -radius); } inline double nearZ() const { return m_nearZ; } inline double farZ() const { return m_farZ; } private: double m_nearZ; double m_farZ; Vector4d m_planeNormals[4]; }; static inline Matrix4d cubicHermiteCoefficients(const Vector4d& p0, const Vector4d& p1, const Vector4d& v0, const Vector4d& v1) { Matrix4d coeff; coeff.col(0) = p0; coeff.col(1) = v0; coeff.col(2) = 3.0 * (p1 - p0) - (2.0 * v0 + v1); coeff.col(3) = 2.0 * (p0 - p1) + (v1 + v0); return coeff; } // Test a point to see if it lies within the frustum defined by // planes z=nearZ, z=farZ, and the four side planes with specified // normals. #if 0 static inline bool frustumCull(const Vector4d& curvePoint, double curveBoundingRadius, double nearZ, double farZ, const Vector4d viewFrustumPlaneNormals[]) { return (curvePoint.z() - curveBoundingRadius > nearZ || curvePoint.z() + curveBoundingRadius < farZ || curvePoint.dot(viewFrustumPlaneNormals[0]) < -curveBoundingRadius || curvePoint.dot(viewFrustumPlaneNormals[1]) < -curveBoundingRadius || curvePoint.dot(viewFrustumPlaneNormals[2]) < -curveBoundingRadius || curvePoint.dot(viewFrustumPlaneNormals[3]) < -curveBoundingRadius); } #endif class HighPrec_VertexBuffer { public: HighPrec_VertexBuffer() : currentPosition(0), capacity(4096), data(nullptr), vbobj(0), currentStripLength(0) { data = new Vertex[(capacity + 1) * 2]; } ~HighPrec_VertexBuffer() { delete[] data; } void setup() { #if USE_VERTEX_BUFFER if (vbobj) { glBindBuffer(GL_ARRAY_BUFFER, vbobj); } glEnableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex); glEnableVertexAttribArray(CelestiaGLProgram::ColorAttributeIndex); glEnableVertexAttribArray(CelestiaGLProgram::NextVCoordAttributeIndex); glEnableVertexAttribArray(CelestiaGLProgram::ScaleFactorAttributeIndex); Vector4f* vertexBase = vbobj ? (Vector4f*) offsetof(Vertex, position) : &data[0].position; glVertexAttribPointer(CelestiaGLProgram::VertexCoordAttributeIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), vertexBase); Vector4f* colorBase = vbobj ? (Vector4f*) offsetof(Vertex, color) : &data[0].color; glVertexAttribPointer(CelestiaGLProgram::ColorAttributeIndex, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), colorBase); float* scaleBase = vbobj ? (float*) offsetof(Vertex, scale) : &data[0].scale; glVertexAttribPointer(CelestiaGLProgram::ScaleFactorAttributeIndex, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), scaleBase); Vector4f* nextVertexBase = vbobj ? (Vector4f*) (offsetof(Vertex, position) + (2 * sizeof(Vertex))) : &data[2].position; glVertexAttribPointer(CelestiaGLProgram::NextVCoordAttributeIndex, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), nextVertexBase); stripLengths.clear(); currentStripLength = 0; currentPosition = 0; #endif } void finish() { #if USE_VERTEX_BUFFER if (vbobj) { glDisableVertexAttribArray(CelestiaGLProgram::ColorAttributeIndex); glDisableVertexAttribArray(CelestiaGLProgram::VertexCoordAttributeIndex); glDisableVertexAttribArray(CelestiaGLProgram::NextVCoordAttributeIndex); glDisableVertexAttribArray(CelestiaGLProgram::ScaleFactorAttributeIndex); glBindBuffer(GL_ARRAY_BUFFER, 0); } #endif } inline void vertex(const Vector3d& v) { #if USE_VERTEX_BUFFER Vector3f pos = v.cast(); int index = currentPosition * 2; data[index].position.segment<3>(0) = pos; data[index].color = color; data[index].scale = -0.5f; data[index + 1].position.segment<3>(0) = pos; data[index + 1].color = color; data[index + 1].scale = 0.5f; ++currentPosition; ++currentStripLength; if (currentPosition == capacity) { flush(); data[0].position.segment<3>(0) = pos; data[0].color = color; data[0].scale = -0.5f; data[1].position.segment<3>(0) = pos; data[1].color = color; data[1].scale = 0.5f; currentPosition = 1; currentStripLength = 1; } #else glVertex3dv(v.data()); #endif } inline void vertex(const Vector4d& v) { vertex(v, color); } inline void vertex(const Vector4d& v, const Vector4f& color) { #if USE_VERTEX_BUFFER Vector4f pos = v.cast(); int index = currentPosition * 2; data[index].position = pos; data[index].color = color; data[index].scale = -0.5f; data[index + 1].position = pos; data[index + 1].color = color; data[index + 1].scale = 0.5f; ++currentPosition; ++currentStripLength; if (currentPosition == capacity) { flush(); data[0].position = pos; data[0].color = color; data[0].scale = -0.5f; data[1].position = pos; data[1].color = color; data[1].scale = 0.5f; currentPosition = 1; currentStripLength = 1; } #else glColor4fv(color.data()); glVertex3dv(v.data()); #endif } inline void begin() { #if !USE_VERTEX_BUFFER glBegin(GL_LINE_STRIP); #endif } inline void end() { #if USE_VERTEX_BUFFER if (currentPosition > 1) { int index = currentPosition * 2; memcpy(&data[index], &data[index - 4], 2 * sizeof(Vertex)); currentPosition += 1; } stripLengths.push_back(currentStripLength); currentStripLength = 0; #else glEnd(); #endif } inline void flush() { #if USE_VERTEX_BUFFER if (currentPosition > 0) { glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex) * (currentPosition + 1) * 2, data); // Finish the current line strip if (currentStripLength > 1) end(); unsigned int startIndex = 0; for (vector::const_iterator iter = stripLengths.begin(); iter != stripLengths.end(); ++iter) { unsigned int drawCount = *iter * 2; glDrawArrays(GL_TRIANGLE_STRIP, startIndex, drawCount); startIndex += drawCount + 2; } currentPosition = 0; stripLengths.clear(); } currentStripLength = 0; #endif } void createVertexBuffer() { #if USE_VERTEX_BUFFER if (!vbobj) { glGenBuffers(1, &vbobj); glBindBuffer(GL_ARRAY_BUFFER, vbobj); glBufferData(GL_ARRAY_BUFFER, (2 * (capacity + 1)) * sizeof(Vertex), nullptr, GL_STREAM_DRAW); } #endif } void setColor(const Vector4f &aColor) { #if USE_VERTEX_BUFFER color = aColor; #else glColor4fv(aColor.data()); #endif } private: unsigned int currentPosition; unsigned int capacity; struct Vertex { EIGEN_MAKE_ALIGNED_OPERATOR_NEW Vector4f position; Vector4f color; float scale; }; Vertex* data; GLuint vbobj; unsigned int currentStripLength; vector stripLengths; Vector4f color; }; class HighPrec_RenderContext { public: HighPrec_RenderContext(HighPrec_VertexBuffer& vbuf, HighPrec_Frustum& viewFrustum, double subdivisionThreshold) : m_vbuf(vbuf), m_viewFrustum(viewFrustum), m_subdivisionThreshold(subdivisionThreshold) { } ~HighPrec_RenderContext() { /* vbuf.flush(); vbuf.finish(); */ } // Return the GL restart status: true if the last segment of the // curve was culled and we need to start a new primitive sequence // with glBegin(). bool renderCubic(bool restartCurve, const Matrix4d& coeff, double t0, double t1, double curveBoundingRadius, int depth) const { const double dt = (t1 - t0) * InvSubdivisionFactor; double segmentBoundingRadius = curveBoundingRadius * InvSubdivisionFactor; #if DEBUG_ADAPTIVE_SPLINE { int c = depth % 10; glColor4f(SplineColors[c][0], SplineColors[c][1], SplineColors[c][2], 1.0f); ++SegmentCounts[depth]; } #endif Vector4d lastP = coeff * Vector4d(1.0, t0, t0 * t0, t0 * t0 * t0); for (unsigned int i = 1; i <= SubdivisionFactor; i++) { double t = t0 + dt * i; Vector4d p = coeff * Vector4d(1.0, t, t * t, t * t * t); double minDistance = max(-m_viewFrustum.nearZ(), abs(p.z()) - segmentBoundingRadius); if (segmentBoundingRadius >= m_subdivisionThreshold * minDistance) { if (m_viewFrustum.cullSphere(p, segmentBoundingRadius)) { if (!restartCurve) { m_vbuf.end(); restartCurve = true; } } else { restartCurve = renderCubic(restartCurve, coeff, t - dt, t, segmentBoundingRadius, depth + 1); } } else { #if DEBUG_ADAPTIVE_SPLINE { int c = depth % 10; glColor4f(SplineColors[c][0], SplineColors[c][1], SplineColors[c][2], i % 2 ? 0.25f : 1.0f); } #endif if (restartCurve) { m_vbuf.begin(); m_vbuf.vertex(lastP); restartCurve = false; } m_vbuf.vertex(p); } lastP = p; } return restartCurve; } // Return the GL restart status: true if the last segment of the // curve was culled and we need to start a new primitive sequence // with glBegin(). bool renderCubicFaded(bool restartCurve, const Matrix4d& coeff, double t0, double t1, const Vector4f& color, double fadeStart, double fadeRate, double curveBoundingRadius, int depth) const { const double dt = (t1 - t0) * InvSubdivisionFactor; double segmentBoundingRadius = curveBoundingRadius * InvSubdivisionFactor; #if DEBUG_ADAPTIVE_SPLINE { int c = depth % 10; glColor4f(SplineColors[c][0], SplineColors[c][1], SplineColors[c][2], 1.0f); ++SegmentCounts[depth]; } #endif Vector4d lastP = coeff * Vector4d(1.0, t0, t0 * t0, t0 * t0 * t0); double lastOpacity = (t0 - fadeStart) * fadeRate; lastOpacity = max(0.0, min(1.0, lastOpacity)); // clamp for (unsigned int i = 1; i <= SubdivisionFactor; i++) { double t = t0 + dt * i; Vector4d p = coeff * Vector4d(1.0, t, t * t, t * t * t); double opacity = (t - fadeStart) * fadeRate; opacity = max(0.0, min(1.0, opacity)); // clamp double minDistance = max(-m_viewFrustum.nearZ(), abs(p.z()) - segmentBoundingRadius); if (segmentBoundingRadius >= m_subdivisionThreshold * minDistance) { if (m_viewFrustum.cullSphere(p, segmentBoundingRadius)) { if (!restartCurve) { m_vbuf.end(); restartCurve = true; } } else { restartCurve = renderCubicFaded(restartCurve, coeff, t - dt, t, color, fadeStart, fadeRate, segmentBoundingRadius, depth + 1); } } else { #if DEBUG_ADAPTIVE_SPLINE { int c = depth % 10; glColor4f(SplineColors[c][0], SplineColors[c][1], SplineColors[c][2], i % 2 ? 0.25f : 1.0f); } #endif if (restartCurve) { m_vbuf.begin(); m_vbuf.vertex(lastP, Vector4f(color.x(), color.y(), color.z(), color.w() * float(lastOpacity))); restartCurve = false; } m_vbuf.vertex(p, Vector4f(color.x(), color.y(), color.z(), color.w() * float(opacity))); } lastP = p; lastOpacity = opacity; } return restartCurve; } private: HighPrec_VertexBuffer& m_vbuf; HighPrec_Frustum& m_viewFrustum; double m_subdivisionThreshold; }; static HighPrec_VertexBuffer vbuf; CurvePlot::CurvePlot() { } /** Add a new sample to the path. If the sample time is less than the first time, * it is added at the end. If it is greater than the last time, it is appended * to the path. The sample is ignored if it has a time in between the first and * last times of the path. */ void CurvePlot::addSample(const CurvePlotSample& sample) { bool addToBack = false; if (m_samples.empty() || sample.t > m_samples.back().t) { addToBack = true; } else if (sample.t < m_samples.front().t) { addToBack = false; } else { // Sample falls within range of current samples; discard it return; } if (addToBack) m_samples.push_back(sample); else m_samples.push_front(sample); if (m_samples.size() > 1) { // Calculate a bounding radius for this segment. No point on the curve will // be further from the start point than the bounding radius. if (addToBack) { const CurvePlotSample& lastSample = m_samples[m_samples.size() - 2]; double dt = sample.t - lastSample.t; Matrix4d coeff = cubicHermiteCoefficients(zeroExtend(lastSample.position), zeroExtend(sample.position), zeroExtend(lastSample.velocity * dt), zeroExtend(sample.velocity * dt)); Vector4d extents = coeff.cwiseAbs() * Vector4d(0.0, 1.0, 1.0, 1.0); m_samples[m_samples.size() - 1].boundingRadius = extents.norm(); } else { const CurvePlotSample& nextSample = m_samples[1]; double dt = nextSample.t - sample.t; Matrix4d coeff = cubicHermiteCoefficients(zeroExtend(sample.position), zeroExtend(nextSample.position), zeroExtend(sample.velocity * dt), zeroExtend(nextSample.velocity * dt)); Vector4d extents = coeff.cwiseAbs() * Vector4d(0.0, 1.0, 1.0, 1.0); m_samples[1].boundingRadius = extents.norm(); } } } /** Remove all samples before the specified time. */ void CurvePlot::removeSamplesBefore(double t) { while (!m_samples.empty() && m_samples.front().t < t) { m_samples.pop_front(); } } /** Delete all samples after the specified time. */ void CurvePlot::removeSamplesAfter(double t) { while (!m_samples.empty() && m_samples.back().t > t) { m_samples.pop_back(); } } void CurvePlot::setDuration(double duration) { m_duration = duration; } // Trajectory consists of segments, each of which is a cubic // polynomial. /** Draw a piecewise curve with transformation and frustum clipping. * * @param modelview an affine transformation that will be applied to the curve * @param nearZ z coordinate of the near plane * @param farZ z coordinate of the far plane * @param viewFrustumPlaneNormals array of four normals (top, bottom, left, and right frustum planes) * @param subdivisionThreshold the threashhold for subdivision */ void CurvePlot::render(const Affine3d& modelview, double nearZ, double farZ, const Vector3d viewFrustumPlaneNormals[], double subdivisionThreshold, const Vector4f& color) const { // Flag to indicate whether we need to issue a glBegin() bool restartCurve = true; const Vector3d& p0_ = m_samples[0].position; const Vector3d& v0_ = m_samples[0].velocity; Vector4d p0 = modelview * Vector4d(p0_.x(), p0_.y(), p0_.z(), 1.0); Vector4d v0 = modelview * Vector4d(v0_.x(), v0_.y(), v0_.z(), 0.0); HighPrec_Frustum viewFrustum(nearZ, farZ, viewFrustumPlaneNormals); HighPrec_RenderContext rc(vbuf, viewFrustum, subdivisionThreshold); #if DEBUG_ADAPTIVE_SPLINE for (unsigned int i = 0; i < sizeof(SegmentCounts) / sizeof(SegmentCounts[0]); i++) SegmentCounts[i] = 0; #endif vbuf.createVertexBuffer(); vbuf.setup(); vbuf.setColor(color); for (unsigned int i = 1; i < m_samples.size(); i++) { // Transform the points into camera space. const Vector3d& p1_ = m_samples[i].position; const Vector3d& v1_ = m_samples[i].velocity; Vector4d p1 = modelview * Vector4d(p1_.x(), p1_.y(), p1_.z(), 1.0); Vector4d v1 = modelview * Vector4d(v1_.x(), v1_.y(), v1_.z(), 0.0); // O(t) is an approximating function for this segment of // the orbit, with 0 <= t <= 1 // C is the viewer position // d(t) = |O(t) - C|, the distance from viewer to the // orbit segment. double curveBoundingRadius = m_samples[i].boundingRadius; // Estimate the minimum possible distance from the // curve to the z=0 plane. If the curve is far enough // away to be approximated as a straight line, we'll just // render it. Otherwise, it should be a performance win // to do a sphere-frustum cull test before subdividing and // rendering segment. double minDistance = abs(p0.z()) - curveBoundingRadius; // Render close segments as splines with adaptive subdivision. The // subdivisions eliminates kinks between line segments and also // prevents clipping precision problems that occur when a // very long line is rendered with a relatively small view // volume. if (curveBoundingRadius >= subdivisionThreshold * minDistance) { #if DEBUG_ADAPTIVE_SPLINE ++SegmentCounts[0]; #endif // Skip rendering this section if it lies outside the view // frustum. if (viewFrustum.cullSphere(p0, curveBoundingRadius)) { if (!restartCurve) { vbuf.end(); restartCurve = true; } } else { double dt = m_samples[i].t - m_samples[i - 1].t; Matrix4d coeff = cubicHermiteCoefficients(p0, p1, v0 * dt, v1 * dt); restartCurve = rc.renderCubic(restartCurve, coeff, 0.0, 1.0, curveBoundingRadius, 1); } } else { #if DEBUG_ADAPTIVE_SPLINE glColor4f(SplineColors[0][0], SplineColors[0][1], SplineColors[0][2], 1.0f); #endif // Apparent size of curve is small enough that we can approximate // it as a line. // Simple cull test--just check the far plane if (p0.z() + curveBoundingRadius < farZ) { if (!restartCurve) { vbuf.end(); restartCurve = true; } } else { if (restartCurve) { vbuf.begin(); vbuf.vertex(p0); restartCurve = false; } vbuf.vertex(p1); } } p0 = p1; v0 = v1; } if (!restartCurve) { vbuf.end(); } vbuf.flush(); vbuf.finish(); #if DEBUG_ADAPTIVE_SPLINE3 for (unsigned int i = 0; SegmentCounts[i] != 0 || i < 3; i++) { clog << i << ":" << SegmentCounts[i] << ", "; } clog << endl; #endif } /** Draw some range of a piecewise curve with transformation and frustum clipping. * * @param modelview an affine transformation that will be applied to the curve * @param nearZ z coordinate of the near plane * @param farZ z coordinate of the far plane * @param viewFrustumPlaneNormals array of four normals (top, bottom, left, and right frustum planes) * @param subdivisionThreshold the threashhold for subdivision * @param startTime the beginning of the time interval * @param endTime the end of the time interval */ void CurvePlot::render(const Affine3d& modelview, double nearZ, double farZ, const Vector3d viewFrustumPlaneNormals[], double subdivisionThreshold, double startTime, double endTime, const Vector4f& color) const { // Flag to indicate whether we need to issue a glBegin() bool restartCurve = true; if (m_samples.empty() || endTime <= m_samples.front().t || startTime >= m_samples.back().t) return; // Linear search for the first sample unsigned int startSample = 0; while (startSample < m_samples.size() - 1 && startTime > m_samples[startSample].t) startSample++; // Start at the first sample with time <= startTime if (startSample > 0) startSample--; const Vector3d& p0_ = m_samples[startSample].position; const Vector3d& v0_ = m_samples[startSample].velocity; Vector4d p0 = modelview * Vector4d(p0_.x(), p0_.y(), p0_.z(), 1.0); Vector4d v0 = modelview * Vector4d(v0_.x(), v0_.y(), v0_.z(), 0.0); HighPrec_Frustum viewFrustum(nearZ, farZ, viewFrustumPlaneNormals); HighPrec_RenderContext rc(vbuf, viewFrustum, subdivisionThreshold); vbuf.createVertexBuffer(); vbuf.setup(); vbuf.setColor(color); bool firstSegment = true; bool lastSegment = false; for (unsigned int i = startSample + 1; i < m_samples.size() && !lastSegment; i++) { // Transform the points into camera space. const Vector3d& p1_ = m_samples[i].position; const Vector3d& v1_ = m_samples[i].velocity; Vector4d p1 = modelview * Vector4d(p1_.x(), p1_.y(), p1_.z(), 1.0); Vector4d v1 = modelview * Vector4d(v1_.x(), v1_.y(), v1_.z(), 0.0); if (endTime <= m_samples[i].t) { lastSegment = true; } // O(t) is an approximating function for this segment of // the orbit, with 0 <= t <= 1 // C is the viewer position // d(t) = |O(t) - C|, the distance from viewer to the // orbit segment. double curveBoundingRadius = m_samples[i].boundingRadius; // Estimate the minimum possible distance from the // curve to the z=0 plane. If the curve is far enough // away to be approximated as a straight line, we'll just // render it. Otherwise, it should be a performance win // to do a sphere-frustum cull test before subdividing and // rendering segment. double minDistance = abs(p0.z()) - curveBoundingRadius; // Render close segments as splines with adaptive subdivision. The // subdivisions eliminates kinks between line segments and also // prevents clipping precision problems that occur when a // very long line is rendered with a relatively small view // volume. if (curveBoundingRadius >= subdivisionThreshold * minDistance || lastSegment || firstSegment) { // Skip rendering this section if it lies outside the view // frustum. if (viewFrustum.cullSphere(p0, curveBoundingRadius)) { if (!restartCurve) { vbuf.end(); restartCurve = true; } } else { double dt = m_samples[i].t - m_samples[i - 1].t; double t0 = 0.0; double t1 = 1.0; if (firstSegment) { t0 = (startTime - m_samples[i - 1].t) / dt; t0 = std::max(0.0, std::min(1.0, t0)); firstSegment = false; } if (lastSegment) { t1 = (endTime - m_samples[i - 1].t) / dt; } Matrix4d coeff = cubicHermiteCoefficients(p0, p1, v0 * dt, v1 * dt); restartCurve = rc.renderCubic(restartCurve, coeff, t0, t1, curveBoundingRadius, 1); } } else { // Apparent size of curve is small enough that we can approximate // it as a line. // Simple cull test--just check the far plane. This is required because // apparent clipping precision limitations can cause a GPU to draw lines // that lie completely beyond the far plane. if (p0.z() + curveBoundingRadius < farZ) { if (!restartCurve) { vbuf.end(); restartCurve = true; } } else { if (restartCurve) { vbuf.begin(); vbuf.vertex(p0); restartCurve = false; } vbuf.vertex(p1); } } p0 = p1; v0 = v1; } if (!restartCurve) { vbuf.end(); } vbuf.flush(); vbuf.finish(); } /** Draw a piecewise cubic curve with transformation and frustum clipping. Only * the part of the curve between startTime and endTime will be drawn. Additionally, * the curve is drawn with a fade effect. The curve is at full opacity at fadeStartTime * and completely transparent at fadeEndTime. fadeStartTime may be greater than * fadeEndTime--this just means that the fade direction will be reversed. * * @param modelview an affine transformation that will be applied to the curve * @param nearZ z coordinate of the near plane * @param farZ z coordinate of the far plane * @param viewFrustumPlaneNormals array of four normals (top, bottom, left, and right frustum planes) * @param subdivisionThreshold the threashhold for subdivision * @param startTime the beginning of the time interval * @param endTime the end of the time interval * @param fadeStartTime points on the curve before this time are drawn with full opacity * @param fadeEndTime points on the curve after this time are not drawn */ void CurvePlot::renderFaded(const Eigen::Affine3d& modelview, double nearZ, double farZ, const Eigen::Vector3d viewFrustumPlaneNormals[], double subdivisionThreshold, double startTime, double endTime, const Vector4f& color, double fadeStartTime, double fadeEndTime) const { // Flag to indicate whether we need to issue a glBegin() bool restartCurve = true; if (m_samples.empty() || endTime <= m_samples.front().t || startTime >= m_samples.back().t) return; // Linear search for the first sample unsigned int startSample = 0; while (startSample < m_samples.size() - 1 && startTime > m_samples[startSample].t) startSample++; // Start at the first sample with time <= startTime if (startSample > 0) startSample--; double fadeDuration = fadeEndTime - fadeStartTime; double fadeRate = 1.0 / fadeDuration; const Vector3d& p0_ = m_samples[startSample].position; const Vector3d& v0_ = m_samples[startSample].velocity; Vector4d p0 = modelview * Vector4d(p0_.x(), p0_.y(), p0_.z(), 1.0); Vector4d v0 = modelview * Vector4d(v0_.x(), v0_.y(), v0_.z(), 0.0); double opacity0 = (m_samples[startSample].t - fadeStartTime) * fadeRate; opacity0 = max(0.0, min(1.0, opacity0)); HighPrec_Frustum viewFrustum(nearZ, farZ, viewFrustumPlaneNormals); HighPrec_RenderContext rc(vbuf, viewFrustum, subdivisionThreshold); vbuf.createVertexBuffer(); vbuf.setup(); bool firstSegment = true; bool lastSegment = false; for (unsigned int i = startSample + 1; i < m_samples.size() && !lastSegment; i++) { // Transform the points into camera space. const Vector3d& p1_ = m_samples[i].position; const Vector3d& v1_ = m_samples[i].velocity; Vector4d p1 = modelview * Vector4d(p1_.x(), p1_.y(), p1_.z(), 1.0); Vector4d v1 = modelview * Vector4d(v1_.x(), v1_.y(), v1_.z(), 0.0); double opacity1 = (m_samples[i].t - fadeStartTime) * fadeRate; opacity1 = max(0.0, min(1.0, opacity1)); if (endTime <= m_samples[i].t) { lastSegment = true; } // O(t) is an approximating function for this segment of // the orbit, with 0 <= t <= 1 // C is the viewer position // d(t) = |O(t) - C|, the distance from viewer to the // orbit segment. double curveBoundingRadius = m_samples[i].boundingRadius; // Estimate the minimum possible distance from the // curve to the z=0 plane. If the curve is far enough // away to be approximated as a straight line, we'll just // render it. Otherwise, it should be a performance win // to do a sphere-frustum cull test before subdividing and // rendering segment. double minDistance = abs(p0.z()) - curveBoundingRadius; // Render close segments as splines with adaptive subdivision. The // subdivisions eliminates kinks between line segments and also // prevents clipping precision problems that occur when a // very long line is rendered with a relatively small view // volume. if (curveBoundingRadius >= subdivisionThreshold * minDistance || lastSegment || firstSegment) { // Skip rendering this section if it lies outside the view // frustum. if (viewFrustum.cullSphere(p0, curveBoundingRadius)) { if (!restartCurve) { vbuf.end(); restartCurve = true; } } else { double dt = m_samples[i].t - m_samples[i - 1].t; double t0 = 0.0; double t1 = 1.0; if (firstSegment) { t0 = (startTime - m_samples[i - 1].t) / dt; t0 = std::max(0.0, std::min(1.0, t0)); firstSegment = false; } if (lastSegment) { t1 = (endTime - m_samples[i - 1].t) / dt; } Matrix4d coeff = cubicHermiteCoefficients(p0, p1, v0 * dt, v1 * dt); restartCurve = rc.renderCubicFaded(restartCurve, coeff, t0, t1, color, (fadeStartTime - m_samples[i - 1].t) / dt, fadeRate * dt, curveBoundingRadius, 1); } } else { // Apparent size of curve is small enough that we can approximate // it as a line. // Simple cull test--just check the far plane. This is required because // apparent clipping precision limitations can cause a GPU to draw lines // that lie completely beyond the far plane. if (p0.z() + curveBoundingRadius < farZ) { if (!restartCurve) { vbuf.end(); restartCurve = true; } } else { if (restartCurve) { vbuf.begin(); vbuf.vertex(p0, Vector4f(color.x(), color.y(), color.z(), color.w() * float(opacity0))); restartCurve = false; } vbuf.vertex(p1, Vector4f(color.x(), color.y(), color.z(), color.w() * float(opacity1))); } } p0 = p1; v0 = v1; opacity0 = opacity1; } if (!restartCurve) { vbuf.end(); } vbuf.flush(); vbuf.finish(); }