Browse Source

style: style: format subface_integrator header and source files

feat-integrator
mckay 5 months ago
parent
commit
b263538d73
  1. 48
      surface_integral/interface/mesh_algorithm.hpp
  2. 3
      surface_integral/interface/subface_integrator.hpp
  3. 50
      surface_integral/src/polygon_winding_number.cpp
  4. 31
      surface_integral/src/subface_integrator.cpp
  5. 27
      surface_integral/test/SurfaceIntegrator_test.cpp
  6. 25
      surface_integral/test/test_mesh.cpp
  7. 199
      surface_integral/test/test_winding_number.cpp

48
surface_integral/interface/mesh_algorithm.hpp

@ -2,33 +2,24 @@
#include <cstdio> #include <cstdio>
#include <solve.h> // polymesh_t #include <solve.h> // polymesh_t
// Vector subtraction: a - b // Vector subtraction: a - b
inline vector3d vec3_sub(const vector3d& a, const vector3d& b) { inline vector3d vec3_sub(const vector3d& a, const vector3d& b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
return { a.x - b.x, a.y - b.y, a.z - b.z };
}
// Vector cross product: a × b // Vector cross product: a × b
inline vector3d vec3_cross(const vector3d& a, const vector3d& b) { inline vector3d vec3_cross(const vector3d& a, const vector3d& b)
return { {
a.y * b.z - a.z * b.y, return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
};
} }
// Vector dot product: a · b // Vector dot product: a · b
inline double vec3_dot(const vector3d& a, const vector3d& b) { inline double vec3_dot(const vector3d& a, const vector3d& b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
return a.x * b.x + a.y * b.y + a.z * b.z;
}
// Vector length: ||a|| // Vector length: ||a||
inline double vec3_norm(const vector3d& a) { inline double vec3_norm(const vector3d& a) { return std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z); }
return std::sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
}
// Compute triangle area: 0.5 * || (b-a) × (c-a) || // Compute triangle area: 0.5 * || (b-a) × (c-a) ||
inline double triangle_area(const vector3d& a, const vector3d& b, const vector3d& c) { inline double triangle_area(const vector3d& a, const vector3d& b, const vector3d& c)
{
vector3d ab = vec3_sub(b, a); vector3d ab = vec3_sub(b, a);
vector3d ac = vec3_sub(c, a); vector3d ac = vec3_sub(c, a);
vector3d cross_product = vec3_cross(ab, ac); vector3d cross_product = vec3_cross(ab, ac);
@ -36,13 +27,15 @@ inline double triangle_area(const vector3d& a, const vector3d& b, const vector3d
} }
// Compute triangle signed volume contribution (w.r.t origin): (a · (b × c)) / 6 // Compute triangle signed volume contribution (w.r.t origin): (a · (b × c)) / 6
inline double triangle_signed_volume(const vector3d& a, const vector3d& b, const vector3d& c) { inline double triangle_signed_volume(const vector3d& a, const vector3d& b, const vector3d& c)
{
vector3d cross_product = vec3_cross(b, c); vector3d cross_product = vec3_cross(b, c);
return vec3_dot(a, cross_product) / 6.0; return vec3_dot(a, cross_product) / 6.0;
} }
// Compute polygon face area (triangulate as fan) // Compute polygon face area (triangulate as fan)
double polygon_area(const vector3d* vertices, const uint32_t* face_indices, uint32_t n) { double polygon_area(const vector3d* vertices, const uint32_t* face_indices, uint32_t n)
{
if (n < 3) return 0.0; if (n < 3) return 0.0;
double area = 0.0; double area = 0.0;
const vector3d& v0 = vertices[face_indices[0]]; const vector3d& v0 = vertices[face_indices[0]];
@ -55,7 +48,8 @@ double polygon_area(const vector3d* vertices, const uint32_t* face_indices, uint
} }
// Compute polygon signed volume contribution (sum of triangle contributions) // Compute polygon signed volume contribution (sum of triangle contributions)
double polygon_signed_volume(const vector3d* vertices, const uint32_t* face_indices, uint32_t n) { double polygon_signed_volume(const vector3d* vertices, const uint32_t* face_indices, uint32_t n)
{
if (n < 3) return 0.0; if (n < 3) return 0.0;
double volume = 0.0; double volume = 0.0;
const vector3d& v0 = vertices[face_indices[0]]; const vector3d& v0 = vertices[face_indices[0]];
@ -68,10 +62,9 @@ double polygon_signed_volume(const vector3d* vertices, const uint32_t* face_indi
} }
// 🟩 Main: compute total mesh surface area // 🟩 Main: compute total mesh surface area
double compute_surface_area(const polymesh_t* mesh) { double compute_surface_area(const polymesh_t* mesh)
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) { {
return 0.0; if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) { return 0.0; }
}
double total_area = 0.0; double total_area = 0.0;
const uint32_t* face_ptr = mesh->faces; // current index into faces array const uint32_t* face_ptr = mesh->faces; // current index into faces array
@ -86,10 +79,9 @@ double compute_surface_area(const polymesh_t* mesh) {
} }
// 🟦 Main: compute total mesh volume (requires closed mesh with outward normals) // 🟦 Main: compute total mesh volume (requires closed mesh with outward normals)
double compute_volume(const polymesh_t* mesh) { double compute_volume(const polymesh_t* mesh)
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) { {
return 0.0; if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) { return 0.0; }
}
double total_volume = 0.0; double total_volume = 0.0;
const uint32_t* face_ptr = mesh->faces; const uint32_t* face_ptr = mesh->faces;

3
surface_integral/interface/subface_integrator.hpp

@ -67,8 +67,7 @@ private:
double v_min, double v_min,
double v_max, double v_max,
stl_vector_mp<double>& intersections, stl_vector_mp<double>& intersections,
stl_vector_mp<uint16_t>& intersected_chains stl_vector_mp<uint16_t>& intersected_chains) const;
) const;
bool is_point_inside_domain(const subface& subface, const parametric_plane_t& param_plane, double u, double v) const; bool is_point_inside_domain(const subface& subface, const parametric_plane_t& param_plane, double u, double v) const;

50
surface_integral/src/polygon_winding_number.cpp

@ -16,29 +16,24 @@ const double eps = 1e-8;
* @param B End point of the edge. * @param B End point of the edge.
* @return +1 if upward crossing, -1 if downward, 0 otherwise. * @return +1 if upward crossing, -1 if downward, 0 otherwise.
*/ */
int winding_contribution(const Point2d& P, const Point2d& A, const Point2d& B) { int winding_contribution(const Point2d& P, const Point2d& A, const Point2d& B)
{
// 1. If the edge is horizontal, no contribution // 1. If the edge is horizontal, no contribution
if (std::abs(A.y() - B.y()) < eps) { if (std::abs(A.y() - B.y()) < eps) { return 0; }
return 0;
}
// 2. Determine edge direction: does it cross P.y from below to above or vice versa? // 2. Determine edge direction: does it cross P.y from below to above or vice versa?
bool a_below = (A.y() < P.y() - eps); bool a_below = (A.y() < P.y() - eps);
bool b_below = (B.y() < P.y() - eps); bool b_below = (B.y() < P.y() - eps);
// 3. If both endpoints are on the same side of the ray, no crossing // 3. If both endpoints are on the same side of the ray, no crossing
if (a_below == b_below) { if (a_below == b_below) { return 0; }
return 0;
}
// 4. Compute x-coordinate of intersection with y = P.y // 4. Compute x-coordinate of intersection with y = P.y
double t = (P.y() - A.y()) / (B.y() - A.y()); double t = (P.y() - A.y()) / (B.y() - A.y());
double x_intersect = A.x() + t * (B.x() - A.x()); double x_intersect = A.x() + t * (B.x() - A.x());
// 5. Only consider intersections to the right of P (or directly on P) // 5. Only consider intersections to the right of P (or directly on P)
if (x_intersect < P.x() - eps) { if (x_intersect < P.x() - eps) { return 0; }
return 0;
}
// 6. Upward crossing: from below to above → +1 // 6. Upward crossing: from below to above → +1
if (a_below && !b_below) { if (a_below && !b_below) {
@ -55,7 +50,8 @@ int winding_contribution(const Point2d& P, const Point2d& A, const Point2d& B) {
/** /**
* @brief Computes the total winding number of point P with respect to a closed ring. * @brief Computes the total winding number of point P with respect to a closed ring.
*/ */
int compute_winding_number_ring(const Point2d& P, const PolygonRing& ring) { int compute_winding_number_ring(const Point2d& P, const PolygonRing& ring)
{
if (ring.size() < 3) return 0; if (ring.size() < 3) return 0;
int wn = 0; int wn = 0;
int n = ring.size(); int n = ring.size();
@ -68,11 +64,10 @@ int compute_winding_number_ring(const Point2d& P, const PolygonRing& ring) {
} }
// Helper: Check if P is on segment A-B // Helper: Check if P is on segment A-B
bool is_point_on_segment(const Point2d& P, const Point2d& A, const Point2d& B) { bool is_point_on_segment(const Point2d& P, const Point2d& A, const Point2d& B)
if (P.x() < std::min(A.x(), B.x()) - eps || {
P.x() > std::max(A.x(), B.x()) + eps || if (P.x() < std::min(A.x(), B.x()) - eps || P.x() > std::max(A.x(), B.x()) + eps || P.y() < std::min(A.y(), B.y()) - eps
P.y() < std::min(A.y(), B.y()) - eps || || P.y() > std::max(A.y(), B.y()) + eps) {
P.y() > std::max(A.y(), B.y()) + eps) {
return false; return false;
} }
@ -91,18 +86,13 @@ bool is_point_on_segment(const Point2d& P, const Point2d& A, const Point2d& B) {
* - Points on hole boundary outside * - Points on hole boundary outside
* - Otherwise: wn(outer) + sum(wn(hole)) != 0 inside * - Otherwise: wn(outer) + sum(wn(hole)) != 0 inside
*/ */
bool point_in_polygon_with_holes( bool point_in_polygon_with_holes(const Point2d& P, const PolygonRing& outer_ring, const std::vector<PolygonRing>& holes)
const Point2d& P,
const PolygonRing& outer_ring,
const std::vector<PolygonRing>& holes)
{ {
// Check boundary first // Check boundary first
auto on_ring = [&](const PolygonRing& ring) { auto on_ring = [&](const PolygonRing& ring) {
int n = ring.size(); int n = ring.size();
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
if (is_point_on_segment(P, ring[i], ring[(i+1)%n])) { if (is_point_on_segment(P, ring[i], ring[(i + 1) % n])) { return true; }
return true;
}
} }
return false; return false;
}; };
@ -121,8 +111,6 @@ bool point_in_polygon_with_holes(
return total_wn != 0; // 非零即内部 return total_wn != 0; // 非零即内部
} }
/** /**
* @brief Validates the polygon input for correct orientation and sufficient vertices. * @brief Validates the polygon input for correct orientation and sufficient vertices.
* *
@ -132,8 +120,7 @@ bool point_in_polygon_with_holes(
* @param context Optional context string for logging. * @param context Optional context string for logging.
* @return true if valid, false otherwise. * @return true if valid, false otherwise.
*/ */
bool validate_polygon_input( bool validate_polygon_input(const Point2d& query_point,
const Point2d& query_point,
const PolygonRing& outer_ring, const PolygonRing& outer_ring,
const std::vector<PolygonRing>& holes, const std::vector<PolygonRing>& holes,
const std::string& context) const std::string& context)
@ -175,22 +162,21 @@ bool validate_polygon_input(
// Output issues if invalid // Output issues if invalid
if (!valid) { if (!valid) {
std::cerr << "[\033[33mVALIDATION ERROR\033[0m] " << label std::cerr << "[\033[33mVALIDATION ERROR\033[0m] " << label << " | Query: " << query_point << " | Issues: " << issues
<< " | Query: " << query_point << "\n";
<< " | Issues: " << issues << "\n";
} }
return valid; return valid;
} }
/** /**
* @brief Computes the signed area of a polygon ring. * @brief Computes the signed area of a polygon ring.
* *
* @param ring The polygon ring (closed). * @param ring The polygon ring (closed).
* @return The signed area (positive if CCW, negative if CW). * @return The signed area (positive if CCW, negative if CW).
*/ */
double compute_ring_area(const PolygonRing& ring) { double compute_ring_area(const PolygonRing& ring)
{
if (ring.size() < 3) return 0.0; if (ring.size() < 3) return 0.0;
double area = 0.0; double area = 0.0;
int n = ring.size(); int n = ring.size();

31
surface_integral/src/subface_integrator.cpp

@ -22,14 +22,12 @@ integrator_t(const stl_vector_mp<object_with_index_mapping<subface>>& surfaces,
auto& chain_bboxs = m_chain_bboxes_hash[subface_index]; auto& chain_bboxs = m_chain_bboxes_hash[subface_index];
chain_bboxes.clear(); chain_bboxes.clear();
chain_bboxs.reverse(chain_vertices.size()); chain_bboxs.reverse(chain_vertices.size());
for (const auto& chain: param_plane.chain_vertices){ for (const auto& chain : param_plane.chain_vertices) {
Eigen::AlignedBox2d bbox; Eigen::AlignedBox2d bbox;
bbox.setEmpty(); bbox.setEmpty();
if (!chain.empty()) { if (!chain.empty()) {
for (const auto& pt : chain) { for (const auto& pt : chain) { bbox.extend(pt); }
bbox.extend(pt);
}
} }
chain_bboxes.push_back(bbox); chain_bboxes.push_back(bbox);
} }
@ -172,18 +170,17 @@ void integrator_t::find_v_intersections_at_u(const subface& subface,
double v_min, double v_min,
double v_max, double v_max,
stl_vector_mp<double>& intersections, stl_vector_mp<double>& intersections,
stl_vector_mp<uint16_t>& intersected_chains stl_vector_mp<uint16_t>& intersected_chains) const
) const
{ {
intersections.clear(); intersections.clear();
intersected_chains.clear(); intersected_chains.clear();
uint16_t chain_idx =0; uint16_t chain_idx = 0;
// Iterate over each boundary chain // Iterate over each boundary chain
for (const auto& chain : param_plane.chain_vertices) { for (const auto& chain : param_plane.chain_vertices) {
const size_t n_vertices = chain.size()-1; const size_t n_vertices = chain.size() - 1;
if (n_vertices < 2){ if (n_vertices < 2) {
std::cout << "Warning: Degenerate chain with less than 2 vertices." << std::endl; std::cout << "Warning: Degenerate chain with less than 2 vertices." << std::endl;
chain_idx++; chain_idx++;
continue; // Skip degenerate chains continue; // Skip degenerate chains
@ -191,7 +188,7 @@ void integrator_t::find_v_intersections_at_u(const subface& subface,
// filter bbox // filter bbox
if (chain_bbox[chain_idx].isEmpty() if (chain_bbox[chain_idx].isEmpty()
|| (chain_bbox[chain_idx].x()<u_val-1e-8 || chain_bbox[chain_idx].x()>u_val+1e-8)) { || (chain_bbox[chain_idx].x() < u_val - 1e-8 || chain_bbox[chain_idx].x() > u_val + 1e-8)) {
chain_idx++; chain_idx++;
continue; continue;
} }
@ -264,29 +261,23 @@ void integrator_t::find_v_intersections_at_u(const subface& subface,
} }
} }
} }
chain_idx ++; chain_idx++;
} }
// Final step: sort and remove duplicates within tolerance // Final step: sort and remove duplicates within tolerance
//if (!intersections.empty()) { sort_and_unique_with_tol(intersections, 1e-8); } // if (!intersections.empty()) { sort_and_unique_with_tol(intersections, 1e-8); }
} }
bool is_edge_inside_domain(const stl_vector_mp<stl_vector_mp<uint16_t>>& chain_group_indices,
bool is_edge_inside_domain(const stl_vector_mp<stl_vector_mp<uint16_t>> & chain_group_indices,
uint16_t chain_idx1, uint16_t chain_idx1,
uint16_t chain_idx2) const uint16_t chain_idx2) const
{ {
const auto& group1 = chain_group_indices[chain_idx1]; const auto& group1 = chain_group_indices[chain_idx1];
const auto& group2 = chain_group_indices[chain_idx2]; const auto& group2 = chain_group_indices[chain_idx2];
if (group1.find(chain_idx2) != group1.end() && group2.find(chain_idx1) != group2.end()) { if (group1.find(chain_idx2) != group1.end() && group2.find(chain_idx1) != group2.end()) { return true; }
return true;
}
return false; return false;
} }
bool integrator_t::is_u_near_singularity(double u, double tol) const { return false; } bool integrator_t::is_u_near_singularity(double u, double tol) const { return false; }
void integrator_t::sort_and_unique_with_tol(stl_vector_mp<double>& vec, double epsilon) const void integrator_t::sort_and_unique_with_tol(stl_vector_mp<double>& vec, double epsilon) const

27
surface_integral/test/SurfaceIntegrator_test.cpp

@ -5,7 +5,8 @@
#include <SurfaceIntegrator.hpp> #include <SurfaceIntegrator.hpp>
// ✅ 封装成函数:通过引用填充一个 parametric_plane_t 实例 // ✅ 封装成函数:通过引用填充一个 parametric_plane_t 实例
void populate_parametric_plane(parametric_plane_t& plane) { void populate_parametric_plane(parametric_plane_t& plane)
{
// 确保是空的(可选) // 确保是空的(可选)
// plane = parametric_plane_t(); // plane = parametric_plane_t();
auto& chain_vertices = plane.chain_vertices; auto& chain_vertices = plane.chain_vertices;
@ -20,10 +21,18 @@ void populate_parametric_plane(parametric_plane_t& plane) {
e_flags.reserve(vertex_count - 1); e_flags.reserve(vertex_count - 1);
// 1. 添加一条链的顶点 (chain 0) // 1. 添加一条链的顶点 (chain 0)
chain_vertices.push_back({{0.0, 0.0}}); chain_vertices.push_back({
chain_vertices.push_back({{1.0, 0.0}}); {0.0, 0.0}
chain_vertices.push_back({{1.0, 1.0}}); });
chain0_vertices.push_back({{0.0, 1.0}}); chain_vertices.push_back({
{1.0, 0.0}
});
chain_vertices.push_back({
{1.0, 1.0}
});
chain0_vertices.push_back({
{0.0, 1.0}
});
chain_vertices.reserve(unique_chain_indices.size()); chain_vertices.reserve(unique_chain_indices.size());
@ -64,7 +73,8 @@ void populate_parametric_plane(parametric_plane_t& plane) {
} }
// --- 使用示例 --- // --- 使用示例 ---
int main() { int main()
{
std::cout << "=== 使用函数填充 parametric_plane_t 实例 ===" << std::endl; std::cout << "=== 使用函数填充 parametric_plane_t 实例 ===" << std::endl;
// 先创建实例 // 先创建实例
@ -81,9 +91,7 @@ int main() {
std::cout << "第一条链的顶点特殊标志: " << plane.vertex_special_flags[0] << std::endl; std::cout << "第一条链的顶点特殊标志: " << plane.vertex_special_flags[0] << std::endl;
std::cout << "第一条链的边平行标志: " << plane.edge_near_parallel_flags[0] << std::endl; std::cout << "第一条链的边平行标志: " << plane.edge_near_parallel_flags[0] << std::endl;
std::cout << "第一条链所属的组: "; std::cout << "第一条链所属的组: ";
for (auto idx : plane.chain_group_indices[0]) { for (auto idx : plane.chain_group_indices[0]) { std::cout << idx << " "; }
std::cout << idx << " ";
}
std::cout << std::endl; std::cout << std::endl;
} }
@ -107,7 +115,6 @@ int main()
uv_planes[0].u_min = 0.0; uv_planes[0].u_min = 0.0;
// box_descriptor_t box{ // box_descriptor_t box{
// {0., 0., 0.}, // {0., 0., 0.},
// {1., 1., 1.} // {1., 1., 1.}

25
surface_integral/test/test_mesh.cpp

@ -1,21 +1,28 @@
#include <iostream> #include <iostream>
#include <mesh_algorithm.hpp> #include <mesh_algorithm.hpp>
int main() { int main()
{
// 8 vertices: unit cube // 8 vertices: unit cube
vector3d verts[8] = { vector3d verts[8] = {
{0,0,0}, {1,0,0}, {1,1,0}, {0,1,0}, {0, 0, 0},
{0,0,1}, {1,0,1}, {1,1,1}, {0,1,1} {1, 0, 0},
{1, 1, 0},
{0, 1, 0},
{0, 0, 1},
{1, 0, 1},
{1, 1, 1},
{0, 1, 1}
}; };
// 6 faces, each with 4 vertices (counter-clockwise order when viewed from outside) // 6 faces, each with 4 vertices (counter-clockwise order when viewed from outside)
uint32_t face_data[] = { uint32_t face_data[] = {
0,1,2,3, // bottom face 0, 1, 2, 3, // bottom face
7,6,5,4, // top face (note CCW when seen from outside) 7, 6, 5, 4, // top face (note CCW when seen from outside)
0,4,5,1, // front face 0, 4, 5, 1, // front face
1,5,6,2, // right face 1, 5, 6, 2, // right face
3,2,6,7, // back face 3, 2, 6, 7, // back face
0,3,7,4 // left face 0, 3, 7, 4 // left face
}; };
uint32_t vertex_counts[6] = {4, 4, 4, 4, 4, 4}; uint32_t vertex_counts[6] = {4, 4, 4, 4, 4, 4};

199
surface_integral/test/test_winding_number.cpp

@ -20,25 +20,32 @@ struct TestCase {
// ----------------------------- // -----------------------------
// Helper: Print Point // Helper: Print Point
// ----------------------------- // -----------------------------
std::ostream& operator<<(std::ostream& os, const Point2d& p) { std::ostream& operator<<(std::ostream& os, const Point2d& p) { return os << "(" << p.x() << ", " << p.y() << ")"; }
return os << "(" << p.x() << ", " << p.y() << ")";
}
// ----------------------------- // -----------------------------
// Helper: Validate Ring Orientation // Helper: Validate Ring Orientation
// ----------------------------- // -----------------------------
bool validate_orientation() { bool validate_orientation()
{
// Test CCW (positive area) // Test CCW (positive area)
PolygonRing ccw = {{0,0}, {1,0}, {1,1}, {0,1}}; PolygonRing ccw = {
{0, 0},
{1, 0},
{1, 1},
{0, 1}
};
if (compute_ring_area(ccw) <= 0) { if (compute_ring_area(ccw) <= 0) {
std::cerr << "Error: CCW ring has non-positive area!\n"; std::cerr << "Error: CCW ring has non-positive area!\n";
return false; return false;
} }
// Test CW (negative area) // Test CW (negative area)
PolygonRing cw = {{0,0}, {0,1}, {1,1}, {1,0}}; PolygonRing cw = {
{0, 0},
{0, 1},
{1, 1},
{1, 0}
};
if (compute_ring_area(cw) >= 0) { if (compute_ring_area(cw) >= 0) {
std::cerr << "Error: CW ring has non-negative area!\n"; std::cerr << "Error: CW ring has non-negative area!\n";
return false; return false;
@ -49,7 +56,8 @@ bool validate_orientation() {
// ----------------------------- // -----------------------------
// Validate Test Case: Orientation of outer ring (CCW) and holes (CW) // Validate Test Case: Orientation of outer ring (CCW) and holes (CW)
// ----------------------------- // -----------------------------
bool validate_test_case(const TestCase& tc) { bool validate_test_case(const TestCase& tc)
{
bool valid = true; bool valid = true;
std::string status = ""; std::string status = "";
@ -74,10 +82,8 @@ bool validate_test_case(const TestCase& tc) {
std::cout << "[" std::cout << "["
<< "\033[33mINVALID\033[0m" // make "INVALID" yellow << "\033[33mINVALID\033[0m" // make "INVALID" yellow
<< "] " << "] " << tc.name << "\n"
<< tc.name << "\n" << " Query: " << tc.query_point << " | Status: \033[33mInvalid Test Case\033[0m"
<< " Query: " << tc.query_point
<< " | Status: \033[33mInvalid Test Case\033[0m"
<< " | Reason: " << (valid ? "OK" : status) << "\n"; << " | Reason: " << (valid ? "OK" : status) << "\n";
return valid; return valid;
@ -86,16 +92,13 @@ bool validate_test_case(const TestCase& tc) {
// ----------------------------- // -----------------------------
// Run Single Test // Run Single Test
// ----------------------------- // -----------------------------
bool run_test(const TestCase& tc) { bool run_test(const TestCase& tc)
{
bool result = point_in_polygon_with_holes(tc.query_point, tc.outer_ring, tc.holes); bool result = point_in_polygon_with_holes(tc.query_point, tc.outer_ring, tc.holes);
bool passed = (result == tc.expected); bool passed = (result == tc.expected);
std::cout << "[" std::cout << "[" << (passed ? "\033[32mPASS\033[0m" : "\033[31mFAIL\033[0m") << "] " << tc.name << "\n"
<< (passed ? "\033[32mPASS\033[0m" : "\033[31mFAIL\033[0m") << " Query: " << tc.query_point << " | Expected: " << (tc.expected ? "Inside" : "Outside")
<< "] "
<< tc.name << "\n"
<< " Query: " << tc.query_point
<< " | Expected: " << (tc.expected ? "Inside" : "Outside")
<< " | Got: " << (result ? "Inside" : "Outside") << "\n"; << " | Got: " << (result ? "Inside" : "Outside") << "\n";
return passed; return passed;
@ -105,19 +108,32 @@ bool run_test(const TestCase& tc) {
// Test Case Factory Functions // Test Case Factory Functions
// ----------------------------- // -----------------------------
std::vector<TestCase> create_test_cases() { std::vector<TestCase> create_test_cases()
{
std::vector<TestCase> tests; std::vector<TestCase> tests;
// -------------------------------------------------- // --------------------------------------------------
// Test 1: Simple hole - point inside hole → Outside // Test 1: Simple hole - point inside hole → Outside
// -------------------------------------------------- // --------------------------------------------------
PolygonRing outer1 = {{0,0}, {2,0}, {2,2}, {0,2}}; PolygonRing outer1 = {
PolygonRing hole1 = {{0.5,0.5}, {0.5,1.5}, {1.5,1.5}, {1.5,0.5}}; {0, 0},
{2, 0},
{2, 2},
{0, 2}
};
PolygonRing hole1 = {
{0.5, 0.5},
{0.5, 1.5},
{1.5, 1.5},
{1.5, 0.5}
};
tests.push_back({ tests.push_back({
"Inside Hole", "Inside Hole",
outer1, {hole1}, {1.0, 1.0}, false outer1,
{hole1},
{1.0, 1.0},
false
}); });
// -------------------------------------------------- // --------------------------------------------------
@ -125,7 +141,10 @@ std::vector<TestCase> create_test_cases() {
// -------------------------------------------------- // --------------------------------------------------
tests.push_back({ tests.push_back({
"Inside Outer, Outside Hole", "Inside Outer, Outside Hole",
outer1, {hole1}, {0.1, 0.1}, true outer1,
{hole1},
{0.1, 0.1},
true
}); });
// -------------------------------------------------- // --------------------------------------------------
@ -133,7 +152,10 @@ std::vector<TestCase> create_test_cases() {
// -------------------------------------------------- // --------------------------------------------------
tests.push_back({ tests.push_back({
"Outside Outer", "Outside Outer",
outer1, {hole1}, {3.0, 3.0}, false outer1,
{hole1},
{3.0, 3.0},
false
}); });
// -------------------------------------------------- // --------------------------------------------------
@ -142,7 +164,10 @@ std::vector<TestCase> create_test_cases() {
// -------------------------------------------------- // --------------------------------------------------
tests.push_back({ tests.push_back({
"On Outer Edge (Bottom)", "On Outer Edge (Bottom)",
outer1, {}, {1.0, 0.0}, true outer1,
{},
{1.0, 0.0},
true
}); });
// -------------------------------------------------- // --------------------------------------------------
@ -150,70 +175,138 @@ std::vector<TestCase> create_test_cases() {
// -------------------------------------------------- // --------------------------------------------------
tests.push_back({ tests.push_back({
"On Outer Vertex Offset Slightly Outside", "On Outer Vertex Offset Slightly Outside",
outer1, {}, {0.0, 0.0-1e-8}, false outer1,
{},
{0.0, 0.0 - 1e-8},
false
}); });
// -------------------------------------------------- // --------------------------------------------------
// Test 6: Point on hole edge // Test 6: Point on hole edge
// Should be outside (on hole boundary still "outside" the solid) // Should be outside (on hole boundary still "outside" the solid)
// -------------------------------------------------- // --------------------------------------------------
PolygonRing hole6 = {{1.0,1.0}, {1.0,2.0}, {2.0,2.0}, {2.0,1.0}}; // CW PolygonRing hole6 = {
{1.0, 1.0},
{1.0, 2.0},
{2.0, 2.0},
{2.0, 1.0}
}; // CW
tests.push_back({ tests.push_back({
"On Hole Edge", "On Hole Edge",
{{0,0},{3,0},{3,3},{0,3}}, {hole6}, {1.5, 1.0}, false {{0, 0}, {3, 0}, {3, 3}, {0, 3}},
{hole6},
{1.5, 1.0},
false
}); });
// -------------------------------------------------- // --------------------------------------------------
// Test 7: Multiple holes // Test 7: Multiple holes
// -------------------------------------------------- // --------------------------------------------------
PolygonRing hole7a = {{0.5,0.5}, {0.5,1.0}, {1.0,1.0}, {1.0,0.5}}; // CW PolygonRing hole7a = {
PolygonRing hole7b = {{1.5,1.5}, {1.5,2.0}, {2.0,2.0}, {2.0,1.5}}; // CW {0.5, 0.5},
{0.5, 1.0},
{1.0, 1.0},
{1.0, 0.5}
}; // CW
PolygonRing hole7b = {
{1.5, 1.5},
{1.5, 2.0},
{2.0, 2.0},
{2.0, 1.5}
}; // CW
tests.push_back({ tests.push_back({
"Multiple Holes - Inside", "Multiple Holes - Inside",
outer1, {hole7a, hole7b}, {0.1, 0.1}, true outer1,
{hole7a, hole7b},
{0.1, 0.1 },
true
}); });
tests.push_back({ tests.push_back({
"Multiple Holes - In First Hole", "Multiple Holes - In First Hole",
outer1, {hole7a, hole7b}, {0.7, 0.7}, false outer1,
{hole7a, hole7b},
{0.7, 0.7 },
false
}); });
tests.push_back({ tests.push_back({
"Multiple Holes - In Second Hole", "Multiple Holes - In Second Hole",
outer1, {hole7a, hole7b}, {1.7, 1.7}, false outer1,
{hole7a, hole7b},
{1.7, 1.7 },
false
}); });
// -------------------------------------------------- // --------------------------------------------------
// Test 8: Nested holes (hole inside hole) - NOT standard, but test behavior // Test 8: Nested holes (hole inside hole) - NOT standard, but test behavior
// Note: This is not valid in simple polygons, but test robustness // Note: This is not valid in simple polygons, but test robustness
// -------------------------------------------------- // --------------------------------------------------
PolygonRing outer8 = {{0,0}, {3,0}, {3,3}, {0,3}}; PolygonRing outer8 = {
PolygonRing hole8a = {{0.5,0.5}, {0.5,2.5}, {2.5,2.5}, {2.5,0.5}}; // large hole {0, 0},
PolygonRing hole8b = {{1.0,1.0}, {1.0,2.0}, {2.0,2.0}, {2.0,1.0}}; // small hole inside hole8a {3, 0},
{3, 3},
{0, 3}
};
PolygonRing hole8a = {
{0.5, 0.5},
{0.5, 2.5},
{2.5, 2.5},
{2.5, 0.5}
}; // large hole
PolygonRing hole8b = {
{1.0, 1.0},
{1.0, 2.0},
{2.0, 2.0},
{2.0, 1.0}
}; // small hole inside hole8a
// This creates a "island" in the hole // This creates a "island" in the hole
tests.push_back({ tests.push_back({
"Nested Holes - On Island", "Nested Holes - On Island",
outer8, {hole8a, hole8b}, {1.5, 1.5}, true // inside outer, inside hole8a, but outside hole8b → solid outer8,
{hole8a, hole8b},
{1.5, 1.5 },
true // inside outer, inside hole8a, but outside hole8b → solid
}); });
// -------------------------------------------------- // --------------------------------------------------
// Test 9: Complex L-shaped polygon with hole // Test 9: Complex L-shaped polygon with hole
// -------------------------------------------------- // --------------------------------------------------
PolygonRing outer9 = { PolygonRing outer9 = {
{0,0}, {3,0}, {3,1}, {2,1}, {2,3}, {0,3}, {0,0} // L-shape, explicitly closed and no self-intersection {0, 0},
{3, 0},
{3, 1},
{2, 1},
{2, 3},
{0, 3},
{0, 0} // L-shape, explicitly closed and no self-intersection
}; // CCW }; // CCW
PolygonRing hole9 = {{0.5,0.5}, {0.5,1.5}, {1.5,1.5}, {1.5,0.5}}; // CW PolygonRing hole9 = {
{0.5, 0.5},
{0.5, 1.5},
{1.5, 1.5},
{1.5, 0.5}
}; // CW
tests.push_back({ tests.push_back({
"L-Shape with Hole - Inside Leg", "L-Shape with Hole - Inside Leg",
outer9, {hole9}, {0.1, 0.1}, true outer9,
{hole9},
{0.1, 0.1},
true
}); });
tests.push_back({ tests.push_back({
"L-Shape with Hole - In Hole", "L-Shape with Hole - In Hole",
outer9, {hole9}, {1.0, 1.0}, false outer9,
{hole9},
{1.0, 1.0},
false
}); });
tests.push_back({ tests.push_back({
"L-Shape with Hole - In Arm", "L-Shape with Hole - In Arm",
outer9, {hole9}, {2.0, 0.5}, true outer9,
{hole9},
{2.0, 0.5},
true
}); });
// -------------------------------------------------- // --------------------------------------------------
@ -221,7 +314,10 @@ std::vector<TestCase> create_test_cases() {
// -------------------------------------------------- // --------------------------------------------------
tests.push_back({ tests.push_back({
"Near Degenerate - Close to Corner", "Near Degenerate - Close to Corner",
outer1, {hole1}, {0.5000001, 0.5000001}, false // just outside hole outer1,
{hole1},
{0.5000001, 0.5000001},
false // just outside hole
}); });
return tests; return tests;
@ -230,7 +326,8 @@ std::vector<TestCase> create_test_cases() {
// ----------------------------- // -----------------------------
// Main Function // Main Function
// ----------------------------- // -----------------------------
int main() { int main()
{
std::cout << "Running Polygon Winding Number Tests...\n\n"; std::cout << "Running Polygon Winding Number Tests...\n\n";
// Optional: Validate orientation logic // Optional: Validate orientation logic
@ -245,12 +342,8 @@ int main() {
int total = test_cases.size(); int total = test_cases.size();
for (const auto& tc : test_cases) { for (const auto& tc : test_cases) {
if (!validate_test_case(tc)) { if (!validate_test_case(tc)) { continue; }
continue; if (run_test(tc)) { passed++; }
}
if (run_test(tc)) {
passed++;
}
} }
std::cout << "\nSummary: " << passed << " / " << total << " tests passed.\n"; std::cout << "\nSummary: " << passed << " / " << total << " tests passed.\n";

Loading…
Cancel
Save