904 lines
35 KiB
C++
904 lines
35 KiB
C++
/**
|
||
* Copyright (c) 2021-2022 Floyd M. Chitalu.
|
||
* All rights reserved.
|
||
*
|
||
* NOTE: This file is licensed under GPL-3.0-or-later (default).
|
||
* A commercial license can be purchased from Floyd M. Chitalu.
|
||
*
|
||
* License details:
|
||
*
|
||
* (A) GNU General Public License ("GPL"); a copy of which you should have
|
||
* recieved with this file.
|
||
* - see also: <http://www.gnu.org/licenses/>
|
||
* (B) Commercial license.
|
||
* - email: floyd.m.chitalu@gmail.com
|
||
*
|
||
* The commercial license options is for users that wish to use MCUT in
|
||
* their products for comercial purposes but do not wish to release their
|
||
* software products under the GPL license.
|
||
*
|
||
* Author(s) : Floyd M. Chitalu
|
||
*/
|
||
|
||
#include "mcut/internal/math.h"
|
||
#include <algorithm> // std::sort
|
||
#include <cstdlib>
|
||
#include <tuple> // std::make_tuple std::get<>
|
||
|
||
|
||
double square_root(const double& number)
|
||
{
|
||
#if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS)
|
||
return std::sqrt(number);
|
||
#else
|
||
arbitrary_precision_number_t out(number);
|
||
mpfr_sqrt(out.get_mpfr_handle(), number.get_mpfr_handle(), arbitrary_precision_number_t::get_default_rounding_mode());
|
||
return out;
|
||
#endif // #if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS)
|
||
}
|
||
|
||
double absolute_value(const double& number)
|
||
{
|
||
#if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS)
|
||
return std::fabs(number);
|
||
#else
|
||
double out(number);
|
||
mpfr_abs(out.get_mpfr_handle(), number.get_mpfr_handle(), arbitrary_precision_number_t::get_default_rounding_mode());
|
||
return out;
|
||
#endif // #if defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS)
|
||
}
|
||
|
||
sign_t sign(const double& number)
|
||
{
|
||
#if !defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS)
|
||
int s = (double(0) < number) - (number < double(0));
|
||
sign_t result = sign_t::ZERO;
|
||
if (s > 0) {
|
||
result = sign_t::POSITIVE;
|
||
} else if (s < 0) {
|
||
result = sign_t::NEGATIVE;
|
||
}
|
||
return result;
|
||
#else
|
||
double out(number);
|
||
int s = mpfr_sgn(number.get_mpfr_handle());
|
||
sign_t result = sign_t::ZERO;
|
||
if (s > 0) {
|
||
result = sign_t::POSITIVE;
|
||
} else if (s < 0) {
|
||
result = sign_t::NEGATIVE;
|
||
}
|
||
return result;
|
||
#endif // #if defined(MCUT_WITH_ARBITRARY_PRECISION_NUMBERS)
|
||
}
|
||
|
||
std::ostream& operator<<(std::ostream& os, const vec3& v)
|
||
{
|
||
return os << static_cast<double>(v.x()) << ", " << static_cast<double>(v.y()) << ", " << static_cast<double>(v.z());
|
||
}
|
||
|
||
bool operator==(const vec3& a, const vec3& b)
|
||
{
|
||
return (a.x() == b.x()) && (a.y() == b.y()) && (a.z() == b.z());
|
||
}
|
||
|
||
vec3 cross_product(const vec3& a, const vec3& b)
|
||
{
|
||
return vec3(
|
||
a.y() * b.z() - a.z() * b.y(),
|
||
a.z() * b.x() - a.x() * b.z(),
|
||
a.x() * b.y() - a.y() * b.x());
|
||
}
|
||
|
||
double orient2d(const vec2& pa, const vec2& pb, const vec2& pc)
|
||
{
|
||
const double pa_[2] = { static_cast<double>(pa.x()), static_cast<double>(pa.y()) };
|
||
const double pb_[2] = { static_cast<double>(pb.x()), static_cast<double>(pb.y()) };
|
||
const double pc_[2] = { static_cast<double>(pc.x()), static_cast<double>(pc.y()) };
|
||
|
||
return ::orient2d(pa_, pb_, pc_); // shewchuk predicate
|
||
}
|
||
|
||
double orient3d(const vec3& pa, const vec3& pb, const vec3& pc,
|
||
const vec3& pd)
|
||
{
|
||
const double pa_[3] = { static_cast<double>(pa.x()), static_cast<double>(pa.y()), static_cast<double>(pa.z()) };
|
||
const double pb_[3] = { static_cast<double>(pb.x()), static_cast<double>(pb.y()), static_cast<double>(pb.z()) };
|
||
const double pc_[3] = { static_cast<double>(pc.x()), static_cast<double>(pc.y()), static_cast<double>(pc.z()) };
|
||
const double pd_[3] = { static_cast<double>(pd.x()), static_cast<double>(pd.y()), static_cast<double>(pd.z()) };
|
||
|
||
return ::orient3d(pa_, pb_, pc_, pd_); // shewchuk predicate
|
||
}
|
||
|
||
#if 0
|
||
void polygon_normal(vec3& normal, const vec3* vertices, const int num_vertices)
|
||
{
|
||
normal = vec3(0.0);
|
||
for (int i = 0; i < num_vertices; ++i) {
|
||
normal = normal + cross_product(vertices[i] - vertices[0], vertices[(i + 1) % num_vertices] - vertices[0]);
|
||
}
|
||
normal = normalize(normal);
|
||
}
|
||
#endif
|
||
|
||
int compute_polygon_plane_coefficients(vec3& normal, double& d_coeff,
|
||
const vec3* polygon_vertices, const int polygon_vertex_count)
|
||
{
|
||
// compute polygon normal (http://cs.haifa.ac.il/~gordon/plane.pdf)
|
||
normal = vec3(0.0);
|
||
for (int i = 0; i < polygon_vertex_count - 1; ++i) {
|
||
normal = normal + cross_product(polygon_vertices[i] - polygon_vertices[0], polygon_vertices[(i + 1) % polygon_vertex_count] - polygon_vertices[0]);
|
||
}
|
||
|
||
// In our calculations we need the normal be be of unit length so that the d-coeff
|
||
// represents the distance of the plane from the origin
|
||
// normal = normalize(normal);
|
||
|
||
d_coeff = dot_product(polygon_vertices[0], normal);
|
||
|
||
double largest_component(0.0);
|
||
double tmp(0.0);
|
||
int largest_component_idx = 0;
|
||
|
||
for (int i = 0; i < 3; ++i) {
|
||
tmp = absolute_value(normal[i]);
|
||
if (tmp > largest_component) {
|
||
largest_component = tmp;
|
||
largest_component_idx = i;
|
||
}
|
||
}
|
||
|
||
return largest_component_idx;
|
||
}
|
||
|
||
// Intersect a line segment with a plane
|
||
//
|
||
// Return values:
|
||
// 'p': The segment lies wholly within the plane.
|
||
// 'q': The(first) q endpoint is on the plane (but not 'p').
|
||
// 'r' : The(second) r endpoint is on the plane (but not 'p').
|
||
// '0' : The segment lies strictly to one side or the other of the plane.
|
||
// '1': The segment intersects the plane, and none of {p, q, r} hold.
|
||
char compute_segment_plane_intersection(vec3& p, const vec3& normal, const double& d_coeff,
|
||
const vec3& q, const vec3& r)
|
||
{
|
||
|
||
// vec3 planeNormal;
|
||
// double planeDCoeff;
|
||
// planeNormalLargestComponent = compute_polygon_plane_coefficients(planeNormal, planeDCoeff, polygonVertices,
|
||
// polygonVertexCount);
|
||
|
||
double num = d_coeff - dot_product(q, normal);
|
||
const vec3 rq = (r - q);
|
||
double denom = dot_product(rq, normal);
|
||
|
||
if (denom == double(0.0) /* Segment is parallel to plane.*/) {
|
||
if (num == double(0.0)) { // 'q' is on plane.
|
||
return 'p'; // The segment lies wholly within the plane
|
||
} else {
|
||
return '0';
|
||
}
|
||
}
|
||
|
||
double t = num / denom;
|
||
|
||
for (int i = 0; i < 3; ++i) {
|
||
p[i] = q[i] + t * (r[i] - q[i]);
|
||
}
|
||
|
||
if ((double(0.0) < t) && (t < double(1.0))) {
|
||
return '1'; // The segment intersects the plane, and none of {p, q, r} hold
|
||
} else if (num == double(0.0)) // t==0
|
||
{
|
||
return 'q'; // The (first) q endpoint is on the plane (but not 'p').
|
||
} else if (num == denom) // t==1
|
||
{
|
||
return 'r'; // The (second) r endpoint is on the plane (but not 'p').
|
||
} else {
|
||
return '0'; // The segment lies strictly to one side or the other of the plane
|
||
}
|
||
}
|
||
|
||
bool determine_three_noncollinear_vertices(int& i, int& j, int& k, const std::vector<vec3>& polygon_vertices,
|
||
|
||
const vec3& polygon_normal, const int polygon_normal_largest_component)
|
||
{
|
||
const int polygon_vertex_count = (int)polygon_vertices.size();
|
||
MCUT_ASSERT(polygon_vertex_count >= 3);
|
||
|
||
std::vector<vec2> x;
|
||
project_to_2d(x, polygon_vertices, polygon_normal, polygon_normal_largest_component);
|
||
MCUT_ASSERT(x.size() == (size_t)polygon_vertex_count);
|
||
|
||
/*
|
||
NOTE: We cannot just use _any_/the first result of "colinear(x[i], x[j], x[k])" which returns true since
|
||
any three points that are _nearly_ colinear (in the limit of floating point precision)
|
||
will be found to be not colinear when using the exact predicate "orient2d".
|
||
This would have implications "further down the line", where e.g. the three nearly colinear points
|
||
are determined to be non-colinear (in the exact sense) but using them to evaluate
|
||
operations like segment-plane intersection would then give a false positive.
|
||
To overcome this, must find the three vertices of the polygon, which maximise non-colinearity.
|
||
|
||
NOTE: if the polygon has 3 vertices and they are indeed nearly colinear then answer is determined by the
|
||
exact predicate (i.e. i j k = 0 1 2)
|
||
*/
|
||
|
||
// get any three vertices that are not collinear
|
||
i = 0;
|
||
j = i + 1;
|
||
k = j + 1;
|
||
std::vector<std::tuple<int, int, int, double>> non_colinear_triplets;
|
||
|
||
for (; i < polygon_vertex_count; ++i) {
|
||
for (; j < polygon_vertex_count; ++j) {
|
||
for (; k < polygon_vertex_count; ++k) {
|
||
double predRes;
|
||
if (!collinear(x[i], x[j], x[k], predRes)) {
|
||
non_colinear_triplets.emplace_back(std::make_tuple(i, j, k, predRes));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
std::sort(non_colinear_triplets.begin(), non_colinear_triplets.end(),
|
||
[](const std::tuple<int, int, int, double>& a, const std::tuple<int, int, int, double>& b) {
|
||
return std::fabs(std::get<3>(a)) > std::fabs(std::get<3>(b));
|
||
});
|
||
|
||
std::tuple<int, int, int, double> best_triplet = non_colinear_triplets.front(); // maximising non-colinearity
|
||
|
||
i = std::get<0>(best_triplet);
|
||
j = std::get<1>(best_triplet);
|
||
k = std::get<2>(best_triplet);
|
||
|
||
return !non_colinear_triplets.empty(); // need at least one non-colinear triplet
|
||
}
|
||
|
||
char compute_segment_plane_intersection_type(const vec3& q, const vec3& r,
|
||
const std::vector<vec3>& polygon_vertices,
|
||
const vec3& polygon_normal,
|
||
const int polygon_normal_largest_component)
|
||
{
|
||
// TODO: we could also return i,j and k so that "determine_three_noncollinear_vertices" is not called multiple times,
|
||
// which we do to determine the type of intersection and the actual intersection point
|
||
const int polygon_vertex_count = (int)polygon_vertices.size();
|
||
// ... any three vertices that are not collinear
|
||
int i = 0;
|
||
int j = 1;
|
||
int k = 2;
|
||
if (polygon_vertex_count > 3) { // case where we'd have the possibility of noncollinearity
|
||
bool b = determine_three_noncollinear_vertices(i, j, k, polygon_vertices, polygon_normal,
|
||
polygon_normal_largest_component);
|
||
|
||
if (!b) {
|
||
return '0'; // all polygon points are collinear
|
||
}
|
||
}
|
||
|
||
double qRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], q);
|
||
double rRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], r);
|
||
|
||
if (qRes == double(0.0) && rRes == double(0.0)) {
|
||
return 'p';
|
||
} else if (qRes == double(0.0)) {
|
||
return 'q';
|
||
} else if (rRes == double(0.0)) {
|
||
return 'r';
|
||
} else if ((rRes < double(0.0) && qRes < double(0.0)) || (rRes > double(0.0) && qRes > double(0.0))) {
|
||
return '0';
|
||
} else {
|
||
return '1';
|
||
}
|
||
}
|
||
|
||
char compute_segment_line_plane_intersection_type(const vec3& q, const vec3& r,
|
||
const std::vector<vec3>& polygon_vertices,
|
||
|
||
const int polygon_normal_max_comp,
|
||
const vec3& polygon_plane_normal)
|
||
{
|
||
const int polygon_vertex_count = (int)polygon_vertices.size();
|
||
// ... any three vertices that are not collinear
|
||
int i = 0;
|
||
int j = 1;
|
||
int k = 2;
|
||
if (polygon_vertex_count > 3) { // case where we'd have the possibility of noncollinearity
|
||
bool b = determine_three_noncollinear_vertices(i, j, k, polygon_vertices, polygon_plane_normal,
|
||
polygon_normal_max_comp);
|
||
|
||
if (!b) {
|
||
return '0'; // all polygon points are collinear
|
||
}
|
||
}
|
||
|
||
double qRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], q);
|
||
double rRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], r);
|
||
|
||
if (qRes == double(0.0) && rRes == double(0.0)) {
|
||
// both points used to define line lie on plane therefore we have an in-plane intersection
|
||
// or the polygon is a degenerate triangle
|
||
return 'p';
|
||
} else {
|
||
if ((rRes < double(0.0) && qRes < double(0.0)) || (rRes > double(0.0) && qRes > double(0.0))) { // both points used to define line lie on same side of plane
|
||
// check if line is parallel to plane
|
||
// const double num = polygon_plane_d_coeff - dot_product(q, polygon_plane_normal);
|
||
const vec3 rq = (r - q);
|
||
const double denom = dot_product(rq, polygon_plane_normal);
|
||
|
||
if (denom == double(0.0) /* Segment is parallel to plane.*/) {
|
||
// MCUT_ASSERT(num != 0.0); // implies 'q' is on plane (see: "compute_segment_plane_intersection(...)")
|
||
// but we have already established that q and r are on same side.
|
||
return '0';
|
||
}
|
||
}
|
||
}
|
||
|
||
// q and r are on difference sides of the plane, therefore we have an intersection
|
||
return '1';
|
||
}
|
||
|
||
// Compute the intersection point between a line (not a segment) and a plane defined by a polygon.
|
||
//
|
||
// Parameters:
|
||
// 'p' : output intersection point (computed if line does indeed intersect the plane)
|
||
// 'q' : first point defining your line
|
||
// 'r' : second point defining your line
|
||
// 'polygon_vertices' : the vertices of the polygon defineing the plane (assumed to not be degenerate)
|
||
// 'polygon_vertex_count' : number of olygon vertices
|
||
// 'polygon_normal_max_comp' : largest component of polygon normal.
|
||
// 'polygon_plane_normal' : normal of the given polygon
|
||
// 'polygon_plane_d_coeff' : the distance coefficient of the plane equation corresponding to the polygon's plane
|
||
//
|
||
// Return values:
|
||
// '0': line is parallel to plane (or polygon is degenerate ... within available precision)
|
||
// '1': an intersection exists.
|
||
// 'p': q and r lie in the plane (technically they are parallel to the plane too but we need to report this because it
|
||
// violates GP).
|
||
char compute_line_plane_intersection(vec3& p, // intersection point
|
||
const vec3& q, const vec3& r,
|
||
const std::vector<vec3>& polygon_vertices, const int polygon_normal_max_comp,
|
||
const vec3& polygon_plane_normal,
|
||
const double& polygon_plane_d_coeff)
|
||
{
|
||
|
||
const int polygon_vertex_count = (int)polygon_vertices.size();
|
||
// ... any three vertices that are not collinear
|
||
int i = 0;
|
||
int j = 1;
|
||
int k = 2;
|
||
if (polygon_vertex_count > 3) { // case where we'd have the possibility of noncollinearity
|
||
bool b = determine_three_noncollinear_vertices(i, j, k, polygon_vertices, polygon_plane_normal,
|
||
polygon_normal_max_comp);
|
||
|
||
if (!b) {
|
||
return '0'; // all polygon points are collinear
|
||
}
|
||
}
|
||
|
||
double qRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], q);
|
||
double rRes = orient3d(polygon_vertices[i], polygon_vertices[j], polygon_vertices[k], r);
|
||
|
||
if (qRes == double(0.) && rRes == double(0.)) {
|
||
return 'p'; // both points used to define line lie on plane therefore we have an in-plane intersection
|
||
} else {
|
||
|
||
const double num = polygon_plane_d_coeff - dot_product(q, polygon_plane_normal);
|
||
const vec3 rq = (r - q);
|
||
const double denom = dot_product(rq, polygon_plane_normal);
|
||
|
||
if ((rRes < double(0.) && qRes < double(0.)) || (rRes > double(0.) && qRes > double(0.))) { // both q an r are on same side of plane
|
||
if (denom == double(0.) /* line is parallel to plane.*/) {
|
||
return '0';
|
||
}
|
||
}
|
||
|
||
// q and r are on difference sides of the plane, therefore we have an intersection
|
||
|
||
// compute the intersection point
|
||
const double t = num / denom;
|
||
|
||
for (int it = 0; it < 3; ++it) {
|
||
p[it] = q[it] + t * (r[it] - q[it]);
|
||
}
|
||
|
||
return '1';
|
||
}
|
||
}
|
||
|
||
// Count the number ray crossings to determine if a point 'q' lies inside or outside a given polygon.
|
||
//
|
||
// Return values:
|
||
// 'i': q is strictly interior
|
||
// 'o': q is strictly exterior (outside).
|
||
// 'e': q is on an edge, but not an endpoint.
|
||
// 'v': q is a vertex.
|
||
char compute_point_in_polygon_test(const vec2& q, const std::vector<vec2>& polygon_vertices)
|
||
{
|
||
const int polygon_vertex_count = (int)polygon_vertices.size();
|
||
|
||
#if 0 // Algorithm 1 in :
|
||
// https://pdf.sciencedirectassets.com/271512/1-s2.0-S0925772100X00545/1-s2.0-S0925772101000128/main.pdf?X-Amz-Security-Token=IQoJb3JpZ2luX2VjEAEaCXVzLWVhc3QtMSJHMEUCIBr6Fu%2F%2FtscErn%2Fl4pn2UGNA45sAw9vggQscK7Tnl0ssAiEAzfnyy4B4%2BXkZp8xcEk7utDBrxgmGyH7pyqk0efKOUEoq%2BgMIKhAEGgwwNTkwMDM1NDY4NjUiDHQT4kOfk4%2B6kBB0xCrXAzd8SWlnELJbM5l93HSvv4nUgtO85%2FGyx5%2BOoHmYoUuVwJjCXChmLJmlz%2BxYUQE%2Fr8vQa2hPlUEPfiTVGgoHaK8NkSMP6LRs%2F3WjyZ9OxzHbqSwZixNceW34OAkq0E1QgYLdGVjpPKxNK1haXDfBTMN6bF2IOU9dKi9wTv3uTlep0kHDa1BNNl6M6yZk5QlF2bPF9XmNjjZCpFQLhr%2BPoHo%2Bx4xy39aH8hCkkTqGdy2KrKGN6lv0%2FduIaItyZfqalYS%2BwW6cll2F5G11g0tSu7yKu6mug94LUTzsRmzD0UhzeGl2WV6Ev2qhw26mwFEKgnTMqGst8XAHjFjjAQyMzXNHCQqNBRIevHIzVWuUY4QZMylSRsodo0dfwwCFhzl0%2BJA1ZXb0%2BoyB8c11meQBO8FpMSshngNcbiAYUtIOFcHNCTCUMk0JPOZ%2FxvANsopnivbrPARL71zU4PaTujg5jfC2zoO6ZDUL8E6Vn%2BNtfb%2BuQV7DwtIH51Bv%2F%2F1m6h5mjbpRiGYcH%2F5SDD%2Fk%2BpHfKRQzKu7jJc%2B0XO0bQvoLSrAte0Qk10PwOvDl5jMIMdmxTBDDiDGErRykYxMQhq5EwjyiWPXzM3ll9uK59Vy0bAEj5Qemj5by1jCDht6IBjqlAV4okAPQO5wWdWojCYeKvluKfXCvudrUxrLhsxb7%2BTZNMODTG%2Fn%2Fbw875Yr6fOQ42u40pOsnPB%2FTP3cWwjiB%2BEHzDqN8AhCVQyoedw7QrU3OBWlSl6lB%2BGLAVqrhcizgFUiM2nj3HaVP2m7S%2FXqpv%2FoWlEXt4gR8iI9XsIlh6L6SBE22FqbsU5ewCxXaqip19VXhAGnlvjTihXUg6yZGWhExHj%2BKcA%3D%3D&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20210814T103958Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=ASIAQ3PHCVTY3PPQSW2L%2F20210814%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=94403dce5c12ede9507e9612c000db5772607e59a2134d30d4e5b44212056dc7&hash=cada083b165f9786e6042e21d3d22147ec08b1e2c8fa2572ceeb2445cb5e730a&host=68042c943591013ac2b2430a89b270f6af2c76d8dfd086a07176afe7c76c2c61&pii=S0925772101000128&tid=spdf-f9e7c4b7-808d-4a8e-a474-6430ff687798&sid=8dddff5b32fff2499908bf6501965aec3d0fgxrqb&type=client
|
||
|
||
const double zero(0.0);
|
||
int i = 0;
|
||
int k = 0;
|
||
|
||
double f = zero;
|
||
double u1 = 0.0;
|
||
double v1 = 0.0;
|
||
double u2 = 0.0;
|
||
double v2 = 0.0;
|
||
const int n = polygon_vertex_count;
|
||
for (i = 0; i < n; ++i)
|
||
{
|
||
v1 = polygon_vertices[i].y() - q.y();
|
||
v2 = polygon_vertices[(i + 1) % n].y() - q.y();
|
||
|
||
if ((v1 < zero && v2 < zero) || (v1 > zero && v2 > zero))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
u1 = polygon_vertices[i].x() - q.x();
|
||
u2 = polygon_vertices[(i + 1) % n].x() - q.x();
|
||
|
||
if (v2 > zero && v1 <= zero)
|
||
{
|
||
f = u1 * v2 - u2 * v1;
|
||
if (f > zero)
|
||
{
|
||
k = k + 1;
|
||
}
|
||
else if (f == zero)
|
||
{
|
||
return 'e'; //-1; // boundary
|
||
}
|
||
}
|
||
else if (v1 > zero && v2 <= zero)
|
||
{
|
||
f = u1 * v2 - u2 * v1;
|
||
if (f < zero)
|
||
{
|
||
k = k + 1;
|
||
}
|
||
else if (f == zero)
|
||
{
|
||
return 'e'; //-1; // boundary
|
||
}
|
||
}
|
||
else if (v2 == zero && v1 < zero)
|
||
{
|
||
f = u1 * v2 - u2 * v1;
|
||
if (f == zero)
|
||
{
|
||
return 'e'; //-1; // // boundary
|
||
}
|
||
}
|
||
else if (v1 == zero && v2 < zero)
|
||
{
|
||
f = u1 * v2 - u2 * v1;
|
||
if (f == zero)
|
||
{
|
||
return 'e'; //-1; // boundary
|
||
}
|
||
}
|
||
else if (v1 == zero && v2 == zero)
|
||
{
|
||
if (u2 <= zero && u1 >= zero)
|
||
{
|
||
return 'e'; //-1; // boundary
|
||
}
|
||
else if (u1 <= zero && u2 >= zero)
|
||
{
|
||
return 'e'; //-1; // boundary
|
||
}
|
||
}
|
||
}
|
||
if (k % 2 == 0)
|
||
{
|
||
return 'o'; //0; // outside
|
||
}
|
||
else
|
||
{
|
||
return 'i'; //1; // inside
|
||
}
|
||
#else // http://www.science.smith.edu/~jorourke/books/compgeom.html
|
||
|
||
std::vector<vec2> vertices(polygon_vertex_count, vec2());
|
||
// Shift so that q is the origin. Note this destroys the polygon.
|
||
// This is done for pedagogical clarity.
|
||
for (int i = 0; i < polygon_vertex_count; i++) {
|
||
for (int d = 0; d < 2; d++) {
|
||
const double& a = polygon_vertices[i][d];
|
||
const double& b = q[d];
|
||
double& c = vertices[i][d];
|
||
c = a - b;
|
||
}
|
||
}
|
||
|
||
int Rcross = 0; /* number of right edge/ray crossings */
|
||
int Lcross = 0; /* number ofleft edge/ray crossings */
|
||
|
||
/* For each edge e = (i<>lj), see if crosses ray. */
|
||
for (int i = 0; i < polygon_vertex_count; i++) {
|
||
|
||
/* First check if q = (0, 0) is a vertex. */
|
||
if (vertices[i].x() == double(0.) && vertices[i].y() == double(0.)) {
|
||
return 'v';
|
||
}
|
||
|
||
int il = (i + polygon_vertex_count - 1) % polygon_vertex_count;
|
||
|
||
// Check if e straddles x axis, with bias above/below.
|
||
|
||
// Rstrad is TRUE iff one endpoint of e is strictly above the x axis and the other is not (i.e., the other is on
|
||
// or below)
|
||
bool Rstrad = (vertices[i].y() > double(0.)) != (vertices[il].y() > double(0.));
|
||
bool Lstrad = (vertices[i].y() < double(0.)) != (vertices[il].y() < double(0.));
|
||
|
||
if (Rstrad || Lstrad) {
|
||
/* Compute intersection of e with x axis. */
|
||
|
||
// The computation of x is needed whenever either of these straddle variables is TRUE, which
|
||
// only excludes edges passing through q = (0, 0) (and incidentally protects against division by 0).
|
||
double x = (vertices[i].x() * vertices[il].y() - vertices[il].x() * vertices[i].y()) / (vertices[il].y() - vertices[i].y());
|
||
if (Rstrad && x > double(0.)) {
|
||
Rcross++;
|
||
}
|
||
if (Lstrad && x < double(0.)) {
|
||
Lcross++;
|
||
}
|
||
} /* end straddle computation*/
|
||
} // end for
|
||
|
||
/* q on an edge if L/Rcross counts are not the same parity.*/
|
||
if ((Rcross % 2) != (Lcross % 2)) {
|
||
return 'e';
|
||
}
|
||
|
||
/* q inside iff an odd number of crossings. */
|
||
if ((Rcross % 2) == 1) {
|
||
return 'i';
|
||
} else {
|
||
return 'o';
|
||
}
|
||
#endif
|
||
}
|
||
|
||
// given a normal vector (not necessarily normalized) and its largest component, calculate
|
||
// a matrix P that will project any 3D vertex to 2D by removing the 3D component that
|
||
// corresponds to the largest component of the normal..
|
||
// https://math.stackexchange.com/questions/180418/calculate-rotation-matrix-to-align-vector-a-to-vector-b-in-3d/2672702#2672702
|
||
matrix_t<double> calculate_projection_matrix(const vec3& polygon_normal,
|
||
const int polygon_normal_largest_component)
|
||
{
|
||
MCUT_ASSERT(squared_length(polygon_normal) > double(0.0));
|
||
MCUT_ASSERT(polygon_normal_largest_component >= 0);
|
||
MCUT_ASSERT(polygon_normal_largest_component <= 2);
|
||
|
||
// unit length normal vector of polygon
|
||
const vec3 a = normalize(polygon_normal);
|
||
// unit length basis vector corresponding to the largest component of the normal vector
|
||
const vec3 b = [&]() {
|
||
vec3 x(double(0.0));
|
||
const sign_t s = sign(polygon_normal[polygon_normal_largest_component]);
|
||
MCUT_ASSERT(s != sign_t::ZERO); // implies that the normal vector has a magnitude of zero
|
||
// The largest component of the normal is the one we will "remove"
|
||
// NOTE: we multiple by the sign here to ensure that "a_plus_b" below is not zero when a == b
|
||
x[polygon_normal_largest_component] = double((1.0) * static_cast<long double>(s));
|
||
return x;
|
||
}();
|
||
|
||
matrix_t<double> I(3, 3); // 3x3 identity with reflection
|
||
I(0, 0) = 1.0;
|
||
I(1, 1) = -1.0;
|
||
I(2, 2) = 1.0;
|
||
|
||
matrix_t<double> R = I;
|
||
|
||
// NOTE: While this will map vectors a to b, it can add a lot of unnecessary "twist".
|
||
// For example, if a=b=e_{z} this formula will produce a 180-degree rotation about the z-axis rather
|
||
// than the identity one might expect.
|
||
if ((a[0] != b[0]) || (a[1] != b[1]) || (a[2] != b[2])) // a != b
|
||
{
|
||
const vec3 a_plus_b = a + b;
|
||
const double a_dot_b = dot_product(a, b);
|
||
|
||
// this will never be zero because we set 'b' as the canonical basis vector
|
||
// that has the largest projection onto the normal
|
||
MCUT_ASSERT(a_dot_b != double(0.0));
|
||
|
||
const matrix_t<double> outer = outer_product(a_plus_b, a_plus_b);
|
||
|
||
// compute the 3x3 rotation matrix R to orient the polygon into the canonical axes "i" and "j",
|
||
// where "i" and "j" != "polygon_normal_largest_component"
|
||
R = ((outer / a_dot_b) * 2.0) - I; // rotation
|
||
}
|
||
|
||
// compute the 2x3 selector matrix K to select the polygon-vertex components that correspond
|
||
// to canonical axes "i" and "j".
|
||
matrix_t<double> K = matrix_t<double>(2, 3);
|
||
|
||
if (polygon_normal_largest_component == 0) // project by removing x-component
|
||
{
|
||
// 1st row
|
||
K(0, 0) = 0.0; // col 0
|
||
K(0, 1) = 1.0; // col 1
|
||
K(0, 2) = 0.0; // col 2
|
||
// 2nd row
|
||
K(1, 0) = 0.0; // col 0
|
||
K(1, 1) = 0.0; // col 1
|
||
K(1, 2) = 1.0; // col 2
|
||
} else if (polygon_normal_largest_component == 1) // project by removing y-component
|
||
{
|
||
// 1st row
|
||
K(0, 0) = 1.0; // col 0
|
||
K(0, 1) = 0.0; // col 1
|
||
K(0, 2) = 0.0; // col 2
|
||
// 2nd row
|
||
K(1, 0) = 0.0; // col 0
|
||
K(1, 1) = 0.0; // col 1
|
||
K(1, 2) = 1.0; // col 2
|
||
} else if (polygon_normal_largest_component == 2) // project by removing z-component
|
||
{
|
||
// 1st row
|
||
K(0, 0) = 1.0; // col 0
|
||
K(0, 1) = 0.0; // col 1
|
||
K(0, 2) = 0.0; // col 2
|
||
// 2nd row
|
||
K(1, 0) = 0.0; // col 0
|
||
K(1, 1) = 1.0; // col 1
|
||
K(1, 2) = 0.0; // col 2
|
||
}
|
||
|
||
return K * R;
|
||
}
|
||
|
||
// https://answers.unity.com/questions/1522620/converting-a-3d-polygon-into-a-2d-polygon.html
|
||
void project_to_2d(std::vector<vec2>& out, const std::vector<vec3>& polygon_vertices, const vec3& polygon_normal)
|
||
{
|
||
const uint32_t N = polygon_vertices.size();
|
||
out.resize(N);
|
||
|
||
const vec3 normal = normalize(polygon_normal);
|
||
|
||
// first unit vector on plane (rotation by 90 degrees)
|
||
vec3 u(-normal.y(), normal.x(), normal.z());
|
||
vec3 v = normalize(cross_product(u, normal));
|
||
|
||
for(uint32_t i =0; i < N; ++i)
|
||
{
|
||
const vec3 point = polygon_vertices[i];
|
||
const vec2 projected(
|
||
dot_product(point, u),
|
||
dot_product(point, v)
|
||
);
|
||
|
||
out[i] = projected;
|
||
}
|
||
}
|
||
|
||
void project_to_2d(std::vector<vec2>& out, const std::vector<vec3>& polygon_vertices,
|
||
const vec3& polygon_normal, const int polygon_normal_largest_component)
|
||
{
|
||
const int polygon_vertex_count = (int)polygon_vertices.size();
|
||
out.clear();
|
||
out.resize(polygon_vertex_count);
|
||
|
||
#if 1
|
||
// 3x3 matrix for projecting a point to 2D
|
||
matrix_t<double> P = calculate_projection_matrix(polygon_normal, polygon_normal_largest_component);
|
||
for (int i = 0; i < polygon_vertex_count; ++i) { // for each vertex
|
||
const vec3& x = polygon_vertices[i];
|
||
out[i] = P * x; // vertex in xz plane
|
||
}
|
||
#else // This code is not reliable because it shadow-projects a polygons which can skew computations
|
||
for (int i = 0; i < polygon_vertex_count; ++i) { // for each vertex
|
||
vec2& Tp = out[i];
|
||
int k = 0;
|
||
for (int j = 0; j < 3; j++) { // for each component
|
||
if (j != polygon_plane_normal_largest_component) { /* skip largest coordinate */
|
||
|
||
Tp[k] = polygon_vertices[i][j];
|
||
k++;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
// TODO: update this function to use "project_to_2d" for projection step
|
||
char compute_point_in_polygon_test(const vec3& p, const std::vector<vec3>& polygon_vertices,
|
||
const vec3& polygon_normal, const int polygon_normal_largest_component)
|
||
{
|
||
const int polygon_vertex_count = (int)polygon_vertices.size();
|
||
/* Project out coordinate m in both p and the triangular face */
|
||
vec2 pp; /*projected p */
|
||
#if 0
|
||
int k = 0;
|
||
|
||
for (int j = 0; j < 3; j++)
|
||
{ // for each component
|
||
if (j != polygon_plane_normal_largest_component)
|
||
{ /* skip largest coordinate */
|
||
pp[k] = p[j];
|
||
k++;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
const matrix_t<double> P = calculate_projection_matrix(polygon_normal, polygon_normal_largest_component);
|
||
|
||
pp = P * p;
|
||
|
||
std::vector<vec2> polygon_vertices2d(polygon_vertex_count, vec2());
|
||
|
||
for (int i = 0; i < polygon_vertex_count; ++i) { // for each vertex
|
||
const vec3& x = polygon_vertices[i];
|
||
polygon_vertices2d[i] = P * x; // vertex in xz plane
|
||
}
|
||
|
||
#if 0
|
||
for (int i = 0; i < polygon_vertex_count; ++i)
|
||
{ // for each vertex
|
||
vec2 &Tp = polygon_vertices2d[i];
|
||
k = 0;
|
||
for (int j = 0; j < 3; j++)
|
||
{ // for each component
|
||
if (j != polygon_plane_normal_largest_component)
|
||
{ /* skip largest coordinate */
|
||
|
||
Tp[k] = polygon_vertices[i][j];
|
||
k++;
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
|
||
return compute_point_in_polygon_test(pp, polygon_vertices2d);
|
||
}
|
||
|
||
inline bool Between(const vec2& a, const vec2& b, const vec2& c)
|
||
{
|
||
vec2 ba, ca;
|
||
/* If ab not vertical check betweenness on x; else on y. */
|
||
if (a[0] != b[0])
|
||
return ((a[0] <= c[0]) && (c[0] <= b[0])) || //
|
||
((a[0] >= c[0]) && (c[0] >= b[0]));
|
||
else
|
||
return ((a[1] <= c[1]) && (c[1] <= b[1])) || //
|
||
((a[1] >= c[1]) && (c[1] >= b[1]));
|
||
}
|
||
|
||
bool coplaner(const vec3& pa, const vec3& pb, const vec3& pc,
|
||
const vec3& pd)
|
||
{
|
||
const double val = orient3d(pa, pb, pc, pd);
|
||
// typedef std::numeric_limits<double> dbl;
|
||
|
||
// double d = 3.14159265358979;
|
||
// std::cout.precision(dbl::max_digits10);
|
||
// std::cout << "value=" << (double)val << std::endl;
|
||
|
||
// NOTE: thresholds are chosen based on benchmark meshes that are used for testing.
|
||
// It is extremely difficult to get this right because of intermediate conversions
|
||
// between exact and fixed precision representations during cutting.
|
||
// Thus, general advise is for user to ensure that the input polygons are really
|
||
// co-planer. It might be possible for MCUT to help here (see eps used during poly
|
||
// partitioning).
|
||
return absolute_value(val) <= double(4e-7);
|
||
}
|
||
|
||
bool collinear(const vec2& a, const vec2& b, const vec2& c, double& predResult)
|
||
{
|
||
predResult = orient2d(a, b, c);
|
||
return predResult == double(0.);
|
||
}
|
||
|
||
bool collinear(const vec2& a, const vec2& b, const vec2& c)
|
||
{
|
||
return orient2d(a, b, c) == double(0.);
|
||
}
|
||
|
||
char Parallellnt(const vec2& a, const vec2& b, const vec2& c, const vec2& d, vec2& p)
|
||
{
|
||
if (!collinear(a, b, c)) {
|
||
return '0';
|
||
}
|
||
|
||
if (Between(a, b, c)) {
|
||
p = c;
|
||
return 'e';
|
||
}
|
||
|
||
if (Between(a, b, d)) {
|
||
p = d;
|
||
return 'e';
|
||
}
|
||
|
||
if (Between(c, d, a)) {
|
||
p = a;
|
||
return 'e';
|
||
}
|
||
|
||
if (Between(c, d, b)) {
|
||
p = b;
|
||
return 'e';
|
||
}
|
||
|
||
return '0';
|
||
}
|
||
|
||
char compute_segment_intersection(
|
||
const vec2& a, const vec2& b, const vec2& c,
|
||
const vec2& d, vec2& p, double& s, double& t)
|
||
{
|
||
// double s, t; /* The two parameters of the parametric eqns. */
|
||
double num, denom; /* Numerator and denominator of equations. */
|
||
char code = '?'; /* Return char characterizing intersection.*/
|
||
|
||
denom = a[0] * (d[1] - c[1]) + //
|
||
b[0] * (c[1] - d[1]) + //
|
||
d[0] * (b[1] - a[1]) + //
|
||
c[0] * (a[1] - b[1]);
|
||
|
||
/* If denom is zero, then segments are parallel: handle separately. */
|
||
if (denom == double(0.0)) {
|
||
return Parallellnt(a, b, c, d, p);
|
||
}
|
||
|
||
num = a[0] * (d[1] - c[1]) + //
|
||
c[0] * (a[1] - d[1]) + //
|
||
d[0] * (c[1] - a[1]);
|
||
|
||
if ((num == double(0.0)) || (num == denom)) {
|
||
code = 'v';
|
||
}
|
||
|
||
s = num / denom;
|
||
|
||
num = -(a[0] * (c[1] - b[1]) + //
|
||
b[0] * (a[1] - c[1]) + //
|
||
c[0] * (b[1] - a[1]));
|
||
|
||
if ((num == double(0.0)) || (num == denom)) {
|
||
code = 'v';
|
||
}
|
||
|
||
t = num / denom;
|
||
|
||
if ((double(0.0) < s) && (s < double(1.0)) && (double(0.0) < t) && (t < double(1.0))) {
|
||
code = '1';
|
||
} else if ((double(0.0) > s) || (s > double(1.0)) || (double(0.0) > t) || (t > double(1.0))) {
|
||
code = '0';
|
||
}
|
||
|
||
p[0] = a[0] + s * (b[0] - a[0]);
|
||
p[1] = a[1] + s * (b[1] - a[1]);
|
||
|
||
return code;
|
||
}
|
||
|
||
inline bool point_in_bounding_box(const vec2& point, const bounding_box_t<vec2>& bbox)
|
||
{
|
||
if ((point.x() < bbox.m_minimum.x() || point.x() > bbox.m_maximum.x()) || //
|
||
(point.y() < bbox.m_minimum.y() || point.y() > bbox.m_maximum.y())) {
|
||
return false;
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
inline bool point_in_bounding_box(const vec3& point, const bounding_box_t<vec3>& bbox)
|
||
{
|
||
if ((point.x() < bbox.m_minimum.x() || point.x() > bbox.m_maximum.x()) || //
|
||
(point.y() < bbox.m_minimum.y() || point.y() > bbox.m_maximum.y()) || //
|
||
(point.z() < bbox.m_minimum.z() || point.z() > bbox.m_maximum.z())) { //
|
||
return false;
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
|