You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

337 lines
11 KiB

3 months ago
// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2021
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// Version: 4.0.2019.08.13
#pragma once
#include <Mathematics/Logger.h>
#include <Mathematics/MinHeap.h>
#include <Mathematics/DistPointSegment.h>
#include <vector>
// The continuous level of detail (CLOD) algorithm implemented here is
// described in
// https://www.geometrictools.com/Documentation/PolylineReduction.pdf
// It is an algorithm to reduce incrementally the number of vertices in a
// polyline (open or closed). The sequence of vertex collapses is based on
// vertex weights associated with distance from vertices to polyline segments.
namespace gte
{
template <int N, typename Real>
class CLODPolyline
{
public:
// Constructor and destructor. The number of vertices must be 2 or
// larger. The vertices are assumed to be ordered. The segments are
// <V[i],V[i+1]> for 0 <= i <= numVertices-2 for an open polyline.
// If the polyline is closed, an additional segment is
// <V[numVertices-1],V[0]>.
CLODPolyline(std::vector<Vector<N, Real>> const& vertices, bool closed)
:
mNumVertices(static_cast<int>(vertices.size())),
mVertices(vertices),
mClosed(closed),
mNumEdges(0),
mVMin(mClosed ? 3 : 2),
mVMax(mNumVertices)
{
LogAssert(closed ? mNumVertices >= 3 : mNumVertices >= 2, "Invalid inputs.");
// Compute the sequence of vertex collapses. The polyline starts
// out at the full level of detail (mNumVertices equals mVMax).
VertexCollapse collapser;
collapser(mVertices, mClosed, mIndices, mNumEdges, mEdges);
}
~CLODPolyline() = default;
// Member access.
inline int GetNumVertices() const
{
return mNumVertices;
}
inline std::vector<Vector<N, Real>> const& GetVertices() const
{
return mVertices;
}
inline bool GetClosed() const
{
return mClosed;
}
inline int GetNumEdges() const
{
return mNumEdges;
}
inline std::vector<int> const& GetEdges() const
{
return mEdges;
}
// Accessors to level of detail (MinLOD <= LOD <= MaxLOD is required).
inline int GetMinLevelOfDetail() const
{
return mVMin;
}
inline int GetMaxLevelOfDetail() const
{
return mVMax;
}
inline int GetLevelOfDetail() const
{
return mNumVertices;
}
void SetLevelOfDetail(int numVertices)
{
if (numVertices < mVMin || numVertices > mVMax)
{
return;
}
// Decrease the level of detail.
while (mNumVertices > numVertices)
{
--mNumVertices;
mEdges[mIndices[mNumVertices]] = mEdges[2 * mNumEdges - 1];
--mNumEdges;
}
// Increase the level of detail.
while (mNumVertices < numVertices)
{
++mNumEdges;
mEdges[mIndices[mNumVertices]] = mNumVertices;
++mNumVertices;
}
}
private:
// Support for vertex collapses in a polyline.
class VertexCollapse
{
public:
void operator()(std::vector<Vector<N, Real>>& vertices,
bool closed, std::vector<int>& indices, int& numEdges,
std::vector<int>& edges)
{
int numVertices = static_cast<int>(vertices.size());
indices.resize(vertices.size());
if (closed)
{
numEdges = numVertices;
edges.resize(2 * numEdges);
if (numVertices == 3)
{
indices[0] = 0;
indices[1] = 1;
indices[2] = 3;
edges[0] = 0; edges[1] = 1;
edges[2] = 1; edges[3] = 2;
edges[4] = 2; edges[5] = 0;
return;
}
}
else
{
numEdges = numVertices - 1;
edges.resize(2 * numEdges);
if (numVertices == 2)
{
indices[0] = 0;
indices[1] = 1;
edges[0] = 0; edges[1] = 1;
return;
}
}
// Create the heap of weights.
MinHeap<int, Real> heap(numVertices);
int qm1 = numVertices - 1;
if (closed)
{
int qm2 = numVertices - 2;
heap.Insert(0, GetWeight(qm1, 0, 1, vertices));
heap.Insert(qm1, GetWeight(qm2, qm1, 0, vertices));
}
else
{
heap.Insert(0, std::numeric_limits<Real>::max());
heap.Insert(qm1, std::numeric_limits<Real>::max());
}
for (int m = 0, z = 1, p = 2; z < qm1; ++m, ++z, ++p)
{
heap.Insert(z, GetWeight(m, z, p, vertices));
}
// Create the level of detail information for the polyline.
std::vector<int> collapses(numVertices);
CollapseVertices(heap, numVertices, collapses);
ComputeEdges(numVertices, closed, collapses, indices, numEdges, edges);
ReorderVertices(numVertices, vertices, collapses, numEdges, edges);
}
protected:
// Weight calculation for vertex triple <V[m],V[z],V[p]>.
Real GetWeight(int m, int z, int p, std::vector<Vector<N, Real>>& vertices)
{
Vector<N, Real> direction = vertices[p] - vertices[m];
Real length = Normalize(direction);
if (length > (Real)0)
{
Segment<N, Real> segment(vertices[m], vertices[p]);
DCPQuery<Real, Vector<N, Real>, Segment<N, Real>> query;
Real distance = query(vertices[z], segment).distance;
return distance / length;
}
else
{
return std::numeric_limits<Real>::max();
}
}
// Create data structures for the polyline.
void CollapseVertices(MinHeap<int, Real>& heap,
int numVertices, std::vector<int>& collapses)
{
for (int i = numVertices - 1; i >= 0; --i)
{
Real weight;
heap.Remove(collapses[i], weight);
}
}
void ComputeEdges(int numVertices, bool closed, std::vector<int>& collapses,
std::vector<int>& indices, int numEdges, std::vector<int>& edges)
{
// Compute the edges (first to collapse is last in array). Do
// not collapse the last line segment of an open polyline. Do
// not collapse further when a closed polyline becomes a
// triangle.
int i, vIndex, eIndex = 2 * numEdges - 1;
if (closed)
{
for (i = numVertices - 1; i >= 0; --i)
{
vIndex = collapses[i];
edges[eIndex--] = (vIndex + 1) % numVertices;
edges[eIndex--] = vIndex;
}
}
else
{
for (i = numVertices - 1; i >= 2; --i)
{
vIndex = collapses[i];
edges[eIndex--] = vIndex + 1;
edges[eIndex--] = vIndex;
}
vIndex = collapses[0];
edges[0] = vIndex;
edges[1] = vIndex + 1;
}
// In the given edge order, find the index in the edge array
// that corresponds to a collapse vertex and save the index
// for the dynamic change in level of detail. This relies on
// the assumption that a vertex is shared by at most two
// edges.
eIndex = 2 * numEdges - 1;
for (i = numVertices - 1; i >= 0; --i)
{
vIndex = collapses[i];
for (int e = 0; e < 2 * numEdges; ++e)
{
if (vIndex == edges[e])
{
indices[i] = e;
edges[e] = edges[eIndex];
break;
}
}
eIndex -= 2;
if (closed)
{
if (eIndex == 5)
{
break;
}
}
else
{
if (eIndex == 1)
{
break;
}
}
}
// Restore the edge array to full level of detail.
if (closed)
{
for (i = 3; i < numVertices; ++i)
{
edges[indices[i]] = collapses[i];
}
}
else
{
for (i = 2; i < numVertices; ++i)
{
edges[indices[i]] = collapses[i];
}
}
}
void ReorderVertices(int numVertices, std::vector<Vector<N, Real>>& vertices,
std::vector<int>& collapses, int numEdges, std::vector<int>& edges)
{
std::vector<int> permute(numVertices);
std::vector<Vector<N, Real>> permutedVertex(numVertices);
for (int i = 0; i < numVertices; ++i)
{
int vIndex = collapses[i];
permute[vIndex] = i;
permutedVertex[i] = vertices[vIndex];
}
for (int i = 0; i < 2 * numEdges; ++i)
{
edges[i] = permute[edges[i]];
}
vertices = permutedVertex;
}
};
private:
// The polyline vertices.
int mNumVertices;
std::vector<Vector<N, Real>> mVertices;
bool mClosed;
// The polyline edges.
int mNumEdges;
std::vector<int> mEdges;
// The level of detail information.
int mVMin, mVMax;
std::vector<int> mIndices;
};
}