Browse Source

style: style: format subface_integrator header and source files

feat-integrator
mckay 5 months ago
parent
commit
b263538d73
  1. 92
      surface_integral/interface/mesh_algorithm.hpp
  2. 13
      surface_integral/interface/subface_integrator.hpp
  3. 90
      surface_integral/src/polygon_winding_number.cpp
  4. 87
      surface_integral/src/subface_integrator.cpp
  5. 39
      surface_integral/test/SurfaceIntegrator_test.cpp
  6. 39
      surface_integral/test/test_mesh.cpp
  7. 217
      surface_integral/test/test_winding_number.cpp

92
surface_integral/interface/mesh_algorithm.hpp

@ -1,104 +1,96 @@
#include <cmath> #include <cmath>
#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 ac = vec3_sub(c, a); vector3d ab = vec3_sub(b, a);
vector3d ac = vec3_sub(c, a);
vector3d cross_product = vec3_cross(ab, ac); vector3d cross_product = vec3_cross(ab, ac);
return 0.5 * vec3_norm(cross_product); return 0.5 * vec3_norm(cross_product);
} }
// 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]];
for (uint32_t i = 1; i < n - 1; ++i) { for (uint32_t i = 1; i < n - 1; ++i) {
const vector3d& v1 = vertices[face_indices[i]]; const vector3d& v1 = vertices[face_indices[i]];
const vector3d& v2 = vertices[face_indices[i + 1]]; const vector3d& v2 = vertices[face_indices[i + 1]];
area += triangle_area(v0, v1, v2); area += triangle_area(v0, v1, v2);
} }
return area; return area;
} }
// 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]];
for (uint32_t i = 1; i < n - 1; ++i) { for (uint32_t i = 1; i < n - 1; ++i) {
const vector3d& v1 = vertices[face_indices[i]]; const vector3d& v1 = vertices[face_indices[i]];
const vector3d& v2 = vertices[face_indices[i + 1]]; const vector3d& v2 = vertices[face_indices[i + 1]];
volume += triangle_signed_volume(v0, v1, v2); volume += triangle_signed_volume(v0, v1, v2);
} }
return volume; return volume;
} }
// 🟩 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
for (uint32_t i = 0; i < mesh->num_faces; ++i) { for (uint32_t i = 0; i < mesh->num_faces; ++i) {
uint32_t n = mesh->vertex_counts[i]; uint32_t n = mesh->vertex_counts[i];
total_area += polygon_area(mesh->vertices, face_ptr, n); total_area += polygon_area(mesh->vertices, face_ptr, n);
face_ptr += n; // move to the next face's start index face_ptr += n; // move to the next face's start index
} }
return total_area; return total_area;
} }
// 🟦 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;
for (uint32_t i = 0; i < mesh->num_faces; ++i) { for (uint32_t i = 0; i < mesh->num_faces; ++i) {
uint32_t n = mesh->vertex_counts[i]; uint32_t n = mesh->vertex_counts[i];
total_volume += polygon_signed_volume(mesh->vertices, face_ptr, n); total_volume += polygon_signed_volume(mesh->vertices, face_ptr, n);
face_ptr += n; face_ptr += n;
} }
return std::abs(total_volume); // ensure positive volume return std::abs(total_volume); // ensure positive volume
} }

13
surface_integral/interface/subface_integrator.hpp

@ -62,13 +62,12 @@ private:
stl_vector_mp<double> compute_u_breaks(const parametric_plane_t& param_plane, double u_min, double u_max) const; stl_vector_mp<double> compute_u_breaks(const parametric_plane_t& param_plane, double u_min, double u_max) const;
void find_v_intersections_at_u(const subface& subface, void find_v_intersections_at_u(const subface& subface,
const parametric_plane_t& param_plane, const parametric_plane_t& param_plane,
double u_val, double u_val,
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;

90
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,31 +50,31 @@ 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();
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
const Point2d& A = ring[i]; const Point2d& A = ring[i];
const Point2d& B = ring[(i + 1) % n]; const Point2d& B = ring[(i + 1) % n];
wn += winding_contribution(P, A, B); wn += winding_contribution(P, A, B);
} }
return wn; return wn;
} }
// 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;
} }
double cross = (P.y() - A.y()) * (B.x() - A.x()) - (P.x() - A.x()) * (B.y() - A.y()); double cross = (P.y() - A.y()) * (B.x() - A.x()) - (P.x() - A.x()) * (B.y() - A.y());
if (std::abs(cross) > eps) return false; if (std::abs(cross) > eps) return false;
double dot = (P.x() - A.x()) * (B.x() - A.x()) + (P.y() - A.y()) * (B.y() - A.y()); double dot = (P.x() - A.x()) * (B.x() - A.x()) + (P.y() - A.y()) * (B.y() - A.y());
double len_sq = (B.x() - A.x()) * (B.x() - A.x()) + (B.y() - A.y()) * (B.y() - A.y()); double len_sq = (B.x() - A.x()) * (B.x() - A.x()) + (B.y() - A.y()) * (B.y() - A.y());
return (dot >= -eps && dot <= len_sq + eps); return (dot >= -eps && dot <= len_sq + eps);
} }
@ -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;
}; };
@ -118,11 +108,9 @@ bool point_in_polygon_with_holes(
total_wn += compute_winding_number_ring(P, hole); // hole 是 CW,返回负值 total_wn += compute_winding_number_ring(P, hole); // hole 是 CW,返回负值
} }
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,71 +120,69 @@ 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)
{ {
std::string label = context.empty() ? "Polygon" : context; std::string label = context.empty() ? "Polygon" : context;
bool valid = true; bool valid = true;
std::string issues = ""; std::string issues = "";
// Check outer ring has enough points // Check outer ring has enough points
if (outer_ring.size() < 3) { if (outer_ring.size() < 3) {
issues += "Outer ring has fewer than 3 vertices."; issues += "Outer ring has fewer than 3 vertices.";
valid = false; valid = false;
} else { } else {
double outer_area = compute_ring_area(outer_ring); double outer_area = compute_ring_area(outer_ring);
if (outer_area <= 0) { if (outer_area <= 0) {
issues += "Outer ring is not CCW (area = " + std::to_string(outer_area) + ")"; issues += "Outer ring is not CCW (area = " + std::to_string(outer_area) + ")";
valid = false; valid = false;
} }
} }
// Check each hole // Check each hole
for (size_t i = 0; i < holes.size(); ++i) { for (size_t i = 0; i < holes.size(); ++i) {
const auto& hole = holes[i]; const auto& hole = holes[i];
std::string hole_label = "Hole " + std::to_string(i); std::string hole_label = "Hole " + std::to_string(i);
if (hole.size() < 3) { if (hole.size() < 3) {
if (!issues.empty()) issues += ", "; if (!issues.empty()) issues += ", ";
issues += hole_label + " has fewer than 3 vertices"; issues += hole_label + " has fewer than 3 vertices";
valid = false; valid = false;
} else { } else {
double hole_area = compute_ring_area(hole); double hole_area = compute_ring_area(hole);
if (hole_area >= 0) { if (hole_area >= 0) {
if (!issues.empty()) issues += ", "; if (!issues.empty()) issues += ", ";
issues += hole_label + " is not CW (area = " + std::to_string(hole_area) + ")"; issues += hole_label + " is not CW (area = " + std::to_string(hole_area) + ")";
valid = false; valid = false;
} }
} }
} }
// 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();
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
int j = (i + 1) % n; int j = (i + 1) % n;
area += ring[i].x() * ring[j].y() - ring[j].x() * ring[i].y(); area += ring[i].x() * ring[j].y() - ring[j].x() * ring[i].y();
} }
return area * 0.5; return area * 0.5;
} }

87
surface_integral/src/subface_integrator.cpp

@ -9,7 +9,7 @@ namespace internal
{ {
integrator_t(const stl_vector_mp<object_with_index_mapping<subface>>& surfaces, integrator_t(const stl_vector_mp<object_with_index_mapping<subface>>& surfaces,
const flat_hash_map_mp<uint32_t, parametric_plane_t>& uv_planes) const flat_hash_map_mp<uint32_t, parametric_plane_t>& uv_planes)
: m_subfaces(surfaces), m_uv_planes(uv_planes) : m_subfaces(surfaces), m_uv_planes(uv_planes)
{ {
std::cout << "Integrator initialized with " << surfaces.size() << " subfaces." << std::endl; std::cout << "Integrator initialized with " << surfaces.size() << " subfaces." << std::endl;
@ -19,17 +19,15 @@ integrator_t(const stl_vector_mp<object_with_index_mapping<subface>>& surfaces,
for (int subface_index = 0; subface_index < m_subfaces.size(); ++subface_index) { for (int subface_index = 0; subface_index < m_subfaces.size(); ++subface_index) {
if (m_uv_planes.find(subface_index) != m_uv_planes.end()) { if (m_uv_planes.find(subface_index) != m_uv_planes.end()) {
const auto& param_plane = m_uv_planes.at(subface_index); const auto& param_plane = m_uv_planes.at(subface_index);
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);
} }
@ -46,9 +44,9 @@ double integrator_t::calculate(
double total_integral = 0.0; double total_integral = 0.0;
for (int subface_index = 0; subface_index < m_subfaces.size(); ++subface_index) { for (int subface_index = 0; subface_index < m_subfaces.size(); ++subface_index) {
if (m_uv_planes.find(subface_index) != m_uv_planes.end()) { if (m_uv_planes.find(subface_index) != m_uv_planes.end()) {
const auto& param_plane = m_uv_planes.at(subface_index); const auto& param_plane = m_uv_planes.at(subface_index);
const auto& subface = m_subfaces[subface_index].object_ptr.get(); const auto& subface = m_subfaces[subface_index].object_ptr.get();
total_integral += calculate_one_subface(subface, param_plane, gauss_order, func); total_integral += calculate_one_subface(subface, param_plane, gauss_order, func);
} else { } else {
// the subface has no associated parametric plane, meaning it is compete face without trimming // the subface has no associated parametric plane, meaning it is compete face without trimming
} }
@ -62,16 +60,16 @@ double integrator_t::calculate_one_subface(
int gauss_order, int gauss_order,
double (*func)(double u, double v, const Eigen::Vector3d& p, const Eigen::Vector3d& dU, const Eigen::Vector3d& dV)) const double (*func)(double u, double v, const Eigen::Vector3d& p, const Eigen::Vector3d& dU, const Eigen::Vector3d& dV)) const
{ {
auto solver = subface.fetch_solver_evaluator(); auto solver = subface.fetch_solver_evaluator();
const auto& uv_bounds = param_plane.uv_bounds; const auto& uv_bounds = param_plane.uv_bounds;
auto u_min = uv_bounds.min().x(); auto u_min = uv_bounds.min().x();
auto u_max = uv_bounds.max().x(); auto u_max = uv_bounds.max().x();
auto v_min = uv_bounds.min().y(); auto v_min = uv_bounds.min().y();
auto v_max = uv_bounds.max().y(); auto v_max = uv_bounds.max().y();
// Gaussian integration in u direction // Gaussian integration in u direction
auto u_integrand = [&](double u) { auto u_integrand = [&](double u) {
// Find exact v intersections for each u // Find exact v intersections for each u
stl_vector_mp<double> v_intersections; stl_vector_mp<double> v_intersections;
stl_vector_mp<uint16_t> intersected_chains; stl_vector_mp<uint16_t> intersected_chains;
find_v_intersections_at_u(subface, param_plane, u, v_min, v_max, v_intersections, intersected_chains); find_v_intersections_at_u(subface, param_plane, u, v_min, v_max, v_intersections, intersected_chains);
@ -123,8 +121,8 @@ double integrator_t::calculate_one_subface(
double a = u_breaks[i]; double a = u_breaks[i];
double b = u_breaks[i + 1]; double b = u_breaks[i + 1];
double mid_u = (a + b) / 2.0; double mid_u = (a + b) / 2.0;
stl_vector_mp<double> v_intersections; stl_vector_mp<double> v_intersections;
stl_vector_mp<uint16_t> intersected_chains; stl_vector_mp<uint16_t> intersected_chains;
find_v_intersections_at_u(subface, param_plane, mid_u, v_min, v_max, v_intersections, intersected_chains); find_v_intersections_at_u(subface, param_plane, mid_u, v_min, v_max, v_intersections, intersected_chains);
@ -165,40 +163,39 @@ stl_vector_mp<double> integrator_t::compute_u_breaks(const parametric_plane_t& p
return stl_vector_mp<double>(break_set.begin(), break_set.end()); return stl_vector_mp<double>(break_set.begin(), break_set.end());
} }
void integrator_t::find_v_intersections_at_u(const subface& subface, void integrator_t::find_v_intersections_at_u(const subface& subface,
const parametric_plane_t& param_plane, const parametric_plane_t& param_plane,
const stl_vector_mp<Eigen::AlignedBox2d>& chain_bboxs, const stl_vector_mp<Eigen::AlignedBox2d>& chain_bboxs,
double u_val, double u_val,
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
} }
// 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;
} }
// Iterate over each edge in the chain (including closing edge if desired) // Iterate over each edge in the chain (including closing edge if desired)
for (size_t i = 0; i < n_vertices; ++i) { for (size_t i = 0; i < n_vertices; ++i) {
const Eigen::Vector2d& v1 = chain[i]; // Current vertex: (u1, v1) const Eigen::Vector2d& v1 = chain[i]; // Current vertex: (u1, v1)
const Eigen::Vector2d& v2 = chain[i + 1]; // Next vertex: (u2, v2) const Eigen::Vector2d& v2 = chain[i + 1]; // Next vertex: (u2, v2)
double u1 = v1.x(), v1_val = v1.y(); double u1 = v1.x(), v1_val = v1.y();
@ -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,
uint16_t chain_idx1,
bool is_edge_inside_domain(const stl_vector_mp<stl_vector_mp<uint16_t>> & chain_group_indices, uint16_t chain_idx2) const
uint16_t chain_idx1,
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

39
surface_integral/test/SurfaceIntegrator_test.cpp

@ -5,13 +5,14 @@
#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;
auto& chain_group_indices = plane.chain_group_indices; auto& chain_group_indices = plane.chain_group_indices;
auto& v_flags = plane.vertex_special_flags; auto& v_flags = plane.vertex_special_flags;
auto& e_flags = plane.edge_near_parallel_flags; auto& e_flags = plane.edge_near_parallel_flags;
int vertex_count = 4; int vertex_count = 4;
@ -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());
@ -45,7 +54,7 @@ void populate_parametric_plane(parametric_plane_t& plane) {
} }
v_flags.reverse(); v_flags.reverse();
dynamic_bitset_mp<> v_flags; dynamic_bitset_mp<> v_flags;
v_flags.set(0); v_flags.set(0);
v_flags.set(3); v_flags.set(3);
@ -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;
} }
@ -98,16 +106,15 @@ int main()
internal::sphere_face_t sphere_face; internal::sphere_face_t sphere_face;
stl_vector_mp<object_with_index_mapping<subface>> subfaces; stl_vector_mp<object_with_index_mapping<subface>> subfaces;
flat_hash_map_mp<uint32_t, parametric_plane_t> uv_planes; flat_hash_map_mp<uint32_t, parametric_plane_t> uv_planes;
subfaces.reserve(1); subfaces.reserve(1);
subfaces.emplace_back(&sphere_face, 0); subfaces.emplace_back(&sphere_face, 0);
uv_planes[0] = parametric_plane_t{}; uv_planes[0] = parametric_plane_t{};
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.}

39
surface_integral/test/test_mesh.cpp

@ -1,37 +1,44 @@
#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};
polymesh_t mesh; polymesh_t mesh;
mesh.vertices = verts; mesh.vertices = verts;
mesh.faces = face_data; mesh.faces = face_data;
mesh.vertex_counts = vertex_counts; mesh.vertex_counts = vertex_counts;
mesh.num_vertices = 8; mesh.num_vertices = 8;
mesh.num_faces = 6; mesh.num_faces = 6;
double area = compute_surface_area(&mesh); double area = compute_surface_area(&mesh);
double volume = compute_volume(&mesh); double volume = compute_volume(&mesh);
std::cout << "area: " << area << std::endl; // expected: 6 std::cout << "area: " << area << std::endl; // expected: 6
std::cout << "volume: " << volume << std::endl; // expected: 1 std::cout << "volume: " << volume << std::endl; // expected: 1
return 0; return 0;
} }

217
surface_integral/test/test_winding_number.cpp

@ -10,35 +10,42 @@
// ----------------------------- // -----------------------------
struct TestCase { struct TestCase {
std::string name; std::string name;
PolygonRing outer_ring; PolygonRing outer_ring;
std::vector<PolygonRing> holes; std::vector<PolygonRing> holes;
Point2d query_point; Point2d query_point;
bool expected; // true = inside, false = outside bool expected; // true = inside, false = outside
}; };
// ----------------------------- // -----------------------------
// 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,15 +56,16 @@ 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 = "";
// Validate outer ring orientation (should be CCW → positive area) // Validate outer ring orientation (should be CCW → positive area)
double outer_area = compute_ring_area(tc.outer_ring); double outer_area = compute_ring_area(tc.outer_ring);
if (outer_area <= 0) { if (outer_area <= 0) {
status += "Outer ring not CCW (area = " + std::to_string(outer_area) + ")"; status += "Outer ring not CCW (area = " + std::to_string(outer_area) + ")";
valid = false; valid = false;
} }
// Validate holes orientation (should be CW → negative area) // Validate holes orientation (should be CW → negative area)
@ -66,18 +74,16 @@ bool validate_test_case(const TestCase& tc) {
if (hole_area >= 0) { if (hole_area >= 0) {
if (!status.empty()) status += ", "; if (!status.empty()) status += ", ";
status += "Hole " + std::to_string(i) + " not CW (area = " + std::to_string(hole_area) + ")"; status += "Hole " + std::to_string(i) + " not CW (area = " + std::to_string(hole_area) + ")";
valid = false; valid = false;
} }
} }
if (valid) return true; if (valid) return true;
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
@ -242,15 +339,11 @@ int main() {
auto test_cases = create_test_cases(); auto test_cases = create_test_cases();
int passed = 0; int passed = 0;
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