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.
 
 

575 lines
20 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/Logger.h>
#include <Mathematics/ContPointInPolygon2.h>
#include <Mathematics/IntrRay3Plane3.h>
#include <Mathematics/IntrRay3Triangle3.h>
#include <vector>
// This class contains various implementations for point-in-polyhedron
// queries. The planes stored with the faces are used in all cases to
// reject ray-face intersection tests, a quick culling operation.
//
// The algorithm is to cast a ray from the input point P and test for
// intersection against each face of the polyhedron. If the ray only
// intersects faces at interior points (not vertices, not edge points),
// then the point is inside when the number of intersections is odd and
// the point is outside when the number of intersections is even. If the
// ray intersects an edge or a vertex, then the counting must be handled
// differently. The details are tedious. As an alternative, the approach
// here is to allow you to specify 2*N+1 rays, where N >= 0. You should
// choose these rays randomly. Each ray reports "inside" or "outside".
// Whichever result occurs N+1 or more times is the "winner". The input
// rayQuantity is 2*N+1. The input array Direction must have rayQuantity
// elements. If you are feeling lucky, choose rayQuantity to be 1.
namespace gte
{
template <typename Real>
class PointInPolyhedron3
{
public:
// For simple polyhedra with triangle faces.
class TriangleFace
{
public:
// When you view the face from outside, the vertices are
// counterclockwise ordered. The indices array stores the indices
// into the vertex array.
std::array<int, 3> indices;
// The normal vector is unit length and points to the outside of
// the polyhedron.
Plane3<Real> plane;
};
// The Contains query will use ray-triangle intersection queries.
PointInPolyhedron3(int numPoints, Vector3<Real> const* points,
int numFaces, TriangleFace const* faces, int numRays,
Vector3<Real> const* directions)
:
mNumPoints(numPoints),
mPoints(points),
mNumFaces(numFaces),
mTFaces(faces),
mCFaces(nullptr),
mSFaces(nullptr),
mMethod(0),
mNumRays(numRays),
mDirections(directions)
{
}
// For simple polyhedra with convex polygon faces.
class ConvexFace
{
public:
// When you view the face from outside, the vertices are
// counterclockwise ordered. The indices array stores the indices
// into the vertex array.
std::vector<int> indices;
// The normal vector is unit length and points to the outside of
// the polyhedron.
Plane3<Real> plane;
};
// The Contains() query will use ray-convexpolygon intersection
// queries. A ray-convexpolygon intersection query can be implemented
// in many ways. In this context, uiMethod is one of three value:
// 0 : Use a triangle fan and perform a ray-triangle intersection
// query for each triangle.
// 1 : Find the point of intersection of ray and plane of polygon.
// Test whether that point is inside the convex polygon using an
// O(N) test.
// 2 : Find the point of intersection of ray and plane of polygon.
// Test whether that point is inside the convex polygon using an
// O(log N) test.
PointInPolyhedron3(int numPoints, Vector3<Real> const* points,
int numFaces, ConvexFace const* faces, int numRays,
Vector3<Real> const* directions, unsigned int method)
:
mNumPoints(numPoints),
mPoints(points),
mNumFaces(numFaces),
mTFaces(nullptr),
mCFaces(faces),
mSFaces(nullptr),
mMethod(method),
mNumRays(numRays),
mDirections(directions)
{
}
// For simple polyhedra with simple polygon faces that are generally
// not all convex.
class SimpleFace
{
public:
// When you view the face from outside, the vertices are
// counterclockwise ordered. The Indices array stores the indices
// into the vertex array.
std::vector<int> indices;
// The normal vector is unit length and points to the outside of
// the polyhedron.
Plane3<Real> plane;
// Each simple face may be triangulated. The indices are relative
// to the vertex array. Each triple of indices represents a
// triangle in the triangulation.
std::vector<int> triangles;
};
// The Contains query will use ray-simplepolygon intersection queries.
// A ray-simplepolygon intersection query can be implemented in a
// couple of ways. In this context, uiMethod is one of two value:
// 0 : Iterate over the triangles of each face and perform a
// ray-triangle intersection query for each triangle. This
// requires that the SimpleFace::Triangles array be initialized
// for each face.
// 1 : Find the point of intersection of ray and plane of polygon.
// Test whether that point is inside the polygon using an O(N)
// test. The SimpleFace::Triangles array is not used for this
// method, so it does not have to be initialized for each face.
PointInPolyhedron3(int numPoints, Vector3<Real> const* points,
int numFaces, SimpleFace const* faces, int numRays,
Vector3<Real> const* directions, unsigned int method)
:
mNumPoints(numPoints),
mPoints(points),
mNumFaces(numFaces),
mTFaces(nullptr),
mCFaces(nullptr),
mSFaces(faces),
mMethod(method),
mNumRays(numRays),
mDirections(directions)
{
}
// This function will select the actual algorithm based on which
// constructor you used for this class.
bool Contains(Vector3<Real> const& p) const
{
if (mTFaces)
{
return ContainsT0(p);
}
if (mCFaces)
{
if (mMethod == 0)
{
return ContainsC0(p);
}
return ContainsC1C2(p, mMethod);
}
if (mSFaces)
{
if (mMethod == 0)
{
return ContainsS0(p);
}
if (mMethod == 1)
{
return ContainsS1(p);
}
}
return false;
}
private:
// For all types of faces. The ray origin is the test point. The ray
// direction is one of those passed to the constructors. The plane
// origin is a point on the plane of the face. The plane normal is a
// unit-length normal to the face and that points outside the
// polyhedron.
static bool FastNoIntersect(Ray3<Real> const& ray, Plane3<Real> const& plane)
{
Real planeDistance = Dot(plane.normal, ray.origin) - plane.constant;
Real planeAngle = Dot(plane.normal, ray.direction);
if (planeDistance < (Real)0)
{
// The ray origin is on the negative side of the plane.
if (planeAngle <= (Real)0)
{
// The ray points away from the plane.
return true;
}
}
if (planeDistance > (Real)0)
{
// The ray origin is on the positive side of the plane.
if (planeAngle >= (Real)0)
{
// The ray points away from the plane.
return true;
}
}
return false;
}
// For triangle faces.
bool ContainsT0(Vector3<Real> const& p) const
{
int insideCount = 0;
TIQuery<Real, Ray3<Real>, Triangle3<Real>> rtQuery;
Triangle3<Real> triangle;
Ray3<Real> ray;
ray.origin = p;
for (int j = 0; j < mNumRays; ++j)
{
ray.direction = mDirections[j];
// Zero intersections to start with.
bool odd = false;
TriangleFace const* face = mTFaces;
for (int i = 0; i < mNumFaces; ++i, ++face)
{
// Attempt to quickly cull the triangle.
if (FastNoIntersect(ray, face->plane))
{
continue;
}
// Get the triangle vertices.
for (int k = 0; k < 3; ++k)
{
triangle.v[k] = mPoints[face->indices[k]];
}
// Test for intersection.
if (rtQuery(ray, triangle).intersect)
{
// The ray intersects the triangle.
odd = !odd;
}
}
if (odd)
{
insideCount++;
}
}
return insideCount > mNumRays / 2;
}
// For convex faces.
bool ContainsC0(Vector3<Real> const& p) const
{
int insideCount = 0;
TIQuery<Real, Ray3<Real>, Triangle3<Real>> rtQuery;
Triangle3<Real> triangle;
Ray3<Real> ray;
ray.origin = p;
for (int j = 0; j < mNumRays; ++j)
{
ray.direction = mDirections[j];
// Zero intersections to start with.
bool odd = false;
ConvexFace const* face = mCFaces;
for (int i = 0; i < mNumFaces; ++i, ++face)
{
// Attempt to quickly cull the triangle.
if (FastNoIntersect(ray, face->plane))
{
continue;
}
// Process the triangles in a trifan of the face.
size_t numVerticesM1 = face->indices.size() - 1;
triangle.v[0] = mPoints[face->indices[0]];
for (size_t k = 1; k < numVerticesM1; ++k)
{
triangle.v[1] = mPoints[face->indices[k]];
triangle.v[2] = mPoints[face->indices[k + 1]];
if (rtQuery(ray, triangle).intersect)
{
// The ray intersects the triangle.
odd = !odd;
}
}
}
if (odd)
{
insideCount++;
}
}
return insideCount > mNumRays / 2;
}
bool ContainsC1C2(Vector3<Real> const& p, unsigned int method) const
{
int insideCount = 0;
FIQuery<Real, Ray3<Real>, Plane3<Real>> rpQuery;
Ray3<Real> ray;
ray.origin = p;
for (int j = 0; j < mNumRays; ++j)
{
ray.direction = mDirections[j];
// Zero intersections to start with.
bool odd = false;
ConvexFace const* face = mCFaces;
for (int i = 0; i < mNumFaces; ++i, ++face)
{
// Attempt to quickly cull the triangle.
if (FastNoIntersect(ray, face->plane))
{
continue;
}
// Compute the ray-plane intersection.
auto result = rpQuery(ray, face->plane);
// If you trigger this assertion, numerical round-off
// errors have led to a discrepancy between
// FastNoIntersect and the Find() result.
LogAssert(result.intersect, "Unexpected condition.");
// Get a coordinate system for the plane. Use vertex 0
// as the origin.
Vector3<Real> const& V0 = mPoints[face->indices[0]];
Vector3<Real> basis[3];
basis[0] = face->plane.normal;
ComputeOrthogonalComplement(1, basis);
// Project the intersection onto the plane.
Vector3<Real> diff = result.point - V0;
Vector2<Real> projIntersect{ Dot(basis[1], diff), Dot(basis[2], diff) };
// Project the face vertices onto the plane of the face.
if (face->indices.size() > mProjVertices.size())
{
mProjVertices.resize(face->indices.size());
}
// Project the remaining vertices. Vertex 0 is always the
// origin.
size_t numIndices = face->indices.size();
mProjVertices[0] = Vector2<Real>::Zero();
for (size_t k = 1; k < numIndices; ++k)
{
diff = mPoints[face->indices[k]] - V0;
mProjVertices[k][0] = Dot(basis[1], diff);
mProjVertices[k][1] = Dot(basis[2], diff);
}
// Test whether the intersection point is in the convex
// polygon.
PointInPolygon2<Real> PIP(static_cast<int>(mProjVertices.size()),
&mProjVertices[0]);
if (method == 1)
{
if (PIP.ContainsConvexOrderN(projIntersect))
{
// The ray intersects the triangle.
odd = !odd;
}
}
else
{
if (PIP.ContainsConvexOrderLogN(projIntersect))
{
// The ray intersects the triangle.
odd = !odd;
}
}
}
if (odd)
{
insideCount++;
}
}
return insideCount > mNumRays / 2;
}
// For simple faces.
bool ContainsS0(Vector3<Real> const& p) const
{
int insideCount = 0;
TIQuery<Real, Ray3<Real>, Triangle3<Real>> rtQuery;
Triangle3<Real> triangle;
Ray3<Real> ray;
ray.origin = p;
for (int j = 0; j < mNumRays; ++j)
{
ray.direction = mDirections[j];
// Zero intersections to start with.
bool odd = false;
SimpleFace const* face = mSFaces;
for (int i = 0; i < mNumFaces; ++i, ++face)
{
// Attempt to quickly cull the triangle.
if (FastNoIntersect(ray, face->plane))
{
continue;
}
// The triangulation must exist to use it.
size_t numTriangles = face->triangles.size() / 3;
LogAssert(numTriangles > 0, "Triangulation must exist.");
// Process the triangles in a triangulation of the face.
int const* currIndex = &face->triangles[0];
for (size_t t = 0; t < numTriangles; ++t)
{
// Get the triangle vertices.
for (int k = 0; k < 3; ++k)
{
triangle.v[k] = mPoints[*currIndex++];
}
// Test for intersection.
if (rtQuery(ray, triangle).intersect)
{
// The ray intersects the triangle.
odd = !odd;
}
}
}
if (odd)
{
insideCount++;
}
}
return insideCount > mNumRays / 2;
}
bool ContainsS1(Vector3<Real> const& p) const
{
int insideCount = 0;
FIQuery<Real, Ray3<Real>, Plane3<Real>> rpQuery;
Ray3<Real> ray;
ray.origin = p;
for (int j = 0; j < mNumRays; ++j)
{
ray.direction = mDirections[j];
// Zero intersections to start with.
bool odd = false;
SimpleFace const* face = mSFaces;
for (int i = 0; i < mNumFaces; ++i, ++face)
{
// Attempt to quickly cull the triangle.
if (FastNoIntersect(ray, face->plane))
{
continue;
}
// Compute the ray-plane intersection.
auto result = rpQuery(ray, face->plane);
// If you trigger this assertion, numerical round-off
// errors have led to a discrepancy between
// FastNoIntersect and the Find() result.
LogAssert(result.intersect, "Unexpected condition.");
// Get a coordinate system for the plane. Use vertex 0
// as the origin.
Vector3<Real> const& V0 = mPoints[face->indices[0]];
Vector3<Real> basis[3];
basis[0] = face->plane.normal;
ComputeOrthogonalComplement(1, basis);
// Project the intersection onto the plane.
Vector3<Real> diff = result.point - V0;
Vector2<Real> projIntersect{ Dot(basis[1], diff), Dot(basis[2], diff) };
// Project the face vertices onto the plane of the face.
if (face->indices.size() > mProjVertices.size())
{
mProjVertices.resize(face->indices.size());
}
// Project the remaining vertices. Vertex 0 is always the
// origin.
size_t numIndices = face->indices.size();
mProjVertices[0] = Vector2<Real>::Zero();
for (size_t k = 1; k < numIndices; ++k)
{
diff = mPoints[face->indices[k]] - V0;
mProjVertices[k][0] = Dot(basis[1], diff);
mProjVertices[k][1] = Dot(basis[2], diff);
}
// Test whether the intersection point is in the convex
// polygon.
PointInPolygon2<Real> PIP(static_cast<int>(mProjVertices.size()),
&mProjVertices[0]);
if (PIP.Contains(projIntersect))
{
// The ray intersects the triangle.
odd = !odd;
}
}
if (odd)
{
insideCount++;
}
}
return insideCount > mNumRays / 2;
}
int mNumPoints;
Vector3<Real> const* mPoints;
int mNumFaces;
TriangleFace const* mTFaces;
ConvexFace const* mCFaces;
SimpleFace const* mSFaces;
unsigned int mMethod;
int mNumRays;
Vector3<Real> const* mDirections;
// Temporary storage for those methods that reduce the problem to 2D
// point-in-polygon queries. The array stores the projections of
// face vertices onto the plane of the face. It is resized as needed.
mutable std::vector<Vector2<Real>> mProjVertices;
};
}