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.
656 lines
26 KiB
656 lines
26 KiB
// 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.2021.04.22
|
|
|
|
#pragma once
|
|
|
|
// Compute the convex hull of 3D points using incremental insertion. The only
|
|
// way to ensure a correct result for the input vertices is to use an exact
|
|
// predicate for computing signs of various expressions. The implementation
|
|
// uses interval arithmetic and rational arithmetic for the predicate.
|
|
//
|
|
// TODO: A couple of potential optimizations need to be explored. The
|
|
// divide-and-conquer algorithm computes two convex hulls for a set of points.
|
|
// The two hulls are then merged into a single convex hull. The merge step
|
|
// is not the theoretical one that attempts to determine mutual visibility
|
|
// of the two hulls; rather, it combines the hulls into a single set of points
|
|
// and computes the convex hull of that set. This can be improved by using the
|
|
// left subhull in its current form and inserting points from the right subhull
|
|
// one at a time. It might be possible to insert points and stop the process
|
|
// when the partially merged polyhedron is the convex hull.
|
|
//
|
|
// The other optimization is based on profiling. The VETManifoldMesh memory
|
|
// management during insert/remove of vertices, edges and triangles suggests
|
|
// that a specialized VET data structure can be designed to avoid this cost.
|
|
//
|
|
// The main cost of the algorithm is testing which side of a plane a point is
|
|
// located. This test uses interval arithmetic to determine an exact sign,
|
|
// if possible. If that test fails, rational arithmetic is used. For typical
|
|
// datasets, the indeterminate sign from interval arithmetic happens rarely.
|
|
|
|
#include <Mathematics/ConvexHull2.h>
|
|
#include <Mathematics/SWInterval.h>
|
|
#include <Mathematics/Vector3.h>
|
|
#include <Mathematics/VETManifoldMesh.h>
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
#include <queue>
|
|
#include <set>
|
|
#include <thread>
|
|
|
|
namespace gte
|
|
{
|
|
template <typename Real>
|
|
class ConvexHull3
|
|
{
|
|
public:
|
|
// Supporting constants and types for rational arithmetic used in
|
|
// the exact predicate for sign computations.
|
|
static int constexpr NumWords = std::is_same<Real, float>::value ? 27 : 197;
|
|
using Rational = BSNumber<UIntegerFP32<NumWords>>;
|
|
|
|
// The class is a functor to support computing the convex hull of
|
|
// multiple data sets using the same class object.
|
|
ConvexHull3()
|
|
:
|
|
mPoints(nullptr),
|
|
mRPoints{},
|
|
mConverted{},
|
|
mDimension(0),
|
|
mVertices{},
|
|
mHull{},
|
|
mHullMesh{}
|
|
{
|
|
}
|
|
|
|
// Compute the exact convex hull using a blend of interval arithmetic
|
|
// and rational arithmetic. The code runs single-threaded when
|
|
// lgNumThreads = 0. It runs multithreaded when lgNumThreads > 0,
|
|
// where the number of threads is 2^{lgNumThreads} > 1.
|
|
void operator()(size_t numPoints, Vector3<Real> const* points,
|
|
size_t lgNumThreads)
|
|
{
|
|
LogAssert(numPoints > 0 && points != nullptr, "Invalid argument.");
|
|
|
|
// Allocate storage for any rational points that must be computed
|
|
// in the exact sign predicates. The rational points are memoized.
|
|
mPoints = points;
|
|
mRPoints.resize(numPoints);
|
|
mConverted.resize(numPoints);
|
|
std::fill(mConverted.begin(), mConverted.end(), 0);
|
|
|
|
// Sort all the points indirectly.
|
|
auto lessThanPoints = [this](size_t s0, size_t s1)
|
|
{
|
|
return mPoints[s0] < mPoints[s1];
|
|
};
|
|
|
|
auto equalPoints = [this](size_t s0, size_t s1)
|
|
{
|
|
return mPoints[s0] == mPoints[s1];
|
|
};
|
|
|
|
std::vector<size_t> sorted(numPoints);
|
|
std::iota(sorted.begin(), sorted.end(), 0);
|
|
std::sort(sorted.begin(), sorted.end(), lessThanPoints);
|
|
auto newEnd = std::unique(sorted.begin(), sorted.end(), equalPoints);
|
|
sorted.erase(newEnd, sorted.end());
|
|
|
|
if (lgNumThreads > 0)
|
|
{
|
|
size_t numThreads = (static_cast<size_t>(1) << lgNumThreads);
|
|
size_t load = sorted.size() / numThreads;
|
|
std::vector<size_t> inNumSorted(numThreads);
|
|
std::vector<size_t*> inSorted(numThreads);
|
|
std::vector<std::vector<size_t>> outVertices(numThreads);
|
|
std::vector<std::thread> process(numThreads);
|
|
inNumSorted.back() = sorted.size();
|
|
inSorted.front() = sorted.data();
|
|
for (size_t i0 = 0, i1 = 1; i1 < numThreads; i0 = i1++)
|
|
{
|
|
inNumSorted[i0] = load;
|
|
inNumSorted.back() -= load;
|
|
inSorted[i1] = inSorted[i0] + load;
|
|
}
|
|
|
|
while (numThreads > 1)
|
|
{
|
|
for (size_t i = 0; i < numThreads; ++i)
|
|
{
|
|
process[i] = std::thread(
|
|
[this, i, &inNumSorted, &inSorted, &outVertices]()
|
|
{
|
|
size_t dimension = 0;
|
|
std::vector<size_t> hull;
|
|
VETManifoldMesh hullMesh;
|
|
ComputeHull(inNumSorted[i], inSorted[i], dimension,
|
|
outVertices[i], hull, hullMesh);
|
|
});
|
|
}
|
|
|
|
numThreads /= 2;
|
|
|
|
auto target = sorted.begin();
|
|
inSorted[0] = sorted.data();
|
|
for (size_t i = 0, k = 0; i < numThreads; ++i)
|
|
{
|
|
process[2 * i].join();
|
|
process[2 * i + 1].join();
|
|
|
|
inNumSorted[i] = 0;
|
|
auto begin = target;
|
|
for (size_t j = 0; j < 2; ++j, ++k)
|
|
{
|
|
size_t numVertices = outVertices[k].size();
|
|
inNumSorted[i] += numVertices;
|
|
std::copy(outVertices[k].begin(), outVertices[k].end(), target);
|
|
target += numVertices;
|
|
}
|
|
inSorted[i + 1] = inSorted[i] + inNumSorted[i];
|
|
std::sort(begin, target, lessThanPoints);
|
|
}
|
|
}
|
|
|
|
ComputeHull(inNumSorted[0], inSorted[0], mDimension, mVertices,
|
|
mHull, mHullMesh);
|
|
}
|
|
else
|
|
{
|
|
ComputeHull(sorted.size(), sorted.data(), mDimension, mVertices,
|
|
mHull, mHullMesh);
|
|
}
|
|
}
|
|
|
|
void operator()(std::vector<Vector3<Real>> const& points, size_t lgNumThreads)
|
|
{
|
|
operator()(points.size(), points.data(), lgNumThreads);
|
|
}
|
|
|
|
// The dimension is 0 (hull is a single point), 1 (hull is a line
|
|
// segment), 2 (hull is a convex polygon in 3D) or 3 (hull is a convex
|
|
// polyhedron).
|
|
inline size_t GetDimension() const
|
|
{
|
|
return mDimension;
|
|
}
|
|
|
|
// Get the indices into the input 'points[]' that correspond to hull
|
|
// vertices.
|
|
inline std::vector<size_t> const& GetVertices() const
|
|
{
|
|
return mVertices;
|
|
}
|
|
|
|
// Get the indices into the input 'points[]' that correspond to hull
|
|
// vertices. The returned array is organized according to the hull
|
|
// dimension.
|
|
// 0: The hull is a single point. The returned array has size 1 with
|
|
// index corresponding to that point.
|
|
// 1: The hull is a line segment. The returned array has size 2 with
|
|
// indices corresponding to the segment endpoints.
|
|
// 2: The hull is a convex polygon in 3D. The returned array has
|
|
// size N with indices corresponding to the polygon vertices.
|
|
// The vertices are ordered.
|
|
// 3: The hull is a convex polyhedron. The returned array has T
|
|
// triples of indices, each triple corresponding to a triangle
|
|
// face of the hull. The face vertices are counterclockwise when
|
|
// viewed by an observer outside the polyhedron. It is possible
|
|
// that some triangle faces are coplanar.
|
|
// The number of vertices and triangles can vary depending on the
|
|
// number of threads used for computation. This is not an error. For
|
|
// example, when running with N threads it is possible to have a
|
|
// convex quadrilateral face formed by 2 coplanar triangles {v0,v1,v2}
|
|
// and {v0,v2,v3}. When running with M threads, it is possible that
|
|
// the same convex quadrilateral face is formed by 4 coplanar triangles
|
|
// {v0,v1,v2}, {v1,v2,v4}, {v2,v3,v4} and {v3,v0,v4}, where the
|
|
// vertices v0, v2 and v4 are colinear. In both cases, if V is the
|
|
// number of vertices and T is the number of triangles, then the
|
|
// number of edges is E = T/2 and Euler's formula is satisfied:
|
|
// V - E + T = 2.
|
|
inline std::vector<size_t> const& GetHull() const
|
|
{
|
|
return mHull;
|
|
}
|
|
|
|
// Get the hull mesh, which is valid only when the dimension is 3.
|
|
// This allows access to the graph of vertices, edges and triangles
|
|
// of the convex (polyhedron) hull.
|
|
inline VETManifoldMesh const& GetHullMesh() const
|
|
{
|
|
return mHullMesh;
|
|
}
|
|
|
|
private:
|
|
void ComputeHull(size_t numSorted, size_t* sorted, size_t& dimension,
|
|
std::vector<size_t>& vertices, std::vector<size_t>& hull,
|
|
VETManifoldMesh& hullMesh)
|
|
{
|
|
dimension = 0;
|
|
vertices.clear();
|
|
hull.reserve(numSorted);
|
|
hull.clear();
|
|
hullMesh.Clear();
|
|
|
|
size_t current = 0;
|
|
if (Hull0(hull, numSorted, sorted, dimension, current))
|
|
{
|
|
vertices.resize(1);
|
|
vertices[0] = hull[0];
|
|
return;
|
|
}
|
|
|
|
if (Hull1(hull, numSorted, sorted, dimension, current))
|
|
{
|
|
vertices.resize(2);
|
|
vertices[0] = hull[0];
|
|
vertices[1] = hull[1];
|
|
return;
|
|
}
|
|
|
|
if (Hull2(hull, numSorted, sorted, dimension, current))
|
|
{
|
|
vertices.resize(hull.size());
|
|
std::copy(hull.begin(), hull.end(), vertices.begin());
|
|
return;
|
|
}
|
|
|
|
Hull3(hull, numSorted, sorted, hullMesh, current);
|
|
|
|
auto const& vMap = hullMesh.GetVertices();
|
|
vertices.resize(vMap.size());
|
|
size_t index = 0;
|
|
for (auto const& element : vMap)
|
|
{
|
|
vertices[index++] = element.first;
|
|
}
|
|
|
|
auto const& tMap = hullMesh.GetTriangles();
|
|
hull.resize(3 * tMap.size());
|
|
index = 0;
|
|
for (auto const& element : tMap)
|
|
{
|
|
hull[index++] = element.first.V[0];
|
|
hull[index++] = element.first.V[1];
|
|
hull[index++] = element.first.V[2];
|
|
}
|
|
}
|
|
|
|
// Support for computing a 0-dimensional convex hull.
|
|
bool Hull0(std::vector<size_t>& hull, size_t numSorted, size_t* sorted,
|
|
size_t& dimension, size_t& current)
|
|
{
|
|
hull.push_back(sorted[current]); // hull[0]
|
|
for (++current; current < numSorted; ++current)
|
|
{
|
|
if (!Colocated(hull[0], sorted[current]))
|
|
{
|
|
dimension = 1;
|
|
break;
|
|
}
|
|
}
|
|
return dimension == 0;
|
|
}
|
|
|
|
// Support for computing a 1-dimensional convex hull.
|
|
bool Hull1(std::vector<size_t>& hull, size_t numSorted, size_t* sorted,
|
|
size_t& dimension, size_t& current)
|
|
{
|
|
hull.push_back(sorted[current]); // hull[1]
|
|
for (++current; current < numSorted; ++current)
|
|
{
|
|
if (!Colinear(hull[0], hull[1], sorted[current]))
|
|
{
|
|
dimension = 2;
|
|
break;
|
|
}
|
|
hull.push_back(sorted[current]);
|
|
}
|
|
|
|
if (hull.size() > 2)
|
|
{
|
|
// Sort the points and choose the extreme points as the
|
|
// endpoints of the line segment that is the convex hull.
|
|
std::sort(hull.begin(), hull.end(),
|
|
[this](size_t v0, size_t v1)
|
|
{
|
|
return mPoints[v0] < mPoints[v1];
|
|
});
|
|
|
|
size_t hmin = hull.front();
|
|
size_t hmax = hull.back();
|
|
hull.clear();
|
|
hull.push_back(hmin);
|
|
hull.push_back(hmax);
|
|
}
|
|
|
|
return dimension == 1;
|
|
}
|
|
|
|
// Support for computing a 2-dimensional convex hull.
|
|
bool Hull2(std::vector<size_t>& hull, size_t numSorted, size_t* sorted,
|
|
size_t& dimension, size_t& current)
|
|
{
|
|
hull.push_back(sorted[current]); // hull[2]
|
|
for (++current; current < numSorted; ++current)
|
|
{
|
|
if (ToPlane(hull[0], hull[1], hull[2], sorted[current]) != 0)
|
|
{
|
|
dimension = 3;
|
|
break;
|
|
}
|
|
hull.push_back(sorted[current]);
|
|
}
|
|
|
|
if (hull.size() > 3)
|
|
{
|
|
// Compute the planar convex hull of the points. The coplanar
|
|
// points are projected onto a 2D plane determined by the
|
|
// maximum absolute component of the normal of the first
|
|
// triangle. The extreme points of the projected hull generate
|
|
// the extreme points of the planar hull in 3D.
|
|
auto const& rV0 = GetRationalPoint(hull[0]);
|
|
auto const& rV1 = GetRationalPoint(hull[1]);
|
|
auto const& rV2 = GetRationalPoint(hull[2]);
|
|
auto const rDiff1 = rV1 - rV0;
|
|
auto const rDiff2 = rV2 - rV0;
|
|
auto rNormal = Cross(rDiff1, rDiff2);
|
|
|
|
// The signs are used to select 2 of the 3 point components so
|
|
// that when the planar hull is viewed from the side of the
|
|
// plane to which rNormal is directed, the triangles are
|
|
// counterclockwise ordered.
|
|
std::array<int32_t, 3> sign{};
|
|
for (int32_t i = 0; i < 3; ++i)
|
|
{
|
|
sign[i] = rNormal[i].GetSign();
|
|
rNormal[i].SetSign(std::abs(sign[i]));
|
|
};
|
|
|
|
std::pair<int32_t, int32_t> c;
|
|
if (rNormal[0] > rNormal[1])
|
|
{
|
|
if (rNormal[0] > rNormal[2])
|
|
{
|
|
c = (sign[0] > 0 ? std::make_pair(1, 2) : std::make_pair(2, 1));
|
|
}
|
|
else
|
|
{
|
|
c = (sign[2] > 0 ? std::make_pair(0, 1) : std::make_pair(1, 0));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (rNormal[1] > rNormal[2])
|
|
{
|
|
c = (sign[1] > 0 ? std::make_pair(2, 0) : std::make_pair(0, 2));
|
|
}
|
|
else
|
|
{
|
|
c = (sign[2] > 0 ? std::make_pair(0, 1) : std::make_pair(1, 0));
|
|
}
|
|
}
|
|
|
|
std::vector<Vector2<Real>> projections(hull.size());
|
|
for (size_t i = 0; i < projections.size(); ++i)
|
|
{
|
|
size_t h = hull[i];
|
|
projections[i][0] = mPoints[h][c.first];
|
|
projections[i][1] = mPoints[h][c.second];
|
|
}
|
|
|
|
ConvexHull2<Real> ch2;
|
|
ch2((int)projections.size(), projections.data(), static_cast<Real>(0));
|
|
auto const& hull2 = ch2.GetHull();
|
|
|
|
std::vector<size_t> tempHull(hull2.size());
|
|
for (size_t i = 0; i < hull2.size(); ++i)
|
|
{
|
|
tempHull[i] = hull[static_cast<size_t>(hull2[i])];
|
|
}
|
|
hull.clear();
|
|
for (size_t i = 0; i < hull2.size(); ++i)
|
|
{
|
|
hull.push_back(tempHull[i]);
|
|
}
|
|
}
|
|
|
|
return dimension == 2;
|
|
}
|
|
|
|
// Support for computing a 3-dimensional convex hull.
|
|
void Hull3(std::vector<size_t>& hull, size_t numSorted, size_t* sorted,
|
|
VETManifoldMesh& hullMesh, size_t& current)
|
|
{
|
|
using TrianglePtr = VETManifoldMesh::Triangle*;
|
|
// The hull points previous to the current one are coplanar and
|
|
// are the vertices of a convex polygon. To initialize the 3D
|
|
// hull, use triangles from a triangle fan of the convex polygon
|
|
// and use triangles connecting the current point to the edges
|
|
// of the convex polygon. The vertex ordering of these triangles
|
|
// depends on whether sorted[current] is on the positive or
|
|
// negative side of the plane determined by hull[0], hull[1] and
|
|
// hull[2].
|
|
int32_t sign = ToPlane(hull[0], hull[1], hull[2], sorted[current]);
|
|
int32_t h0, h1, h2;
|
|
if (sign > 0)
|
|
{
|
|
h0 = static_cast<int32_t>(hull[0]);
|
|
for (size_t i1 = 1, i2 = 2; i2 < hull.size(); i1 = i2++)
|
|
{
|
|
h1 = static_cast<int32_t>(hull[i1]);
|
|
h2 = static_cast<int32_t>(hull[i2]);
|
|
auto inserted = hullMesh.Insert(h0, h2, h1);
|
|
LogAssert(
|
|
inserted != nullptr,
|
|
"Unexpected insertion failure.");
|
|
}
|
|
|
|
h0 = static_cast<int32_t>(sorted[current]);
|
|
for (size_t i1 = hull.size() - 1, i2 = 0; i2 < hull.size(); i1 = i2++)
|
|
{
|
|
h1 = static_cast<int32_t>(hull[i1]);
|
|
h2 = static_cast<int32_t>(hull[i2]);
|
|
auto inserted = hullMesh.Insert(h0, h1, h2);
|
|
LogAssert(
|
|
inserted != nullptr,
|
|
"Unexpected insertion failure.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
h0 = static_cast<int32_t>(hull[0]);
|
|
for (size_t i1 = 1, i2 = 2; i2 < hull.size(); i1 = i2++)
|
|
{
|
|
h1 = static_cast<int32_t>(hull[i1]);
|
|
h2 = static_cast<int32_t>(hull[i2]);
|
|
auto inserted = hullMesh.Insert(h0, h1, h2);
|
|
LogAssert(
|
|
inserted != nullptr,
|
|
"Unexpected insertion failure.");
|
|
}
|
|
|
|
h0 = static_cast<int32_t>(sorted[current]);
|
|
for (size_t i1 = hull.size() - 1, i2 = 0; i2 < hull.size(); i1 = i2++)
|
|
{
|
|
h1 = static_cast<int32_t>(hull[i1]);
|
|
h2 = static_cast<int32_t>(hull[i2]);
|
|
auto inserted = hullMesh.Insert(h0, h2, h1);
|
|
LogAssert(
|
|
inserted != nullptr,
|
|
"Unexpected insertion failure.");
|
|
}
|
|
}
|
|
|
|
// The hull is now maintained in hullMesh, so there is no need
|
|
// to add members to hull. At the time the full hull is known,
|
|
// hull will be assigned the triangle indices.
|
|
std::vector<std::array<int32_t, 2>> terminator;
|
|
for (++current; current < numSorted; ++current)
|
|
{
|
|
// The index h0 refers to the previously inserted hull point.
|
|
// The index h1 refers to the current point to be inserted
|
|
// into the hull.
|
|
auto const& vMap = hullMesh.GetVertices();
|
|
auto vIter = vMap.find(h0);
|
|
LogAssert(vIter != vMap.end(), "Unexpected condition.");
|
|
h1 = static_cast<int32_t>(sorted[current]);
|
|
|
|
// The sorting guarantees that the point at h0 is visible to
|
|
// the point at h1. Find the triangles that share h0 and are
|
|
// visible to h1
|
|
std::queue<TrianglePtr> visible;
|
|
std::set<TrianglePtr> visited;
|
|
for (auto const& tri : vIter->second->TAdjacent)
|
|
{
|
|
sign = ToPlane(tri->V[0], tri->V[1], tri->V[2], h1);
|
|
if (sign > 0)
|
|
{
|
|
visible.push(tri);
|
|
visited.insert(tri);
|
|
break;
|
|
}
|
|
}
|
|
LogAssert(visible.size() > 0, "Unexpected condition.");
|
|
|
|
// Remove the connected component of visible triangles. Save
|
|
// the terminator edges for insertion of the new visible set
|
|
// of triangles.
|
|
terminator.clear();
|
|
while (visible.size() > 0)
|
|
{
|
|
TrianglePtr tri = visible.front();
|
|
visible.pop();
|
|
for (size_t i = 0; i < 3; ++i)
|
|
{
|
|
auto adj = tri->T[i];
|
|
if (adj)
|
|
{
|
|
if (ToPlane(adj->V[0], adj->V[1], adj->V[2], h1) <= 0)
|
|
{
|
|
// The shared edge of tri and adj is a
|
|
// terminator.
|
|
terminator.push_back({ tri->V[i], tri->V[(i + 1) % 3] });
|
|
}
|
|
else
|
|
{
|
|
if (visited.find(adj) == visited.end())
|
|
{
|
|
visible.push(adj);
|
|
visited.insert(adj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
visited.erase(tri);
|
|
bool removed = hullMesh.Remove(tri->V[0], tri->V[1], tri->V[2]);
|
|
LogAssert(
|
|
removed,
|
|
"Unexpected removal failure.");
|
|
}
|
|
|
|
// Insert the new hull triangles.
|
|
for (auto const& edge : terminator)
|
|
{
|
|
auto inserted = hullMesh.Insert(edge[0], edge[1], h1);
|
|
LogAssert(
|
|
inserted != nullptr,
|
|
"Unexpected insertion failure.");
|
|
}
|
|
|
|
// The current index h1 becomes the previous index h0 for the
|
|
// next pass of the 'current' loop.
|
|
h0 = h1;
|
|
}
|
|
}
|
|
|
|
// Memoized access to the rational representation of the points.
|
|
Vector3<Rational> const& GetRationalPoint(size_t index)
|
|
{
|
|
if (mConverted[index] == 0)
|
|
{
|
|
mConverted[index] = 1;
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
mRPoints[index][i] = mPoints[index][i];
|
|
}
|
|
}
|
|
return mRPoints[index];
|
|
}
|
|
|
|
bool Colocated(size_t v0, size_t v1)
|
|
{
|
|
auto const& r0 = GetRationalPoint(v0);
|
|
auto const& r1 = GetRationalPoint(v1);
|
|
return r0 == r1;
|
|
}
|
|
|
|
bool Colinear(size_t v0, size_t v1, size_t v2)
|
|
{
|
|
auto const& rvec0 = GetRationalPoint(v0);
|
|
auto const& rvec1 = GetRationalPoint(v1);
|
|
auto const& rvec2 = GetRationalPoint(v2);
|
|
auto const rdiff1 = rvec1 - rvec0;
|
|
auto const rdiff2 = rvec2 - rvec0;
|
|
auto const rcross = Cross(rdiff1, rdiff2);
|
|
return rcross[0].GetSign() == 0
|
|
&& rcross[1].GetSign() == 0
|
|
&& rcross[2].GetSign() == 0;
|
|
}
|
|
|
|
// For a plane with origin V0 and normal N = Cross(V1-V0,V2-V0),
|
|
// ToPlane returns
|
|
// +1, V3 on positive side of plane (side to which N points)
|
|
// -1, V3 on negative side of plane (side to which -N points)
|
|
// 0, V3 on the plane
|
|
int ToPlane(size_t v0, size_t v1, size_t v2, size_t v3)
|
|
{
|
|
using SInterval = SWInterval<Real>;
|
|
using SVector3 = Vector3<SInterval>;
|
|
|
|
// Attempt to classify the sign using interval arithmetic.
|
|
SVector3 const s0{ mPoints[v0][0], mPoints[v0][1], mPoints[v0][2] };
|
|
SVector3 const s1{ mPoints[v1][0], mPoints[v1][1], mPoints[v1][2] };
|
|
SVector3 const s2{ mPoints[v2][0], mPoints[v2][1], mPoints[v2][2] };
|
|
SVector3 const s3{ mPoints[v3][0], mPoints[v3][1], mPoints[v3][2] };
|
|
auto const sDiff1 = s1 - s0;
|
|
auto const sDiff2 = s2 - s0;
|
|
auto const sDiff3 = s3 - s0;
|
|
auto const sDet = DotCross(sDiff1, sDiff2, sDiff3);
|
|
if (sDet[0] > 0)
|
|
{
|
|
return +1;
|
|
}
|
|
if (sDet[1] < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// The sign is indeterminate using interval arithmetic.
|
|
auto const& r0 = GetRationalPoint(v0);
|
|
auto const& r1 = GetRationalPoint(v1);
|
|
auto const& r2 = GetRationalPoint(v2);
|
|
auto const& r3 = GetRationalPoint(v3);
|
|
auto const rDiff1 = r1 - r0;
|
|
auto const rDiff2 = r2 - r0;
|
|
auto const rDiff3 = r3 - r0;
|
|
auto const rDet = DotCross(rDiff1, rDiff2, rDiff3);
|
|
return rDet.GetSign();
|
|
}
|
|
|
|
private:
|
|
// A blend of interval arithmetic and exact arithmetic is used to
|
|
// ensure correctness.
|
|
Vector3<Real> const* mPoints;
|
|
std::vector<Vector3<Rational>> mRPoints;
|
|
std::vector<uint32_t> mConverted;
|
|
|
|
// The output data.
|
|
size_t mDimension;
|
|
std::vector<size_t> mVertices;
|
|
std::vector<size_t> mHull;
|
|
VETManifoldMesh mHullMesh;
|
|
};
|
|
}
|
|
|