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.
401 lines
14 KiB
401 lines
14 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.2019.08.13
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/TIQuery.h>
|
|
#include <Mathematics/FIQuery.h>
|
|
#include <Mathematics/Hyperplane.h>
|
|
#include <Mathematics/Vector.h>
|
|
#include <list>
|
|
#include <vector>
|
|
|
|
// The intersection queries are based on the document
|
|
// https://www.geometrictools.com/Documentation/ClipConvexPolygonByHyperplane.pdf
|
|
|
|
namespace gte
|
|
{
|
|
template <int N, typename Real>
|
|
class TIQuery<Real, std::vector<Vector<N, Real>>, Hyperplane<N, Real>>
|
|
{
|
|
public:
|
|
enum class Configuration
|
|
{
|
|
SPLIT,
|
|
POSITIVE_SIDE_VERTEX,
|
|
POSITIVE_SIDE_EDGE,
|
|
POSITIVE_SIDE_STRICT,
|
|
NEGATIVE_SIDE_VERTEX,
|
|
NEGATIVE_SIDE_EDGE,
|
|
NEGATIVE_SIDE_STRICT,
|
|
CONTAINED,
|
|
INVALID_POLYGON
|
|
};
|
|
|
|
struct Result
|
|
{
|
|
bool intersect;
|
|
Configuration configuration;
|
|
};
|
|
|
|
Result operator()(std::vector<Vector<N, Real>> const& polygon, Hyperplane<N, Real> const& hyperplane)
|
|
{
|
|
Result result;
|
|
|
|
size_t const numVertices = polygon.size();
|
|
if (numVertices < 3)
|
|
{
|
|
// The convex polygon must have at least 3 vertices.
|
|
result.intersect = false;
|
|
result.configuration = Configuration::INVALID_POLYGON;
|
|
return result;
|
|
}
|
|
|
|
// Determine on which side of the hyperplane each vertex lies.
|
|
size_t numPositive = 0, numNegative = 0, numZero = 0;
|
|
for (size_t i = 0; i < numVertices; ++i)
|
|
{
|
|
Real h = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant;
|
|
if (h > (Real)0)
|
|
{
|
|
++numPositive;
|
|
}
|
|
else if (h < (Real)0)
|
|
{
|
|
++numNegative;
|
|
}
|
|
else
|
|
{
|
|
++numZero;
|
|
}
|
|
}
|
|
|
|
if (numPositive > 0)
|
|
{
|
|
if (numNegative > 0)
|
|
{
|
|
result.intersect = true;
|
|
result.configuration = Configuration::SPLIT;
|
|
}
|
|
else if (numZero == 0)
|
|
{
|
|
result.intersect = false;
|
|
result.configuration = Configuration::POSITIVE_SIDE_STRICT;
|
|
}
|
|
else if (numZero == 1)
|
|
{
|
|
result.intersect = true;
|
|
result.configuration = Configuration::POSITIVE_SIDE_VERTEX;
|
|
}
|
|
else // numZero > 1
|
|
{
|
|
result.intersect = true;
|
|
result.configuration = Configuration::POSITIVE_SIDE_EDGE;
|
|
}
|
|
}
|
|
else if (numNegative > 0)
|
|
{
|
|
if (numZero == 0)
|
|
{
|
|
result.intersect = false;
|
|
result.configuration = Configuration::NEGATIVE_SIDE_STRICT;
|
|
}
|
|
else if (numZero == 1)
|
|
{
|
|
// The polygon touches the plane in a vertex or an edge.
|
|
result.intersect = true;
|
|
result.configuration = Configuration::NEGATIVE_SIDE_VERTEX;
|
|
}
|
|
else // numZero > 1
|
|
{
|
|
result.intersect = true;
|
|
result.configuration = Configuration::NEGATIVE_SIDE_EDGE;
|
|
}
|
|
}
|
|
else // numZero == numVertices
|
|
{
|
|
result.intersect = true;
|
|
result.configuration = Configuration::CONTAINED;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
template <int N, typename Real>
|
|
class FIQuery<Real, std::vector<Vector<N, Real>>, Hyperplane<N, Real>>
|
|
{
|
|
public:
|
|
enum class Configuration
|
|
{
|
|
SPLIT,
|
|
POSITIVE_SIDE_VERTEX,
|
|
POSITIVE_SIDE_EDGE,
|
|
POSITIVE_SIDE_STRICT,
|
|
NEGATIVE_SIDE_VERTEX,
|
|
NEGATIVE_SIDE_EDGE,
|
|
NEGATIVE_SIDE_STRICT,
|
|
CONTAINED,
|
|
INVALID_POLYGON
|
|
};
|
|
|
|
struct Result
|
|
{
|
|
// The intersection is either empty, a single vertex, a single
|
|
// edge or the polygon is contained by the hyperplane.
|
|
Configuration configuration;
|
|
std::vector<Vector<N, Real>> intersection;
|
|
|
|
// If 'configuration' is POSITIVE_* or SPLIT, this polygon is the
|
|
// portion of the query input 'polygon' on the positive side of
|
|
// the hyperplane with possibly a vertex or edge on the hyperplane.
|
|
std::vector<Vector<N, Real>> positivePolygon;
|
|
|
|
// If 'configuration' is NEGATIVE_* or SPLIT, this polygon is the
|
|
// portion of the query input 'polygon' on the negative side of
|
|
// the hyperplane with possibly a vertex or edge on the hyperplane.
|
|
std::vector<Vector<N, Real>> negativePolygon;
|
|
};
|
|
|
|
Result operator()(std::vector<Vector<N, Real>> const& polygon, Hyperplane<N, Real> const& hyperplane)
|
|
{
|
|
Result result;
|
|
|
|
size_t const numVertices = polygon.size();
|
|
if (numVertices < 3)
|
|
{
|
|
// The convex polygon must have at least 3 vertices.
|
|
result.configuration = Configuration::INVALID_POLYGON;
|
|
return result;
|
|
}
|
|
|
|
// Determine on which side of the hyperplane the vertices live.
|
|
// The index maxPosIndex stores the index of the vertex on the
|
|
// positive side of the hyperplane that is farthest from the
|
|
// hyperplane. The index maxNegIndex stores the index of the
|
|
// vertex on the negative side of the hyperplane that is farthest
|
|
// from the hyperplane. If one or the other such vertex does not
|
|
// exist, the corresponding index will remain its initial value of
|
|
// max(size_t).
|
|
std::vector<Real> height(numVertices);
|
|
std::vector<size_t> zeroHeightIndices;
|
|
zeroHeightIndices.reserve(numVertices);
|
|
size_t numPositive = 0, numNegative = 0;
|
|
Real maxPosHeight = -std::numeric_limits<Real>::max();
|
|
Real maxNegHeight = std::numeric_limits<Real>::max();
|
|
size_t maxPosIndex = std::numeric_limits<size_t>::max();
|
|
size_t maxNegIndex = std::numeric_limits<size_t>::max();
|
|
for (size_t i = 0; i < numVertices; ++i)
|
|
{
|
|
height[i] = Dot(hyperplane.normal, polygon[i]) - hyperplane.constant;
|
|
if (height[i] > (Real)0)
|
|
{
|
|
++numPositive;
|
|
if (height[i] > maxPosHeight)
|
|
{
|
|
maxPosHeight = height[i];
|
|
maxPosIndex = i;
|
|
}
|
|
}
|
|
else if (height[i] < (Real)0)
|
|
{
|
|
++numNegative;
|
|
if (height[i] < maxNegHeight)
|
|
{
|
|
maxNegHeight = height[i];
|
|
maxNegIndex = i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
zeroHeightIndices.push_back(i);
|
|
}
|
|
}
|
|
|
|
if (numPositive > 0)
|
|
{
|
|
if (numNegative > 0)
|
|
{
|
|
result.configuration = Configuration::SPLIT;
|
|
|
|
bool doSwap = (maxPosHeight < -maxNegHeight);
|
|
if (doSwap)
|
|
{
|
|
for (auto& h : height)
|
|
{
|
|
h = -h;
|
|
}
|
|
std::swap(maxPosIndex, maxNegIndex);
|
|
}
|
|
|
|
SplitPolygon(polygon, height, maxPosIndex, result);
|
|
|
|
if (doSwap)
|
|
{
|
|
std::swap(result.positivePolygon, result.negativePolygon);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
size_t numZero = zeroHeightIndices.size();
|
|
if (numZero == 0)
|
|
{
|
|
result.configuration = Configuration::POSITIVE_SIDE_STRICT;
|
|
}
|
|
else if (numZero == 1)
|
|
{
|
|
result.configuration = Configuration::POSITIVE_SIDE_VERTEX;
|
|
result.intersection =
|
|
{
|
|
polygon[zeroHeightIndices[0]]
|
|
};
|
|
}
|
|
else // numZero > 1
|
|
{
|
|
result.configuration = Configuration::POSITIVE_SIDE_EDGE;
|
|
result.intersection =
|
|
{
|
|
polygon[zeroHeightIndices[0]],
|
|
polygon[zeroHeightIndices[1]]
|
|
};
|
|
}
|
|
result.positivePolygon = polygon;
|
|
}
|
|
}
|
|
else if (numNegative > 0)
|
|
{
|
|
size_t numZero = zeroHeightIndices.size();
|
|
if (numZero == 0)
|
|
{
|
|
result.configuration = Configuration::NEGATIVE_SIDE_STRICT;
|
|
}
|
|
else if (numZero == 1)
|
|
{
|
|
result.configuration = Configuration::NEGATIVE_SIDE_VERTEX;
|
|
result.intersection =
|
|
{
|
|
polygon[zeroHeightIndices[0]]
|
|
};
|
|
}
|
|
else // numZero > 1
|
|
{
|
|
result.configuration = Configuration::NEGATIVE_SIDE_EDGE;
|
|
result.intersection =
|
|
{
|
|
polygon[zeroHeightIndices[0]],
|
|
polygon[zeroHeightIndices[1]]
|
|
};
|
|
}
|
|
result.negativePolygon = polygon;
|
|
}
|
|
else // numZero == numVertices
|
|
{
|
|
result.configuration = Configuration::CONTAINED;
|
|
result.intersection = polygon;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected:
|
|
void SplitPolygon(std::vector<Vector<N, Real>> const& polygon,
|
|
std::vector<Real> const& height, size_t maxPosIndex, Result& result)
|
|
{
|
|
// Find the largest contiguous subset of indices for which
|
|
// height[i] >= 0.
|
|
size_t const numVertices = polygon.size();
|
|
std::list<Vector<N, Real>> positiveList;
|
|
positiveList.push_back(polygon[maxPosIndex]);
|
|
size_t end0 = maxPosIndex;
|
|
size_t end0prev = std::numeric_limits<size_t>::max();
|
|
for (size_t i = 0; i < numVertices; ++i)
|
|
{
|
|
end0prev = (end0 + numVertices - 1) % numVertices;
|
|
if (height[end0prev] >= (Real)0)
|
|
{
|
|
positiveList.push_front(polygon[end0prev]);
|
|
end0 = end0prev;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
size_t end1 = maxPosIndex;
|
|
size_t end1next = std::numeric_limits<size_t>::max();
|
|
for (size_t i = 0; i < numVertices; ++i)
|
|
{
|
|
end1next = (end1 + 1) % numVertices;
|
|
if (height[end1next] >= (Real)0)
|
|
{
|
|
positiveList.push_back(polygon[end1next]);
|
|
end1 = end1next;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
size_t index = end1next;
|
|
std::list<Vector<N, Real>> negativeList;
|
|
for (size_t i = 0; i < numVertices; ++i)
|
|
{
|
|
negativeList.push_back(polygon[index]);
|
|
index = (index + 1) % numVertices;
|
|
if (index == end0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Clip the polygon.
|
|
if (height[end0] > (Real)0)
|
|
{
|
|
Real t = -height[end0prev] / (height[end0] - height[end0prev]);
|
|
Real omt = (Real)1 - t;
|
|
Vector<N, Real> V = omt * polygon[end0prev] + t * polygon[end0];
|
|
positiveList.push_front(V);
|
|
negativeList.push_back(V);
|
|
result.intersection.push_back(V);
|
|
}
|
|
else
|
|
{
|
|
negativeList.push_back(polygon[end0]);
|
|
result.intersection.push_back(polygon[end0]);
|
|
}
|
|
|
|
if (height[end1] > (Real)0)
|
|
{
|
|
Real t = -height[end1next] / (height[end1] - height[end1next]);
|
|
Real omt = (Real)1 - t;
|
|
Vector<N, Real> V = omt * polygon[end1next] + t * polygon[end1];
|
|
positiveList.push_back(V);
|
|
negativeList.push_front(V);
|
|
result.intersection.push_back(V);
|
|
}
|
|
else
|
|
{
|
|
negativeList.push_front(polygon[end1]);
|
|
result.intersection.push_back(polygon[end1]);
|
|
}
|
|
|
|
result.positivePolygon.reserve(positiveList.size());
|
|
for (auto const& p : positiveList)
|
|
{
|
|
result.positivePolygon.push_back(p);
|
|
}
|
|
|
|
result.negativePolygon.reserve(negativeList.size());
|
|
for (auto const& p : negativeList)
|
|
{
|
|
result.negativePolygon.push_back(p);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|