Browse Source

style: style: format subface_integrator header and source files

feat-integrator
mckay 2 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 <solve.h> // polymesh_t
// Vector subtraction: a - b
inline vector3d vec3_sub(const vector3d& a, const vector3d& b) {
return { a.x - b.x, a.y - b.y, a.z - b.z };
}
inline vector3d vec3_sub(const vector3d& a, const vector3d& b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
// Vector cross product: a × b
inline vector3d vec3_cross(const vector3d& a, const vector3d& b) {
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
};
inline vector3d vec3_cross(const vector3d& a, const vector3d& b)
{
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};
}
// Vector dot product: a · b
inline double vec3_dot(const vector3d& a, const vector3d& b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
inline double vec3_dot(const vector3d& a, const vector3d& b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
// Vector length: ||a||
inline double vec3_norm(const vector3d& a) {
return std::sqrt(a.x*a.x + a.y*a.y + a.z*a.z);
}
inline double vec3_norm(const vector3d& a) { return std::sqrt(a.x * a.x + a.y * a.y + a.z * a.z); }
// 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 ac = vec3_sub(c, a);
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
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);
return vec3_dot(a, cross_product) / 6.0;
}
// 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;
double area = 0.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)
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;
double volume = 0.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
double compute_surface_area(const polymesh_t* mesh) {
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) {
return 0.0;
}
double compute_surface_area(const polymesh_t* mesh)
{
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) { return 0.0; }
double total_area = 0.0;
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)
double compute_volume(const polymesh_t* mesh) {
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) {
return 0.0;
}
double compute_volume(const polymesh_t* mesh)
{
if (!mesh || !mesh->vertices || !mesh->faces || !mesh->vertex_counts) { return 0.0; }
double total_volume = 0.0;
const uint32_t* face_ptr = mesh->faces;

3
surface_integral/interface/subface_integrator.hpp

@ -67,8 +67,7 @@ private:
double v_min,
double v_max,
stl_vector_mp<double>& intersections,
stl_vector_mp<uint16_t>& intersected_chains
) const;
stl_vector_mp<uint16_t>& intersected_chains) 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.
* @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
if (std::abs(A.y() - B.y()) < eps) {
return 0;
}
if (std::abs(A.y() - B.y()) < eps) { return 0; }
// 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 b_below = (B.y() < P.y() - eps);
// 3. If both endpoints are on the same side of the ray, no crossing
if (a_below == b_below) {
return 0;
}
if (a_below == b_below) { return 0; }
// 4. Compute x-coordinate of intersection with y = P.y
double t = (P.y() - A.y()) / (B.y() - A.y());
double x_intersect = A.x() + t * (B.x() - A.x());
// 5. Only consider intersections to the right of P (or directly on P)
if (x_intersect < P.x() - eps) {
return 0;
}
if (x_intersect < P.x() - eps) { return 0; }
// 6. Upward crossing: from below to above → +1
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.
*/
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;
int wn = 0;
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
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 ||
P.y() < std::min(A.y(), B.y()) - eps ||
P.y() > std::max(A.y(), B.y()) + eps) {
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 || P.y() < std::min(A.y(), B.y()) - eps
|| P.y() > std::max(A.y(), B.y()) + eps) {
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
* - Otherwise: wn(outer) + sum(wn(hole)) != 0 inside
*/
bool point_in_polygon_with_holes(
const Point2d& P,
const PolygonRing& outer_ring,
const std::vector<PolygonRing>& holes)
bool point_in_polygon_with_holes(const Point2d& P, const PolygonRing& outer_ring, const std::vector<PolygonRing>& holes)
{
// Check boundary first
auto on_ring = [&](const PolygonRing& ring) {
int n = ring.size();
for (int i = 0; i < n; ++i) {
if (is_point_on_segment(P, ring[i], ring[(i+1)%n])) {
return true;
}
if (is_point_on_segment(P, ring[i], ring[(i + 1) % n])) { return true; }
}
return false;
};
@ -121,8 +111,6 @@ bool point_in_polygon_with_holes(
return total_wn != 0; // 非零即内部
}
/**
* @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.
* @return true if valid, false otherwise.
*/
bool validate_polygon_input(
const Point2d& query_point,
bool validate_polygon_input(const Point2d& query_point,
const PolygonRing& outer_ring,
const std::vector<PolygonRing>& holes,
const std::string& context)
@ -175,22 +162,21 @@ bool validate_polygon_input(
// Output issues if invalid
if (!valid) {
std::cerr << "[\033[33mVALIDATION ERROR\033[0m] " << label
<< " | Query: " << query_point
<< " | Issues: " << issues << "\n";
std::cerr << "[\033[33mVALIDATION ERROR\033[0m] " << label << " | Query: " << query_point << " | Issues: " << issues
<< "\n";
}
return valid;
}
/**
* @brief Computes the signed area of a polygon ring.
*
* @param ring The polygon ring (closed).
* @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;
double area = 0.0;
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];
chain_bboxes.clear();
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;
bbox.setEmpty();
if (!chain.empty()) {
for (const auto& pt : chain) {
bbox.extend(pt);
}
for (const auto& pt : chain) { bbox.extend(pt); }
}
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_max,
stl_vector_mp<double>& intersections,
stl_vector_mp<uint16_t>& intersected_chains
) const
stl_vector_mp<uint16_t>& intersected_chains) const
{
intersections.clear();
intersected_chains.clear();
uint16_t chain_idx =0;
uint16_t chain_idx = 0;
// Iterate over each boundary chain
for (const auto& chain : param_plane.chain_vertices) {
const size_t n_vertices = chain.size()-1;
if (n_vertices < 2){
const size_t n_vertices = chain.size() - 1;
if (n_vertices < 2) {
std::cout << "Warning: Degenerate chain with less than 2 vertices." << std::endl;
chain_idx++;
continue; // Skip degenerate chains
@ -191,7 +188,7 @@ void integrator_t::find_v_intersections_at_u(const subface& subface,
// filter bbox
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++;
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
//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_idx2) const
{
const auto& group1 = chain_group_indices[chain_idx1];
const auto& group2 = chain_group_indices[chain_idx2];
if (group1.find(chain_idx2) != group1.end() && group2.find(chain_idx1) != group2.end()) {
return true;
}
if (group1.find(chain_idx2) != group1.end() && group2.find(chain_idx1) != group2.end()) { return true; }
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

27
surface_integral/test/SurfaceIntegrator_test.cpp

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

25
surface_integral/test/test_mesh.cpp

@ -1,21 +1,28 @@
#include <iostream>
#include <mesh_algorithm.hpp>
int main() {
int main()
{
// 8 vertices: unit cube
vector3d verts[8] = {
{0,0,0}, {1,0,0}, {1,1,0}, {0,1,0},
{0,0,1}, {1,0,1}, {1,1,1}, {0,1,1}
{0, 0, 0},
{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)
uint32_t face_data[] = {
0,1,2,3, // bottom face
7,6,5,4, // top face (note CCW when seen from outside)
0,4,5,1, // front face
1,5,6,2, // right face
3,2,6,7, // back face
0,3,7,4 // left face
0, 1, 2, 3, // bottom face
7, 6, 5, 4, // top face (note CCW when seen from outside)
0, 4, 5, 1, // front face
1, 5, 6, 2, // right face
3, 2, 6, 7, // back face
0, 3, 7, 4 // left face
};
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
// -----------------------------
std::ostream& operator<<(std::ostream& os, const Point2d& p) {
return os << "(" << p.x() << ", " << p.y() << ")";
}
std::ostream& operator<<(std::ostream& os, const Point2d& p) { return os << "(" << p.x() << ", " << p.y() << ")"; }
// -----------------------------
// Helper: Validate Ring Orientation
// -----------------------------
bool validate_orientation() {
bool validate_orientation()
{
// 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) {
std::cerr << "Error: CCW ring has non-positive area!\n";
return false;
}
// 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) {
std::cerr << "Error: CW ring has non-negative area!\n";
return false;
@ -49,7 +56,8 @@ bool validate_orientation() {
// -----------------------------
// 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;
std::string status = "";
@ -74,10 +82,8 @@ bool validate_test_case(const TestCase& tc) {
std::cout << "["
<< "\033[33mINVALID\033[0m" // make "INVALID" yellow
<< "] "
<< tc.name << "\n"
<< " Query: " << tc.query_point
<< " | Status: \033[33mInvalid Test Case\033[0m"
<< "] " << tc.name << "\n"
<< " Query: " << tc.query_point << " | Status: \033[33mInvalid Test Case\033[0m"
<< " | Reason: " << (valid ? "OK" : status) << "\n";
return valid;
@ -86,16 +92,13 @@ bool validate_test_case(const TestCase& tc) {
// -----------------------------
// 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 passed = (result == tc.expected);
std::cout << "["
<< (passed ? "\033[32mPASS\033[0m" : "\033[31mFAIL\033[0m")
<< "] "
<< tc.name << "\n"
<< " Query: " << tc.query_point
<< " | Expected: " << (tc.expected ? "Inside" : "Outside")
std::cout << "[" << (passed ? "\033[32mPASS\033[0m" : "\033[31mFAIL\033[0m") << "] " << tc.name << "\n"
<< " Query: " << tc.query_point << " | Expected: " << (tc.expected ? "Inside" : "Outside")
<< " | Got: " << (result ? "Inside" : "Outside") << "\n";
return passed;
@ -105,19 +108,32 @@ bool run_test(const TestCase& tc) {
// Test Case Factory Functions
// -----------------------------
std::vector<TestCase> create_test_cases() {
std::vector<TestCase> create_test_cases()
{
std::vector<TestCase> tests;
// --------------------------------------------------
// Test 1: Simple hole - point inside hole → Outside
// --------------------------------------------------
PolygonRing outer1 = {{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}};
PolygonRing outer1 = {
{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({
"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({
"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({
"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({
"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({
"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
// 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({
"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
// --------------------------------------------------
PolygonRing hole7a = {{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
PolygonRing hole7a = {
{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({
"Multiple Holes - Inside",
outer1, {hole7a, hole7b}, {0.1, 0.1}, true
outer1,
{hole7a, hole7b},
{0.1, 0.1 },
true
});
tests.push_back({
"Multiple Holes - In First Hole",
outer1, {hole7a, hole7b}, {0.7, 0.7}, false
outer1,
{hole7a, hole7b},
{0.7, 0.7 },
false
});
tests.push_back({
"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
// Note: This is not valid in simple polygons, but test robustness
// --------------------------------------------------
PolygonRing outer8 = {{0,0}, {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
PolygonRing outer8 = {
{0, 0},
{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
tests.push_back({
"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
// --------------------------------------------------
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
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({
"L-Shape with Hole - Inside Leg",
outer9, {hole9}, {0.1, 0.1}, true
outer9,
{hole9},
{0.1, 0.1},
true
});
tests.push_back({
"L-Shape with Hole - In Hole",
outer9, {hole9}, {1.0, 1.0}, false
outer9,
{hole9},
{1.0, 1.0},
false
});
tests.push_back({
"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({
"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;
@ -230,7 +326,8 @@ std::vector<TestCase> create_test_cases() {
// -----------------------------
// Main Function
// -----------------------------
int main() {
int main()
{
std::cout << "Running Polygon Winding Number Tests...\n\n";
// Optional: Validate orientation logic
@ -245,12 +342,8 @@ int main() {
int total = test_cases.size();
for (const auto& tc : test_cases) {
if (!validate_test_case(tc)) {
continue;
}
if (run_test(tc)) {
passed++;
}
if (!validate_test_case(tc)) { continue; }
if (run_test(tc)) { passed++; }
}
std::cout << "\nSummary: " << passed << " / " << total << " tests passed.\n";

Loading…
Cancel
Save