1141 lines
37 KiB
C++
1141 lines
37 KiB
C++
// curveplot.cpp
|
|
//
|
|
// Copyright (C) 2009-2010 Chris Laurel <claurel@gmail.com>.
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#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 <vector>
|
|
#include <iostream>
|
|
|
|
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<float>();
|
|
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<float>();
|
|
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<unsigned int>::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<unsigned int> 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();
|
|
}
|