From 435cb2bf9ea7dc23181f8cb5f10e0cd203c05a24 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 1 Aug 2024 13:48:01 -0600 Subject: [PATCH 01/25] Add the changes from the 2d paper figures --- src/axom/primal/CMakeLists.txt | 2 + src/axom/primal/operators/clip.hpp | 2 +- .../detail/intersect_bezier_impl.hpp | 93 +- .../operators/detail/winding_number_impl.hpp | 494 ++- src/axom/primal/operators/intersect.hpp | 58 +- src/axom/primal/operators/printers.hpp | 899 ++++++ src/axom/primal/operators/winding_number.hpp | 352 ++- src/axom/primal/tests/CMakeLists.txt | 1 + .../tests/primal_2d_paper_figure_data.cpp | 2802 +++++++++++++++++ 9 files changed, 4682 insertions(+), 21 deletions(-) create mode 100644 src/axom/primal/operators/printers.hpp create mode 100644 src/axom/primal/tests/primal_2d_paper_figure_data.cpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 329085b383..a395d5d76c 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -58,6 +58,8 @@ set( primal_headers operators/split.hpp operators/winding_number.hpp + operators/printers.hpp + operators/detail/clip_impl.hpp operators/detail/compute_moments_impl.hpp operators/detail/fuzzy_comparators.hpp diff --git a/src/axom/primal/operators/clip.hpp b/src/axom/primal/operators/clip.hpp index 9e3fd0473b..38b15ea4e1 100644 --- a/src/axom/primal/operators/clip.hpp +++ b/src/axom/primal/operators/clip.hpp @@ -119,7 +119,7 @@ Polygon clip(const Triangle& tri, const BoundingBox& bbox) * * \return A polygon of the subject polygon clipped against the clip polygon. * - * \note Function is based off the Sutherland–Hodgman algorithm. + * \note Function is based off the Sutherland�Hodgman algorithm. * * \warning Polygons with static array types must have enough vertices * preallocated for the output polygon. It is mandatory that diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index a87750c077..db0d25fc5f 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -19,6 +19,7 @@ #include "axom/primal/operators/intersect.hpp" #include "axom/primal/operators/detail/intersect_impl.hpp" +#include "axom/primal/operators/detail/intersect_ray_impl.hpp" #include @@ -68,7 +69,19 @@ bool intersect_bezier_curves(const BezierCurve &c1, double s_offset, double s_scale, double t_offset, - double t_scale); + double t_scale, + int &nevals); + +template +bool intersect_ray_bezier(const BezierCurve &c, + const BezierCurve &r, + std::vector &cp, + std::vector &rp, + double sq_tol, + int order, + double c_offset, + double c_scale, + int &nevals); /*! * \brief Tests intersection of two line segments defined by @@ -118,7 +131,8 @@ bool intersect_bezier_curves(const BezierCurve &c1, double s_offset, double s_scale, double t_offset, - double t_scale) + double t_scale, + int &nevals) { using BCurve = BezierCurve; @@ -148,7 +162,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, BCurve c3(order1); BCurve c4(order1); c1.split(splitVal, c3, c4); - + nevals += 1; s_scale *= scaleFac; // Note: we want to find all intersections, so don't short-circuit @@ -162,7 +176,8 @@ bool intersect_bezier_curves(const BezierCurve &c1, t_offset, t_scale, s_offset, - s_scale)) + s_scale, + nevals)) { foundIntersection = true; } @@ -176,7 +191,75 @@ bool intersect_bezier_curves(const BezierCurve &c1, t_offset, t_scale, s_offset + s_scale, - s_scale)) + s_scale, + nevals)) + { + foundIntersection = true; + } + } + + return foundIntersection; +} + +template +bool intersect_ray_bezier(const BezierCurve &c, + const Ray &r, + std::vector &cp, + std::vector &rp, + double sq_tol, + int order, + double c_offset, + double c_scale, + int &nevals) +{ + using BCurve = BezierCurve; + + // Check bounding boxe to short-circuit the intersection + T r0, s0, c0; + Point ip; + if(!intersect(r, c.boundingBox(), ip)) + { + return false; + } + + bool foundIntersection = false; + //desmos_print(c); + if(c.isLinear(sq_tol)) + { + Segment seg(c[0], c[order]); + + if(intersect(r, seg, r0, s0) && s0 <= 1.0 - 1e-8) + { + rp.push_back(r0); + cp.push_back(c_offset + c_scale * s0); + foundIntersection = true; + } + } + else + { + constexpr double splitVal = 0.5; + constexpr double scaleFac = 0.5; + + BCurve c1(order); + BCurve c2(order); + c.split(splitVal, c1, c2); + nevals += 1; + c_scale *= scaleFac; + + // Note: we want to find all intersections, so don't short-circuit + if(intersect_ray_bezier(c1, r, cp, rp, sq_tol, order, c_offset, c_scale, nevals)) + { + foundIntersection = true; + } + if(intersect_ray_bezier(c2, + r, + cp, + rp, + sq_tol, + order, + c_offset + c_scale, + c_scale, + nevals)) { foundIntersection = true; } diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index 2f3d652359..1fd8289dca 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -15,9 +15,13 @@ #include "axom/primal/operators/in_polygon.hpp" #include "axom/primal/operators/is_convex.hpp" #include "axom/primal/operators/squared_distance.hpp" +#include "axom/primal/operators/intersect.hpp" // C++ includes #include +#include +#include +#include // MFEM includes #ifdef AXOM_USE_MFEM @@ -98,8 +102,7 @@ double linear_winding_number(const Point& q, template double convex_endpoint_winding_number(const Point& q, const BezierCurve& c, - double edge_tol, - double EPS) + double edge_tol) { const int ord = c.getOrder(); if(ord == 1) @@ -110,7 +113,7 @@ double convex_endpoint_winding_number(const Point& q, double edge_tol_sq = edge_tol * edge_tol; // Verify that the shape is convex, and that the query point is at an endpoint - SLIC_ASSERT(is_convex(Polygon(c.getControlPoints()), EPS)); + SLIC_ASSERT(is_convex(Polygon(c.getControlPoints()), PRIMAL_TINY)); SLIC_ASSERT((squared_distance(q, c[0]) <= edge_tol_sq) || (squared_distance(q, c[ord]) <= edge_tol_sq)); @@ -144,7 +147,7 @@ double convex_endpoint_winding_number(const Point& q, // This means the bounding vectors are anti-parallel. // Parallel tangents can't happen with nontrivial convex control polygons - if((ord > 3) && axom::utilities::isNearlyEqual(tri_area, 0.0, EPS)) + if((ord > 3) && axom::utilities::isNearlyEqual(tri_area, 0.0, PRIMAL_TINY)) { for(int i = 1; i < ord; ++i) { @@ -157,7 +160,7 @@ double convex_endpoint_winding_number(const Point& q, // clang-format on // Because we are convex, a single non-collinear vertex tells us the orientation - if(!axom::utilities::isNearlyEqual(tri_area, 0.0, EPS)) + if(!axom::utilities::isNearlyEqual(tri_area, 0.0, PRIMAL_TINY)) { return (tri_area > 0) ? 0.5 : -0.5; } @@ -200,8 +203,8 @@ template double curve_winding_number_recursive(const Point& q, const BezierCurve& c, bool isConvexControlPolygon, - double edge_tol = 1e-8, - double EPS = 1e-8) + int& nevals, + double edge_tol = 1e-8) { const int ord = c.getOrder(); if(ord <= 0) @@ -221,7 +224,7 @@ double curve_winding_number_recursive(const Point& q, } // Use linearity as base case for recursion. - if(c.isLinear(EPS)) + if(c.isLinear(edge_tol)) { return linear_winding_number(q, c[0], c[ord], edge_tol); } @@ -234,12 +237,12 @@ double curve_winding_number_recursive(const Point& q, if(!isConvexControlPolygon) { - isConvexControlPolygon = is_convex(controlPolygon, EPS); + isConvexControlPolygon = is_convex(controlPolygon, PRIMAL_TINY); } else // Formulas for winding number only work if shape is convex { // Bezier curves are always contained in their convex control polygon - if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, EPS)) + if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, PRIMAL_TINY)) { return 0.0 - linear_winding_number(q, c[ord], c[0], edge_tol); } @@ -248,16 +251,481 @@ double curve_winding_number_recursive(const Point& q, if((squared_distance(q, c[0]) <= edge_tol * edge_tol) || (squared_distance(q, c[ord]) <= edge_tol * edge_tol)) { - return convex_endpoint_winding_number(q, c, edge_tol, EPS); + return convex_endpoint_winding_number(q, c, edge_tol); } } // Recursively split curve until query is outside some known convex region BezierCurve c1, c2; c.split(0.5, c1, c2); + nevals += 1; + + return curve_winding_number_recursive(q, + c1, + isConvexControlPolygon, + nevals, + edge_tol) + + curve_winding_number_recursive(q, c2, isConvexControlPolygon, nevals, edge_tol); +} + +template +double approxogon_winding_number(const Point& q, + const Polygon& approxogon, + double edge_tol) +{ + bool isOnEdge = false; + const int n = approxogon.numVertices(); + + // Catch some early edge cases + if(n <= 1) return 0.0; + + int integer_wn = winding_number(q, approxogon, isOnEdge, false, PRIMAL_TINY); + double closure_wn = + linear_winding_number(q, approxogon[n - 1], approxogon[0], edge_tol); + + if(isOnEdge) + { + // If on edge, can't use integer winding number. Have to compute it + // through winding number of each edge + + //std::cout << "on edge" << std::endl; + + // Main leg is oriented in the reverse + double wn = -closure_wn; + for(int i = 1; i < n; ++i) + { + wn += detail::linear_winding_number(q, + approxogon[i - 1], + approxogon[i], + edge_tol); + } + + return wn; + } + + return integer_wn - closure_wn; +} + +template +struct BezierCurveMemo +{ + bool isConvexControlPolygon; + BezierCurve curve; +}; + +struct PairHash +{ + using result_type = std::size_t; + + size_t operator()(const std::pair& p) const + { + // Combine hashes of the two integers + size_t hash1 = std::hash()(p.first); + size_t hash2 = std::hash()(p.second); + return hash1 ^ (hash2 << 1); // Simple combining function + } +}; + +//bool operator==(const std::pair& lhs, const std::pair& rhs) +//{ +// return lhs.first == rhs.first && lhs.second == rhs.second; +//} + +template +void winding_number_adaptive_linear_memoized( + Point q, + const std::vector>>& array_memo, + axom::FlatMap, BezierCurveMemo, PairHash>& hash_memo, + double edge_tol, + double linear_tol, + Polygon& approxogon, + std::stack>& curve_stack, + double& end_wn) +{ + constexpr int LEVEL = 3; + const int ord = array_memo[0][0].curve.getOrder(); + + //std::stack> curve_stack; + curve_stack.push(std::make_pair(0, 0)); + + while(!curve_stack.empty()) + { + std::pair the_pair = curve_stack.top(); + curve_stack.pop(); + + //std::cout << the_pair.first << std::endl; + + //std::cout << the_pair.first << " " << the_pair.second << std::endl; + BezierCurveMemo curve_memo; + if(the_pair.first < LEVEL) + curve_memo = array_memo[the_pair.first][the_pair.second]; + else + curve_memo = hash_memo[the_pair]; + + //if(curve_memo.isLinear) + //{ + // approxogon.addVertex(curve_memo.curve[0]); + // continue; + //} + + BoundingBox bBox(curve_memo.curve.boundingBox()); + if(!bBox.contains(q)) + { + approxogon.addVertex(curve_memo.curve[0]); + continue; + } + + if(curve_memo.isConvexControlPolygon) + { + constexpr bool includeBoundary = true; + constexpr bool useNonzeroRule = true; + + if(!in_polygon(q, + Polygon(curve_memo.curve.getControlPoints()), + includeBoundary, + useNonzeroRule, + PRIMAL_TINY)) + { + approxogon.addVertex(curve_memo.curve[0]); + + continue; + } + + if((squared_distance(q, curve_memo.curve[0]) <= edge_tol * edge_tol) || + (squared_distance(q, curve_memo.curve[ord]) <= edge_tol * edge_tol)) + { + end_wn += approxogon_winding_number(q, approxogon, edge_tol); + approxogon.clear(); + + end_wn += convex_endpoint_winding_number(q, curve_memo.curve, edge_tol); + approxogon.addVertex(curve_memo.curve[ord]); + continue; + } + } + + auto ref1 = std::make_pair(the_pair.first + 1, 2 * the_pair.second); + auto ref2 = std::make_pair(the_pair.first + 1, 2 * the_pair.second + 1); + + if(the_pair.first >= LEVEL - 1 && hash_memo.find(ref1) == hash_memo.end()) + { + BezierCurve c1, c2; + curve_memo.curve.split(0.5, c1, c2); + + hash_memo[ref1] = { + //c1.isLinear(linear_tol), + curve_memo.isConvexControlPolygon || + is_convex(Polygon(c1.getControlPoints()), PRIMAL_TINY), + c1}; + + hash_memo[ref2] = { + //c2.isLinear(linear_tol), + curve_memo.isConvexControlPolygon || + is_convex(Polygon(c2.getControlPoints()), PRIMAL_TINY), + c2}; + } + + curve_stack.push(ref2); + curve_stack.push(ref1); + } +} + +template +void winding_number_adaptive_linear(const Point& q, + const BezierCurve& c, + bool isConvexControlPolygon, + int& num_evals, + double edge_tol, + double linear_tol, + Polygon& approxogon, + double& end_wn) +{ + const int ord = c.getOrder(); + // If q is outside a convex shape that contains the entire curve, the winding + // number for the shape connected at the endpoints with straight lines is zero. + // We then subtract the contribution of this line segment. + + // Simplest convex shape containing c is its bounding box + BoundingBox bBox(c.boundingBox()); + if(!bBox.contains(q)) + { + // Don't need to bisect any further + //desmos_print(c); + return; + } + + // Use linearity as base case for recursion. + if(c.isLinear(linear_tol)) + { + // todo: why do we need to start over if we hit this case? + + //std::cout << "is linear case" << std::endl; + //std::cout << approxogon.numVertices() << std::endl; + //desmos_print(c); + + // Don't need to bisect any further, but we do need + // to start the polygon over, + //end_wn += approxogon_winding_number(q, approxogon, edge_tol); + //approxogon.clear(); + + // and use the direct formula for the line segment + //end_wn += linear_winding_number(q, c[0], c[ord], edge_tol); + //++num_evals; + //approxogon.addVertex(c[ord]); + return; + } + + // Check if our control polygon is convex. + // If so, all subsequent control polygons will be convex as well + Polygon controlPolygon(c.getControlPoints()); + const bool includeBoundary = true; + const bool useNonzeroRule = true; + + if(!isConvexControlPolygon) + { + isConvexControlPolygon = is_convex(controlPolygon, PRIMAL_TINY); + } + + // Formulas for winding number only work if shape is convex + if(isConvexControlPolygon) + { + // Bezier curves are always contained in their convex control polygon + if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, PRIMAL_TINY)) + { + // Don't need to bisect any further + //desmos_print(c); + return; + } + + // If the query point is at either endpoint, use direct formula + if(squared_distance(q, c[0]) <= edge_tol * edge_tol || + squared_distance(q, c[ord]) <= edge_tol * edge_tol) + { + //std::cout << approxogon << std::endl; + + // Need to start the polygon over, + end_wn += approxogon_winding_number(q, approxogon, edge_tol); + std::cout << "using endpoint formula" << std::endl; + approxogon.clear(); + + // and use the direct formula for the endpoint + end_wn += convex_endpoint_winding_number(q, c, edge_tol); + + approxogon.addVertex(c[ord]); + //desmos_print(c); + return; + } + } + + // Do a single iteration of Newton's Method to get a better guess than 0.5 + //double split_val = 0.5; + //Point eval; + //Vector Dt, DtDt; + + //for(int i = 0; i < 15; ++i) + //{ + // c.evaluate_second_derivative(split_val, eval, Dt, DtDt); + + // Vector q_eval(q, eval); + // if(q_eval.squared_norm() < edge_tol * edge_tol) break; + + // split_val = axom::utilities::clampVal( + // split_val - Dt.dot(q_eval) / (Dt.dot(Dt) + DtDt.dot(q_eval)), + // 0.0, + // 1.0); + //} + + // Recursively split curve until query is outside some known convex region + BezierCurve c1, c2; + c.split(0.5, c1, c2); + + // clang-format off + winding_number_adaptive_linear(q, c1, isConvexControlPolygon, num_evals, edge_tol, linear_tol, approxogon, end_wn); + + //std::cout << t1 << " " << c2[0] << c.evaluate( t1 ) << std::endl; + approxogon.addVertex(c2[0]); + ++num_evals; + + + winding_number_adaptive_linear(q, c2, isConvexControlPolygon, num_evals, edge_tol, linear_tol, approxogon, end_wn); + // clang-format on + + return; +} + +template +int ray_casting_bezier_clipping(const BezierCurve& curve, + const Ray& ray, + int& nevals, + double tol = 1E-8) +{ + const int ord = curve.getOrder(); + const bool isRational = curve.isRational(); + + // Make an array of curves to append to later + axom::Array> curves(0); + curves.push_back(curve); + int inter = 0; + + while(!curves.empty()) + { + BezierCurve the_curve = curves[curves.size() - 1]; + curves.erase(curves.end() - 1); + + Point origin = ray.origin(); + Vector e1 = ray.direction().unitVector(); + Vector e2 = Vector {-e1[1], e1[0]}; + + // Iterate over the control points and find the coordinates + // in terms of e1 and e2 + + int8_t flag = ~0; + for(int p = 0; flag && p <= ord; ++p) + { + auto pt_vec = Vector(origin, the_curve[p]); + double alpha = pt_vec.dot(e1); + double beta = pt_vec.dot(e2); + + if(alpha > 0 && beta > 0) // quad 1 + { + // Turn off bits 1, 2, 3, 5, 6 + flag &= 0x89; + } + else if(alpha < 0 && beta > 0) // quad 2 + { + // Turn off bits 0, 2, 3, 6, 7 + flag &= 0x4C; + } + else if(alpha < 0 && beta < 0) + { + // Turn off bits 0, 1, 3, 4, 7 + flag &= 0x26; + } + else if(alpha > 0 && beta < 0) + { + // Turn off bits 0, 1, 2, 4, 5 + flag &= 0x13; + } + } + + if(flag == 0x0) + { + // Can't check intersections if the curve isn't convex + Polygon controlPolygon(the_curve.getControlPoints()); + if(!is_convex(controlPolygon, tol)) + { + BezierCurve c1, c2; + the_curve.split(0.5, c1, c2); + curves.push_back(c1); + curves.push_back(c2); + nevals += 1; + continue; + } + + BoundingBox bb = the_curve.boundingBox(); + double size = axom::utilities::max(bb.range()[0], bb.range()[1]); + + if(size < tol) + { + std::cout << "is On the curve lol" << std::endl; + return 1; + } + + double a = e1[1]; + double b = -e1[0]; + double c = e1[0] * origin[1] - e1[1] * origin[0]; + double u, tmp; + + // Do a very sloppy way of computing intersections with the convex hull + double umin = 1.0, umax = 0.0; + Ray axis(Point {0.0, 0.0}, Vector {1.0, 0.0}); + + // Find the maximum and minimum coordinates of all the intersections. + for(double p = 0; p < ord; ++p) + { + double R0 = a * the_curve[p][0] + b * the_curve[p][1] + c; + double R1 = a * the_curve[p + 1][0] + b * the_curve[p + 1][1] + c; + + if(R0 * R1 > 0) continue; + + if(the_curve.isRational()) + { + R0 *= the_curve.getWeight(p); + R1 *= the_curve.getWeight(p + 1); + } + + // Compute intersection between the ray and the segment + Segment seg(Point {p / ord, R0}, + Point {(p + 1) / ord, R1}); + intersect(axis, seg, u, tmp, 0.0); + + umin = axom::utilities::min(umin, u); + umax = axom::utilities::max(umax, u); + } + + // Do the final check to see if the ray intersects the curve + double R0 = a * the_curve[ord][0] + b * the_curve[ord][1] + c; + double R1 = a * the_curve[0][0] + b * the_curve[0][1] + c; + + if(R0 * R1 < 0) + { + if(the_curve.isRational()) + { + R0 *= the_curve.getWeight(ord); + R1 *= the_curve.getWeight(0); + } + + // Compute intersection between the ray and the segment + Segment seg(Point {1.0, R0}, Point {0.0, R1}); + intersect(axis, seg, u, tmp, 0.0); + + umin = axom::utilities::min(umin, u); + umax = axom::utilities::max(umax, u); + } + + // Make minor numerical adjustments + umin = 0.99 * umin; + umax = 0.99 * umax + 0.01; + + // Heuristic to account for multiple intersections + if(umax - umin > 0.8) + { + BezierCurve c1, c2; + the_curve.split(0.5, c1, c2); + curves.push_back(c1); + curves.push_back(c2); + nevals += 1; + } + else + { + // Now we have the min and max, so we can split the curve + BezierCurve c1, c2, c3; + the_curve.split(umin, c1, c2); + c2.split((umax - umin) / (1.0 - umin), c2, c3); + + nevals += 2; + curves.push_back(c1); + curves.push_back(c2); + curves.push_back(c3); + } + } + else if(flag == 0x1) + { + //std::cout << "Case B: Maybe intersections" << std::endl; + double alpha1 = Vector(origin, the_curve[0]).dot(e2); + double alpha2 = Vector(origin, the_curve[ord]).dot(e2); + + if(alpha1 * alpha2 < 0) + { + (alpha1 < 0) ? inter++ : inter--; + } + } + //else + //{ + // std::cout << "Case A: No intersections" << std::endl; + //} + } - return curve_winding_number_recursive(q, c1, isConvexControlPolygon, edge_tol, EPS) + - curve_winding_number_recursive(q, c2, isConvexControlPolygon, edge_tol, EPS); + return inter; } /// Type to indicate orientation of singularities relative to surface diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index d7f5ef96e6..9d1cad446d 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -513,6 +513,7 @@ bool intersect(const BezierCurve& c1, const BezierCurve& c2, std::vector& sp, std::vector& tp, + int& nevals, double tol = 1E-8) { const double offset = 0.; @@ -531,7 +532,62 @@ bool intersect(const BezierCurve& c1, offset, scale, offset, - scale); + scale, + nevals); +} + +/*! + * \brief Tests if Bezier Curve \a c and ray \a r intersect. + * \return status true iff \a c intersects \a r, otherwise false. + * + * \param [in] c the BezierCurve, parametrized in [0,1) + * \param [in] r the Ray, parametrized in [0,inf) + * \param [out] cp vector of parameter space intersection points for \a c + * \param [out] rp vector of parameter space intersection points for \a r + * \param [in] tol tolerance parameter for determining if a curve can + * be approximated by a line segment. + * \return True if the curve and line intersect, false otherwise. Intersection + * parameters are stored in \a sp and \a tp + * + * Finds all intersection points between the curve and the ray. + * + * \note This function assumes two dimensional curves and rays in a plane. + * + * \note This function assumes that the curves are in general position. + * Specifically, we assume that all intersections are at points and that + * the curves don't overlap. + * + * \note This function assumes the all intersections have multiplicity + * one, i.e. there are no points at which the curves and their derivatives + * both intersect. Thus, the function does not find tangencies. + * + * \note This function assumes that the curves are half-open, i.e. they + * contain their first endpoint, but not their last endpoint. Thus, the + * curves do not intersect at \f$ s==1 \f$ or at \f$ t==1 \f$. + */ +template +bool intersect(const BezierCurve& c, + const Ray& r, + std::vector& cp, + std::vector& rp, + int& nevals, + double tol = 1E-8) +{ + const double offset = 0.; + const double scale = 1.; + + // for efficiency, linearity check actually uses a squared tolerance + const double sq_tol = tol * tol; + + return detail::intersect_ray_bezier(c, + r, + cp, + rp, + sq_tol, + c.getOrder(), + offset, + scale, + nevals); } /// @} diff --git a/src/axom/primal/operators/printers.hpp b/src/axom/primal/operators/printers.hpp new file mode 100644 index 0000000000..fc9dd3ffce --- /dev/null +++ b/src/axom/primal/operators/printers.hpp @@ -0,0 +1,899 @@ +// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \file printers.hpp + * + * \brief Consists of jacob's neat little debugging print statements + * + * \note If you see this in a repo, it reflects a failure on jacob's part. + * Reprimand him for his error. + */ + +#ifndef PRIMAL_PRINTERS_HPP_ +#define PRIMAL_PRINTERS_HPP_ + +// Axom includes +#include "axom/config.hpp" +#include "axom/primal.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/BezierPatch.hpp" +#include "axom/primal/geometry/OrientedBoundingBox.hpp" + +#include +#include +#include +#include +#include + +namespace axom +{ +namespace primal +{ + +template +void python_print(const Point& point, const char* plot_lines = nullptr) +{ + printf("plot_query( fig, ax, (%.17f, %.17f, %.17f), ", + point[0], + point[1], + point[2]); + if(plot_lines) printf("plot_lines=\"%s\", ", plot_lines); + printf("size=111)\n\n"); +} + +template +void python_print(const Vector& vec, + const Point& point, + const char* color = "blue") +{ + printf("plot_vector( fig, ax, (%.17f, %.17f, %.17f), ", vec[0], vec[1], vec[2]); + printf("origin=(%.17f, %.17f, %.17f),", point[0], point[1], point[2]); + printf("color=\"%s\")\n", color); +} + +template +void desmos_print(const Point& point) +{ + printf("Q = [%.17f, %.17f, %.17f]\n", point[0], point[1], point[2]); +} + +template +void desmos_print(const Point& point) +{ + printf("Q = (%.17f, %.17f)\n", point[0], point[1]); +} + +template +void python_print(const BezierCurve& curve, + bool plot_tangent = false, + bool plot_endpoints = false, + char color = 'k') +{ + const int ord = curve.getOrder(); + + printf("my_BezierCurve( ["); + printf("(%.17f,%.17f,%.17f)", curve[0][0], curve[0][1], curve[0][2]); + for(int i = 1; i <= ord; ++i) + printf(",(%.17f,%.17f,%.17f)", curve[i][0], curve[i][1], curve[i][2]); + if(curve.isRational()) + { + printf(" ], [%.17f", curve.getWeight(0)); + if(curve.isRational()) + for(int i = 1; i <= ord; ++i) printf(",%.17f", curve.getWeight(i)); + } + printf("] ).plot( fig, ax, None, '%c'", color); + if(plot_tangent) printf(", plot_tangent=True"); + if(plot_endpoints) printf(", plot_endpoints=True"); + printf(" )\n\n"); +} + +template +void desmos_print(const BezierCurve& curve, int num = 0) +{ + printf("C(P_{%d}, W_{%d})\n", num, num); + printf("P_{%d} = [(%.17f,%.17f)", num, curve[0][0], curve[0][1]); + for(int p = 1; p <= curve.getOrder(); ++p) + printf(",(%.17f,%.17f)", curve[p][0], curve[p][1]); + printf("]\n"); + + if(curve.isRational()) + { + printf("W_{%d} = [%.17f", num, curve.getWeight(0)); + for(int p = 1; p <= curve.getOrder(); ++p) + printf(",%.17f", curve.getWeight(p)); + printf("]\n"); + } + else + { + printf("W_{%d} = [1", num); + for(int p = 1; p <= curve.getOrder(); ++p) printf(",1"); + printf("]\n"); + } +} + +template +void desmos_print(const BezierCurve& curve) +{ + const int ord = curve.getOrder(); + for(int i = 0; i <= ord; ++i) + { + printf("P_{%d} = [%.17f, %.17f, %.17f]\n", + i + 1, + curve[i][0], + curve[i][1], + curve[i][2]); + } + + if(curve.isRational()) + { + printf("W = [%.17f", curve.getWeight(0)); + for(int i = 1; i <= ord; ++i) + { + printf(", %.17f", curve.getWeight(i)); + } + printf("]\n"); + } +} + +template +void python_print(const BezierPatch& patch, + const char* cmap = "cm.Reds", + char cpcolor = '\0', + bool plot_normal = false) +{ + const int ord_u = patch.getOrder_u(); + const int ord_v = patch.getOrder_v(); + + printf("my_BezierPatch(%d, %d, [", ord_u, ord_v); + printf("(%.17f,%.17f,%.17f)", patch(0, 0)[0], patch(0, 0)[1], patch(0, 0)[2]); + for(int i = 0; i <= ord_u; ++i) + for(int j = (i == 0 ? 1 : 0); j <= ord_v; ++j) + printf(",(%.17f,%.17f,%.17f)", + patch(i, j)[0], + patch(i, j)[1], + patch(i, j)[2]); + if(patch.isRational()) + { + printf(" ], [%.17f", patch.getWeight(0, 0)); + if(patch.isRational()) + for(int i = 0; i <= ord_u; ++i) + for(int j = (i == 0 ? 1 : 0); j <= ord_v; ++j) + printf(",%.17f", patch.getWeight(i, j)); + } + printf("] ).plot( fig, ax, %s", cmap); + if(plot_normal) printf(", plot_normal=True"); + if(cpcolor != '\0') printf(", cpcolor='%c'", cpcolor); + printf(")\n\n"); +} + +template +void desmos_print(const BezierPatch& patch) +{ + // clang-format off + const int ord_u = patch.getOrder_u(); + const int ord_v = patch.getOrder_v(); + + printf("M = %d\n", ord_u); + printf("N = %d\n", ord_v); + + // Print u basis functions + for(int u = 0; u <= ord_u; ++u) + printf("B_{%dM}(u) = nCr\\left(M, %d\\right)(1 - u)^{(M - %d)}u^{%d}\n", u + 1, u, u, u); + + // Print v basis functions + for(int v = 0; v <= ord_v; ++v) + printf("B_{%dN}(v) = nCr\\left(N, %d\\right)(1 - v)^{(N - %d)}v^{%d}\n", v + 1, v, v, v); + + // ===================== Print weight ====================== + printf("w(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nw_{u}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nw_{v}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nw_{uu}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nw_{vv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nw_{uv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + // ===================== Print X ====================== + printf("\nX(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nX_{u}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nX_{v}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nX_{uu}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nX_{vv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nX_{uv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + // ===================== Print Y ====================== + printf("\nY(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nY_{u}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nY_{v}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nY_{uu}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nY_{vv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nY_{uv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + // ===================== Print Z ====================== + printf("\nZ(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nZ_{u}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nZ_{v}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nZ_{uu}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nZ_{vv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nZ_{uv}(u, v) = "); + for(int u = 0; u <= ord_u; ++u) + for(int v = 0; v <= ord_v; ++v) + { + printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); + if(u != ord_u || v != ord_v) printf(" + "); + } + + printf("\nP(u, v) = [X(u, v), Y(u, v), Z(u, v)]"); + printf("\nP_{u}(u, v) = [X_{u}(u, v), Y_{u}(u, v), Z_{u}(u, v)]"); + printf("\nP_{v}(u, v) = [X_{v}(u, v), Y_{v}(u, v), Z_{v}(u, v)]"); + printf("\nP_{uu}(u, v) = [X_{uu}(u, v), Y_{uu}(u, v), Z_{uu}(u, v)]"); + printf("\nP_{vv}(u, v) = [X_{vv}(u, v), Y_{vv}(u, v), Z_{vv}(u, v)]"); + printf("\nP_{uv}(u, v) = [X_{uv}(u, v), Y_{uv}(u, v), Z_{uv}(u, v)]"); + + printf("\nS(u, v) = \\frac{P(u, v)}{w(u, v)}"); + printf("\nS_u(u, v) = \\frac{P_{u}(u, v) - S(u, v)w_{u}(u, v)}{w(u, v)}"); + printf("\nS_v(u, v) = \\frac{P_{v}(u, v) - S(u, v)w_{v}(u, v)}{w(u, v)}"); + printf("\nS_{uu}(u, v) = \\frac{P_{uu}(u, v) - 2S_u(u, v)w_u(u, v) - S(u, v)w_{uu}(u, v)}{w(u, v)}"); + printf("\nS_{vv}(u, v) = \\frac{P_{vv}(u, v) - 2S_v(u, v)w_v(u, v) - S(u, v)w_{vv}(u, v)}{w(u, v)}"); + printf("\nS_{uv}(u, v) = \\frac{P_{uv}(u, v) - S_u(u, v)w_v(u, v) - S_v(u, v)w_u(u, v) - S(u, v)w_{uv}(u, v)}{w(u, v)}"); + + printf("\ns = 0.0000000001"); + printf("\nu_0 = "); + printf("\nv_0 = "); + + printf("\nS_{uu}(u_0, v_0)[1]"); + printf("\nS_{uu}(u_0, v_0)[2]"); + printf("\nS_{uu}(u_0, v_0)[3]"); + + printf("\nS_{vv}(u_0, v_0)[1]"); + printf("\nS_{vv}(u_0, v_0)[2]"); + printf("\nS_{vv}(u_0, v_0)[3]"); + + printf("\nS_{uv}(u_0, v_0)[1]"); + printf("\nS_{uv}(u_0, v_0)[2]"); + printf("\nS_{uv}(u_0, v_0)[3]"); + printf("\n\n"); + for(int v = 0; v <= ord_v; ++v) + { + printf("W_{%d} = [%f", v + 1, patch.getWeight(0, v)); + for(int u = 1; u <= ord_u; ++u) + { + printf(", %f", patch.getWeight(u, v)); + } + printf("]\nX_{%d} = [%f", v + 1, patch(0, v)[0]); + for(int u = 1; u <= ord_u; ++u) + { + printf(", %f", patch(u, v)[0]); + } + printf("]\nY_{%d} = [%f", v + 1, patch(0, v)[1]); + for(int u = 1; u <= ord_u; ++u) + { + printf(", %f", patch(u, v)[1]); + } + printf("]\nZ_{%d} = [%f", v + 1, patch(0, v)[2]); + for(int u = 1; u <= ord_u; ++u) + { + printf(", %f", patch(u, v)[2]); + } + printf("]\n"); + } + // clang-format on +} + +template +void python_print(const OrientedBoundingBox oBox, + const char* color = "blue") +{ + auto verts = oBox.vertices(); + printf("plot_box( fig, ax, ["); + printf("(%.17f,%.17f,%.17f)", verts[0][0], verts[0][1], verts[0][2]); + for(int i = 1; i < verts.size(); ++i) + printf(",(%.17f,%.17f,%.17f)", verts[i][0], verts[i][1], verts[i][2]); + printf("], color='%s' )\n\n", color); +} + +template +void python_print(const OrientedBoundingBox oBox, + Lambda rotate_point, + numerics::Matrix rotator, + const char* color = "blue") +{ + auto verts = oBox.vertices(); + printf("plot_box( fig, ax, ["); + auto rot_point = rotate_point(rotator, verts[0]); + printf("(%.17f,%.17f,%.17f)", rot_point[0], rot_point[1], rot_point[2]); + for(int i = 1; i < verts.size(); ++i) + { + rot_point = rotate_point(rotator, verts[i]); + printf(",(%.17f,%.17f,%.17f)", rot_point[0], rot_point[1], rot_point[2]); + } + printf("], color='%s' )\n\n", color); +} + +template +void python_print(const BoundingBox& bBox, const char* color = "blue") +{ + std::vector> verts; + BoundingBox::getPoints(bBox, verts); + printf("plot_bounding_box( fig, ax, ["); + printf("(%.17f,%.17f,%.17f)", verts[0][0], verts[0][1], verts[0][2]); + for(int i = 1; i < verts.size(); ++i) + printf(",(%.17f,%.17f,%.17f)", verts[i][0], verts[i][1], verts[i][2]); + printf("], color='%s' )\n", color); +} + +template +void python_print(const Polygon& poly, bool closed = true) +{ + printf("plot_polygon(fig, ax, ["); + printf("(%.17f,%.17f,%.17f)", poly[0][0], poly[0][1], poly[0][2]); + for(int i = 1; i < poly.numVertices(); ++i) + printf(",(%.17f,%.17f,%.17f)", poly[i][0], poly[i][1], poly[i][2]); + printf("]"); + if(!closed) printf(", closed=False"); + printf(")\n"); +} + +template +void desmos_print(const Polygon& poly) +{ + printf("polygon("); + printf("(%.17f,%.17f)", poly[0][0], poly[0][1]); + for(int i = 1; i < poly.numVertices(); ++i) + printf(",(%.17f,%.17f)", poly[i][0], poly[i][1]); + printf(")\n"); +} + +template +void convert_from_svg(std::string filename, axom::Array>& curves) +{ + // Get regex pattern for all path elements + std::regex pattern("<\\s*path[^>]*\\bd\\s*=\\s*[\"']([^\"']+)\"[^>]*>"); + + // Read in file + std::ifstream svg_file(filename); + + if(!svg_file.is_open()) + { + std::cerr << "Failed to open the SVG file." << std::endl; + return; // Exit with an error code + } + + std::string input((std::istreambuf_iterator(svg_file)), + std::istreambuf_iterator()); + + std::sregex_iterator iter(input.begin(), input.end(), pattern); + std::sregex_iterator end; + + while(iter != end) + { + std::smatch match = *iter; + std::string dAttribute = match[1].str(); + + // Replace all ',' with ' ' + dAttribute = + std::regex_replace(dAttribute, std::regex(","), std::string(" ")); + + // Put a space before and after every letter + dAttribute = std::regex_replace(dAttribute, + std::regex("([a-zA-Z])"), + std::string(" $1 ")); + + char command = '\0'; + double init_x = 0.0, init_y = 0.0; + double curr_x = 0.0, curr_y = 0.0; + double x1, y1, x2, y2, x, y; + double dx1, dy1, dx2, dy2, dx, dy; + + // Iterate over words in the string + std::istringstream iss(dAttribute); + std::string word; + + while(iss >> word) + { + // Check if the word is a command + if(isalpha(word[0])) + { + command = word[0]; + if(command == 'Z' || command == 'z') + { + axom::Array> nodes = {Point {curr_x, curr_y}, + Point {init_x, init_y}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 1)); + + // Update the current position + curr_x = init_x; + curr_y = init_y; + } + } + else + { + // Check if the command is a move command + if(command == 'M' || command == 'm') + { + // If the command is relative, add the value to the current position + if(command == 'm') + { + dx = std::stod(word); + iss >> dy; + curr_x += dx; + curr_y += dy; + command = 'l'; + } + else + { + x = std::stod(word); + iss >> y; + curr_x = x; + curr_y = y; + command = 'L'; + } + + // Set the initial position + init_x = curr_x; + init_y = curr_y; + } + else if(command == 'L' || command == 'l') + { + // If the command is relative, add the value to the current position + if(command == 'l') + { + dx = std::stod(word); + iss >> dy; + axom::Array> nodes = { + Point {curr_x, curr_y}, + Point {curr_x + dx, curr_y + dy}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 1)); + + // Update the current position + curr_x += dx; + curr_y += dy; + } + else + { + x = std::stod(word); + iss >> y; + axom::Array> nodes = {Point {curr_x, curr_y}, + Point {x, y}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 1)); + + // Update the current position + curr_x = x; + curr_y = y; + } + } + else if(command == 'C' || command == 'c') + { + // If the command is relative, add the value to the current position + if(command == 'c') + { + dx1 = std::stod(word); + iss >> dy1 >> dx2 >> dy2 >> dx >> dy; + axom::Array> nodes = { + Point {curr_x, curr_y}, + Point {curr_x + dx1, curr_y + dy1}, + Point {curr_x + dx2, curr_y + dy2}, + Point {curr_x + dx, curr_y + dy}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 3)); + + // Update the current position + curr_x += dx; + curr_y += dy; + } + else + { + x1 = std::stod(word); + iss >> y1 >> x2 >> y2 >> x >> y; + axom::Array> nodes = {Point {curr_x, curr_y}, + Point {x1, y1}, + Point {x2, y2}, + Point {x, y}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 3)); + + // Update the current position + curr_x = x; + curr_y = y; + } + } + else if(command == 'H' || command == 'h') + { + // If the command is relative, add the value to the current position + if(command == 'h') + { + dx = std::stod(word); + axom::Array> nodes = {Point {curr_x, curr_y}, + Point {curr_x + dx, curr_y}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 1)); + + // Update the current position + curr_x += dx; + } + else + { + x = std::stod(word); + axom::Array> nodes = {Point {curr_x, curr_y}, + Point {x, curr_y}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 1)); + + // Update the current position + curr_x = x; + } + } + else if(command == 'V' || command == 'v') + { + // If the command is relative, add the value to the current position + if(command == 'v') + { + dy = std::stod(word); + axom::Array> nodes = {Point {curr_x, curr_y}, + Point {curr_x, curr_y + dy}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 1)); + + // Update the current position + curr_y += dy; + } + else + { + y = std::stod(word); + axom::Array> nodes = {Point {curr_x, curr_y}, + Point {curr_x, y}}; + + // Add a line segment + curves.push_back(BezierCurve(nodes, 1)); + + // Update the current position + curr_y = y; + } + } + else + { + std::cout << word << " is an unrecognized command. Whoopsie!" + << std::endl; + } + } + } + ++iter; + } + + svg_file.close(); +} + +template +BoundingBox curves_bbox(axom::Array>& curves, + double scale_factor = 1.0, + bool make_square = false) +{ + // Find maximum and minimum x and y values + double min_x = curves[0][0][0], min_y = curves[0][0][1]; + double max_x = curves[0][0][0], max_y = curves[0][0][1]; + + for(auto& curve : curves) + { + for(int p = 0; p <= curve.getOrder(); ++p) + { + min_x = axom::utilities::min(min_x, curve[p][0]); + max_x = axom::utilities::max(max_x, curve[p][0]); + + min_y = axom::utilities::min(min_y, curve[p][1]); + max_y = axom::utilities::max(max_y, curve[p][1]); + } + } + + // Put those in a bounding box, then expand it + primal::Point bounds[2] = {primal::Point {min_x, min_y}, + primal::Point {max_x, max_y}}; + primal::BoundingBox bb(bounds, 2); + bb.scale(scale_factor); + + if(make_square) + { + int max_dim = bb.getLongestDimension(); + double max_len = bb.getMax()[max_dim] - bb.getMin()[max_dim]; + + primal::Point centroid = bb.getCentroid(); + + primal::Point new_min {centroid[0] - max_len / 2.0, + centroid[1] - max_len / 2.0}; + primal::Point new_max {centroid[0] + max_len / 2.0, + centroid[1] + max_len / 2.0}; + + bb.addPoint(new_min); + bb.addPoint(new_max); + } + + return bb; +} + +template +void simple_grid_test(axom::Array>& curves, + const BoundingBox& bb, + int npts_x, + int npts_y, + std::ofstream& wn_out) +{ + for(int xi = 0; xi < npts_x; ++xi) + { + double x = bb.getMin()[0] + xi * (bb.getMax()[0] - bb.getMin()[0]) / npts_x; + + //std::cout << x << std::endl; + printLoadingBar((x - bb.getMin()[0]) / (bb.getMax()[0] - bb.getMin()[0]) * 100, + 100); + for(int yi = 0; yi < npts_y; ++yi) + { + double y = bb.getMin()[1] + yi * (bb.getMax()[1] - bb.getMin()[1]) / npts_y; + primal::Point query({x, y}); + + int nevals = 0; + + double wn = 0.0; + for(int i = 0; i < curves.size(); ++i) + { + wn += primal::winding_number(query, curves[i], nevals, 1e-16, 1e-16); + } + + wn_out << x << "," << y << "," << wn << std::endl; + } + } +} + +template +void simple_grid_test_memoized( + axom::Array, + primal::detail::BezierCurveMemo, + primal::detail::PairHash>>& marray, + const BoundingBox& bb, + int npts_x, + int npts_y, + std::ofstream& wn_out) +{ + primal::Polygon temp_approxogon(20); + + for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; + x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) + { + //std::cout << x << std::endl; + printLoadingBar((x - bb.getMin()[0]) / (bb.getMax()[0] - bb.getMin()[0]) * 100, + 100); + for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; + y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double wn = 0.0; + + wn += primal::winding_number_approxogon_memoized(query, + marray, + temp_approxogon, + 1e-16); + + wn_out << x << "," << y << "," << wn << std::endl; + } + } +} + +template +void simple_timing_test(axom::Array>& curves, + const BoundingBox& bb, + int npts_x, + int npts_y, + std::ofstream& wn_out) +{ + for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; + x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) + { + //std::cout << x << std::endl; + printLoadingBar((x - bb.getMin()[0]) / (bb.getMax()[0] - bb.getMin()[0]) * 100, + 100); + for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; + y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double wn = 0.0; + for(int i = 0; i < curves.size(); ++i) + { + wn += primal::winding_number(query, curves[i], nevals, 1e-16, 1e-16); + } + + wn_out << x << "," << y << "," << wn << std::endl; + } + } +} +inline void printLoadingBar(int progress, int total, int barWidth = 40) +{ + float percentage = static_cast(progress) / total; + int progressWidth = static_cast(percentage * barWidth); + + std::cout << "["; + for(int i = 0; i < progressWidth; ++i) std::cout << "="; + for(int i = progressWidth; i < barWidth; ++i) std::cout << "-"; + std::cout << "] " << std::setw(3) << static_cast(percentage * 100.0) + << "%\r"; + std::cout.flush(); +} + +} // namespace primal + +} // namespace axom + +#endif diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index eb954a1556..b92b96219c 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -109,16 +109,19 @@ int winding_number(const Point& q, template int winding_number(const Point& R, const Polygon& P, + bool& isOnEdge, bool includeBoundary = false, double EPS = 1e-8) { const int nverts = P.numVertices(); + isOnEdge = false; // If the query is a vertex, return a value interpreted // as "inside" by evenodd or nonzero protocols if(axom::utilities::isNearlyEqual(P[0][0], R[0], EPS) && axom::utilities::isNearlyEqual(P[0][1], R[1], EPS)) { + isOnEdge = true; return includeBoundary; } @@ -131,10 +134,12 @@ int winding_number(const Point& R, { if(axom::utilities::isNearlyEqual(P[j][0], R[0], EPS)) { + isOnEdge = true; return includeBoundary; // On vertex } else if(P[i][1] == R[1] && ((P[j][0] > R[0]) == (P[i][0] < R[0]))) { + isOnEdge = true; return includeBoundary; // On horizontal edge } } @@ -158,6 +163,7 @@ int winding_number(const Point& R, // On edge if(axom::utilities::isNearlyEqual(det, 0.0, EPS)) { + isOnEdge = true; return includeBoundary; } @@ -180,6 +186,7 @@ int winding_number(const Point& R, // On edge if(axom::utilities::isNearlyEqual(det, 0.0, EPS)) { + isOnEdge = true; return includeBoundary; } @@ -196,6 +203,21 @@ int winding_number(const Point& R, return winding_num; } +/*! + * \brief Computes the solid angle winding number for a 2D polygon + * + * Overload function without additional returning parameter + */ +template +int winding_number(const Point& R, + const Polygon& P, + bool includeBoundary = false, + double EPS = 1e-8) +{ + bool isOnEdge = false; + return winding_number(R, P, isOnEdge, includeBoundary, EPS); +} + /*! * \brief Computes the generalized winding number for a single 2D Bezier curve * @@ -212,10 +234,218 @@ int winding_number(const Point& R, template double winding_number(const Point& q, const BezierCurve& c, + int& nevals, double edge_tol = 1e-8, double EPS = 1e-8) { - return detail::curve_winding_number_recursive(q, c, false, edge_tol, EPS); + return detail::curve_winding_number_recursive(q, c, false, nevals, edge_tol); + const int ord = c.getOrder(); + if(ord <= 0) return 0.0; + Polygon approxogon(2); + approxogon.addVertex(c[0]); + + int dummy_int = 0; + + double wn = 0.0; + //std::cout << "------------" << std::endl; + detail::winding_number_adaptive_linear(q, + c, + false, + dummy_int, + edge_tol, + edge_tol, + approxogon, + wn); + //std::cout << "------------" << std::endl; + approxogon.addVertex(c[ord]); + + //std::cout << approxogon.numVertices() << std::endl; + + return wn + detail::approxogon_winding_number(q, approxogon, edge_tol); +} + +template +double winding_number_quad(const Point& q, + const BezierCurve& c, + int npts) +{ + static mfem::IntegrationRules my_IntRules(0, mfem::Quadrature1D::GaussLegendre); + const mfem::IntegrationRule& quad = + my_IntRules.Get(mfem::Geometry::SEGMENT, 2 * npts - 1); + + double quad_result = 0.0; + for(int k = 0; k < quad.GetNPoints(); ++k) + { + Point x_quad = c.evaluate(quad.IntPoint(k).x); + Vector dx_quad = c.dt(quad.IntPoint(k).x); + + double query_dist = squared_distance(x_quad, q); + // clang-format off + double query_orient = axom::numerics::determinant(x_quad[0] - q[0], dx_quad[0], + x_quad[1] - q[1], dx_quad[1]); + // clang-format on + + quad_result += quad.IntPoint(k).weight * query_orient / query_dist; + } + + return 0.5 * M_1_PI * quad_result; +} + +template +double winding_number_clipping(const Point& q, + const BezierCurve& c, + int& nevals, + double edge_tol = 1e-8, + double EPS = 1e-8) +{ + const int ord = c.getOrder(); + Segment closure(c[ord], c[0]); + int inter = 0; + + BoundingBox bb = c.boundingBox(); + if(bb.contains(q)) + { + // Check the 4 edges of the bounding box to find the closest + double edge_dist = bb.getMax()[0] - q[0]; + Vector direction {1.0, 0.0}; + + if(bb.getMax()[1] - q[1] < edge_dist) + { + direction = Vector {0.0, 1.0}; + edge_dist = bb.getMax()[1] - q[1]; + } + if(q[0] - bb.getMin()[0] < edge_dist) + { + direction = Vector {-1.0, 0.0}; + edge_dist = q[0] - bb.getMin()[0]; + } + if(q[1] - bb.getMin()[1] < edge_dist) + { + direction = Vector {0.0, -1.0}; + } + + Ray ray(q, direction); + inter = detail::ray_casting_bezier_clipping(c, ray, nevals, edge_tol); + double u, tmp; + if(intersect(ray, closure, u, tmp, 0.0)) + { + Vector e1 = ray.direction().unitVector(); + Vector e2 = Vector {-e1[1], e1[0]}; + + double alpha = Vector(ray.origin(), c[ord]).dot(e2); + + (alpha < 0) ? inter++ : inter--; + } + } + // If not contained in the bounding box, just do a return + + return inter - winding_number(q, closure, edge_tol); +} + +template +double winding_number_bisection(const Point& q, + const BezierCurve& c, + int& nevals, + double edge_tol = 1e-8, + double EPS = 1e-8) +{ + const int ord = c.getOrder(); + Segment closure(c[ord], c[0]); + int crossing_num = 0; + int inter = 0; + + BoundingBox bb = c.boundingBox(); + if(bb.contains(q)) + { + // Check the 4 edges of the bounding box to find the closest + double edge_dist = bb.getMax()[0] - q[0]; + Vector direction {1.0, 0.0}; + + if(bb.getMax()[1] - q[1] < edge_dist) + { + direction = Vector {0.0, 1.0}; + edge_dist = bb.getMax()[1] - q[1]; + } + if(q[0] - bb.getMin()[0] < edge_dist) + { + direction = Vector {-1.0, 0.0}; + edge_dist = q[0] - bb.getMin()[0]; + } + if(q[1] - bb.getMin()[1] < edge_dist) + { + direction = Vector {0.0, -1.0}; + } + + Ray ray(q, direction); + std::vector cp; + std::vector rp; + intersect(c, ray, cp, rp, nevals, edge_tol); + + for(auto c0 : cp) + { + Vector e1 = ray.direction().unitVector(); + Vector e2 = c.dt(c0); + + double determinant = + axom::numerics::determinant(e1[0], e2[0], e1[1], e2[1]); + + (determinant > 0) ? crossing_num++ : crossing_num--; + } + + double u, tmp; + if(intersect(ray, closure, u, tmp, edge_tol)) + { + Vector e1 = ray.direction().unitVector(); + Vector e2 = Vector {-e1[1], e1[0]}; + + double alpha = Vector(q, c[ord]).dot(e2); + + (alpha < 0) ? crossing_num++ : crossing_num--; + } + } + // If not contained in the bounding box, just do a return + + return crossing_num - winding_number(q, closure, edge_tol); +} + +/// Compute the number of intersections that a ray makes with an array of Bezier curves +template +int crossing_number(const Point& q, + const axom::Array>& cs, + const BoundingBox& bb, + double edge_tol = 1e-8, + double EPS = 1e-8) +{ + int nevals = 0; + int inter = 0; + + double edge_dist = bb.getMax()[0] - q[0]; + Vector direction {1.0, 0.0}; + + // Uncomment this to get a reasonably fast method + if(bb.getMax()[1] - q[1] < edge_dist) + { + direction = Vector {0.0, 1.0}; + edge_dist = bb.getMax()[1] - q[1]; + } + if(q[0] - bb.getMin()[0] < edge_dist) + { + direction = Vector {-1.0, 0.0}; + edge_dist = q[0] - bb.getMin()[0]; + } + if(q[1] - bb.getMin()[1] < edge_dist) + { + direction = Vector {0.0, -1.0}; + } + + // Get a ray extending in a random direction + Ray ray(q, direction); + for(auto& c : cs) + { + inter += detail::ray_casting_bezier_clipping(c, ray, nevals, edge_tol); + } + + return inter; } /*! @@ -246,6 +476,126 @@ double winding_number(const Point& q, return ret_val; } +template +double winding_number_approxogon_memoized( + Point q, + const axom::Array>>>& array_memos, + axom::Array< + axom::FlatMap, detail::BezierCurveMemo, detail::PairHash>>& + hash_memos, + Polygon& temp_approxogon, + std::stack>& curve_stack, + double edge_tol = 1e-8, + double linear_tol = 1e-8) +{ + double wn = 0; + + for(int ci = 0; ci < array_memos.size(); ++ci) + { + auto& the_curve = array_memos[ci][0][0].curve; + + if(!the_curve.boundingBox().contains(q)) + { + wn += detail::linear_winding_number(q, + the_curve[0], + the_curve[the_curve.getOrder()], + edge_tol); + } + else + { + //temp_approxogon.addVertex(the_curve[0]); + detail::winding_number_adaptive_linear_memoized(q, + array_memos[ci], + hash_memos[ci], + edge_tol, + linear_tol, + temp_approxogon, + curve_stack, + wn); + + temp_approxogon.addVertex(the_curve[the_curve.getOrder()]); + + wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); + + //desmos_print(c); + //desmos_print(q); + //desmos_print(temp_approxogon); + + temp_approxogon.clear(); + //int xx = 0; + } + } + + return wn; +} + +template +double winding_number_approxogon(const Point& q, + const axom::Array>& carray, + Polygon& temp_approxogon, + int& num_evals, + int& max_depth, + double edge_tol = 1e-8, + double linear_tol = 1e-8) +{ + double wn = 0; + + for(int i = 0; i < carray.size(); i++) + { + // Check exterior bounding box + if(!carray[i].boundingBox().contains(q)) + { + wn += detail::linear_winding_number(q, + carray[i][0], + carray[i][carray[i].getOrder()], + edge_tol); + } + else + { + //wn += detail::curve_winding_number_recursive(q, + // carray[i], + // false, + // dummy_val, + // edge_tol); + temp_approxogon.addVertex(carray[i][0]); + detail::winding_number_adaptive_linear(q, + carray[i], + false, + num_evals, + edge_tol, + linear_tol, + temp_approxogon, + wn); + temp_approxogon.addVertex(carray[i][carray[i].getOrder()]); + max_depth = + axom::utilities::max(max_depth, temp_approxogon.numVertices() - 2); + wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); + temp_approxogon.clear(); + } + } + + return wn; +} + +template +double winding_number(const Point& q, + const axom::Array>& carray, + double edge_tol = 1e-8, + double EPS = 1e-8) +{ + double ret_val = 0.0; + int dummy_val = 0; + for(int i = 0; i < carray.size(); i++) + { + ret_val += detail::curve_winding_number_recursive(q, + carray[i], + false, + dummy_val, + edge_tol); + } + + return ret_val; +} //@} //@{ diff --git a/src/axom/primal/tests/CMakeLists.txt b/src/axom/primal/tests/CMakeLists.txt index bf5d2aaa20..05819cd9d4 100644 --- a/src/axom/primal/tests/CMakeLists.txt +++ b/src/axom/primal/tests/CMakeLists.txt @@ -7,6 +7,7 @@ #------------------------------------------------------------------------------ set( primal_tests + primal_2d_paper_figure_data.cpp primal_bezier_curve.cpp primal_bezier_intersect.cpp primal_bezier_patch.cpp diff --git a/src/axom/primal/tests/primal_2d_paper_figure_data.cpp b/src/axom/primal/tests/primal_2d_paper_figure_data.cpp new file mode 100644 index 0000000000..ca5581a108 --- /dev/null +++ b/src/axom/primal/tests/primal_2d_paper_figure_data.cpp @@ -0,0 +1,2802 @@ +// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \file primal_bezier_curve.cpp + * \brief This file tests primal's Bezier curve functionality + */ + +#include "gtest/gtest.h" + +#include "axom/core/FlatMap.hpp" +#include "axom/slic.hpp" + +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/operators/squared_distance.hpp" +#include "axom/primal/operators/winding_number.hpp" +#include "axom/primal/operators/detail/winding_number_impl.hpp" + +#include "axom/primal/operators/printers.hpp" + +#include +#include +#include + +namespace primal = axom::primal; + +TEST(primal_2d_paper_figure_data, generalized_winding) +{ + return; + + std::string data_dir = + "E:\\Code\\winding_number\\figures\\generalized_winding\\"; + std::string curve_name = "polygon"; + + axom::Array> polygon; + + // Read in the polygon, write out the curves for python + primal::convert_from_svg(data_dir + curve_name + ".svg", polygon); + std::ofstream polygon_curve_out(data_dir + curve_name + "_full_curves.txt"); + for(auto& edge : polygon) + { + polygon_curve_out << edge << std::endl; + } + + std::ofstream edge_curve_out(data_dir + curve_name + "_edge_curves.txt"); + edge_curve_out << polygon[3] << std::endl; + + // Set up fines in whcih to write out winding number data + std::ofstream full_winding_out(data_dir + curve_name + "_full_wn.csv"); + std::ofstream edge_winding_out(data_dir + curve_name + "_edge_wn.csv"); + + // Find maximum and minimum x and y values + double min_x = polygon[0][0][0], min_y = polygon[0][0][1]; + double max_x = polygon[0][0][0], max_y = polygon[0][0][1]; + + for(auto& edge : polygon) + { + for(int p = 0; p <= edge.getOrder(); ++p) + { + min_x = axom::utilities::min(min_x, edge[p][0]); + max_x = axom::utilities::max(max_x, edge[p][0]); + + min_y = axom::utilities::min(min_y, edge[p][1]); + max_y = axom::utilities::max(max_y, edge[p][1]); + } + } + + // Put those in a bounding box, then expand it + primal::Point bounds[2] = {primal::Point {min_x, min_y}, + primal::Point {max_x, max_y}}; + primal::BoundingBox bb(bounds, 2); + bb.scale(1.25); + + // Iterate over 250 points in the x and y direction between max and min + double npts_x = 250; + double npts_y = 250; + + for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; + x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) + { + std::cout << x << std::endl; + + for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; + y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double edge_winding_number = + primal::winding_number(query, polygon[3], nevals); + double full_winding_number = edge_winding_number; + + for(int i = 0; i < polygon.size(); ++i) + { + if(i == 3) continue; + + full_winding_number += primal::winding_number(query, polygon[i], nevals); + } + + full_winding_out << x << "," << y << "," << full_winding_number + << std::endl; + edge_winding_out << x << "," << y << "," << edge_winding_number + << std::endl; + } + } +} + +TEST(primal_2d_paper_figure_data, closure_example) +{ + return; + + std::string data_dir = "E:\\Code\\winding_number\\figures\\closure_example\\"; + std::string curve_name = "closure_example"; + + axom::Array> closed_curve; + + // Read in the polygon, write out the curves for python + primal::convert_from_svg(data_dir + curve_name + ".svg", closed_curve); + for(auto& curve : closed_curve) curve.reverseOrientation(); + + std::ofstream curve_out(data_dir + curve_name + "_curve_out.txt"); + std::ofstream curve_winding_out(data_dir + curve_name + "_curve_wn.csv"); + for(int i = 0; i < closed_curve.size() - 1; ++i) + { + curve_out << closed_curve[i] << std::endl; + } + + std::ofstream closure_out(data_dir + curve_name + "_closure_out.txt"); + std::ofstream closure_winding_out(data_dir + curve_name + "_closure_wn.csv"); + closure_out << closed_curve[closed_curve.size() - 1] << std::endl; + + // Find maximum and minimum x and y values + double min_x = closed_curve[0][0][0], min_y = closed_curve[0][0][1]; + double max_x = closed_curve[0][0][0], max_y = closed_curve[0][0][1]; + + for(auto& edge : closed_curve) + { + for(int p = 0; p <= edge.getOrder(); ++p) + { + min_x = axom::utilities::min(min_x, edge[p][0]); + max_x = axom::utilities::max(max_x, edge[p][0]); + + min_y = axom::utilities::min(min_y, edge[p][1]); + max_y = axom::utilities::max(max_y, edge[p][1]); + } + } + + // Put those in a bounding box, then expand it + primal::Point bounds[2] = {primal::Point {min_x, min_y}, + primal::Point {max_x, max_y}}; + primal::BoundingBox bb(bounds, 2); + bb.scale(1.25); + + // Iterate over 250 points in the x and y direction between max and min + double npts_x = 250; + double npts_y = 250; + + for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; + x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) + { + std::cout << x << std::endl; + + for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; + y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double curve_wn = 0.0; + for(int i = 0; i < closed_curve.size() - 1; ++i) + { + curve_wn += primal::winding_number(query, closed_curve[i], nevals); + } + + double closure_wn = + primal::winding_number(query, + closed_curve[closed_curve.size() - 1], + nevals); + + curve_winding_out << x << "," << y << "," << curve_wn << std::endl; + closure_winding_out << x << "," << y << "," << closure_wn << std::endl; + } + } +} + +TEST(primal_2d_paper_figure_data, generalized_winding_curves) +{ + return; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\generalized_winding_curves\\"; + + int npts_x = 500; + int npts_y = 500; + + std::string name = "heart"; + CurveArray shape; + primal::convert_from_svg(data_dir + name + ".svg", shape); + std::ofstream shape_curve_out(data_dir + name + ".txt"); + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + shape_curve_out << shape[i] << std::endl; + } + BoundingBox shape_bbox = curves_bbox(shape, 1.25); + std::ofstream shape_wn_out(data_dir + name + "_wn.csv"); + simple_grid_test(shape, shape_bbox, npts_x, npts_y, shape_wn_out); + + name = "exploded_heart2"; + CurveArray exploded_shape; + primal::convert_from_svg(data_dir + name + ".svg", exploded_shape); + std::ofstream exploded_shape_out(data_dir + name + ".txt"); + std::ofstream exploded_shape_wn_out(data_dir + name + "_wn.csv"); + for(int i = 0; i < exploded_shape.size(); ++i) + { + exploded_shape[i].reverseOrientation(); + exploded_shape_out << exploded_shape[i] << std::endl; + } + //BoundingBox exploded_shape_bbox = curves_bbox(exploded_shape, 1.25); + simple_grid_test(exploded_shape, shape_bbox, + npts_x, + npts_y, + exploded_shape_wn_out); + + std::cout << exploded_shape << std::endl; + + name = "heart_curve_1"; + CurveArray curve_1; + curve_1.push_back(shape[0]); + curve_1.push_back(shape[5]); + std::ofstream curve_1_out(data_dir + name + ".txt"); + std::ofstream curve_1_wn_out(data_dir + name + "_wn.csv"); + curve_1_out << curve_1[0] << std::endl; + curve_1_out << curve_1[1] << std::endl; + BoundingBox curve_1_bbox = curves_bbox(curve_1, 1.5, true); + simple_grid_test(curve_1, curve_1_bbox, npts_x, npts_y, curve_1_wn_out); + + name = "heart_curve_2"; + CurveArray curve_2; + curve_2.push_back(shape[3]); + curve_2.push_back(shape[4]); + std::ofstream curve_2_out(data_dir + name + ".txt"); + std::ofstream curve_2_wn_out(data_dir + name + "_wn.csv"); + curve_2_out << curve_2[0] << std::endl; + curve_2_out << curve_2[1] << std::endl; + BoundingBox curve_2_bbox = curves_bbox(curve_2, 1.5, true); + //curve_2_bbox.shift( (curve_1_bbox.getCentroid() - curve_2_bbox.getCentroid()) ); + simple_grid_test(curve_2, curve_2_bbox, npts_x, npts_y, curve_2_wn_out); + + name = "heart_curve_3"; + CurveArray curve_3; + curve_3.push_back(shape[1]); + std::ofstream curve_3_out(data_dir + name + ".txt"); + std::ofstream curve_3_wn_out(data_dir + name + "_wn.csv"); + curve_3_out << curve_3[0] << std::endl; + BoundingBox curve_3_bbox = curves_bbox(curve_3, 1.5, true); + //curve_3_bbox.shift((curve_1_bbox.getCentroid() - curve_3_bbox.getCentroid())); + simple_grid_test(curve_3, curve_3_bbox, npts_x, npts_y, curve_3_wn_out); + + name = "heart_curve_4"; + CurveArray curve_4; + curve_4.push_back(shape[2]); + std::ofstream curve_4_out(data_dir + name + ".txt"); + std::ofstream curve_4_wn_out(data_dir + name + "_wn.csv"); + curve_4_out << curve_4[0] << std::endl; + BoundingBox curve_4_bbox = curves_bbox(curve_4, 1.5, true); + //curve_4_bbox.shift((curve_1_bbox.getCentroid() - curve_4_bbox.getCentroid())); + simple_grid_test(curve_4, curve_4_bbox, npts_x, npts_y, curve_4_wn_out); +} + +TEST(primal_2d_paper_figure_data, linearize_example) +{ + return; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "E:\\Code\\winding_number\\figures\\linearize_example\\"; + + int npts_x = 250; + int npts_y = 250; + + std::string name = "curve_1"; + CurveArray curve_1; + std::ofstream curve_1_out(data_dir + name + ".txt"); + std::ofstream curve_1_wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", curve_1); + for(int i = 0; i < curve_1.size(); ++i) + { + curve_1_out << curve_1[i] << std::endl; + } + BoundingBox largest_bbox = curves_bbox(curve_1, 1.5, true); + simple_grid_test(curve_1, largest_bbox, npts_x, npts_y, curve_1_wn_out); + + name = "curve_2"; + CurveArray curve_2; + std::ofstream curve_2_out(data_dir + name + ".txt"); + std::ofstream curve_2_wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", curve_2); + for(int i = 0; i < curve_2.size(); ++i) + { + curve_2_out << curve_2[i] << std::endl; + } + simple_grid_test(curve_2, largest_bbox, npts_x, npts_y, curve_2_wn_out); + + name = "curve_3"; + CurveArray curve_3; + std::ofstream curve_3_out(data_dir + name + ".txt"); + std::ofstream curve_3_wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", curve_3); + for(int i = 0; i < curve_3.size(); ++i) + { + curve_3_out << curve_3[i] << std::endl; + } + BoundingBox curve_3_bbox = curves_bbox(curve_3, 1.5, true); + simple_grid_test(curve_3, largest_bbox, npts_x, npts_y, curve_3_wn_out); + + name = "curve_4"; + CurveArray curve_4; + std::ofstream curve_4_out(data_dir + name + ".txt"); + std::ofstream curve_4_wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", curve_4); + for(int i = 0; i < curve_4.size(); ++i) + { + curve_4[i].reverseOrientation(); + curve_4_out << curve_4[i] << std::endl; + } + BoundingBox curve_4_bbox = curves_bbox(curve_4, 1.5, true); + simple_grid_test(curve_4, largest_bbox, npts_x, npts_y, curve_4_wn_out); +} +//------------------------------------------------------------------------------ + +TEST(primal_2d_paper_figure_data, ray_casting_bad) +{ + return; + + std::cout << "Running: \"ray_casting_bad\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\ray_casting_bad\\"; + + int npts_x = 500; + int npts_y = 500; + + std::string name = "butterfly"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream shape_cn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + for(int i = 0; i < shape.size(); ++i) + { + shape_out << shape[i] << std::endl; + } + + // Replace curve 100 with a point + //std::cout << shape[100] << std::endl; + for(int i = 0; i <= shape[100].getOrder(); ++i) + { + shape[100][i] = shape[100][0]; + } + //std::cout << shape[100] << std::endl; + + BoundingBox shape_bbox = curves_bbox(shape, 1.1, false); + + for(double x = shape_bbox.getMin()[0]; x <= shape_bbox.getMax()[0]; + x += (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) / npts_x) + { + std::cout << x << std::endl; + + for(double y = shape_bbox.getMin()[1]; y <= shape_bbox.getMax()[1]; + y += (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int crossing_number = primal::crossing_number(query, shape, shape_bbox); + + shape_cn_out << std::setprecision(16) << x << ',' << y << "," + << crossing_number << std::endl; + } + } +} + +TEST(primal_2d_paper_figure_data, winding_number_good) +{ + return; + std::cout << "Running: \"winding_number_good\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\winding_number_" + "good\\"; + + int npts_x = 500; + int npts_y = 500; + + std::string name = "butterfly"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream shape_wn_out(data_dir + name + "_wn.csv"); + std::ofstream zoomed_wn_out(data_dir + name + "_zoomed_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + BoundingBox shape_bbox = curves_bbox(shape, 1.1, false); + + BoundingBox zoomed_bbox(shape[100].boundingBox()); + double max_dim = + axom::utilities::max(zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0], + zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]); + zoomed_bbox.addPoint( + primal::Point {zoomed_bbox.getMin()[0] + max_dim, + zoomed_bbox.getMin()[1] + max_dim}); + zoomed_bbox.scale(2.0); + + // Delete the one curve + for(int i = 0; i <= shape[100].getOrder(); ++i) shape[100][i] = shape[100][0]; + + // Print the rest to the file + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + shape_out << shape[i] << std::endl; + } + + simple_grid_test(shape, shape_bbox, npts_x, npts_y, shape_wn_out); + simple_grid_test(shape, zoomed_bbox, npts_x, npts_y, zoomed_wn_out); +} + +TEST(primal_2d_paper_figure_data, fish_figure) +{ + return; + std::cout << "Running: \"fish_figure\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\fish_figure\\"; + + std::string name = "fish"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream shape_wn_out(data_dir + name + "_wn.csv"); + std::ofstream zoomed_wn_out(data_dir + name + "_zoomed_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + // Jiggle the curves, and print them all to the file + srand(100); + + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + + shape[i][0][0] += (rand() % 30) - 15; + shape[i][0][1] += (rand() % 30) - 15; + + shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; + shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; + + shape_out << shape[i] << std::endl; + } + + BoundingBox shape_bbox = curves_bbox(shape, 1.01, false); + BoundingBox zoomed_bbox(shape[240].boundingBox()); + double max_dim = + axom::utilities::max(zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0], + zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]); + zoomed_bbox.addPoint( + primal::Point {zoomed_bbox.getMin()[0] + max_dim, + zoomed_bbox.getMin()[1] + max_dim}); + zoomed_bbox.scale(12.0); + + simple_grid_test(shape, shape_bbox, 1000, 1000, shape_wn_out); + simple_grid_test(shape, zoomed_bbox, 1000, 1000, zoomed_wn_out); +} + +TEST(primal_2d_paper_figure_data, proximity_test) +{ + return; + std::cout << "Running: \"proximity_test\"" << std::endl; + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + using Point2D = primal::Point; + using Vector2D = primal::Vector; + + std::string data_dir = "E:\\Code\\winding_number\\figures\\proximity_test\\"; + + std::string name = "parabola"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + + Point2D ellipse_nodes[] = {Point2D {2.0, 0.0}, + Point2D {2.0, 1.0}, + Point2D {0.0, 1.0}}; + + double ellipse_weights[] = {1.0, 1.0 / sqrt(2.0), 1.0}; + primal::BezierCurve ellipse(ellipse_nodes, ellipse_weights, 2); + shape.push_back(ellipse); + + axom::Array ts({0.09, 0.51, 0.91}); + axom::Array dists({-0.1, -1e-2, -1e-3, -1e-4, -1e-5, -1e-6, -1e-7, + -1e-8, -1e-9, -1e-10, 0.1, 1e-2, 1e-3, 1e-4, + 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-10}); + + std::ofstream bisection_evals_out(data_dir + "bisection_evals.csv"); + std::ofstream clipping_evals_out(data_dir + "clipping_evals.csv"); + std::ofstream boxes_evals_out(data_dir + "boxes_evals.csv"); + + bisection_evals_out << std::setprecision(16) << "0"; + clipping_evals_out << std::setprecision(16) << "0"; + boxes_evals_out << std::setprecision(16) << "0"; + + for(auto dist : dists) + { + bisection_evals_out << ',' << dist; + clipping_evals_out << ',' << dist; + boxes_evals_out << ',' << dist; + } + + bisection_evals_out << std::endl; + clipping_evals_out << std::endl; + boxes_evals_out << std::endl; + + for(auto t0 : ts) + { + Point2D on_curve(ellipse.evaluate(t0)); + Vector2D tangent(ellipse.dt(t0)); + + bisection_evals_out << t0; + clipping_evals_out << t0; + boxes_evals_out << t0; + + for(auto dist : dists) + { + Point2D q_int({ + -tangent[1] * dist / tangent.norm() + on_curve[0], + tangent[0] * dist / tangent.norm() + on_curve[1], + }); + + int bisection_nevals = 0; + int clipping_nevals = 0; + int boxes_nevals = 0; + + double bisection_wn = primal::winding_number_bisection(q_int, + ellipse, + bisection_nevals, + 1e-16, + 1e-16); + double clipping_wn = primal::winding_number_clipping(q_int, + ellipse, + clipping_nevals, + 1e-16, + 1e-16); + double boxes_wn = + primal::winding_number(q_int, ellipse, boxes_nevals, 1e-16, 1e-16); + + bisection_evals_out << ',' << bisection_nevals; + clipping_evals_out << ',' << clipping_nevals; + boxes_evals_out << ',' << boxes_nevals; + } + + bisection_evals_out << std::endl; + clipping_evals_out << std::endl; + boxes_evals_out << std::endl; + } +} + +TEST(primal_2d_paper_figure_data, linearization_test) +{ + return; + + std::cout << "Running: \"linearization_test\"" << std::endl; + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\linearization_test\\"; + + std::string name = "bubble"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream misclassify_vals_out(data_dir + "misclassify_vals.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + srand(100); + + for(int i = 0; i < shape.size(); ++i) + { + shape_out << shape[i] << std::endl; + } + + BoundingBox shape_bbox = curves_bbox(shape, 1.05, false); + + std::ofstream shape_wn_out(data_dir + name + "_wnn.csv"); + simple_grid_test(shape, shape_bbox, 1000, 1000, shape_wn_out); + + std::ofstream shape_grid_out(data_dir + name + "_grid.txt"); + simple_grid_test(shape, shape_bbox, 2, 2, shape_grid_out); + + constexpr double tol = 1e-10; + int npts = 1e5; + int max_refinement = 25; + int dummy_int = 0; + + for(int n = 4; n <= 0; ++n) + { + std::cout << std::endl << "Doing refinement " << n << std::endl; + std::ofstream linearized_shape_out(data_dir + name + std::to_string(n) + + ".txt"); + + // Construct the linear approximation with the given level of refinement + CurveArray linearized_shape; + for(int i = 0; i < shape.size(); ++i) + { + for(double j = 0; j < n; ++j) + { + primal::BezierCurve line(1); + line[0] = shape[i].evaluate(j / n); + line[1] = shape[i].evaluate((j + 1) / n); + + linearized_shape.push_back(line); + } + } + + std::ofstream linearized_shape_wn_out(data_dir + name + + "_linearized_wn.csv"); + simple_grid_test(linearized_shape, shape_bbox, 500, 500, linearized_shape_wn_out); + + for(int i = 0; i < linearized_shape.size(); ++i) + { + linearized_shape_out << linearized_shape[i] << std::endl; + } + + std::ofstream misclassifications_out(data_dir + "misclassifications_" + + std::to_string(n) + ".csv"); + int misclassifications = 0; + + for(int m = 0; m < npts; ++m) + { + primal::printLoadingBar(m, npts); + double x = axom::utilities::random_real(shape_bbox.getMin()[0], + shape_bbox.getMax()[0]); + double y = axom::utilities::random_real(shape_bbox.getMin()[1], + shape_bbox.getMax()[1]); + + primal::Point query({x, y}); + + double linearized_wn = 0; + for(int i = 0; i < linearized_shape.size(); ++i) + { + linearized_wn += primal::winding_number(query, + linearized_shape[i], + dummy_int, + 1e-16, + 1e-16); + } + bool linearized_is_in = std::lround(linearized_wn) != 0; + + double true_wn = 0; + for(int i = 0; i < shape.size(); ++i) + { + true_wn += + primal::winding_number(query, shape[i], dummy_int, 1e-16, 1e-16); + } + bool true_is_in = std::lround(true_wn) != 0; + + if(linearized_is_in != true_is_in) + { + misclassifications++; + } + } + + misclassifications_out << misclassifications << std::endl; + } +} + +TEST(primal_2d_paper_figure_data, tolerance_test) +{ + return; + + std::cout << "Running : \"tolerance test\"" << std::endl; + + using CurveArray = axom::Array>; + using SegmentArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = "E:\\Code\\winding_number\\figures\\tolerance_test\\"; + + std::string name = "tangle"; + CurveArray shape; + primal::Point tangle_nodes[] = { + primal::Point {1.000, 0.000}, + primal::Point {-3.000, 0.000}, + primal::Point {-3.225, 3.175}, + primal::Point {1.008, 3.300}, + primal::Point {5.223, -3.836}, + primal::Point {-3.273, -4.321}, + primal::Point {-5.485, 2.728}, + primal::Point {2.681, 3.014}, + primal::Point {3.000, 0.000}, + primal::Point {-2.000, 0.000}}; + + double tangle_weights[] = {1.0, 1.1, 0.7, 1.1, 1.0, 1.2, 1.1, 1.0, 1.0, 1.0}; + primal::BezierCurve tangle_curve(tangle_nodes, tangle_weights, 9); + //shape.push_back(tangle_curve); + //tangle_curve.reverseOrientation(); + + primal::Point closure_nodes[] = { + primal::Point {-2.0, 0.0}, + primal::Point {-0.5, -3}, + primal::Point {1.0, 0.0} + + }; + primal::BezierCurve closure_curve(closure_nodes, 2); + shape.push_back(closure_curve); + + primal::Point closure_closure_nodes[] = { + primal::Point {1.0, 0.0}, + primal::Point {-2.0, 0.0}}; + + primal::BezierCurve closure_closure_curve(closure_closure_nodes, 2); + shape.push_back(closure_closure_curve); + + std::ofstream curves_out(data_dir + name + ".txt"); + + //primal::convert_from_svg(data_dir + name + ".svg", shape); + + srand(100); + + double shape_radius = 1; + BoundingBox shape_bbox; + primal::Point center({0.2, 0.3}); + shape_bbox.addPoint(primal::Point {center[0] + shape_radius, + center[1] + shape_radius}); + shape_bbox.addPoint(primal::Point {center[0] - shape_radius, + center[1] - shape_radius}); + + // Get the largest axis of the bounding box + double max_dim = + axom::utilities::max(shape_bbox.getMax()[0] - shape_bbox.getMin()[0], + shape_bbox.getMax()[1] - shape_bbox.getMin()[1]); + + for(int i = 0; i < shape.size(); ++i) + { + std::cout << shape[i] << std::endl; + for(int j = 0; j <= shape[i].getOrder(); ++j) + { + shape[i][j][0] = 0.25 * shape[i][j][0] + 0.62; + shape[i][j][1] = 0.25 * shape[i][j][1] + 0.55; + } + + curves_out << shape[i] << std::endl; + + // Make a fuller version of the shape that has three times as many control nodes + // primal::BezierCurve fuller_curve(3 * shape[i].getOrder()); + // for (int j = 0; j <= 3 * shape[i].getOrder(); ++j) + // { + // fuller_curve[j] = shape[i][j / 3]; + //} + } + + shape_bbox.clear(); + + shape_bbox.addPoint(primal::Point {-0.1, -0.1}); + shape_bbox.addPoint(primal::Point {1.1, 1.1}); + + std::ofstream shape_grid_out(data_dir + name + "_grid.txt"); + simple_grid_test(shape, shape_bbox, 500, 500, shape_grid_out); + + //return; + + std::ofstream tolerance_wn_timing_out(data_dir + + "tolerance_wn_timing_out_simple.csv"); + + //constexpr double EPS = 0.0; + int npts = 1e5; + const int max_refinement = 25; + int dummy_int = 0; + bool dummy_bool = false; + int idx; + + const int NUM_CURVES = 20; + + // Make an array of duration objects + double avg_depth[15]; + int misclassifications[15]; + int nevals[15]; + for(int i = 0; i < 15; ++i) + { + misclassifications[i] = 0; + nevals[i] = 0; + avg_depth[i] = 0; + } + + //int idx2 = 0; + //for(int i = 2; i <= 0; ++i) + //{ + // double the_tol = std::pow(10, -i); + + // std::cout << "===================" << the_tol + // << "===================" << std::endl; + + // for(int m = 0; m < npts; ++m) + // { + // double x = axom::utilities::random_real(shape_bbox.getMin()[0], + // shape_bbox.getMax()[0]); + // double y = axom::utilities::random_real(shape_bbox.getMin()[1], + // shape_bbox.getMax()[1]); + // primal::Point query({x, y}); + // primal::Polygon temp_approxogon(20); + + // double adaptive_wn = primal::winding_number_approxogon(query, + // shape, + // temp_approxogon, + // 0.0, + // the_tol); + // } + + // idx2++; + //} + + //return; + // Loop over the number of points + for(int m = 0; m < npts; ++m) + { + primal::printLoadingBar(m, npts); + double x = axom::utilities::random_real(shape_bbox.getMin()[0], + shape_bbox.getMax()[0]); + double y = axom::utilities::random_real(shape_bbox.getMin()[1], + shape_bbox.getMax()[1]); + + primal::Point query({x, y}); + + // Loop over the tolerances, 1e-2 to 1e-16 + idx = 0; + for(int i = 2; i <= 16; ++i) + { + double the_tol = std::pow(10, -i); + + //std::cout << the_tol << std::endl; + + double adaptive_wn = 0.0; + primal::Polygon temp_approxogon(20); + + double true_wn = primal::winding_number(query, shape, 0.0); + int this_depth = 0; + adaptive_wn = primal::winding_number_approxogon(query, + shape, + temp_approxogon, + nevals[idx], + this_depth, + 0.0, + the_tol); + + if((std::lround(adaptive_wn) != 0) != (std::lround(true_wn) != 0)) + { + misclassifications[idx]++; + } + avg_depth[idx] += static_cast(this_depth) / npts; + + idx++; + } + } + + idx = 0; + for(int i = 2; i <= 16; ++i) + { + double the_tol = std::pow(10, -i); + + tolerance_wn_timing_out << the_tol << "," << avg_depth[idx] << "," + << misclassifications[idx] << "," << nevals[idx] + << std::endl; + idx++; + } +} + +TEST(primal_2d_paper_figure_data, timing_test) +{ + return; + + std::cout << "Running: \"timing_test\"" << std::endl; + using CurveArray = axom::Array>; + using SegmentArray = axom::Array>; + using PolygonArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string prefix = "run_3\\"; + + std::string data_dir = "E:\\Code\\winding_number\\figures\\timing_test\\"; + + std::string name = "bubble"; + CurveArray shape; + std::ofstream curves_out(data_dir + name + ".txt"); + std::ofstream adaptive_wn_timing_out(data_dir + prefix + + "method0_wn_timing_out.csv"); + std::ofstream memoized_wn_timing_out(data_dir + prefix + + "method00_wn_timing_out.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + srand(10101); + + for(int i = 0; i < shape.size(); ++i) + { + //curves_out << shape[i] << std::endl; + + primal::BezierCurve fuller_curve(3 * shape[i].getOrder()); + for(int j = 0; j <= 3 * shape[i].getOrder(); ++j) + { + fuller_curve[j] = shape[i][j / 3]; + } + fuller_curve.makeRational(); + shape[i].makeRational(); + //shape[i] = fuller_curve; + //shape_fuller.push_back(fuller_curve); + + curves_out << shape[i] << std::endl; + } + + BoundingBox shape_bbox = curves_bbox(shape, 1.05, false); + + std::ofstream shape_grid_out(data_dir + name + "_grid.txt"); + simple_grid_test(shape, shape_bbox, 2, 2, shape_grid_out); + + constexpr double tol = 1e-10; + int npts = 1e5; + const int max_refinement = 25; + int dummy_int = 0; + bool dummy_bool = false; + + const int NUM_CURVES = 20; + + // Do the preprocessing to get the linearization for each level of refinement + //SegmentArray bag_of_linearizations[max_refinement]; + //SegmentArray batches_of_linearizations[max_refinement][NUM_CURVES]; // There are 20 curves in the bubble + PolygonArray bag_of_polygons[max_refinement]; + std::unordered_map, primal::Segment, primal::detail::PairHash> + segment_hashes[max_refinement]; + + // Array for low order refinement + std::vector>> + array_memos[NUM_CURVES]; + + // Hash for higher order refinement + axom::FlatMap, + primal::detail::BezierCurveMemo, + primal::detail::PairHash> + hash_memos[NUM_CURVES]; + + std::ofstream method1_wn_timing_out[max_refinement]; // Big bag of winding numbers. Completely naive + std::ofstream method2_wn_timing_out[max_refinement]; // Semi-sophisticated. If you're outside the box, do one calculation. If you're inside the box, do linearization + std::ofstream method3_wn_timing_out[max_refinement]; // Sophisticated. Make a polygon with a fixed linearization, then do polygon_wn - closure + std::ofstream method4_wn_timing_out[max_refinement]; // Method 3 with no pre-processing + + std::chrono::duration preprocessing_times[max_refinement][4]; + std::ofstream preprocessing_times_out = + std::ofstream(data_dir + prefix + "preprocessing_times.csv"); + + // Preprocessing for method 00 + constexpr int LEVEL = 3; + auto start = std::chrono::high_resolution_clock::now(); + for(int ci = 0; ci < NUM_CURVES; ++ci) + { + array_memos[ci].resize(LEVEL); + for(int i = 0; i < LEVEL; ++i) array_memos[ci][i].resize(1 << i); + + array_memos[ci][0][0] = { + //shape[ci].isLinear(), + primal::is_convex(primal::Polygon(shape[ci].getControlPoints()), + primal::PRIMAL_TINY), + shape[ci]}; + + for(int i = 0; i < LEVEL - 1; ++i) + { + for(int j = 0; j < (1 << i); ++j) + { + primal::BezierCurve c1, c2; + array_memos[ci][i][j].curve.split(0.5, c1, c2); + + array_memos[ci][i + 1][2 * j] = { + //c1.isLinear(), + primal::is_convex(primal::Polygon(c1.getControlPoints()), + primal::PRIMAL_TINY), + c1}; + + array_memos[ci][i + 1][2 * j + 1] = { + //c2.isLinear(), + primal::is_convex(primal::Polygon(c2.getControlPoints()), + primal::PRIMAL_TINY), + c2}; + } + } + + //hash_memos.push_back(axom::FlatMap, + // primal::detail::BezierCurveMemo, + // primal::detail::PairHash>()); + } + auto end = std::chrono::high_resolution_clock::now(); + + for(int i = 0; i < max_refinement; ++i) + { + preprocessing_times[i][3] = end - start; + } + + /*std::ofstream shape_memoized_grid_out(data_dir + name + "_memoized_grid.txt"); + simple_grid_test_memoized(shape_marray, + shape_bbox, + 200, + 200, + shape_memoized_grid_out);*/ + + // Set up the linearization + for(int ref = 0; ref < max_refinement; ++ref) + { + method1_wn_timing_out[ref] = + std::ofstream(data_dir + prefix + "method1_wn_timing_out_" + + std::to_string(ref + 1) + ".csv"); + method2_wn_timing_out[ref] = + std::ofstream(data_dir + prefix + "method2_wn_timing_out_" + + std::to_string(ref + 1) + ".csv"); + method3_wn_timing_out[ref] = + std::ofstream(data_dir + prefix + "method3_wn_timing_out_" + + std::to_string(ref + 1) + ".csv"); + method4_wn_timing_out[ref] = + std::ofstream(data_dir + prefix + "method4_wn_timing_out_" + + std::to_string(ref + 1) + ".csv"); + + // Big boy preprocessing + start = std::chrono::high_resolution_clock::now(); + for(int ci = 0; ci < shape.size(); ++ci) + { + for(int i = 0; i < (ref + 1); ++i) + { + primal::BezierCurve curve = shape[ci]; + primal::Point point = curve.evaluate(1.0 * i / (ref + 1.0)); + primal::Point next_point = + curve.evaluate(1.0 * (i + 1.0) / (ref + 1.0)); + primal::Segment line(point, next_point); + std::pair the_pair = std::make_pair(ci, i); + segment_hashes[ref][the_pair] = line; + } + } + end = std::chrono::high_resolution_clock::now(); + preprocessing_times[ref][0] = end - start; + + // Preprocessing for method 1 + //start = std::chrono::high_resolution_clock::now(); + //for(int ci = 0; ci < NUM_CURVES; ++ci) + //{ + // for(int i = 0; i < (ref + 1); ++i) + // { + // primal::Segment line( + // shape[ci].evaluate(1.0 * i / (ref + 1.0)), + // shape[ci].evaluate(1.0 * (i + 1.0) / (ref + 1.0))); + // bag_of_linearizations[ref].push_back(line); + // } + //} + //end = std::chrono::high_resolution_clock::now(); + //preprocessing_times[ref][0] = end - start; + + // Preprocessing for method 2 + start = std::chrono::high_resolution_clock::now(); + //for(int ci = 0; ci < NUM_CURVES; ci++) + //{ + // for(int i = 0; i < (ref + 1); ++i) + // { + // primal::Segment line( + // shape[ci].evaluate(1.0 * i / (ref + 1.0)), + // shape[ci].evaluate(1.0 * (i + 1.0) / (ref + 1.0))); + + // batches_of_linearizations[ref][ci].push_back(line); + // } + //} + end = std::chrono::high_resolution_clock::now(); + preprocessing_times[ref][1] = end - start; + + // Preprocessing for method 3 + start = std::chrono::high_resolution_clock::now(); + //for(int ci = 0; ci < NUM_CURVES; ++ci) + //{ + // primal::Polygon poly(ref + 1); + + // for(int i = 0; i < (ref + 1); ++i) + // { + // poly.addVertex(shape[ci].evaluate(1.0 * i / (ref + 1.0))); + // } + + // poly.addVertex(shape[ci].evaluate(1.0)); + // bag_of_polygons[ref].push_back(poly); + //} + end = std::chrono::high_resolution_clock::now(); + preprocessing_times[ref][2] = end - start; + + // Construct the linear approximation out of BezierCurves for plotting + CurveArray linearized_curves; + for(int ci = 0; ci < shape.size(); ++ci) + { + for(int i = 0; i < (ref + 1); ++i) + { + primal::BezierCurve curve(1); + curve[0] = shape[ci].evaluate(1.0 * i / (ref + 1.0)); + curve[1] = shape[ci].evaluate(1.0 * (i + 1.0) / (ref + 1.0)); + linearized_curves.push_back(curve); + } + } + + std::ofstream fixed_wn_shape_out(data_dir + "fixed_wn_shape_out_" + + std::to_string(ref + 1) + ".txt"); + + for(auto& line : linearized_curves) + { + fixed_wn_shape_out << line << std::endl; + } + } + + // Write the preprocessing times to the file + for(int ref = 0; ref < max_refinement; ++ref) + { + preprocessing_times_out << ref + 1 << ',' + << preprocessing_times[ref][0].count() << ',' + << preprocessing_times[ref][1].count() << ',' + << preprocessing_times[ref][2].count() << ',' + << preprocessing_times[ref][3].count() << std::endl; + } + + // Loop over the number of points + for(int m = 0; m < npts; ++m) + { + primal::printLoadingBar(m, npts); + double x = axom::utilities::random_real(shape_bbox.getMin()[0], + shape_bbox.getMax()[0]); + double y = axom::utilities::random_real(shape_bbox.getMin()[1], + shape_bbox.getMax()[1]); + + primal::Point query({x, y}); + //184.194, 113.01 + //query[0] = 184.194; + //query[1] = 113.01; + + double adaptive_wn = 0.0, memoized_wn = 0.0; + primal::Polygon temp_approxogon(20); + std::stack> curve_stack; + + auto start = std::chrono::high_resolution_clock::now(); + adaptive_wn = primal::winding_number_approxogon(query, + shape, + temp_approxogon, + dummy_int, + dummy_int, + tol); + auto end = std::chrono::high_resolution_clock::now(); + + adaptive_wn_timing_out << x << "," << y << "," + << std::chrono::duration(end - start).count() + << std::endl; + + for(int ref = 0; ref < max_refinement; ++ref) + { + double wn1 = 0.0, wn3 = 0.0, wn4 = 0.0; + + /* Do method 2: bounding box + small bag version */ + start = std::chrono::high_resolution_clock::now(); + double wn2 = 0.0; + for(int i = 0; i < NUM_CURVES; ++i) + { + if(!shape[i].boundingBox().contains(query)) + { + wn2 -= primal::winding_number( + query, + primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), + tol); + } + else + { + for(int j = 0; j < ref + 1; ++j) + { + auto the_pair = std::make_pair(i, j); + wn2 += + primal::winding_number(query, segment_hashes[ref][the_pair], tol); + } + } + } + end = std::chrono::high_resolution_clock::now(); + + method2_wn_timing_out[ref] + << x << ',' << y << ',' + << (std::chrono::duration(end - start)).count() << std::endl; + + /* Do method 1: big bag o winding numbers version */ + auto start = std::chrono::high_resolution_clock::now(); + //for(int i = 0; i < bag_of_linearizations[ref].size(); ++i) + //{ + //wn1 += primal::winding_number(query, bag_of_linearizations[ref][i], tol); + //} + auto end = std::chrono::high_resolution_clock::now(); + + method1_wn_timing_out[ref] + << x << ',' << y << ',' + << (std::chrono::duration(end - start)).count() << std::endl; + + /* Do method 3: polygon version */ + start = std::chrono::high_resolution_clock::now(); + for(int i = 0; i < NUM_CURVES; ++i) + { + /*if(!shape[i].boundingBox().contains(query)) + { + wn3 -= primal::winding_number( + query, + primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), + tol); + } + else + { + wn3 += + primal::detail::approxogon_winding_number(query, + bag_of_polygons[ref][i], + tol); + }*/ + } + end = std::chrono::high_resolution_clock::now(); + + method3_wn_timing_out[ref] + << x << ',' << y << ',' + << (std::chrono::duration(end - start)).count() << std::endl; + + ///* Do method 4: polygon with no pre-processing*/ + //primal::Polygon this_poly(ref + 1); + + //start = std::chrono::high_resolution_clock::now(); + //for(int i = 0; i < NUM_CURVES; ++i) + //{ + // if(!shape[i].boundingBox().contains(query)) + // { + // wn4 -= primal::winding_number( + // query, + // primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), + // tol); + // } + // else + // { + // this_poly.clear(); + + // for(int j = 0; j < (ref + 1); ++j) + // { + // this_poly.addVertex(shape[i].evaluate(1.0 * j / (ref + 1.0))); + // } + // this_poly.addVertex(shape[i].evaluate(1.0)); + + // wn4 += primal::detail::approxogon_winding_number(query, this_poly, tol); + // } + //} + //end = std::chrono::high_resolution_clock::now(); + + //method4_wn_timing_out[ref] + // << x << ',' << y << ',' + // << std::chrono::duration(end - start).count() << std::endl; + } + + start = std::chrono::high_resolution_clock::now(); + //memoized_wn = primal::winding_number_approxogon_memoized(query, + // array_memos, + // hash_memos, + // temp_approxogon, + // curve_stack, + // tol); + for(int i = 0; i < NUM_CURVES; ++i) + { + if(!shape[i].boundingBox().contains(query)) + { + memoized_wn -= primal::winding_number( + query, + primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), + tol); + } + else + { + primal::detail::winding_number_adaptive_linear_memoized(query, + array_memos[i], + hash_memos[i], + 0, + tol, + temp_approxogon, + curve_stack, + memoized_wn); + temp_approxogon.addVertex(shape[i][shape[i].getOrder()]); + memoized_wn += + primal::detail::approxogon_winding_number(query, temp_approxogon, 0); + temp_approxogon.clear(); + } + } + end = std::chrono::high_resolution_clock::now(); + + memoized_wn_timing_out << x << "," << y << "," + << std::chrono::duration(end - start).count() + << std::endl; + } +} + +TEST(primal_2d_paper_figure_data, rose_figure) +{ + return; + bool collectGridData = false; + bool collectRandomData = true; + + std::cout << "Running: \"rose_figure\"" << std::endl; + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = "E:\\Code\\winding_number\\figures\\rose_figure\\"; + + std::string name = "rose"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + // Jiggle the curves, and print them all to the file + srand(100); + + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + shape_out << shape[i] << std::endl; + } + + BoundingBox shape_bbox = curves_bbox(shape, 1.05, false); + + if(collectGridData) + { + std::ofstream grid_boxes_nevals_out(data_dir + "boxes_grid_nevals.csv"); + std::ofstream grid_clipping_nevals_out(data_dir + + "clipping_grid_nevals.csv"); + std::ofstream grid_bisection_nevals_out(data_dir + + "bisection_grid_nevals.csv"); + + std::ofstream grid_boxes_wn_out(data_dir + "boxes_grid_wn.csv"); + std::ofstream grid_clipping_wn_out(data_dir + "clipping_grid_wn.csv"); + std::ofstream grid_bisection_wn_out(data_dir + "bisection_grid_wn.csv"); + + int npts_x = 250; + int npts_y = 250; + + for(double x = shape_bbox.getMin()[0]; x <= shape_bbox.getMax()[0]; + x += (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) / npts_x) + { + std::cout << x << std::endl; + + for(double y = shape_bbox.getMin()[1]; y <= shape_bbox.getMax()[1]; + y += (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals_boxes = 0; + int nevals_bisection = 0; + int nevals_clipping = 0; + + double wn_boxes = 0.0; + double wn_bisection = 0.0; + double wn_clipping = 0.0; + + for(int i = 0; i < shape.size(); ++i) + { + wn_boxes += + primal::winding_number(query, shape[i], nevals_boxes, 1e-16, 1e-16); + wn_clipping += primal::winding_number_clipping(query, + shape[i], + nevals_clipping, + 1e-16, + 1e-16); + wn_bisection += primal::winding_number_bisection(query, + shape[i], + nevals_bisection, + 1e-16, + 1e-16); + } + + grid_boxes_nevals_out << x << "," << y << "," << nevals_boxes + << std::endl; + grid_clipping_nevals_out << x << "," << y << "," << nevals_clipping + << std::endl; + grid_bisection_nevals_out << x << "," << y << "," << nevals_bisection + << std::endl; + + grid_boxes_wn_out << x << "," << y << "," << wn_boxes << std::endl; + grid_clipping_wn_out << x << "," << y << "," << wn_clipping << std::endl; + grid_bisection_wn_out << x << "," << y << "," << wn_bisection + << std::endl; + } + } + } + + if(collectRandomData) + { + std::ofstream random_boxes_nevals_out(data_dir + "boxes_random_nevals.csv"); + std::ofstream random_clipping_nevals_out(data_dir + + "clipping_random_nevals.csv"); + std::ofstream random_bisection_nevals_out(data_dir + + "bisection_random_nevals.csv"); + + int npts = 250000; + constexpr double tol = 1e-10; + + for(int n = 0; n < npts; ++n) + { + primal::printLoadingBar(n, npts); + + double x = axom::utilities::random_real(shape_bbox.getMin()[0], + shape_bbox.getMax()[0]); + double y = axom::utilities::random_real(shape_bbox.getMin()[1], + shape_bbox.getMax()[1]); + + primal::Point query({x, y}); + + int nevals_boxes = 0; + int nevals_bisection = 0; + int nevals_clipping = 0; + + double wn_boxes = 0.0; + double wn_bisection = 0.0; + double wn_clipping = 0.0; + for(int i = 0; i < shape.size(); ++i) + { + //desmos_print(shape[i]); + //desmos_print(query); + wn_boxes += + primal::winding_number(query, shape[i], nevals_boxes, tol, tol); + wn_clipping += primal::winding_number_clipping(query, + shape[i], + nevals_clipping, + tol, + tol); + wn_bisection += primal::winding_number_bisection(query, + shape[i], + nevals_bisection, + tol, + tol); + } + + if(!axom::utilities::isNearlyEqual(wn_bisection, wn_boxes, 1e-5)) + { + std::cout << std::setprecision(16) << n << ": " << x << ", " << y + << std::endl; + std::cout << "bisection machine broke" << std::endl; + } + + if(!axom::utilities::isNearlyEqual(wn_boxes, wn_clipping, 1e-5)) + { + std::cout << std::setprecision(16) << n << ": " << x << ", " << y + << std::endl; + std::cout << "clipping machine broke" << std::endl; + } + + random_boxes_nevals_out << x << "," << y << "," << nevals_boxes + << std::endl; + random_clipping_nevals_out << x << "," << y << "," << nevals_clipping + << std::endl; + random_bisection_nevals_out << x << "," << y << "," << nevals_bisection + << std::endl; + } + } +} + +TEST(primal_2d_paper_figure_data, spiral_figure) +{ + return; + + std::cout << "Running: \"spiral_figure\"" << std::endl; + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + using Point2D = primal::Point; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\spiral_figure\\"; + + std::string name = "spiral"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + + Point2D spiral_nodes[] = {Point2D {-0.824, -0.927}, + Point2D {-3.344, -0.802}, + Point2D {-2.085, 3.242}, + Point2D {1.794, 4.861}, + Point2D {2.692, -4.142}, + Point2D {-2.313, -3.279}, + Point2D {-6.041, 2.037}, + Point2D {1.98, 2.444}, + Point2D {-0.966, -0.939}, + Point2D {-1.034, 0.113}}; + + double spiral_weights[] = {1.0, 1.2, 1.2, 0.9, 1.1, 0.9, 1.0, 1.0, 1.1, 1.0}; + primal::BezierCurve spiral_curve(spiral_nodes, spiral_weights, 9); + shape.push_back(spiral_curve); + + //Point2D spiral_nodes[] = {Point2D {2.0, 0.0}, + // Point2D {2.0, 1.0}, + // Point2D {0.0, 1.0}}; + + //double spiral_weights[] = {1.0, 1.0 / sqrt(2.0), 1.0}; + //primal::BezierCurve spiral_curve(spiral_nodes, spiral_weights, 2); + //shape.push_back(spiral_curve); + + // Jiggle the curves, and print them all to the file + srand(100); + + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + shape_out << shape[i] << std::endl; + } + + BoundingBox shape_bbox; // = curves_bbox(shape, 1.05, true); + + shape_bbox.addPoint(spiral_nodes[9]); + shape_bbox.addPoint( + Point2D {spiral_nodes[9][0] + 1.5, spiral_nodes[9][1] + 1.5}); + shape_bbox.addPoint( + Point2D {spiral_nodes[9][0] - 1.5, spiral_nodes[9][1] - 1.5}); + std::cout << shape_bbox << std::endl; + + std::ofstream wn_out(data_dir + name + "_wn.csv"); + simple_grid_test(shape, shape_bbox, 1000, 1000, wn_out); + return; + + std::ofstream grid_nevals_out(data_dir + "grid_nevals.csv"); + int npts_x = 3; + int npts_y = 3; + + for(int xi = 0; xi < 2; ++xi) + { + for(int yi = 0; yi < 2; ++yi) + { + double x = shape_bbox.getMin()[0] + + (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) * xi; + double y = shape_bbox.getMin()[1] + + (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) * yi; + primal::Point query({x, y}); + + int nevals = 0; + + double wn = 0.0; + for(int i = 0; i < shape.size(); ++i) + { + primal::winding_number(query, shape[i], nevals, 1e-16, 1e-16); + } + + grid_nevals_out << x << "," << y << "," << nevals << std::endl; + } + } + + std::ofstream random_boxes_nevals_out(data_dir + "boxes_random_nevals.csv"); + std::ofstream random_clipping_nevals_out(data_dir + + "clipping_random_nevals.csv"); + std::ofstream random_bisection_nevals_out(data_dir + + "bisection_random_nevals.csv"); + + int npts = 250000; + constexpr double tol = 1e-10; + + for(int n = 0; n < npts; ++n) + { + primal::printLoadingBar(n, npts); + + double x = axom::utilities::random_real(shape_bbox.getMin()[0], + shape_bbox.getMax()[0]); + double y = axom::utilities::random_real(shape_bbox.getMin()[1], + shape_bbox.getMax()[1]); + + primal::Point query({x, y}); + + int nevals_boxes = 0; + int nevals_bisection = 0; + int nevals_clipping = 0; + + double wn_boxes = 0.0; + double wn_bisection = 0.0; + double wn_clipping = 0.0; + for(int i = 0; i < shape.size(); ++i) + { + //desmos_print(shape[i]); + //desmos_print(query); + wn_boxes += primal::winding_number(query, shape[i], nevals_boxes, tol, tol); + wn_clipping += + primal::winding_number_clipping(query, shape[i], nevals_clipping, tol, tol); + wn_bisection += primal::winding_number_bisection(query, + shape[i], + nevals_bisection, + tol, + tol); + } + + if(!axom::utilities::isNearlyEqual(wn_bisection, wn_boxes, 1e-5)) + { + std::cout << std::setprecision(16) << n << ": " << x << ", " << y + << std::endl; + std::cout << "bisection machine broke" << std::endl; + } + + if(!axom::utilities::isNearlyEqual(wn_boxes, wn_clipping, 1e-5)) + { + std::cout << std::setprecision(16) << n << ": " << x << ", " << y + << std::endl; + std::cout << "clipping machine broke" << std::endl; + } + + random_boxes_nevals_out << x << "," << y << "," << nevals_boxes << std::endl; + random_clipping_nevals_out << x << "," << y << "," << nevals_clipping + << std::endl; + random_bisection_nevals_out << x << "," << y << "," << nevals_bisection + << std::endl; + } +} + +TEST(primal_2d_paper_figure_data, tangle_figure) +{ + return; + + std::cout << "Running: \"tangle_figure\"" << std::endl; + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + using Point2D = primal::Point; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\tangle_figure\\"; + + std::string name = "tangle"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + + Point2D tangle_nodes[] = {Point2D {1.000, 0.000}, + Point2D {-3.000, 0.000}, + Point2D {-3.225, 3.175}, + Point2D {1.008, 3.300}, + Point2D {5.223, -3.836}, + Point2D {-3.273, -4.321}, + Point2D {-5.485, 2.728}, + Point2D {2.681, 3.014}, + Point2D {3.000, 0.000}, + Point2D {-2.000, 0.000}}; + + double tangle_weights[] = {1.0, 1.1, 0.7, 1.1, 1.0, 1.2, 1.1, 1.0, 1.0, 1.0}; + primal::BezierCurve tangle_curve(tangle_nodes, tangle_weights, 9); + shape.push_back(tangle_curve); + + // Jiggle the curves, and print them all to the file + srand(100); + + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + shape_out << shape[i] << std::endl; + } + + BoundingBox shape_bbox; + Point2D center({-0.33, 0.2}); + shape_bbox.addPoint(Point2D {center[0] + 2, center[1] + 2}); + shape_bbox.addPoint(Point2D {center[0] - 2, center[1] - 2}); + + std::ofstream wn_out(data_dir + name + "_wn.csv"); + simple_grid_test(shape, shape_bbox, 1000, 1000, wn_out); + + return; + + std::ofstream grid_nevals_out(data_dir + "grid_nevals.csv"); + int npts_x = 3; + int npts_y = 3; + + for(int xi = 0; xi < 2; ++xi) + { + for(int yi = 0; yi < 2; ++yi) + { + double x = shape_bbox.getMin()[0] + + (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) * xi; + double y = shape_bbox.getMin()[1] + + (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) * yi; + primal::Point query({x, y}); + + int nevals = 0; + + double wn = 0.0; + for(int i = 0; i < shape.size(); ++i) + { + primal::winding_number(query, shape[i], nevals, 1e-16, 1e-16); + } + + grid_nevals_out << x << "," << y << "," << nevals << std::endl; + } + } + + std::ofstream random_boxes_nevals_out(data_dir + "boxes_random_nevals.csv"); + std::ofstream random_clipping_nevals_out(data_dir + + "clipping_random_nevals.csv"); + std::ofstream random_bisection_nevals_out(data_dir + + "bisection_random_nevals.csv"); + + int npts = 1e5 + 1e5 / 2; + constexpr double tol = 1e-10; + + for(int n = 0; n < npts; ++n) + { + primal::printLoadingBar(n, npts); + + double x = axom::utilities::random_real(shape_bbox.getMin()[0], + shape_bbox.getMax()[0]); + double y = axom::utilities::random_real(shape_bbox.getMin()[1], + shape_bbox.getMax()[1]); + + primal::Point query({x, y}); + + int nevals_boxes = 0; + int nevals_bisection = 0; + int nevals_clipping = 0; + + double wn_boxes = 0.0; + double wn_bisection = 0.0; + double wn_clipping = 0.0; + for(int i = 0; i < shape.size(); ++i) + { + //desmos_print(shape[i]); + //desmos_print(query); + wn_boxes += primal::winding_number(query, shape[i], nevals_boxes, tol, tol); + wn_clipping += + primal::winding_number_clipping(query, shape[i], nevals_clipping, tol, tol); + wn_bisection += primal::winding_number_bisection(query, + shape[i], + nevals_bisection, + tol, + tol); + } + + if(!axom::utilities::isNearlyEqual(wn_bisection, wn_boxes, 1e-5)) + { + std::cout << std::setprecision(16) << n << ": " << x << ", " << y + << std::endl; + std::cout << "bisection machine broke" << std::endl; + } + + if(!axom::utilities::isNearlyEqual(wn_boxes, wn_clipping, 1e-5)) + { + std::cout << std::setprecision(16) << n << ": " << x << ", " << y + << std::endl; + std::cout << "clipping machine broke" << std::endl; + } + + random_boxes_nevals_out << x << "," << y << "," << nevals_boxes << std::endl; + random_clipping_nevals_out << x << "," << y << "," << nevals_clipping + << std::endl; + random_bisection_nevals_out << x << "," << y << "," << nevals_bisection + << std::endl; + } +} + +TEST(primal_2d_paper_figure_data, orientation_bad) +{ + return; + std::cout << "Running: \"orientation_bad\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\orientation_bad\\"; + + int npts_x = 1000; + int npts_y = 1000; + + std::string name = "butterfly"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + //BoundingBox bbox(shape[100].boundingBox()); + //double max_dim = axom::utilities::max(bbox.getMax()[0] - bbox.getMin()[0], + // bbox.getMax()[1] - bbox.getMin()[1]); + //bbox.addPoint(primal::Point {bbox.getMin()[0] + max_dim, + // bbox.getMin()[1] + max_dim}); + //bbox.scale(2.5); + + BoundingBox bbox = curves_bbox(shape, 1.1, false); + + // Reverse one curve + shape[100][1] = shape[100][0]; //.reverseOrientation(); + shape[100][2] = shape[100][0]; //.reverseOrientation(); + shape[100][3] = shape[100][0]; //.reverseOrientation(); + + // Print them all to the file + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + shape_out << shape[i] << std::endl; + } + + simple_grid_test(shape, bbox, npts_x, npts_y, wn_out); +} + +TEST(primal_2d_paper_figure_data, quadrature_bad) +{ + return; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\quadrature_bad\\"; + + + int npts_x = 600; + int npts_y = 600; + + std::string name = "curve"; + CurveArray curve; + primal::convert_from_svg(data_dir + name + ".svg", curve); + std::ofstream curve_out(data_dir + name + ".txt"); + for(int i = 0; i < curve.size(); ++i) + { + curve_out << curve[i] << std::endl; + } + BoundingBox curve_bbox = curves_bbox(curve, 1.5, true); + std::ofstream curve_15_wn_out(data_dir + name + "_wn_15.csv"); + std::ofstream curve_30_wn_out(data_dir + name + "_wn_30.csv"); + std::ofstream curve_50_wn_out(data_dir + name + "_wn_50.csv"); + std::ofstream curve_true_wn_out(data_dir + name + "_wn_true.csv"); + + for(double x = curve_bbox.getMin()[0]; x <= curve_bbox.getMax()[0]; + x += (curve_bbox.getMax()[0] - curve_bbox.getMin()[0]) / npts_x) + { + std::cout << x << std::endl; + + for(double y = curve_bbox.getMin()[1]; y <= curve_bbox.getMax()[1]; + y += (curve_bbox.getMax()[1] - curve_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double wn_15 = 0.0; + double wn_30 = 0.0; + double wn_50 = 0.0; + double wn_true = 0.0; + + for(int i = 0; i < curve.size(); ++i) + { + wn_15 += primal::winding_number_quad(query, curve[i], 15); + wn_30 += primal::winding_number_quad(query, curve[i], 30); + wn_50 += primal::winding_number_quad(query, curve[i], 50); + wn_true += primal::winding_number(query, curve[i], nevals); + } + + curve_15_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_15 + << std::endl; + curve_30_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_30 + << std::endl; + curve_50_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_50 + << std::endl; + curve_true_wn_out << std::setprecision(16) << x << ',' << y << "," + << wn_true << std::endl; + } + } + + std::string spade_name = "twirl"; + CurveArray spade; + primal::convert_from_svg(data_dir + spade_name + ".svg", spade); + std::ofstream spade_out(data_dir + spade_name + ".txt"); + for(int i = 0; i < spade.size(); ++i) + { + spade_out << spade[i] << std::endl; + } + //BoundingBox spade_bbox = curves_bbox(spade, 1.0); + primal::Point spade_center({83.73, 75}); + double spade_radius = 12.0; + BoundingBox spade_bbox; + spade_bbox.addPoint(primal::Point {spade_center[0] + spade_radius, + spade_center[1] + spade_radius}); + spade_bbox.addPoint(primal::Point {spade_center[0] - spade_radius, + spade_center[1] - spade_radius}); + + std::ofstream spade_15_wn_out(data_dir + spade_name + "_wn_15.csv"); + std::ofstream spade_30_wn_out(data_dir + spade_name + "_wn_30.csv"); + std::ofstream spade_50_wn_out(data_dir + spade_name + "_wn_50.csv"); + std::ofstream spade_true_wn_out(data_dir + spade_name + "_wn_true.csv"); + + for(double x = spade_bbox.getMin()[0]; x <= spade_bbox.getMax()[0]; + x += (spade_bbox.getMax()[0] - spade_bbox.getMin()[0]) / npts_x) + { + std::cout << x << std::endl; + + for(double y = spade_bbox.getMin()[1]; y <= spade_bbox.getMax()[1]; + y += (spade_bbox.getMax()[1] - spade_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double wn_15 = 0.0; + double wn_30 = 0.0; + double wn_50 = 0.0; + double wn_true = 0.0; + + for(int i = 0; i < spade.size(); ++i) + { + wn_15 += primal::winding_number_quad(query, spade[i], 15); + wn_30 += primal::winding_number_quad(query, spade[i], 30); + wn_50 += primal::winding_number_quad(query, spade[i], 50); + wn_true += primal::winding_number(query, spade[i], nevals); + } + + spade_15_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_15 + << std::endl; + spade_30_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_30 + << std::endl; + spade_50_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_50 + << std::endl; + spade_true_wn_out << std::setprecision(16) << x << ',' << y << "," + << wn_true << std::endl; + } + } + + std::string zoomed_name = "zoomed"; + + primal::Point zoomed_center = spade[2][3]; + double zoomed_radius = 1.0; + BoundingBox zoomed_bbox; + zoomed_bbox.addPoint( + primal::Point {zoomed_center[0] + zoomed_radius, + zoomed_center[1] + zoomed_radius / 2.0}); + zoomed_bbox.addPoint( + primal::Point {zoomed_center[0] - zoomed_radius, + zoomed_center[1] - zoomed_radius / 2.0}); + + std::ofstream zoomed_15_wn_out(data_dir + zoomed_name + "_wn_15.csv"); + std::ofstream zoomed_30_wn_out(data_dir + zoomed_name + "_wn_30.csv"); + std::ofstream zoomed_50_wn_out(data_dir + zoomed_name + "_wn_50.csv"); + std::ofstream zoomed_true_wn_out(data_dir + zoomed_name + "_wn_true.csv"); + + npts_x = 600; + npts_y = 300; + + for(double x = zoomed_bbox.getMin()[0]; x <= zoomed_bbox.getMax()[0]; + x += (zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0]) / npts_x) + { + std::cout << x << std::endl; + + for(double y = zoomed_bbox.getMin()[1]; y <= zoomed_bbox.getMax()[1]; + y += (zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double wn_15 = 0.0; + double wn_30 = 0.0; + double wn_50 = 0.0; + double wn_true = 0.0; + + for(int i = 0; i < spade.size(); ++i) + { + wn_15 += primal::winding_number_quad(query, spade[i], 15); + wn_30 += primal::winding_number_quad(query, spade[i], 30); + wn_50 += primal::winding_number_quad(query, spade[i], 50); + wn_true += primal::winding_number(query, spade[i], nevals); + } + + zoomed_15_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_15 + << std::endl; + zoomed_30_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_30 + << std::endl; + zoomed_50_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_50 + << std::endl; + zoomed_true_wn_out << std::setprecision(16) << x << ',' << y << "," + << wn_true << std::endl; + } + } +} + +TEST(primal_2d_paper_figure_data, graphical_abstract) +{ + return; + std::cout << "Running: \"graphical_abstract\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\graphical_abstract\\"; +// "E:\\Code\\winding_number\\figures\\\\"; + + int npts_x = 1000; + int npts_y = 1000; + + std::string name = "butterfly"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + for(int i = 0; i < shape.size(); ++i) + { + shape_out << shape[i] << std::endl; + } + + // Replace curve 100 with a point + + CurveArray exploded_shape; + std::ofstream exploded_shape_out(data_dir + name + "_exploded.txt"); + std::ofstream exploded_shape_cn_out(data_dir + name + "_exploded_cn.csv"); + std::ofstream exploded_shape_wn_out(data_dir + name + "_exploded_wn.csv"); + primal::convert_from_svg(data_dir + name + "_exploded.svg", exploded_shape); + + for(int i = 0; i < exploded_shape.size(); ++i) + { + exploded_shape_out << exploded_shape[i] << std::endl; + } + + BoundingBox shape_bbox = curves_bbox(shape, 1.1, false); + + // Get winding number and crossing number for the exploded shape + BoundingBox exploded_shape_bbox = curves_bbox(exploded_shape, 1.1, false); + + for(double x = exploded_shape_bbox.getMin()[0]; + x <= exploded_shape_bbox.getMax()[0]; + x += (exploded_shape_bbox.getMax()[0] - exploded_shape_bbox.getMin()[0]) / + npts_x) + { + std::cout << x << "/" << exploded_shape_bbox.getMax()[0] << std::endl; + + for(double y = exploded_shape_bbox.getMin()[1]; + y <= exploded_shape_bbox.getMax()[1]; + y += (exploded_shape_bbox.getMax()[1] - exploded_shape_bbox.getMin()[1]) / + npts_y) + { + primal::Point query({x, y}); + + int crossing_number = + primal::crossing_number(query, exploded_shape, exploded_shape_bbox); + double winding_number = primal::winding_number(query, exploded_shape); + + exploded_shape_cn_out << std::setprecision(16) << x << ',' << y << "," + << crossing_number << std::endl; + + exploded_shape_wn_out << std::setprecision(16) << x << ',' << y << "," + << winding_number << std::endl; + } + } + + // Get a linearized version of the shape + std::ofstream linearized_shape_out(data_dir + name + "_linearized.txt"); + + // Construct the linear approximation with the given level of refinement + CurveArray linearized_shape; + for(int i = 0; i < exploded_shape.size(); ++i) + { + int n = 5; + for(double j = 0; j < n; ++j) + { + primal::BezierCurve line(1); + line[0] = exploded_shape[i].evaluate(j / n); + line[1] = exploded_shape[i].evaluate((j + 1) / n); + + linearized_shape.push_back(line); + } + } + + std::ofstream linearized_shape_wn_out(data_dir + name + "_linearized_wn.csv"); + + for(int i = 0; i < linearized_shape.size(); ++i) + { + linearized_shape_out << linearized_shape[i] << std::endl; + } + + // Zoom in around curve 106 + std::string zoomed_name = "zoomed"; + + primal::Point zoomed_center = exploded_shape[106].evaluate(0.85); + double zoomed_radius = 400.0; + BoundingBox zoomed_bbox; + zoomed_bbox.addPoint( + primal::Point {zoomed_center[0] + zoomed_radius, + zoomed_center[1] + zoomed_radius}); + zoomed_bbox.addPoint( + primal::Point {zoomed_center[0] - zoomed_radius, + zoomed_center[1] - zoomed_radius}); + + std::ofstream zoomed_true_out(data_dir + zoomed_name + "_true.csv"); + std::ofstream zoomed_linearized_out(data_dir + zoomed_name + + "_linearized.csv"); + std::ofstream zoomed_quadrature_out(data_dir + zoomed_name + + "_quadrature.csv"); + + for(double x = zoomed_bbox.getMin()[0]; x <= zoomed_bbox.getMax()[0]; + x += (zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0]) / npts_x) + { + std::cout << x << "/" << exploded_shape_bbox.getMax()[0] << std::endl; + + for(double y = zoomed_bbox.getMin()[1]; y <= zoomed_bbox.getMax()[1]; + y += (zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double true_wn = 0.0; + double linearized = 0.0; + double quadrature = 0.0; + + for(int i = 0; i < exploded_shape.size(); ++i) + { + true_wn += primal::winding_number(query, exploded_shape[i], nevals); + quadrature += primal::winding_number_quad(query, exploded_shape[i], 30); + } + + for(int i = 0; i < linearized_shape.size(); ++i) + { + linearized += primal::winding_number(query, linearized_shape[i], nevals); + } + + zoomed_true_out << std::setprecision(16) << x << ',' << y << "," + << true_wn << std::endl; + zoomed_linearized_out << std::setprecision(16) << x << ',' << y << "," + << linearized << std::endl; + zoomed_quadrature_out << std::setprecision(16) << x << ',' << y << "," + << quadrature << std::endl; + } + } +} + +TEST(primal_2d_paper_figure_data, graphical_abstract_trumpet) +{ + return; + + std::cout << "Running: \"graphical_abstract_trumpet\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\graphical_abstract\\"; + //"E:\\Code\\winding_number\\figures\\graphical_abstract\\"; + + int npts_x = 1000; + int npts_y = 1000; + + CurveArray closed_trumpet; + std::ofstream closed_trumpet_out(data_dir + "closed_trumpet.txt"); + primal::convert_from_svg(data_dir + "closed_trumpet.svg", closed_trumpet); + for(int i = 0; i < closed_trumpet.size(); ++i) + { + closed_trumpet_out << closed_trumpet[i] << std::endl; + } + BoundingBox closed_bbox = curves_bbox(closed_trumpet, 1.1, false); + + CurveArray open_trumpet; + std::ofstream open_trumpet_out(data_dir + "open_trumpet.txt"); + primal::convert_from_svg(data_dir + "open_trumpet.svg", open_trumpet); + for(int i = 0; i < open_trumpet.size(); ++i) + { + open_trumpet_out << open_trumpet[i] << std::endl; + } + BoundingBox open_bbox = curves_bbox(open_trumpet, 1.1, false); + + std::ofstream closed_trumpet_wn_out(data_dir + "closed_trumpet_wn.csv"); + std::ofstream closed_trumpet_cn_out(data_dir + "closed_trumpet_cn.csv"); + std::ofstream open_trumpet_wn_out(data_dir + "open_trumpet_wn.csv"); + std::ofstream open_trumpet_cn_out(data_dir + "open_trumpet_cn.csv"); + + // Compute winding numbers and crossing numbers for both trumpets + for(double x = open_bbox.getMin()[0]; x <= open_bbox.getMax()[0]; + x += (open_bbox.getMax()[0] - open_bbox.getMin()[0]) / npts_x) + { + std::cout << x << "/" << open_bbox.getMax()[0] << std::endl; + + for(double y = open_bbox.getMin()[1]; y <= open_bbox.getMax()[1]; + y += (open_bbox.getMax()[1] - open_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int crossing_number = + primal::crossing_number(query, open_trumpet, open_bbox); + double winding_number = primal::winding_number(query, open_trumpet); + + open_trumpet_wn_out << std::setprecision(16) << x << ',' << y << "," + << winding_number << std::endl; + open_trumpet_cn_out << std::setprecision(16) << x << ',' << y << "," + << crossing_number << std::endl; + } + } + + for(double x = closed_bbox.getMin()[0]; x <= closed_bbox.getMax()[0]; + x += (closed_bbox.getMax()[0] - closed_bbox.getMin()[0]) / npts_x) + { + std::cout << x << "/" << closed_bbox.getMax()[0] << std::endl; + + for(double y = closed_bbox.getMin()[1]; y <= closed_bbox.getMax()[1]; + y += (closed_bbox.getMax()[1] - closed_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int crossing_number = + primal::crossing_number(query, closed_trumpet, closed_bbox); + double winding_number = primal::winding_number(query, closed_trumpet); + + closed_trumpet_wn_out << std::setprecision(16) << x << ',' << y << "," + << winding_number << std::endl; + closed_trumpet_cn_out << std::setprecision(16) << x << ',' << y << "," + << crossing_number << std::endl; + } + } + + // Get a linearized version of the shape + std::ofstream linearized_shape_out(data_dir + "zoomed_linearized.txt"); + + // Construct the linear approximation with the given level of refinement + CurveArray linearized_shape; + for(int i = 0; i < open_trumpet.size(); ++i) + { + int n = 5; + for(double j = 0; j < n; ++j) + { + primal::BezierCurve line(1); + line[0] = open_trumpet[i].evaluate(j / n); + line[1] = open_trumpet[i].evaluate((j + 1) / n); + + linearized_shape.push_back(line); + } + } + + for(int i = 0; i < linearized_shape.size(); ++i) + { + linearized_shape_out << linearized_shape[i] << std::endl; + } + + primal::Point zoomed_center = + open_trumpet[open_trumpet.size() - 1].evaluate(0.21); + double zoomed_radius = 22.0; + BoundingBox zoomed_bbox; + zoomed_bbox.addPoint( + primal::Point {zoomed_center[0] + zoomed_radius, + zoomed_center[1] + zoomed_radius}); + zoomed_bbox.addPoint( + primal::Point {zoomed_center[0] - zoomed_radius, + zoomed_center[1] - zoomed_radius}); + + std::ofstream zoomed_true_out(data_dir + "zoomed_true.csv"); + std::ofstream zoomed_linearized_out(data_dir + "zoomed_linearized.csv"); + std::ofstream zoomed_quadrature_out(data_dir + "zoomed_quadrature.csv"); + + npts_x = 1000; + npts_y = 1000; + + for(double x = zoomed_bbox.getMin()[0]; x <= zoomed_bbox.getMax()[0]; + x += (zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0]) / npts_x) + { + std::cout << x << "/" << zoomed_bbox.getMax()[0] << std::endl; + + for(double y = zoomed_bbox.getMin()[1]; y <= zoomed_bbox.getMax()[1]; + y += (zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]) / npts_y) + { + primal::Point query({x, y}); + + int nevals = 0; + + double true_wn = 0.0; + double linearized = 0.0; + double quadrature = 0.0; + + for(int i = 0; i < open_trumpet.size(); ++i) + { + true_wn += primal::winding_number(query, open_trumpet[i], nevals); + quadrature += primal::winding_number_quad(query, open_trumpet[i], 30); + } + + for(int i = 0; i < linearized_shape.size(); ++i) + { + linearized += primal::winding_number(query, linearized_shape[i], nevals); + } + + zoomed_true_out << std::setprecision(16) << x << ',' << y << "," + << true_wn << std::endl; + zoomed_linearized_out << std::setprecision(16) << x << ',' << y << "," + << linearized << std::endl; + zoomed_quadrature_out << std::setprecision(16) << x << ',' << y << "," + << quadrature << std::endl; + } + } +} + +TEST(primal_2d_paper_figure_data, animation_still) +{ + //return; + std::cout << "Running: \"animation_still\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\animation\\"; + + int npts_x = 200 * 11; + int npts_y = 200 * 8.5; + + std::string name = "all_logos"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + for(int i = 0; i < shape.size(); ++i) + { + shape_out << shape[i] << std::endl; + } + + BoundingBox bbox = curves_bbox(shape, 1.2, false); + // Scale the box so that it has a ratio of 11x8.5 + primal::Point min_pt = bbox.getMin(); + primal::Point max_pt = bbox.getMax(); + double ratio = 11.0 / 8.5; + //double ratio = 8.5 / 11.0; + double width = max_pt[0] - min_pt[0]; + double height = max_pt[1] - min_pt[1]; + if(width / height > ratio) + { + double new_height = width / ratio; + min_pt[1] -= (new_height - height) / 2.0; + max_pt[1] += (new_height - height) / 2.0; + } + else + { + double new_width = height * ratio; + min_pt[0] -= (new_width - width) / 2.0; + max_pt[0] += (new_width - width) / 2.0; + } + simple_grid_test(shape, bbox, npts_x, npts_y, wn_out); +} + +TEST(primal_2d_paper_figure_data, background) +{ + return; + std::cout << "Running: \"background\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = "E:\\Code\\winding_number\\figures\\animation\\"; + + int npts_x = 400 * 16; + int npts_y = 400 * 9; + + std::string name = "background"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + for(int i = 0; i < shape.size(); ++i) + { + shape_out << shape[i] << std::endl; + } + + BoundingBox bbox = curves_bbox(shape, 1.0, false); + + simple_grid_test(shape, bbox, npts_x, npts_y, wn_out); +} + +TEST(primal_2d_paper_figure_data, animation) +{ + return; + std::cout << "Running: \"animation\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = "E:\\Code\\winding_number\\figures\\animation\\"; + + int permutation[] = { + 111, 32, 88, 120, 101, 66, 68, 7, 54, 113, 65, 117, 129, 67, 110, + 125, 92, 85, 95, 8, 23, 83, 122, 26, 64, 3, 104, 51, 22, 30, + 63, 123, 84, 0, 91, 99, 62, 40, 61, 42, 94, 102, 33, 20, 100, + 126, 49, 53, 69, 107, 56, 78, 58, 71, 127, 108, 13, 130, 41, 43, + 19, 5, 31, 15, 86, 80, 79, 128, 90, 11, 24, 9, 16, 121, 116, + 14, 89, 74, 4, 25, 28, 57, 97, 47, 10, 82, 87, 133, 46, 44, + 36, 55, 1, 81, 119, 114, 35, 21, 27, 77, 98, 124, 6, 38, 45, + 34, 106, 29, 76, 96, 52, 48, 39, 105, 17, 72, 73, 37, 75, 103, + 70, 109, 2, 18, 132, 131, 12, 59, 60, 93, 112, 50, 118, 115}; + + int npts_x = 500; + int npts_y = 500; + + std::string name = "butterfly"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream shuffled_shape_out(data_dir + name + "shuffled_.txt"); + std::ofstream wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + CurveArray shuffled_shape; + primal::convert_from_svg(data_dir + name + "_simple_bonus_edger.svg", + shuffled_shape); + + BoundingBox bbox = curves_bbox(shape, 1.25, true); + primal::Point min_pt = bbox.getMin(); + primal::Point max_pt = bbox.getMax(); + auto length = bbox.getMax()[0] - bbox.getMin()[0]; + min_pt[0] -= length * 7.0 / 9.0 * 0.9; + max_pt[0] += length * 7.0 / 9.0 * 0.1; + bbox.addPoint(min_pt); + bbox.addPoint(max_pt); + + for(int i = 0; i < shape.size(); ++i) + { + shape_out << shape[i] << std::endl; + shuffled_shape_out << shuffled_shape[i] << std::endl; + } + + // Curves one at a time + if(false) + { + CurveArray shape_increment; + + // Print them all to the file + for(int i = 0; i < shape.size(); ++i) + { + shape_increment.push_back(shape[permutation[i]]); + std::ofstream increment_shape_out(data_dir + "animation1_frames\\" + + name + "_" + std::to_string(i) + ".txt"); + std::ofstream increment_wn_out(data_dir + "animation1_frames\\" + name + + "_" + std::to_string(i) + "_wn.csv"); + + for(int j = 0; j < shape_increment.size(); ++j) + { + increment_shape_out << shape_increment[j] << std::endl; + } + + simple_grid_test(shape_increment, bbox, npts_x, npts_y, increment_wn_out); + } + } + + // Explode n Back + if(false) + { + auto interp_curve = [](const primal::BezierCurve& curve1, + const primal::BezierCurve& curve2, + double t) -> primal::BezierCurve { + primal::BezierCurve result(curve1.getOrder()); + for(int i = 0; i <= curve1.getOrder(); ++i) + { + result[i][0] = curve1[i][0] * (1.0 - t) + curve2[i][0] * t; + result[i][1] = curve1[i][1] * (1.0 - t) + curve2[i][1] * t; + } + + return result; + }; + + double a = 10; + auto speed_func = [&a](double t) -> double { + double g0 = 1.0 / (1 + std::exp(0.5 * a)); + return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); + }; + + for(int frame = 0; frame <= 100; ++frame) + { + double t; + if(frame < 50) + { + t = speed_func(frame / 50.0); + } + else + { + t = speed_func((100 - frame) / 50.0); + } + + CurveArray midshift_shape; + std::ofstream midshift_shape_out(data_dir + "animation3_frames\\frame_" + + std::to_string(frame) + "_shape.txt"); + std::ofstream midshift_wn_out(data_dir + "animation3_frames\\frame_" + + std::to_string(frame) + "_wn.csv"); + + for(int i = 0; i < shape.size(); ++i) + { + primal::BezierCurve interp = + interp_curve(shape[i], shuffled_shape[i], t); + midshift_shape_out << interp << std::endl; + midshift_shape.push_back(interp); + } + + simple_grid_test(midshift_shape, bbox, npts_x, npts_y, midshift_wn_out); + } + } + + // Slide a curve across the screen + if(true) + { + auto interp_curve = [](const primal ::BezierCurve& curve1, + const primal::BezierCurve& curve2, + double t) -> primal::BezierCurve { + primal::BezierCurve result(curve1.getOrder()); + for(int i = 0; i <= curve1.getOrder(); ++i) + { + result[i][0] = curve1[i][0] * (1.0 - t) + curve2[i][0] * t; + result[i][1] = curve1[i][1] * (1.0 - t) + curve2[i][1] * t; + } + + return result; + }; + + double a = 5; + auto speed1 = [&a](double t) -> double { + double g0 = 1.0 / (1 + std::exp(0.5 * a)); + return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); + }; + + auto speed2 = [](double t) -> double { + if(t < 0.5) + return (std::pow(20, t) - 1) / (std::sqrt(20) - 1); + else + return (std::pow(20, 1 - t) - 1) / (std::sqrt(20) - 1); + }; + + shuffled_shape[103].reverseOrientation(); + primal::BezierCurve midtwist_curve(3); + midtwist_curve[0] = primal::Point {1417.0, 67.0}; + midtwist_curve[1] = primal::Point {1920.88, -95.604}; + midtwist_curve[2] = primal::Point {1920.88, -95.604}; + midtwist_curve[3] = primal::Point {2473.0, -240.0}; + + double nframes = 150; + for(int frame = 0; frame <= nframes; ++frame) + { + double t = speed1(frame / nframes); + + CurveArray midshift_shape; + std::ofstream midshift_shape_out(data_dir + "animation3_frames\\frame_" + + std::to_string(frame) + "_shape.txt"); + std::ofstream midshift_wn_out(data_dir + "animation3_frames\\frame_" + + std::to_string(frame) + "_wn.csv"); + + primal::BezierCurve motion_curve1 = + shuffled_shape[shuffled_shape.size() - 2]; + primal::BezierCurve motion_curve2 = + shuffled_shape[shuffled_shape.size() - 1]; + + primal::Point new_point, reference_point = shape[0][0]; + + if(frame < (nframes / 2)) + new_point = motion_curve1.evaluate(speed2(frame / nframes)); + else + new_point = motion_curve2.evaluate(1.0 - speed2(frame / nframes)); + + primal::Point the_shift {new_point[0] - reference_point[0], + new_point[1] - reference_point[1]}; + + primal::BezierCurve new_curve_1(3), new_curve_2(3); + + for(int i = 0; i <= 3; ++i) + { + new_curve_1[i][0] = shape[0][i][0] + the_shift[0]; + new_curve_1[i][1] = shape[0][i][1] + the_shift[1]; + + new_curve_2[i][0] = shape[1][i][0] + the_shift[0]; + new_curve_2[i][1] = shape[1][i][1] + the_shift[1]; + } + + midshift_shape_out << new_curve_1 << std::endl; + midshift_shape_out << new_curve_2 << std::endl; + + midshift_shape.push_back(new_curve_1); + midshift_shape.push_back(new_curve_2); + + //shuffled_shape[103][0] = shape[103][3]; + //shuffled_shape[103][1][0] += 2.5; + //shuffled_shape[103][2][0] += 2.5; + //shuffled_shape[103][3] = shape[103][0]; + + for(int i = 2; i < shape.size(); ++i) + { + primal::BezierCurve interp(3); + + if(i == 103) + interp = + interp_curve(interp_curve(shape[i], midtwist_curve, t), + interp_curve(midtwist_curve, shuffled_shape[i], t), + t); + else + interp = interp_curve(shape[i], shuffled_shape[i], t); + midshift_shape_out << interp << std::endl; + midshift_shape.push_back(interp); + } + + simple_grid_test(midshift_shape, bbox, npts_x, npts_y, midshift_wn_out); + + if(frame == nframes) shape = midshift_shape; + } + } + + // zoom in + if(false) + { + int idx = 109; + auto zoom_pt = shape[idx][shape[idx].getOrder()]; + zoom_pt[1] -= 0.5; + double zoom_r = 1; + BoundingBox zoomed_bbox; + zoomed_bbox.addPoint( + primal::Point {zoom_pt[0] + zoom_r, zoom_pt[1] + zoom_r}); + zoomed_bbox.addPoint( + primal::Point {zoom_pt[0] - zoom_r, zoom_pt[1] - zoom_r}); + + primal::Point zoomed_min_pt = zoomed_bbox.getMin(); + primal::Point zoomed_max_pt = zoomed_bbox.getMax(); + + zoomed_max_pt[0] += 2 * zoom_r * 7.0 / 9.0 * 0.1; + zoomed_min_pt[0] -= 2 * zoom_r * 7.0 / 9.0 * 0.9; + zoomed_bbox.addPoint(zoomed_min_pt); + zoomed_bbox.addPoint(zoomed_max_pt); + + double a = 15; + auto speed_func = [&a](double t) -> double { + double g0 = 1.0 / (1 + std::exp(0.5 * a)); + return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); + }; + + auto interp_box = [](const primal::BoundingBox& box1, + const primal::BoundingBox& box2, + double t) -> primal::BoundingBox { + primal::BoundingBox result; + result.addPoint(primal::Point { + box1.getMin()[0] * (1.0 - t) + box2.getMin()[0] * t, + box1.getMin()[1] * (1.0 - t) + box2.getMin()[1] * t}); + + result.addPoint(primal::Point { + box1.getMax()[0] * (1.0 - t) + box2.getMax()[0] * t, + box1.getMax()[1] * (1.0 - t) + box2.getMax()[1] * t}); + + return result; + }; + + double nframes = 150; + for(int frame = 0; frame <= nframes; ++frame) + { + double t = speed_func(frame / nframes); + + std::ofstream zoomed_wn_out(data_dir + "animation2_frames\\frame_" + + std::to_string(frame) + "_wn.csv"); + + auto semizoomed_bbox = interp_box(bbox, zoomed_bbox, t); + simple_grid_test(shape, semizoomed_bbox, npts_x, npts_y, zoomed_wn_out); + } + } +} + +TEST(primal_2d_paper_figure_data, fish_zoom) +{ + return; + std::cout << "Running: \"fish zoom\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\animation\\"; + + int npts_x = 500; + int npts_y = 500; + + std::string name = "fish"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + // Jiggle the curves, and print them all to the file + srand(100); + + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + + shape[i][0][0] += (rand() % 30) - 15; + shape[i][0][1] += (rand() % 30) - 15; + + shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; + shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; + + shape_out << shape[i] << std::endl; + } + + BoundingBox bbox = curves_bbox(shape, 1.1, true); + + // zoom in + BoundingBox zoomed_bbox(shape[240].boundingBox()); + double max_dim = + axom::utilities::max(zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0], + zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]); + zoomed_bbox.addPoint( + primal::Point {zoomed_bbox.getMin()[0] + max_dim, + zoomed_bbox.getMin()[1] + max_dim}); + zoomed_bbox.scale(12.0); + + double a = 15; + auto speed_func = [&a](double t) -> double { + double g0 = 1.0 / (1 + std::exp(0.5 * a)); + return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); + }; + + auto interp_box = [](const primal::BoundingBox& box1, + const primal::BoundingBox& box2, + double t) -> primal::BoundingBox { + primal::BoundingBox result; + result.addPoint(primal::Point { + box1.getMin()[0] * (1.0 - t) + box2.getMin()[0] * t, + box1.getMin()[1] * (1.0 - t) + box2.getMin()[1] * t}); + + result.addPoint(primal::Point { + box1.getMax()[0] * (1.0 - t) + box2.getMax()[0] * t, + box1.getMax()[1] * (1.0 - t) + box2.getMax()[1] * t}); + + return result; + }; + + double nframes = 150; + for(int frame = 0; frame <= nframes; ++frame) + { + double t = speed_func(frame / nframes); + + std::ofstream zoomed_wn_out(data_dir + "fish_animation\\frame_" + + std::to_string(frame) + "_wn.csv"); + + auto semizoomed_bbox = interp_box(bbox, zoomed_bbox, t); + simple_grid_test(shape, semizoomed_bbox, npts_x, npts_y, zoomed_wn_out); + } +} + +TEST(primal_2d_paper_figure_data, axom_trace) +{ + return; + std::cout << "Running: \"axom trace\"" << std::endl; + + using CurveArray = axom::Array>; + using BoundingBox = primal::BoundingBox; + + std::string data_dir = + "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\axom_animation\\"; + + int npts_x = 500; + int npts_y = 500; + + std::string name = "siggraph_logo"; + CurveArray shape; + std::ofstream shape_out(data_dir + name + ".txt"); + std::ofstream wn_out(data_dir + name + "_wn.csv"); + primal::convert_from_svg(data_dir + name + ".svg", shape); + + for(int i = 0; i < shape.size(); ++i) + { + shape[i].reverseOrientation(); + shape_out << shape[i] << std::endl; + } + + BoundingBox bbox = curves_bbox(shape, 1.2, false); + + double nframes = 200; + for(int frame = 0; frame <= nframes; ++frame) + { + double t = frame / nframes; + + std::ofstream traced_wn_out(data_dir + "frames_2\\frame_" + + std::to_string(frame) + "_wn.csv"); + std::ofstream traced_shape_out(data_dir + +"frames_2\\frame_" + + std::to_string(frame) + "_curves.csv"); + + CurveArray traced_shape; + + primal::BezierCurve real_curve, dummy; + for(int i = 0; i < shape.size(); ++i) + { + shape[i].split(t, real_curve, dummy); + traced_shape_out << real_curve << std::endl; + + traced_shape.push_back(real_curve); + } + + simple_grid_test(traced_shape, bbox, npts_x, npts_y, traced_wn_out); + } +} + +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} From 6ecde4c39bea0ca5a822c4a34bc40557569f46f8 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 5 Sep 2024 08:58:14 -0600 Subject: [PATCH 02/25] Remove debug print statements and figure makers --- src/axom/primal/CMakeLists.txt | 2 - src/axom/primal/operators/printers.hpp | 899 ------ .../tests/primal_2d_paper_figure_data.cpp | 2802 ----------------- 3 files changed, 3703 deletions(-) delete mode 100644 src/axom/primal/operators/printers.hpp delete mode 100644 src/axom/primal/tests/primal_2d_paper_figure_data.cpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index a395d5d76c..329085b383 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -58,8 +58,6 @@ set( primal_headers operators/split.hpp operators/winding_number.hpp - operators/printers.hpp - operators/detail/clip_impl.hpp operators/detail/compute_moments_impl.hpp operators/detail/fuzzy_comparators.hpp diff --git a/src/axom/primal/operators/printers.hpp b/src/axom/primal/operators/printers.hpp deleted file mode 100644 index fc9dd3ffce..0000000000 --- a/src/axom/primal/operators/printers.hpp +++ /dev/null @@ -1,899 +0,0 @@ -// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and -// other Axom Project Developers. See the top-level LICENSE file for details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -/*! - * \file printers.hpp - * - * \brief Consists of jacob's neat little debugging print statements - * - * \note If you see this in a repo, it reflects a failure on jacob's part. - * Reprimand him for his error. - */ - -#ifndef PRIMAL_PRINTERS_HPP_ -#define PRIMAL_PRINTERS_HPP_ - -// Axom includes -#include "axom/config.hpp" -#include "axom/primal.hpp" -#include "axom/primal/geometry/BezierCurve.hpp" -#include "axom/primal/geometry/BezierPatch.hpp" -#include "axom/primal/geometry/OrientedBoundingBox.hpp" - -#include -#include -#include -#include -#include - -namespace axom -{ -namespace primal -{ - -template -void python_print(const Point& point, const char* plot_lines = nullptr) -{ - printf("plot_query( fig, ax, (%.17f, %.17f, %.17f), ", - point[0], - point[1], - point[2]); - if(plot_lines) printf("plot_lines=\"%s\", ", plot_lines); - printf("size=111)\n\n"); -} - -template -void python_print(const Vector& vec, - const Point& point, - const char* color = "blue") -{ - printf("plot_vector( fig, ax, (%.17f, %.17f, %.17f), ", vec[0], vec[1], vec[2]); - printf("origin=(%.17f, %.17f, %.17f),", point[0], point[1], point[2]); - printf("color=\"%s\")\n", color); -} - -template -void desmos_print(const Point& point) -{ - printf("Q = [%.17f, %.17f, %.17f]\n", point[0], point[1], point[2]); -} - -template -void desmos_print(const Point& point) -{ - printf("Q = (%.17f, %.17f)\n", point[0], point[1]); -} - -template -void python_print(const BezierCurve& curve, - bool plot_tangent = false, - bool plot_endpoints = false, - char color = 'k') -{ - const int ord = curve.getOrder(); - - printf("my_BezierCurve( ["); - printf("(%.17f,%.17f,%.17f)", curve[0][0], curve[0][1], curve[0][2]); - for(int i = 1; i <= ord; ++i) - printf(",(%.17f,%.17f,%.17f)", curve[i][0], curve[i][1], curve[i][2]); - if(curve.isRational()) - { - printf(" ], [%.17f", curve.getWeight(0)); - if(curve.isRational()) - for(int i = 1; i <= ord; ++i) printf(",%.17f", curve.getWeight(i)); - } - printf("] ).plot( fig, ax, None, '%c'", color); - if(plot_tangent) printf(", plot_tangent=True"); - if(plot_endpoints) printf(", plot_endpoints=True"); - printf(" )\n\n"); -} - -template -void desmos_print(const BezierCurve& curve, int num = 0) -{ - printf("C(P_{%d}, W_{%d})\n", num, num); - printf("P_{%d} = [(%.17f,%.17f)", num, curve[0][0], curve[0][1]); - for(int p = 1; p <= curve.getOrder(); ++p) - printf(",(%.17f,%.17f)", curve[p][0], curve[p][1]); - printf("]\n"); - - if(curve.isRational()) - { - printf("W_{%d} = [%.17f", num, curve.getWeight(0)); - for(int p = 1; p <= curve.getOrder(); ++p) - printf(",%.17f", curve.getWeight(p)); - printf("]\n"); - } - else - { - printf("W_{%d} = [1", num); - for(int p = 1; p <= curve.getOrder(); ++p) printf(",1"); - printf("]\n"); - } -} - -template -void desmos_print(const BezierCurve& curve) -{ - const int ord = curve.getOrder(); - for(int i = 0; i <= ord; ++i) - { - printf("P_{%d} = [%.17f, %.17f, %.17f]\n", - i + 1, - curve[i][0], - curve[i][1], - curve[i][2]); - } - - if(curve.isRational()) - { - printf("W = [%.17f", curve.getWeight(0)); - for(int i = 1; i <= ord; ++i) - { - printf(", %.17f", curve.getWeight(i)); - } - printf("]\n"); - } -} - -template -void python_print(const BezierPatch& patch, - const char* cmap = "cm.Reds", - char cpcolor = '\0', - bool plot_normal = false) -{ - const int ord_u = patch.getOrder_u(); - const int ord_v = patch.getOrder_v(); - - printf("my_BezierPatch(%d, %d, [", ord_u, ord_v); - printf("(%.17f,%.17f,%.17f)", patch(0, 0)[0], patch(0, 0)[1], patch(0, 0)[2]); - for(int i = 0; i <= ord_u; ++i) - for(int j = (i == 0 ? 1 : 0); j <= ord_v; ++j) - printf(",(%.17f,%.17f,%.17f)", - patch(i, j)[0], - patch(i, j)[1], - patch(i, j)[2]); - if(patch.isRational()) - { - printf(" ], [%.17f", patch.getWeight(0, 0)); - if(patch.isRational()) - for(int i = 0; i <= ord_u; ++i) - for(int j = (i == 0 ? 1 : 0); j <= ord_v; ++j) - printf(",%.17f", patch.getWeight(i, j)); - } - printf("] ).plot( fig, ax, %s", cmap); - if(plot_normal) printf(", plot_normal=True"); - if(cpcolor != '\0') printf(", cpcolor='%c'", cpcolor); - printf(")\n\n"); -} - -template -void desmos_print(const BezierPatch& patch) -{ - // clang-format off - const int ord_u = patch.getOrder_u(); - const int ord_v = patch.getOrder_v(); - - printf("M = %d\n", ord_u); - printf("N = %d\n", ord_v); - - // Print u basis functions - for(int u = 0; u <= ord_u; ++u) - printf("B_{%dM}(u) = nCr\\left(M, %d\\right)(1 - u)^{(M - %d)}u^{%d}\n", u + 1, u, u, u); - - // Print v basis functions - for(int v = 0; v <= ord_v; ++v) - printf("B_{%dN}(v) = nCr\\left(N, %d\\right)(1 - v)^{(N - %d)}v^{%d}\n", v + 1, v, v, v); - - // ===================== Print weight ====================== - printf("w(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nw_{u}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nw_{v}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nw_{uu}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nw_{vv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nw_{uv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - // ===================== Print X ====================== - printf("\nX(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nX_{u}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nX_{v}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nX_{uu}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nX_{vv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nX_{uv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("X_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - // ===================== Print Y ====================== - printf("\nY(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nY_{u}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nY_{v}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nY_{uu}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nY_{vv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nY_{uv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Y_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - // ===================== Print Z ====================== - printf("\nZ(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nZ_{u}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nZ_{v}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nZ_{uu}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}''(u)B_{%dN}(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nZ_{vv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}(u)B_{%dN}''(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nZ_{uv}(u, v) = "); - for(int u = 0; u <= ord_u; ++u) - for(int v = 0; v <= ord_v; ++v) - { - printf("Z_{%d}[%d]W_{%d}[%d]B_{%dM}'(u)B_{%dN}'(v)", v + 1, u + 1, v + 1, u + 1, u + 1, v + 1); - if(u != ord_u || v != ord_v) printf(" + "); - } - - printf("\nP(u, v) = [X(u, v), Y(u, v), Z(u, v)]"); - printf("\nP_{u}(u, v) = [X_{u}(u, v), Y_{u}(u, v), Z_{u}(u, v)]"); - printf("\nP_{v}(u, v) = [X_{v}(u, v), Y_{v}(u, v), Z_{v}(u, v)]"); - printf("\nP_{uu}(u, v) = [X_{uu}(u, v), Y_{uu}(u, v), Z_{uu}(u, v)]"); - printf("\nP_{vv}(u, v) = [X_{vv}(u, v), Y_{vv}(u, v), Z_{vv}(u, v)]"); - printf("\nP_{uv}(u, v) = [X_{uv}(u, v), Y_{uv}(u, v), Z_{uv}(u, v)]"); - - printf("\nS(u, v) = \\frac{P(u, v)}{w(u, v)}"); - printf("\nS_u(u, v) = \\frac{P_{u}(u, v) - S(u, v)w_{u}(u, v)}{w(u, v)}"); - printf("\nS_v(u, v) = \\frac{P_{v}(u, v) - S(u, v)w_{v}(u, v)}{w(u, v)}"); - printf("\nS_{uu}(u, v) = \\frac{P_{uu}(u, v) - 2S_u(u, v)w_u(u, v) - S(u, v)w_{uu}(u, v)}{w(u, v)}"); - printf("\nS_{vv}(u, v) = \\frac{P_{vv}(u, v) - 2S_v(u, v)w_v(u, v) - S(u, v)w_{vv}(u, v)}{w(u, v)}"); - printf("\nS_{uv}(u, v) = \\frac{P_{uv}(u, v) - S_u(u, v)w_v(u, v) - S_v(u, v)w_u(u, v) - S(u, v)w_{uv}(u, v)}{w(u, v)}"); - - printf("\ns = 0.0000000001"); - printf("\nu_0 = "); - printf("\nv_0 = "); - - printf("\nS_{uu}(u_0, v_0)[1]"); - printf("\nS_{uu}(u_0, v_0)[2]"); - printf("\nS_{uu}(u_0, v_0)[3]"); - - printf("\nS_{vv}(u_0, v_0)[1]"); - printf("\nS_{vv}(u_0, v_0)[2]"); - printf("\nS_{vv}(u_0, v_0)[3]"); - - printf("\nS_{uv}(u_0, v_0)[1]"); - printf("\nS_{uv}(u_0, v_0)[2]"); - printf("\nS_{uv}(u_0, v_0)[3]"); - printf("\n\n"); - for(int v = 0; v <= ord_v; ++v) - { - printf("W_{%d} = [%f", v + 1, patch.getWeight(0, v)); - for(int u = 1; u <= ord_u; ++u) - { - printf(", %f", patch.getWeight(u, v)); - } - printf("]\nX_{%d} = [%f", v + 1, patch(0, v)[0]); - for(int u = 1; u <= ord_u; ++u) - { - printf(", %f", patch(u, v)[0]); - } - printf("]\nY_{%d} = [%f", v + 1, patch(0, v)[1]); - for(int u = 1; u <= ord_u; ++u) - { - printf(", %f", patch(u, v)[1]); - } - printf("]\nZ_{%d} = [%f", v + 1, patch(0, v)[2]); - for(int u = 1; u <= ord_u; ++u) - { - printf(", %f", patch(u, v)[2]); - } - printf("]\n"); - } - // clang-format on -} - -template -void python_print(const OrientedBoundingBox oBox, - const char* color = "blue") -{ - auto verts = oBox.vertices(); - printf("plot_box( fig, ax, ["); - printf("(%.17f,%.17f,%.17f)", verts[0][0], verts[0][1], verts[0][2]); - for(int i = 1; i < verts.size(); ++i) - printf(",(%.17f,%.17f,%.17f)", verts[i][0], verts[i][1], verts[i][2]); - printf("], color='%s' )\n\n", color); -} - -template -void python_print(const OrientedBoundingBox oBox, - Lambda rotate_point, - numerics::Matrix rotator, - const char* color = "blue") -{ - auto verts = oBox.vertices(); - printf("plot_box( fig, ax, ["); - auto rot_point = rotate_point(rotator, verts[0]); - printf("(%.17f,%.17f,%.17f)", rot_point[0], rot_point[1], rot_point[2]); - for(int i = 1; i < verts.size(); ++i) - { - rot_point = rotate_point(rotator, verts[i]); - printf(",(%.17f,%.17f,%.17f)", rot_point[0], rot_point[1], rot_point[2]); - } - printf("], color='%s' )\n\n", color); -} - -template -void python_print(const BoundingBox& bBox, const char* color = "blue") -{ - std::vector> verts; - BoundingBox::getPoints(bBox, verts); - printf("plot_bounding_box( fig, ax, ["); - printf("(%.17f,%.17f,%.17f)", verts[0][0], verts[0][1], verts[0][2]); - for(int i = 1; i < verts.size(); ++i) - printf(",(%.17f,%.17f,%.17f)", verts[i][0], verts[i][1], verts[i][2]); - printf("], color='%s' )\n", color); -} - -template -void python_print(const Polygon& poly, bool closed = true) -{ - printf("plot_polygon(fig, ax, ["); - printf("(%.17f,%.17f,%.17f)", poly[0][0], poly[0][1], poly[0][2]); - for(int i = 1; i < poly.numVertices(); ++i) - printf(",(%.17f,%.17f,%.17f)", poly[i][0], poly[i][1], poly[i][2]); - printf("]"); - if(!closed) printf(", closed=False"); - printf(")\n"); -} - -template -void desmos_print(const Polygon& poly) -{ - printf("polygon("); - printf("(%.17f,%.17f)", poly[0][0], poly[0][1]); - for(int i = 1; i < poly.numVertices(); ++i) - printf(",(%.17f,%.17f)", poly[i][0], poly[i][1]); - printf(")\n"); -} - -template -void convert_from_svg(std::string filename, axom::Array>& curves) -{ - // Get regex pattern for all path elements - std::regex pattern("<\\s*path[^>]*\\bd\\s*=\\s*[\"']([^\"']+)\"[^>]*>"); - - // Read in file - std::ifstream svg_file(filename); - - if(!svg_file.is_open()) - { - std::cerr << "Failed to open the SVG file." << std::endl; - return; // Exit with an error code - } - - std::string input((std::istreambuf_iterator(svg_file)), - std::istreambuf_iterator()); - - std::sregex_iterator iter(input.begin(), input.end(), pattern); - std::sregex_iterator end; - - while(iter != end) - { - std::smatch match = *iter; - std::string dAttribute = match[1].str(); - - // Replace all ',' with ' ' - dAttribute = - std::regex_replace(dAttribute, std::regex(","), std::string(" ")); - - // Put a space before and after every letter - dAttribute = std::regex_replace(dAttribute, - std::regex("([a-zA-Z])"), - std::string(" $1 ")); - - char command = '\0'; - double init_x = 0.0, init_y = 0.0; - double curr_x = 0.0, curr_y = 0.0; - double x1, y1, x2, y2, x, y; - double dx1, dy1, dx2, dy2, dx, dy; - - // Iterate over words in the string - std::istringstream iss(dAttribute); - std::string word; - - while(iss >> word) - { - // Check if the word is a command - if(isalpha(word[0])) - { - command = word[0]; - if(command == 'Z' || command == 'z') - { - axom::Array> nodes = {Point {curr_x, curr_y}, - Point {init_x, init_y}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 1)); - - // Update the current position - curr_x = init_x; - curr_y = init_y; - } - } - else - { - // Check if the command is a move command - if(command == 'M' || command == 'm') - { - // If the command is relative, add the value to the current position - if(command == 'm') - { - dx = std::stod(word); - iss >> dy; - curr_x += dx; - curr_y += dy; - command = 'l'; - } - else - { - x = std::stod(word); - iss >> y; - curr_x = x; - curr_y = y; - command = 'L'; - } - - // Set the initial position - init_x = curr_x; - init_y = curr_y; - } - else if(command == 'L' || command == 'l') - { - // If the command is relative, add the value to the current position - if(command == 'l') - { - dx = std::stod(word); - iss >> dy; - axom::Array> nodes = { - Point {curr_x, curr_y}, - Point {curr_x + dx, curr_y + dy}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 1)); - - // Update the current position - curr_x += dx; - curr_y += dy; - } - else - { - x = std::stod(word); - iss >> y; - axom::Array> nodes = {Point {curr_x, curr_y}, - Point {x, y}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 1)); - - // Update the current position - curr_x = x; - curr_y = y; - } - } - else if(command == 'C' || command == 'c') - { - // If the command is relative, add the value to the current position - if(command == 'c') - { - dx1 = std::stod(word); - iss >> dy1 >> dx2 >> dy2 >> dx >> dy; - axom::Array> nodes = { - Point {curr_x, curr_y}, - Point {curr_x + dx1, curr_y + dy1}, - Point {curr_x + dx2, curr_y + dy2}, - Point {curr_x + dx, curr_y + dy}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 3)); - - // Update the current position - curr_x += dx; - curr_y += dy; - } - else - { - x1 = std::stod(word); - iss >> y1 >> x2 >> y2 >> x >> y; - axom::Array> nodes = {Point {curr_x, curr_y}, - Point {x1, y1}, - Point {x2, y2}, - Point {x, y}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 3)); - - // Update the current position - curr_x = x; - curr_y = y; - } - } - else if(command == 'H' || command == 'h') - { - // If the command is relative, add the value to the current position - if(command == 'h') - { - dx = std::stod(word); - axom::Array> nodes = {Point {curr_x, curr_y}, - Point {curr_x + dx, curr_y}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 1)); - - // Update the current position - curr_x += dx; - } - else - { - x = std::stod(word); - axom::Array> nodes = {Point {curr_x, curr_y}, - Point {x, curr_y}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 1)); - - // Update the current position - curr_x = x; - } - } - else if(command == 'V' || command == 'v') - { - // If the command is relative, add the value to the current position - if(command == 'v') - { - dy = std::stod(word); - axom::Array> nodes = {Point {curr_x, curr_y}, - Point {curr_x, curr_y + dy}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 1)); - - // Update the current position - curr_y += dy; - } - else - { - y = std::stod(word); - axom::Array> nodes = {Point {curr_x, curr_y}, - Point {curr_x, y}}; - - // Add a line segment - curves.push_back(BezierCurve(nodes, 1)); - - // Update the current position - curr_y = y; - } - } - else - { - std::cout << word << " is an unrecognized command. Whoopsie!" - << std::endl; - } - } - } - ++iter; - } - - svg_file.close(); -} - -template -BoundingBox curves_bbox(axom::Array>& curves, - double scale_factor = 1.0, - bool make_square = false) -{ - // Find maximum and minimum x and y values - double min_x = curves[0][0][0], min_y = curves[0][0][1]; - double max_x = curves[0][0][0], max_y = curves[0][0][1]; - - for(auto& curve : curves) - { - for(int p = 0; p <= curve.getOrder(); ++p) - { - min_x = axom::utilities::min(min_x, curve[p][0]); - max_x = axom::utilities::max(max_x, curve[p][0]); - - min_y = axom::utilities::min(min_y, curve[p][1]); - max_y = axom::utilities::max(max_y, curve[p][1]); - } - } - - // Put those in a bounding box, then expand it - primal::Point bounds[2] = {primal::Point {min_x, min_y}, - primal::Point {max_x, max_y}}; - primal::BoundingBox bb(bounds, 2); - bb.scale(scale_factor); - - if(make_square) - { - int max_dim = bb.getLongestDimension(); - double max_len = bb.getMax()[max_dim] - bb.getMin()[max_dim]; - - primal::Point centroid = bb.getCentroid(); - - primal::Point new_min {centroid[0] - max_len / 2.0, - centroid[1] - max_len / 2.0}; - primal::Point new_max {centroid[0] + max_len / 2.0, - centroid[1] + max_len / 2.0}; - - bb.addPoint(new_min); - bb.addPoint(new_max); - } - - return bb; -} - -template -void simple_grid_test(axom::Array>& curves, - const BoundingBox& bb, - int npts_x, - int npts_y, - std::ofstream& wn_out) -{ - for(int xi = 0; xi < npts_x; ++xi) - { - double x = bb.getMin()[0] + xi * (bb.getMax()[0] - bb.getMin()[0]) / npts_x; - - //std::cout << x << std::endl; - printLoadingBar((x - bb.getMin()[0]) / (bb.getMax()[0] - bb.getMin()[0]) * 100, - 100); - for(int yi = 0; yi < npts_y; ++yi) - { - double y = bb.getMin()[1] + yi * (bb.getMax()[1] - bb.getMin()[1]) / npts_y; - primal::Point query({x, y}); - - int nevals = 0; - - double wn = 0.0; - for(int i = 0; i < curves.size(); ++i) - { - wn += primal::winding_number(query, curves[i], nevals, 1e-16, 1e-16); - } - - wn_out << x << "," << y << "," << wn << std::endl; - } - } -} - -template -void simple_grid_test_memoized( - axom::Array, - primal::detail::BezierCurveMemo, - primal::detail::PairHash>>& marray, - const BoundingBox& bb, - int npts_x, - int npts_y, - std::ofstream& wn_out) -{ - primal::Polygon temp_approxogon(20); - - for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; - x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) - { - //std::cout << x << std::endl; - printLoadingBar((x - bb.getMin()[0]) / (bb.getMax()[0] - bb.getMin()[0]) * 100, - 100); - for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; - y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double wn = 0.0; - - wn += primal::winding_number_approxogon_memoized(query, - marray, - temp_approxogon, - 1e-16); - - wn_out << x << "," << y << "," << wn << std::endl; - } - } -} - -template -void simple_timing_test(axom::Array>& curves, - const BoundingBox& bb, - int npts_x, - int npts_y, - std::ofstream& wn_out) -{ - for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; - x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) - { - //std::cout << x << std::endl; - printLoadingBar((x - bb.getMin()[0]) / (bb.getMax()[0] - bb.getMin()[0]) * 100, - 100); - for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; - y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double wn = 0.0; - for(int i = 0; i < curves.size(); ++i) - { - wn += primal::winding_number(query, curves[i], nevals, 1e-16, 1e-16); - } - - wn_out << x << "," << y << "," << wn << std::endl; - } - } -} -inline void printLoadingBar(int progress, int total, int barWidth = 40) -{ - float percentage = static_cast(progress) / total; - int progressWidth = static_cast(percentage * barWidth); - - std::cout << "["; - for(int i = 0; i < progressWidth; ++i) std::cout << "="; - for(int i = progressWidth; i < barWidth; ++i) std::cout << "-"; - std::cout << "] " << std::setw(3) << static_cast(percentage * 100.0) - << "%\r"; - std::cout.flush(); -} - -} // namespace primal - -} // namespace axom - -#endif diff --git a/src/axom/primal/tests/primal_2d_paper_figure_data.cpp b/src/axom/primal/tests/primal_2d_paper_figure_data.cpp deleted file mode 100644 index ca5581a108..0000000000 --- a/src/axom/primal/tests/primal_2d_paper_figure_data.cpp +++ /dev/null @@ -1,2802 +0,0 @@ -// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and -// other Axom Project Developers. See the top-level LICENSE file for details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -/*! - * \file primal_bezier_curve.cpp - * \brief This file tests primal's Bezier curve functionality - */ - -#include "gtest/gtest.h" - -#include "axom/core/FlatMap.hpp" -#include "axom/slic.hpp" - -#include "axom/primal/geometry/BezierCurve.hpp" -#include "axom/primal/operators/squared_distance.hpp" -#include "axom/primal/operators/winding_number.hpp" -#include "axom/primal/operators/detail/winding_number_impl.hpp" - -#include "axom/primal/operators/printers.hpp" - -#include -#include -#include - -namespace primal = axom::primal; - -TEST(primal_2d_paper_figure_data, generalized_winding) -{ - return; - - std::string data_dir = - "E:\\Code\\winding_number\\figures\\generalized_winding\\"; - std::string curve_name = "polygon"; - - axom::Array> polygon; - - // Read in the polygon, write out the curves for python - primal::convert_from_svg(data_dir + curve_name + ".svg", polygon); - std::ofstream polygon_curve_out(data_dir + curve_name + "_full_curves.txt"); - for(auto& edge : polygon) - { - polygon_curve_out << edge << std::endl; - } - - std::ofstream edge_curve_out(data_dir + curve_name + "_edge_curves.txt"); - edge_curve_out << polygon[3] << std::endl; - - // Set up fines in whcih to write out winding number data - std::ofstream full_winding_out(data_dir + curve_name + "_full_wn.csv"); - std::ofstream edge_winding_out(data_dir + curve_name + "_edge_wn.csv"); - - // Find maximum and minimum x and y values - double min_x = polygon[0][0][0], min_y = polygon[0][0][1]; - double max_x = polygon[0][0][0], max_y = polygon[0][0][1]; - - for(auto& edge : polygon) - { - for(int p = 0; p <= edge.getOrder(); ++p) - { - min_x = axom::utilities::min(min_x, edge[p][0]); - max_x = axom::utilities::max(max_x, edge[p][0]); - - min_y = axom::utilities::min(min_y, edge[p][1]); - max_y = axom::utilities::max(max_y, edge[p][1]); - } - } - - // Put those in a bounding box, then expand it - primal::Point bounds[2] = {primal::Point {min_x, min_y}, - primal::Point {max_x, max_y}}; - primal::BoundingBox bb(bounds, 2); - bb.scale(1.25); - - // Iterate over 250 points in the x and y direction between max and min - double npts_x = 250; - double npts_y = 250; - - for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; - x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) - { - std::cout << x << std::endl; - - for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; - y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double edge_winding_number = - primal::winding_number(query, polygon[3], nevals); - double full_winding_number = edge_winding_number; - - for(int i = 0; i < polygon.size(); ++i) - { - if(i == 3) continue; - - full_winding_number += primal::winding_number(query, polygon[i], nevals); - } - - full_winding_out << x << "," << y << "," << full_winding_number - << std::endl; - edge_winding_out << x << "," << y << "," << edge_winding_number - << std::endl; - } - } -} - -TEST(primal_2d_paper_figure_data, closure_example) -{ - return; - - std::string data_dir = "E:\\Code\\winding_number\\figures\\closure_example\\"; - std::string curve_name = "closure_example"; - - axom::Array> closed_curve; - - // Read in the polygon, write out the curves for python - primal::convert_from_svg(data_dir + curve_name + ".svg", closed_curve); - for(auto& curve : closed_curve) curve.reverseOrientation(); - - std::ofstream curve_out(data_dir + curve_name + "_curve_out.txt"); - std::ofstream curve_winding_out(data_dir + curve_name + "_curve_wn.csv"); - for(int i = 0; i < closed_curve.size() - 1; ++i) - { - curve_out << closed_curve[i] << std::endl; - } - - std::ofstream closure_out(data_dir + curve_name + "_closure_out.txt"); - std::ofstream closure_winding_out(data_dir + curve_name + "_closure_wn.csv"); - closure_out << closed_curve[closed_curve.size() - 1] << std::endl; - - // Find maximum and minimum x and y values - double min_x = closed_curve[0][0][0], min_y = closed_curve[0][0][1]; - double max_x = closed_curve[0][0][0], max_y = closed_curve[0][0][1]; - - for(auto& edge : closed_curve) - { - for(int p = 0; p <= edge.getOrder(); ++p) - { - min_x = axom::utilities::min(min_x, edge[p][0]); - max_x = axom::utilities::max(max_x, edge[p][0]); - - min_y = axom::utilities::min(min_y, edge[p][1]); - max_y = axom::utilities::max(max_y, edge[p][1]); - } - } - - // Put those in a bounding box, then expand it - primal::Point bounds[2] = {primal::Point {min_x, min_y}, - primal::Point {max_x, max_y}}; - primal::BoundingBox bb(bounds, 2); - bb.scale(1.25); - - // Iterate over 250 points in the x and y direction between max and min - double npts_x = 250; - double npts_y = 250; - - for(double x = bb.getMin()[0]; x <= bb.getMax()[0]; - x += (bb.getMax()[0] - bb.getMin()[0]) / npts_x) - { - std::cout << x << std::endl; - - for(double y = bb.getMin()[1]; y <= bb.getMax()[1]; - y += (bb.getMax()[1] - bb.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double curve_wn = 0.0; - for(int i = 0; i < closed_curve.size() - 1; ++i) - { - curve_wn += primal::winding_number(query, closed_curve[i], nevals); - } - - double closure_wn = - primal::winding_number(query, - closed_curve[closed_curve.size() - 1], - nevals); - - curve_winding_out << x << "," << y << "," << curve_wn << std::endl; - closure_winding_out << x << "," << y << "," << closure_wn << std::endl; - } - } -} - -TEST(primal_2d_paper_figure_data, generalized_winding_curves) -{ - return; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\generalized_winding_curves\\"; - - int npts_x = 500; - int npts_y = 500; - - std::string name = "heart"; - CurveArray shape; - primal::convert_from_svg(data_dir + name + ".svg", shape); - std::ofstream shape_curve_out(data_dir + name + ".txt"); - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - shape_curve_out << shape[i] << std::endl; - } - BoundingBox shape_bbox = curves_bbox(shape, 1.25); - std::ofstream shape_wn_out(data_dir + name + "_wn.csv"); - simple_grid_test(shape, shape_bbox, npts_x, npts_y, shape_wn_out); - - name = "exploded_heart2"; - CurveArray exploded_shape; - primal::convert_from_svg(data_dir + name + ".svg", exploded_shape); - std::ofstream exploded_shape_out(data_dir + name + ".txt"); - std::ofstream exploded_shape_wn_out(data_dir + name + "_wn.csv"); - for(int i = 0; i < exploded_shape.size(); ++i) - { - exploded_shape[i].reverseOrientation(); - exploded_shape_out << exploded_shape[i] << std::endl; - } - //BoundingBox exploded_shape_bbox = curves_bbox(exploded_shape, 1.25); - simple_grid_test(exploded_shape, shape_bbox, - npts_x, - npts_y, - exploded_shape_wn_out); - - std::cout << exploded_shape << std::endl; - - name = "heart_curve_1"; - CurveArray curve_1; - curve_1.push_back(shape[0]); - curve_1.push_back(shape[5]); - std::ofstream curve_1_out(data_dir + name + ".txt"); - std::ofstream curve_1_wn_out(data_dir + name + "_wn.csv"); - curve_1_out << curve_1[0] << std::endl; - curve_1_out << curve_1[1] << std::endl; - BoundingBox curve_1_bbox = curves_bbox(curve_1, 1.5, true); - simple_grid_test(curve_1, curve_1_bbox, npts_x, npts_y, curve_1_wn_out); - - name = "heart_curve_2"; - CurveArray curve_2; - curve_2.push_back(shape[3]); - curve_2.push_back(shape[4]); - std::ofstream curve_2_out(data_dir + name + ".txt"); - std::ofstream curve_2_wn_out(data_dir + name + "_wn.csv"); - curve_2_out << curve_2[0] << std::endl; - curve_2_out << curve_2[1] << std::endl; - BoundingBox curve_2_bbox = curves_bbox(curve_2, 1.5, true); - //curve_2_bbox.shift( (curve_1_bbox.getCentroid() - curve_2_bbox.getCentroid()) ); - simple_grid_test(curve_2, curve_2_bbox, npts_x, npts_y, curve_2_wn_out); - - name = "heart_curve_3"; - CurveArray curve_3; - curve_3.push_back(shape[1]); - std::ofstream curve_3_out(data_dir + name + ".txt"); - std::ofstream curve_3_wn_out(data_dir + name + "_wn.csv"); - curve_3_out << curve_3[0] << std::endl; - BoundingBox curve_3_bbox = curves_bbox(curve_3, 1.5, true); - //curve_3_bbox.shift((curve_1_bbox.getCentroid() - curve_3_bbox.getCentroid())); - simple_grid_test(curve_3, curve_3_bbox, npts_x, npts_y, curve_3_wn_out); - - name = "heart_curve_4"; - CurveArray curve_4; - curve_4.push_back(shape[2]); - std::ofstream curve_4_out(data_dir + name + ".txt"); - std::ofstream curve_4_wn_out(data_dir + name + "_wn.csv"); - curve_4_out << curve_4[0] << std::endl; - BoundingBox curve_4_bbox = curves_bbox(curve_4, 1.5, true); - //curve_4_bbox.shift((curve_1_bbox.getCentroid() - curve_4_bbox.getCentroid())); - simple_grid_test(curve_4, curve_4_bbox, npts_x, npts_y, curve_4_wn_out); -} - -TEST(primal_2d_paper_figure_data, linearize_example) -{ - return; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "E:\\Code\\winding_number\\figures\\linearize_example\\"; - - int npts_x = 250; - int npts_y = 250; - - std::string name = "curve_1"; - CurveArray curve_1; - std::ofstream curve_1_out(data_dir + name + ".txt"); - std::ofstream curve_1_wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", curve_1); - for(int i = 0; i < curve_1.size(); ++i) - { - curve_1_out << curve_1[i] << std::endl; - } - BoundingBox largest_bbox = curves_bbox(curve_1, 1.5, true); - simple_grid_test(curve_1, largest_bbox, npts_x, npts_y, curve_1_wn_out); - - name = "curve_2"; - CurveArray curve_2; - std::ofstream curve_2_out(data_dir + name + ".txt"); - std::ofstream curve_2_wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", curve_2); - for(int i = 0; i < curve_2.size(); ++i) - { - curve_2_out << curve_2[i] << std::endl; - } - simple_grid_test(curve_2, largest_bbox, npts_x, npts_y, curve_2_wn_out); - - name = "curve_3"; - CurveArray curve_3; - std::ofstream curve_3_out(data_dir + name + ".txt"); - std::ofstream curve_3_wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", curve_3); - for(int i = 0; i < curve_3.size(); ++i) - { - curve_3_out << curve_3[i] << std::endl; - } - BoundingBox curve_3_bbox = curves_bbox(curve_3, 1.5, true); - simple_grid_test(curve_3, largest_bbox, npts_x, npts_y, curve_3_wn_out); - - name = "curve_4"; - CurveArray curve_4; - std::ofstream curve_4_out(data_dir + name + ".txt"); - std::ofstream curve_4_wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", curve_4); - for(int i = 0; i < curve_4.size(); ++i) - { - curve_4[i].reverseOrientation(); - curve_4_out << curve_4[i] << std::endl; - } - BoundingBox curve_4_bbox = curves_bbox(curve_4, 1.5, true); - simple_grid_test(curve_4, largest_bbox, npts_x, npts_y, curve_4_wn_out); -} -//------------------------------------------------------------------------------ - -TEST(primal_2d_paper_figure_data, ray_casting_bad) -{ - return; - - std::cout << "Running: \"ray_casting_bad\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\ray_casting_bad\\"; - - int npts_x = 500; - int npts_y = 500; - - std::string name = "butterfly"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream shape_cn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - for(int i = 0; i < shape.size(); ++i) - { - shape_out << shape[i] << std::endl; - } - - // Replace curve 100 with a point - //std::cout << shape[100] << std::endl; - for(int i = 0; i <= shape[100].getOrder(); ++i) - { - shape[100][i] = shape[100][0]; - } - //std::cout << shape[100] << std::endl; - - BoundingBox shape_bbox = curves_bbox(shape, 1.1, false); - - for(double x = shape_bbox.getMin()[0]; x <= shape_bbox.getMax()[0]; - x += (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) / npts_x) - { - std::cout << x << std::endl; - - for(double y = shape_bbox.getMin()[1]; y <= shape_bbox.getMax()[1]; - y += (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int crossing_number = primal::crossing_number(query, shape, shape_bbox); - - shape_cn_out << std::setprecision(16) << x << ',' << y << "," - << crossing_number << std::endl; - } - } -} - -TEST(primal_2d_paper_figure_data, winding_number_good) -{ - return; - std::cout << "Running: \"winding_number_good\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\winding_number_" - "good\\"; - - int npts_x = 500; - int npts_y = 500; - - std::string name = "butterfly"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream shape_wn_out(data_dir + name + "_wn.csv"); - std::ofstream zoomed_wn_out(data_dir + name + "_zoomed_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - BoundingBox shape_bbox = curves_bbox(shape, 1.1, false); - - BoundingBox zoomed_bbox(shape[100].boundingBox()); - double max_dim = - axom::utilities::max(zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0], - zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]); - zoomed_bbox.addPoint( - primal::Point {zoomed_bbox.getMin()[0] + max_dim, - zoomed_bbox.getMin()[1] + max_dim}); - zoomed_bbox.scale(2.0); - - // Delete the one curve - for(int i = 0; i <= shape[100].getOrder(); ++i) shape[100][i] = shape[100][0]; - - // Print the rest to the file - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - shape_out << shape[i] << std::endl; - } - - simple_grid_test(shape, shape_bbox, npts_x, npts_y, shape_wn_out); - simple_grid_test(shape, zoomed_bbox, npts_x, npts_y, zoomed_wn_out); -} - -TEST(primal_2d_paper_figure_data, fish_figure) -{ - return; - std::cout << "Running: \"fish_figure\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\fish_figure\\"; - - std::string name = "fish"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream shape_wn_out(data_dir + name + "_wn.csv"); - std::ofstream zoomed_wn_out(data_dir + name + "_zoomed_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - // Jiggle the curves, and print them all to the file - srand(100); - - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - - shape[i][0][0] += (rand() % 30) - 15; - shape[i][0][1] += (rand() % 30) - 15; - - shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; - shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; - - shape_out << shape[i] << std::endl; - } - - BoundingBox shape_bbox = curves_bbox(shape, 1.01, false); - BoundingBox zoomed_bbox(shape[240].boundingBox()); - double max_dim = - axom::utilities::max(zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0], - zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]); - zoomed_bbox.addPoint( - primal::Point {zoomed_bbox.getMin()[0] + max_dim, - zoomed_bbox.getMin()[1] + max_dim}); - zoomed_bbox.scale(12.0); - - simple_grid_test(shape, shape_bbox, 1000, 1000, shape_wn_out); - simple_grid_test(shape, zoomed_bbox, 1000, 1000, zoomed_wn_out); -} - -TEST(primal_2d_paper_figure_data, proximity_test) -{ - return; - std::cout << "Running: \"proximity_test\"" << std::endl; - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - using Point2D = primal::Point; - using Vector2D = primal::Vector; - - std::string data_dir = "E:\\Code\\winding_number\\figures\\proximity_test\\"; - - std::string name = "parabola"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - - Point2D ellipse_nodes[] = {Point2D {2.0, 0.0}, - Point2D {2.0, 1.0}, - Point2D {0.0, 1.0}}; - - double ellipse_weights[] = {1.0, 1.0 / sqrt(2.0), 1.0}; - primal::BezierCurve ellipse(ellipse_nodes, ellipse_weights, 2); - shape.push_back(ellipse); - - axom::Array ts({0.09, 0.51, 0.91}); - axom::Array dists({-0.1, -1e-2, -1e-3, -1e-4, -1e-5, -1e-6, -1e-7, - -1e-8, -1e-9, -1e-10, 0.1, 1e-2, 1e-3, 1e-4, - 1e-5, 1e-6, 1e-7, 1e-8, 1e-9, 1e-10}); - - std::ofstream bisection_evals_out(data_dir + "bisection_evals.csv"); - std::ofstream clipping_evals_out(data_dir + "clipping_evals.csv"); - std::ofstream boxes_evals_out(data_dir + "boxes_evals.csv"); - - bisection_evals_out << std::setprecision(16) << "0"; - clipping_evals_out << std::setprecision(16) << "0"; - boxes_evals_out << std::setprecision(16) << "0"; - - for(auto dist : dists) - { - bisection_evals_out << ',' << dist; - clipping_evals_out << ',' << dist; - boxes_evals_out << ',' << dist; - } - - bisection_evals_out << std::endl; - clipping_evals_out << std::endl; - boxes_evals_out << std::endl; - - for(auto t0 : ts) - { - Point2D on_curve(ellipse.evaluate(t0)); - Vector2D tangent(ellipse.dt(t0)); - - bisection_evals_out << t0; - clipping_evals_out << t0; - boxes_evals_out << t0; - - for(auto dist : dists) - { - Point2D q_int({ - -tangent[1] * dist / tangent.norm() + on_curve[0], - tangent[0] * dist / tangent.norm() + on_curve[1], - }); - - int bisection_nevals = 0; - int clipping_nevals = 0; - int boxes_nevals = 0; - - double bisection_wn = primal::winding_number_bisection(q_int, - ellipse, - bisection_nevals, - 1e-16, - 1e-16); - double clipping_wn = primal::winding_number_clipping(q_int, - ellipse, - clipping_nevals, - 1e-16, - 1e-16); - double boxes_wn = - primal::winding_number(q_int, ellipse, boxes_nevals, 1e-16, 1e-16); - - bisection_evals_out << ',' << bisection_nevals; - clipping_evals_out << ',' << clipping_nevals; - boxes_evals_out << ',' << boxes_nevals; - } - - bisection_evals_out << std::endl; - clipping_evals_out << std::endl; - boxes_evals_out << std::endl; - } -} - -TEST(primal_2d_paper_figure_data, linearization_test) -{ - return; - - std::cout << "Running: \"linearization_test\"" << std::endl; - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\linearization_test\\"; - - std::string name = "bubble"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream misclassify_vals_out(data_dir + "misclassify_vals.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - srand(100); - - for(int i = 0; i < shape.size(); ++i) - { - shape_out << shape[i] << std::endl; - } - - BoundingBox shape_bbox = curves_bbox(shape, 1.05, false); - - std::ofstream shape_wn_out(data_dir + name + "_wnn.csv"); - simple_grid_test(shape, shape_bbox, 1000, 1000, shape_wn_out); - - std::ofstream shape_grid_out(data_dir + name + "_grid.txt"); - simple_grid_test(shape, shape_bbox, 2, 2, shape_grid_out); - - constexpr double tol = 1e-10; - int npts = 1e5; - int max_refinement = 25; - int dummy_int = 0; - - for(int n = 4; n <= 0; ++n) - { - std::cout << std::endl << "Doing refinement " << n << std::endl; - std::ofstream linearized_shape_out(data_dir + name + std::to_string(n) + - ".txt"); - - // Construct the linear approximation with the given level of refinement - CurveArray linearized_shape; - for(int i = 0; i < shape.size(); ++i) - { - for(double j = 0; j < n; ++j) - { - primal::BezierCurve line(1); - line[0] = shape[i].evaluate(j / n); - line[1] = shape[i].evaluate((j + 1) / n); - - linearized_shape.push_back(line); - } - } - - std::ofstream linearized_shape_wn_out(data_dir + name + - "_linearized_wn.csv"); - simple_grid_test(linearized_shape, shape_bbox, 500, 500, linearized_shape_wn_out); - - for(int i = 0; i < linearized_shape.size(); ++i) - { - linearized_shape_out << linearized_shape[i] << std::endl; - } - - std::ofstream misclassifications_out(data_dir + "misclassifications_" + - std::to_string(n) + ".csv"); - int misclassifications = 0; - - for(int m = 0; m < npts; ++m) - { - primal::printLoadingBar(m, npts); - double x = axom::utilities::random_real(shape_bbox.getMin()[0], - shape_bbox.getMax()[0]); - double y = axom::utilities::random_real(shape_bbox.getMin()[1], - shape_bbox.getMax()[1]); - - primal::Point query({x, y}); - - double linearized_wn = 0; - for(int i = 0; i < linearized_shape.size(); ++i) - { - linearized_wn += primal::winding_number(query, - linearized_shape[i], - dummy_int, - 1e-16, - 1e-16); - } - bool linearized_is_in = std::lround(linearized_wn) != 0; - - double true_wn = 0; - for(int i = 0; i < shape.size(); ++i) - { - true_wn += - primal::winding_number(query, shape[i], dummy_int, 1e-16, 1e-16); - } - bool true_is_in = std::lround(true_wn) != 0; - - if(linearized_is_in != true_is_in) - { - misclassifications++; - } - } - - misclassifications_out << misclassifications << std::endl; - } -} - -TEST(primal_2d_paper_figure_data, tolerance_test) -{ - return; - - std::cout << "Running : \"tolerance test\"" << std::endl; - - using CurveArray = axom::Array>; - using SegmentArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = "E:\\Code\\winding_number\\figures\\tolerance_test\\"; - - std::string name = "tangle"; - CurveArray shape; - primal::Point tangle_nodes[] = { - primal::Point {1.000, 0.000}, - primal::Point {-3.000, 0.000}, - primal::Point {-3.225, 3.175}, - primal::Point {1.008, 3.300}, - primal::Point {5.223, -3.836}, - primal::Point {-3.273, -4.321}, - primal::Point {-5.485, 2.728}, - primal::Point {2.681, 3.014}, - primal::Point {3.000, 0.000}, - primal::Point {-2.000, 0.000}}; - - double tangle_weights[] = {1.0, 1.1, 0.7, 1.1, 1.0, 1.2, 1.1, 1.0, 1.0, 1.0}; - primal::BezierCurve tangle_curve(tangle_nodes, tangle_weights, 9); - //shape.push_back(tangle_curve); - //tangle_curve.reverseOrientation(); - - primal::Point closure_nodes[] = { - primal::Point {-2.0, 0.0}, - primal::Point {-0.5, -3}, - primal::Point {1.0, 0.0} - - }; - primal::BezierCurve closure_curve(closure_nodes, 2); - shape.push_back(closure_curve); - - primal::Point closure_closure_nodes[] = { - primal::Point {1.0, 0.0}, - primal::Point {-2.0, 0.0}}; - - primal::BezierCurve closure_closure_curve(closure_closure_nodes, 2); - shape.push_back(closure_closure_curve); - - std::ofstream curves_out(data_dir + name + ".txt"); - - //primal::convert_from_svg(data_dir + name + ".svg", shape); - - srand(100); - - double shape_radius = 1; - BoundingBox shape_bbox; - primal::Point center({0.2, 0.3}); - shape_bbox.addPoint(primal::Point {center[0] + shape_radius, - center[1] + shape_radius}); - shape_bbox.addPoint(primal::Point {center[0] - shape_radius, - center[1] - shape_radius}); - - // Get the largest axis of the bounding box - double max_dim = - axom::utilities::max(shape_bbox.getMax()[0] - shape_bbox.getMin()[0], - shape_bbox.getMax()[1] - shape_bbox.getMin()[1]); - - for(int i = 0; i < shape.size(); ++i) - { - std::cout << shape[i] << std::endl; - for(int j = 0; j <= shape[i].getOrder(); ++j) - { - shape[i][j][0] = 0.25 * shape[i][j][0] + 0.62; - shape[i][j][1] = 0.25 * shape[i][j][1] + 0.55; - } - - curves_out << shape[i] << std::endl; - - // Make a fuller version of the shape that has three times as many control nodes - // primal::BezierCurve fuller_curve(3 * shape[i].getOrder()); - // for (int j = 0; j <= 3 * shape[i].getOrder(); ++j) - // { - // fuller_curve[j] = shape[i][j / 3]; - //} - } - - shape_bbox.clear(); - - shape_bbox.addPoint(primal::Point {-0.1, -0.1}); - shape_bbox.addPoint(primal::Point {1.1, 1.1}); - - std::ofstream shape_grid_out(data_dir + name + "_grid.txt"); - simple_grid_test(shape, shape_bbox, 500, 500, shape_grid_out); - - //return; - - std::ofstream tolerance_wn_timing_out(data_dir + - "tolerance_wn_timing_out_simple.csv"); - - //constexpr double EPS = 0.0; - int npts = 1e5; - const int max_refinement = 25; - int dummy_int = 0; - bool dummy_bool = false; - int idx; - - const int NUM_CURVES = 20; - - // Make an array of duration objects - double avg_depth[15]; - int misclassifications[15]; - int nevals[15]; - for(int i = 0; i < 15; ++i) - { - misclassifications[i] = 0; - nevals[i] = 0; - avg_depth[i] = 0; - } - - //int idx2 = 0; - //for(int i = 2; i <= 0; ++i) - //{ - // double the_tol = std::pow(10, -i); - - // std::cout << "===================" << the_tol - // << "===================" << std::endl; - - // for(int m = 0; m < npts; ++m) - // { - // double x = axom::utilities::random_real(shape_bbox.getMin()[0], - // shape_bbox.getMax()[0]); - // double y = axom::utilities::random_real(shape_bbox.getMin()[1], - // shape_bbox.getMax()[1]); - // primal::Point query({x, y}); - // primal::Polygon temp_approxogon(20); - - // double adaptive_wn = primal::winding_number_approxogon(query, - // shape, - // temp_approxogon, - // 0.0, - // the_tol); - // } - - // idx2++; - //} - - //return; - // Loop over the number of points - for(int m = 0; m < npts; ++m) - { - primal::printLoadingBar(m, npts); - double x = axom::utilities::random_real(shape_bbox.getMin()[0], - shape_bbox.getMax()[0]); - double y = axom::utilities::random_real(shape_bbox.getMin()[1], - shape_bbox.getMax()[1]); - - primal::Point query({x, y}); - - // Loop over the tolerances, 1e-2 to 1e-16 - idx = 0; - for(int i = 2; i <= 16; ++i) - { - double the_tol = std::pow(10, -i); - - //std::cout << the_tol << std::endl; - - double adaptive_wn = 0.0; - primal::Polygon temp_approxogon(20); - - double true_wn = primal::winding_number(query, shape, 0.0); - int this_depth = 0; - adaptive_wn = primal::winding_number_approxogon(query, - shape, - temp_approxogon, - nevals[idx], - this_depth, - 0.0, - the_tol); - - if((std::lround(adaptive_wn) != 0) != (std::lround(true_wn) != 0)) - { - misclassifications[idx]++; - } - avg_depth[idx] += static_cast(this_depth) / npts; - - idx++; - } - } - - idx = 0; - for(int i = 2; i <= 16; ++i) - { - double the_tol = std::pow(10, -i); - - tolerance_wn_timing_out << the_tol << "," << avg_depth[idx] << "," - << misclassifications[idx] << "," << nevals[idx] - << std::endl; - idx++; - } -} - -TEST(primal_2d_paper_figure_data, timing_test) -{ - return; - - std::cout << "Running: \"timing_test\"" << std::endl; - using CurveArray = axom::Array>; - using SegmentArray = axom::Array>; - using PolygonArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string prefix = "run_3\\"; - - std::string data_dir = "E:\\Code\\winding_number\\figures\\timing_test\\"; - - std::string name = "bubble"; - CurveArray shape; - std::ofstream curves_out(data_dir + name + ".txt"); - std::ofstream adaptive_wn_timing_out(data_dir + prefix + - "method0_wn_timing_out.csv"); - std::ofstream memoized_wn_timing_out(data_dir + prefix + - "method00_wn_timing_out.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - srand(10101); - - for(int i = 0; i < shape.size(); ++i) - { - //curves_out << shape[i] << std::endl; - - primal::BezierCurve fuller_curve(3 * shape[i].getOrder()); - for(int j = 0; j <= 3 * shape[i].getOrder(); ++j) - { - fuller_curve[j] = shape[i][j / 3]; - } - fuller_curve.makeRational(); - shape[i].makeRational(); - //shape[i] = fuller_curve; - //shape_fuller.push_back(fuller_curve); - - curves_out << shape[i] << std::endl; - } - - BoundingBox shape_bbox = curves_bbox(shape, 1.05, false); - - std::ofstream shape_grid_out(data_dir + name + "_grid.txt"); - simple_grid_test(shape, shape_bbox, 2, 2, shape_grid_out); - - constexpr double tol = 1e-10; - int npts = 1e5; - const int max_refinement = 25; - int dummy_int = 0; - bool dummy_bool = false; - - const int NUM_CURVES = 20; - - // Do the preprocessing to get the linearization for each level of refinement - //SegmentArray bag_of_linearizations[max_refinement]; - //SegmentArray batches_of_linearizations[max_refinement][NUM_CURVES]; // There are 20 curves in the bubble - PolygonArray bag_of_polygons[max_refinement]; - std::unordered_map, primal::Segment, primal::detail::PairHash> - segment_hashes[max_refinement]; - - // Array for low order refinement - std::vector>> - array_memos[NUM_CURVES]; - - // Hash for higher order refinement - axom::FlatMap, - primal::detail::BezierCurveMemo, - primal::detail::PairHash> - hash_memos[NUM_CURVES]; - - std::ofstream method1_wn_timing_out[max_refinement]; // Big bag of winding numbers. Completely naive - std::ofstream method2_wn_timing_out[max_refinement]; // Semi-sophisticated. If you're outside the box, do one calculation. If you're inside the box, do linearization - std::ofstream method3_wn_timing_out[max_refinement]; // Sophisticated. Make a polygon with a fixed linearization, then do polygon_wn - closure - std::ofstream method4_wn_timing_out[max_refinement]; // Method 3 with no pre-processing - - std::chrono::duration preprocessing_times[max_refinement][4]; - std::ofstream preprocessing_times_out = - std::ofstream(data_dir + prefix + "preprocessing_times.csv"); - - // Preprocessing for method 00 - constexpr int LEVEL = 3; - auto start = std::chrono::high_resolution_clock::now(); - for(int ci = 0; ci < NUM_CURVES; ++ci) - { - array_memos[ci].resize(LEVEL); - for(int i = 0; i < LEVEL; ++i) array_memos[ci][i].resize(1 << i); - - array_memos[ci][0][0] = { - //shape[ci].isLinear(), - primal::is_convex(primal::Polygon(shape[ci].getControlPoints()), - primal::PRIMAL_TINY), - shape[ci]}; - - for(int i = 0; i < LEVEL - 1; ++i) - { - for(int j = 0; j < (1 << i); ++j) - { - primal::BezierCurve c1, c2; - array_memos[ci][i][j].curve.split(0.5, c1, c2); - - array_memos[ci][i + 1][2 * j] = { - //c1.isLinear(), - primal::is_convex(primal::Polygon(c1.getControlPoints()), - primal::PRIMAL_TINY), - c1}; - - array_memos[ci][i + 1][2 * j + 1] = { - //c2.isLinear(), - primal::is_convex(primal::Polygon(c2.getControlPoints()), - primal::PRIMAL_TINY), - c2}; - } - } - - //hash_memos.push_back(axom::FlatMap, - // primal::detail::BezierCurveMemo, - // primal::detail::PairHash>()); - } - auto end = std::chrono::high_resolution_clock::now(); - - for(int i = 0; i < max_refinement; ++i) - { - preprocessing_times[i][3] = end - start; - } - - /*std::ofstream shape_memoized_grid_out(data_dir + name + "_memoized_grid.txt"); - simple_grid_test_memoized(shape_marray, - shape_bbox, - 200, - 200, - shape_memoized_grid_out);*/ - - // Set up the linearization - for(int ref = 0; ref < max_refinement; ++ref) - { - method1_wn_timing_out[ref] = - std::ofstream(data_dir + prefix + "method1_wn_timing_out_" + - std::to_string(ref + 1) + ".csv"); - method2_wn_timing_out[ref] = - std::ofstream(data_dir + prefix + "method2_wn_timing_out_" + - std::to_string(ref + 1) + ".csv"); - method3_wn_timing_out[ref] = - std::ofstream(data_dir + prefix + "method3_wn_timing_out_" + - std::to_string(ref + 1) + ".csv"); - method4_wn_timing_out[ref] = - std::ofstream(data_dir + prefix + "method4_wn_timing_out_" + - std::to_string(ref + 1) + ".csv"); - - // Big boy preprocessing - start = std::chrono::high_resolution_clock::now(); - for(int ci = 0; ci < shape.size(); ++ci) - { - for(int i = 0; i < (ref + 1); ++i) - { - primal::BezierCurve curve = shape[ci]; - primal::Point point = curve.evaluate(1.0 * i / (ref + 1.0)); - primal::Point next_point = - curve.evaluate(1.0 * (i + 1.0) / (ref + 1.0)); - primal::Segment line(point, next_point); - std::pair the_pair = std::make_pair(ci, i); - segment_hashes[ref][the_pair] = line; - } - } - end = std::chrono::high_resolution_clock::now(); - preprocessing_times[ref][0] = end - start; - - // Preprocessing for method 1 - //start = std::chrono::high_resolution_clock::now(); - //for(int ci = 0; ci < NUM_CURVES; ++ci) - //{ - // for(int i = 0; i < (ref + 1); ++i) - // { - // primal::Segment line( - // shape[ci].evaluate(1.0 * i / (ref + 1.0)), - // shape[ci].evaluate(1.0 * (i + 1.0) / (ref + 1.0))); - // bag_of_linearizations[ref].push_back(line); - // } - //} - //end = std::chrono::high_resolution_clock::now(); - //preprocessing_times[ref][0] = end - start; - - // Preprocessing for method 2 - start = std::chrono::high_resolution_clock::now(); - //for(int ci = 0; ci < NUM_CURVES; ci++) - //{ - // for(int i = 0; i < (ref + 1); ++i) - // { - // primal::Segment line( - // shape[ci].evaluate(1.0 * i / (ref + 1.0)), - // shape[ci].evaluate(1.0 * (i + 1.0) / (ref + 1.0))); - - // batches_of_linearizations[ref][ci].push_back(line); - // } - //} - end = std::chrono::high_resolution_clock::now(); - preprocessing_times[ref][1] = end - start; - - // Preprocessing for method 3 - start = std::chrono::high_resolution_clock::now(); - //for(int ci = 0; ci < NUM_CURVES; ++ci) - //{ - // primal::Polygon poly(ref + 1); - - // for(int i = 0; i < (ref + 1); ++i) - // { - // poly.addVertex(shape[ci].evaluate(1.0 * i / (ref + 1.0))); - // } - - // poly.addVertex(shape[ci].evaluate(1.0)); - // bag_of_polygons[ref].push_back(poly); - //} - end = std::chrono::high_resolution_clock::now(); - preprocessing_times[ref][2] = end - start; - - // Construct the linear approximation out of BezierCurves for plotting - CurveArray linearized_curves; - for(int ci = 0; ci < shape.size(); ++ci) - { - for(int i = 0; i < (ref + 1); ++i) - { - primal::BezierCurve curve(1); - curve[0] = shape[ci].evaluate(1.0 * i / (ref + 1.0)); - curve[1] = shape[ci].evaluate(1.0 * (i + 1.0) / (ref + 1.0)); - linearized_curves.push_back(curve); - } - } - - std::ofstream fixed_wn_shape_out(data_dir + "fixed_wn_shape_out_" + - std::to_string(ref + 1) + ".txt"); - - for(auto& line : linearized_curves) - { - fixed_wn_shape_out << line << std::endl; - } - } - - // Write the preprocessing times to the file - for(int ref = 0; ref < max_refinement; ++ref) - { - preprocessing_times_out << ref + 1 << ',' - << preprocessing_times[ref][0].count() << ',' - << preprocessing_times[ref][1].count() << ',' - << preprocessing_times[ref][2].count() << ',' - << preprocessing_times[ref][3].count() << std::endl; - } - - // Loop over the number of points - for(int m = 0; m < npts; ++m) - { - primal::printLoadingBar(m, npts); - double x = axom::utilities::random_real(shape_bbox.getMin()[0], - shape_bbox.getMax()[0]); - double y = axom::utilities::random_real(shape_bbox.getMin()[1], - shape_bbox.getMax()[1]); - - primal::Point query({x, y}); - //184.194, 113.01 - //query[0] = 184.194; - //query[1] = 113.01; - - double adaptive_wn = 0.0, memoized_wn = 0.0; - primal::Polygon temp_approxogon(20); - std::stack> curve_stack; - - auto start = std::chrono::high_resolution_clock::now(); - adaptive_wn = primal::winding_number_approxogon(query, - shape, - temp_approxogon, - dummy_int, - dummy_int, - tol); - auto end = std::chrono::high_resolution_clock::now(); - - adaptive_wn_timing_out << x << "," << y << "," - << std::chrono::duration(end - start).count() - << std::endl; - - for(int ref = 0; ref < max_refinement; ++ref) - { - double wn1 = 0.0, wn3 = 0.0, wn4 = 0.0; - - /* Do method 2: bounding box + small bag version */ - start = std::chrono::high_resolution_clock::now(); - double wn2 = 0.0; - for(int i = 0; i < NUM_CURVES; ++i) - { - if(!shape[i].boundingBox().contains(query)) - { - wn2 -= primal::winding_number( - query, - primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), - tol); - } - else - { - for(int j = 0; j < ref + 1; ++j) - { - auto the_pair = std::make_pair(i, j); - wn2 += - primal::winding_number(query, segment_hashes[ref][the_pair], tol); - } - } - } - end = std::chrono::high_resolution_clock::now(); - - method2_wn_timing_out[ref] - << x << ',' << y << ',' - << (std::chrono::duration(end - start)).count() << std::endl; - - /* Do method 1: big bag o winding numbers version */ - auto start = std::chrono::high_resolution_clock::now(); - //for(int i = 0; i < bag_of_linearizations[ref].size(); ++i) - //{ - //wn1 += primal::winding_number(query, bag_of_linearizations[ref][i], tol); - //} - auto end = std::chrono::high_resolution_clock::now(); - - method1_wn_timing_out[ref] - << x << ',' << y << ',' - << (std::chrono::duration(end - start)).count() << std::endl; - - /* Do method 3: polygon version */ - start = std::chrono::high_resolution_clock::now(); - for(int i = 0; i < NUM_CURVES; ++i) - { - /*if(!shape[i].boundingBox().contains(query)) - { - wn3 -= primal::winding_number( - query, - primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), - tol); - } - else - { - wn3 += - primal::detail::approxogon_winding_number(query, - bag_of_polygons[ref][i], - tol); - }*/ - } - end = std::chrono::high_resolution_clock::now(); - - method3_wn_timing_out[ref] - << x << ',' << y << ',' - << (std::chrono::duration(end - start)).count() << std::endl; - - ///* Do method 4: polygon with no pre-processing*/ - //primal::Polygon this_poly(ref + 1); - - //start = std::chrono::high_resolution_clock::now(); - //for(int i = 0; i < NUM_CURVES; ++i) - //{ - // if(!shape[i].boundingBox().contains(query)) - // { - // wn4 -= primal::winding_number( - // query, - // primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), - // tol); - // } - // else - // { - // this_poly.clear(); - - // for(int j = 0; j < (ref + 1); ++j) - // { - // this_poly.addVertex(shape[i].evaluate(1.0 * j / (ref + 1.0))); - // } - // this_poly.addVertex(shape[i].evaluate(1.0)); - - // wn4 += primal::detail::approxogon_winding_number(query, this_poly, tol); - // } - //} - //end = std::chrono::high_resolution_clock::now(); - - //method4_wn_timing_out[ref] - // << x << ',' << y << ',' - // << std::chrono::duration(end - start).count() << std::endl; - } - - start = std::chrono::high_resolution_clock::now(); - //memoized_wn = primal::winding_number_approxogon_memoized(query, - // array_memos, - // hash_memos, - // temp_approxogon, - // curve_stack, - // tol); - for(int i = 0; i < NUM_CURVES; ++i) - { - if(!shape[i].boundingBox().contains(query)) - { - memoized_wn -= primal::winding_number( - query, - primal::Segment(shape[i][shape[i].getOrder()], shape[i][0]), - tol); - } - else - { - primal::detail::winding_number_adaptive_linear_memoized(query, - array_memos[i], - hash_memos[i], - 0, - tol, - temp_approxogon, - curve_stack, - memoized_wn); - temp_approxogon.addVertex(shape[i][shape[i].getOrder()]); - memoized_wn += - primal::detail::approxogon_winding_number(query, temp_approxogon, 0); - temp_approxogon.clear(); - } - } - end = std::chrono::high_resolution_clock::now(); - - memoized_wn_timing_out << x << "," << y << "," - << std::chrono::duration(end - start).count() - << std::endl; - } -} - -TEST(primal_2d_paper_figure_data, rose_figure) -{ - return; - bool collectGridData = false; - bool collectRandomData = true; - - std::cout << "Running: \"rose_figure\"" << std::endl; - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = "E:\\Code\\winding_number\\figures\\rose_figure\\"; - - std::string name = "rose"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - // Jiggle the curves, and print them all to the file - srand(100); - - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - shape_out << shape[i] << std::endl; - } - - BoundingBox shape_bbox = curves_bbox(shape, 1.05, false); - - if(collectGridData) - { - std::ofstream grid_boxes_nevals_out(data_dir + "boxes_grid_nevals.csv"); - std::ofstream grid_clipping_nevals_out(data_dir + - "clipping_grid_nevals.csv"); - std::ofstream grid_bisection_nevals_out(data_dir + - "bisection_grid_nevals.csv"); - - std::ofstream grid_boxes_wn_out(data_dir + "boxes_grid_wn.csv"); - std::ofstream grid_clipping_wn_out(data_dir + "clipping_grid_wn.csv"); - std::ofstream grid_bisection_wn_out(data_dir + "bisection_grid_wn.csv"); - - int npts_x = 250; - int npts_y = 250; - - for(double x = shape_bbox.getMin()[0]; x <= shape_bbox.getMax()[0]; - x += (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) / npts_x) - { - std::cout << x << std::endl; - - for(double y = shape_bbox.getMin()[1]; y <= shape_bbox.getMax()[1]; - y += (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals_boxes = 0; - int nevals_bisection = 0; - int nevals_clipping = 0; - - double wn_boxes = 0.0; - double wn_bisection = 0.0; - double wn_clipping = 0.0; - - for(int i = 0; i < shape.size(); ++i) - { - wn_boxes += - primal::winding_number(query, shape[i], nevals_boxes, 1e-16, 1e-16); - wn_clipping += primal::winding_number_clipping(query, - shape[i], - nevals_clipping, - 1e-16, - 1e-16); - wn_bisection += primal::winding_number_bisection(query, - shape[i], - nevals_bisection, - 1e-16, - 1e-16); - } - - grid_boxes_nevals_out << x << "," << y << "," << nevals_boxes - << std::endl; - grid_clipping_nevals_out << x << "," << y << "," << nevals_clipping - << std::endl; - grid_bisection_nevals_out << x << "," << y << "," << nevals_bisection - << std::endl; - - grid_boxes_wn_out << x << "," << y << "," << wn_boxes << std::endl; - grid_clipping_wn_out << x << "," << y << "," << wn_clipping << std::endl; - grid_bisection_wn_out << x << "," << y << "," << wn_bisection - << std::endl; - } - } - } - - if(collectRandomData) - { - std::ofstream random_boxes_nevals_out(data_dir + "boxes_random_nevals.csv"); - std::ofstream random_clipping_nevals_out(data_dir + - "clipping_random_nevals.csv"); - std::ofstream random_bisection_nevals_out(data_dir + - "bisection_random_nevals.csv"); - - int npts = 250000; - constexpr double tol = 1e-10; - - for(int n = 0; n < npts; ++n) - { - primal::printLoadingBar(n, npts); - - double x = axom::utilities::random_real(shape_bbox.getMin()[0], - shape_bbox.getMax()[0]); - double y = axom::utilities::random_real(shape_bbox.getMin()[1], - shape_bbox.getMax()[1]); - - primal::Point query({x, y}); - - int nevals_boxes = 0; - int nevals_bisection = 0; - int nevals_clipping = 0; - - double wn_boxes = 0.0; - double wn_bisection = 0.0; - double wn_clipping = 0.0; - for(int i = 0; i < shape.size(); ++i) - { - //desmos_print(shape[i]); - //desmos_print(query); - wn_boxes += - primal::winding_number(query, shape[i], nevals_boxes, tol, tol); - wn_clipping += primal::winding_number_clipping(query, - shape[i], - nevals_clipping, - tol, - tol); - wn_bisection += primal::winding_number_bisection(query, - shape[i], - nevals_bisection, - tol, - tol); - } - - if(!axom::utilities::isNearlyEqual(wn_bisection, wn_boxes, 1e-5)) - { - std::cout << std::setprecision(16) << n << ": " << x << ", " << y - << std::endl; - std::cout << "bisection machine broke" << std::endl; - } - - if(!axom::utilities::isNearlyEqual(wn_boxes, wn_clipping, 1e-5)) - { - std::cout << std::setprecision(16) << n << ": " << x << ", " << y - << std::endl; - std::cout << "clipping machine broke" << std::endl; - } - - random_boxes_nevals_out << x << "," << y << "," << nevals_boxes - << std::endl; - random_clipping_nevals_out << x << "," << y << "," << nevals_clipping - << std::endl; - random_bisection_nevals_out << x << "," << y << "," << nevals_bisection - << std::endl; - } - } -} - -TEST(primal_2d_paper_figure_data, spiral_figure) -{ - return; - - std::cout << "Running: \"spiral_figure\"" << std::endl; - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - using Point2D = primal::Point; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\spiral_figure\\"; - - std::string name = "spiral"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - - Point2D spiral_nodes[] = {Point2D {-0.824, -0.927}, - Point2D {-3.344, -0.802}, - Point2D {-2.085, 3.242}, - Point2D {1.794, 4.861}, - Point2D {2.692, -4.142}, - Point2D {-2.313, -3.279}, - Point2D {-6.041, 2.037}, - Point2D {1.98, 2.444}, - Point2D {-0.966, -0.939}, - Point2D {-1.034, 0.113}}; - - double spiral_weights[] = {1.0, 1.2, 1.2, 0.9, 1.1, 0.9, 1.0, 1.0, 1.1, 1.0}; - primal::BezierCurve spiral_curve(spiral_nodes, spiral_weights, 9); - shape.push_back(spiral_curve); - - //Point2D spiral_nodes[] = {Point2D {2.0, 0.0}, - // Point2D {2.0, 1.0}, - // Point2D {0.0, 1.0}}; - - //double spiral_weights[] = {1.0, 1.0 / sqrt(2.0), 1.0}; - //primal::BezierCurve spiral_curve(spiral_nodes, spiral_weights, 2); - //shape.push_back(spiral_curve); - - // Jiggle the curves, and print them all to the file - srand(100); - - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - shape_out << shape[i] << std::endl; - } - - BoundingBox shape_bbox; // = curves_bbox(shape, 1.05, true); - - shape_bbox.addPoint(spiral_nodes[9]); - shape_bbox.addPoint( - Point2D {spiral_nodes[9][0] + 1.5, spiral_nodes[9][1] + 1.5}); - shape_bbox.addPoint( - Point2D {spiral_nodes[9][0] - 1.5, spiral_nodes[9][1] - 1.5}); - std::cout << shape_bbox << std::endl; - - std::ofstream wn_out(data_dir + name + "_wn.csv"); - simple_grid_test(shape, shape_bbox, 1000, 1000, wn_out); - return; - - std::ofstream grid_nevals_out(data_dir + "grid_nevals.csv"); - int npts_x = 3; - int npts_y = 3; - - for(int xi = 0; xi < 2; ++xi) - { - for(int yi = 0; yi < 2; ++yi) - { - double x = shape_bbox.getMin()[0] + - (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) * xi; - double y = shape_bbox.getMin()[1] + - (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) * yi; - primal::Point query({x, y}); - - int nevals = 0; - - double wn = 0.0; - for(int i = 0; i < shape.size(); ++i) - { - primal::winding_number(query, shape[i], nevals, 1e-16, 1e-16); - } - - grid_nevals_out << x << "," << y << "," << nevals << std::endl; - } - } - - std::ofstream random_boxes_nevals_out(data_dir + "boxes_random_nevals.csv"); - std::ofstream random_clipping_nevals_out(data_dir + - "clipping_random_nevals.csv"); - std::ofstream random_bisection_nevals_out(data_dir + - "bisection_random_nevals.csv"); - - int npts = 250000; - constexpr double tol = 1e-10; - - for(int n = 0; n < npts; ++n) - { - primal::printLoadingBar(n, npts); - - double x = axom::utilities::random_real(shape_bbox.getMin()[0], - shape_bbox.getMax()[0]); - double y = axom::utilities::random_real(shape_bbox.getMin()[1], - shape_bbox.getMax()[1]); - - primal::Point query({x, y}); - - int nevals_boxes = 0; - int nevals_bisection = 0; - int nevals_clipping = 0; - - double wn_boxes = 0.0; - double wn_bisection = 0.0; - double wn_clipping = 0.0; - for(int i = 0; i < shape.size(); ++i) - { - //desmos_print(shape[i]); - //desmos_print(query); - wn_boxes += primal::winding_number(query, shape[i], nevals_boxes, tol, tol); - wn_clipping += - primal::winding_number_clipping(query, shape[i], nevals_clipping, tol, tol); - wn_bisection += primal::winding_number_bisection(query, - shape[i], - nevals_bisection, - tol, - tol); - } - - if(!axom::utilities::isNearlyEqual(wn_bisection, wn_boxes, 1e-5)) - { - std::cout << std::setprecision(16) << n << ": " << x << ", " << y - << std::endl; - std::cout << "bisection machine broke" << std::endl; - } - - if(!axom::utilities::isNearlyEqual(wn_boxes, wn_clipping, 1e-5)) - { - std::cout << std::setprecision(16) << n << ": " << x << ", " << y - << std::endl; - std::cout << "clipping machine broke" << std::endl; - } - - random_boxes_nevals_out << x << "," << y << "," << nevals_boxes << std::endl; - random_clipping_nevals_out << x << "," << y << "," << nevals_clipping - << std::endl; - random_bisection_nevals_out << x << "," << y << "," << nevals_bisection - << std::endl; - } -} - -TEST(primal_2d_paper_figure_data, tangle_figure) -{ - return; - - std::cout << "Running: \"tangle_figure\"" << std::endl; - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - using Point2D = primal::Point; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\tangle_figure\\"; - - std::string name = "tangle"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - - Point2D tangle_nodes[] = {Point2D {1.000, 0.000}, - Point2D {-3.000, 0.000}, - Point2D {-3.225, 3.175}, - Point2D {1.008, 3.300}, - Point2D {5.223, -3.836}, - Point2D {-3.273, -4.321}, - Point2D {-5.485, 2.728}, - Point2D {2.681, 3.014}, - Point2D {3.000, 0.000}, - Point2D {-2.000, 0.000}}; - - double tangle_weights[] = {1.0, 1.1, 0.7, 1.1, 1.0, 1.2, 1.1, 1.0, 1.0, 1.0}; - primal::BezierCurve tangle_curve(tangle_nodes, tangle_weights, 9); - shape.push_back(tangle_curve); - - // Jiggle the curves, and print them all to the file - srand(100); - - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - shape_out << shape[i] << std::endl; - } - - BoundingBox shape_bbox; - Point2D center({-0.33, 0.2}); - shape_bbox.addPoint(Point2D {center[0] + 2, center[1] + 2}); - shape_bbox.addPoint(Point2D {center[0] - 2, center[1] - 2}); - - std::ofstream wn_out(data_dir + name + "_wn.csv"); - simple_grid_test(shape, shape_bbox, 1000, 1000, wn_out); - - return; - - std::ofstream grid_nevals_out(data_dir + "grid_nevals.csv"); - int npts_x = 3; - int npts_y = 3; - - for(int xi = 0; xi < 2; ++xi) - { - for(int yi = 0; yi < 2; ++yi) - { - double x = shape_bbox.getMin()[0] + - (shape_bbox.getMax()[0] - shape_bbox.getMin()[0]) * xi; - double y = shape_bbox.getMin()[1] + - (shape_bbox.getMax()[1] - shape_bbox.getMin()[1]) * yi; - primal::Point query({x, y}); - - int nevals = 0; - - double wn = 0.0; - for(int i = 0; i < shape.size(); ++i) - { - primal::winding_number(query, shape[i], nevals, 1e-16, 1e-16); - } - - grid_nevals_out << x << "," << y << "," << nevals << std::endl; - } - } - - std::ofstream random_boxes_nevals_out(data_dir + "boxes_random_nevals.csv"); - std::ofstream random_clipping_nevals_out(data_dir + - "clipping_random_nevals.csv"); - std::ofstream random_bisection_nevals_out(data_dir + - "bisection_random_nevals.csv"); - - int npts = 1e5 + 1e5 / 2; - constexpr double tol = 1e-10; - - for(int n = 0; n < npts; ++n) - { - primal::printLoadingBar(n, npts); - - double x = axom::utilities::random_real(shape_bbox.getMin()[0], - shape_bbox.getMax()[0]); - double y = axom::utilities::random_real(shape_bbox.getMin()[1], - shape_bbox.getMax()[1]); - - primal::Point query({x, y}); - - int nevals_boxes = 0; - int nevals_bisection = 0; - int nevals_clipping = 0; - - double wn_boxes = 0.0; - double wn_bisection = 0.0; - double wn_clipping = 0.0; - for(int i = 0; i < shape.size(); ++i) - { - //desmos_print(shape[i]); - //desmos_print(query); - wn_boxes += primal::winding_number(query, shape[i], nevals_boxes, tol, tol); - wn_clipping += - primal::winding_number_clipping(query, shape[i], nevals_clipping, tol, tol); - wn_bisection += primal::winding_number_bisection(query, - shape[i], - nevals_bisection, - tol, - tol); - } - - if(!axom::utilities::isNearlyEqual(wn_bisection, wn_boxes, 1e-5)) - { - std::cout << std::setprecision(16) << n << ": " << x << ", " << y - << std::endl; - std::cout << "bisection machine broke" << std::endl; - } - - if(!axom::utilities::isNearlyEqual(wn_boxes, wn_clipping, 1e-5)) - { - std::cout << std::setprecision(16) << n << ": " << x << ", " << y - << std::endl; - std::cout << "clipping machine broke" << std::endl; - } - - random_boxes_nevals_out << x << "," << y << "," << nevals_boxes << std::endl; - random_clipping_nevals_out << x << "," << y << "," << nevals_clipping - << std::endl; - random_bisection_nevals_out << x << "," << y << "," << nevals_bisection - << std::endl; - } -} - -TEST(primal_2d_paper_figure_data, orientation_bad) -{ - return; - std::cout << "Running: \"orientation_bad\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\orientation_bad\\"; - - int npts_x = 1000; - int npts_y = 1000; - - std::string name = "butterfly"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - //BoundingBox bbox(shape[100].boundingBox()); - //double max_dim = axom::utilities::max(bbox.getMax()[0] - bbox.getMin()[0], - // bbox.getMax()[1] - bbox.getMin()[1]); - //bbox.addPoint(primal::Point {bbox.getMin()[0] + max_dim, - // bbox.getMin()[1] + max_dim}); - //bbox.scale(2.5); - - BoundingBox bbox = curves_bbox(shape, 1.1, false); - - // Reverse one curve - shape[100][1] = shape[100][0]; //.reverseOrientation(); - shape[100][2] = shape[100][0]; //.reverseOrientation(); - shape[100][3] = shape[100][0]; //.reverseOrientation(); - - // Print them all to the file - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - shape_out << shape[i] << std::endl; - } - - simple_grid_test(shape, bbox, npts_x, npts_y, wn_out); -} - -TEST(primal_2d_paper_figure_data, quadrature_bad) -{ - return; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\quadrature_bad\\"; - - - int npts_x = 600; - int npts_y = 600; - - std::string name = "curve"; - CurveArray curve; - primal::convert_from_svg(data_dir + name + ".svg", curve); - std::ofstream curve_out(data_dir + name + ".txt"); - for(int i = 0; i < curve.size(); ++i) - { - curve_out << curve[i] << std::endl; - } - BoundingBox curve_bbox = curves_bbox(curve, 1.5, true); - std::ofstream curve_15_wn_out(data_dir + name + "_wn_15.csv"); - std::ofstream curve_30_wn_out(data_dir + name + "_wn_30.csv"); - std::ofstream curve_50_wn_out(data_dir + name + "_wn_50.csv"); - std::ofstream curve_true_wn_out(data_dir + name + "_wn_true.csv"); - - for(double x = curve_bbox.getMin()[0]; x <= curve_bbox.getMax()[0]; - x += (curve_bbox.getMax()[0] - curve_bbox.getMin()[0]) / npts_x) - { - std::cout << x << std::endl; - - for(double y = curve_bbox.getMin()[1]; y <= curve_bbox.getMax()[1]; - y += (curve_bbox.getMax()[1] - curve_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double wn_15 = 0.0; - double wn_30 = 0.0; - double wn_50 = 0.0; - double wn_true = 0.0; - - for(int i = 0; i < curve.size(); ++i) - { - wn_15 += primal::winding_number_quad(query, curve[i], 15); - wn_30 += primal::winding_number_quad(query, curve[i], 30); - wn_50 += primal::winding_number_quad(query, curve[i], 50); - wn_true += primal::winding_number(query, curve[i], nevals); - } - - curve_15_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_15 - << std::endl; - curve_30_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_30 - << std::endl; - curve_50_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_50 - << std::endl; - curve_true_wn_out << std::setprecision(16) << x << ',' << y << "," - << wn_true << std::endl; - } - } - - std::string spade_name = "twirl"; - CurveArray spade; - primal::convert_from_svg(data_dir + spade_name + ".svg", spade); - std::ofstream spade_out(data_dir + spade_name + ".txt"); - for(int i = 0; i < spade.size(); ++i) - { - spade_out << spade[i] << std::endl; - } - //BoundingBox spade_bbox = curves_bbox(spade, 1.0); - primal::Point spade_center({83.73, 75}); - double spade_radius = 12.0; - BoundingBox spade_bbox; - spade_bbox.addPoint(primal::Point {spade_center[0] + spade_radius, - spade_center[1] + spade_radius}); - spade_bbox.addPoint(primal::Point {spade_center[0] - spade_radius, - spade_center[1] - spade_radius}); - - std::ofstream spade_15_wn_out(data_dir + spade_name + "_wn_15.csv"); - std::ofstream spade_30_wn_out(data_dir + spade_name + "_wn_30.csv"); - std::ofstream spade_50_wn_out(data_dir + spade_name + "_wn_50.csv"); - std::ofstream spade_true_wn_out(data_dir + spade_name + "_wn_true.csv"); - - for(double x = spade_bbox.getMin()[0]; x <= spade_bbox.getMax()[0]; - x += (spade_bbox.getMax()[0] - spade_bbox.getMin()[0]) / npts_x) - { - std::cout << x << std::endl; - - for(double y = spade_bbox.getMin()[1]; y <= spade_bbox.getMax()[1]; - y += (spade_bbox.getMax()[1] - spade_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double wn_15 = 0.0; - double wn_30 = 0.0; - double wn_50 = 0.0; - double wn_true = 0.0; - - for(int i = 0; i < spade.size(); ++i) - { - wn_15 += primal::winding_number_quad(query, spade[i], 15); - wn_30 += primal::winding_number_quad(query, spade[i], 30); - wn_50 += primal::winding_number_quad(query, spade[i], 50); - wn_true += primal::winding_number(query, spade[i], nevals); - } - - spade_15_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_15 - << std::endl; - spade_30_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_30 - << std::endl; - spade_50_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_50 - << std::endl; - spade_true_wn_out << std::setprecision(16) << x << ',' << y << "," - << wn_true << std::endl; - } - } - - std::string zoomed_name = "zoomed"; - - primal::Point zoomed_center = spade[2][3]; - double zoomed_radius = 1.0; - BoundingBox zoomed_bbox; - zoomed_bbox.addPoint( - primal::Point {zoomed_center[0] + zoomed_radius, - zoomed_center[1] + zoomed_radius / 2.0}); - zoomed_bbox.addPoint( - primal::Point {zoomed_center[0] - zoomed_radius, - zoomed_center[1] - zoomed_radius / 2.0}); - - std::ofstream zoomed_15_wn_out(data_dir + zoomed_name + "_wn_15.csv"); - std::ofstream zoomed_30_wn_out(data_dir + zoomed_name + "_wn_30.csv"); - std::ofstream zoomed_50_wn_out(data_dir + zoomed_name + "_wn_50.csv"); - std::ofstream zoomed_true_wn_out(data_dir + zoomed_name + "_wn_true.csv"); - - npts_x = 600; - npts_y = 300; - - for(double x = zoomed_bbox.getMin()[0]; x <= zoomed_bbox.getMax()[0]; - x += (zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0]) / npts_x) - { - std::cout << x << std::endl; - - for(double y = zoomed_bbox.getMin()[1]; y <= zoomed_bbox.getMax()[1]; - y += (zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double wn_15 = 0.0; - double wn_30 = 0.0; - double wn_50 = 0.0; - double wn_true = 0.0; - - for(int i = 0; i < spade.size(); ++i) - { - wn_15 += primal::winding_number_quad(query, spade[i], 15); - wn_30 += primal::winding_number_quad(query, spade[i], 30); - wn_50 += primal::winding_number_quad(query, spade[i], 50); - wn_true += primal::winding_number(query, spade[i], nevals); - } - - zoomed_15_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_15 - << std::endl; - zoomed_30_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_30 - << std::endl; - zoomed_50_wn_out << std::setprecision(16) << x << ',' << y << "," << wn_50 - << std::endl; - zoomed_true_wn_out << std::setprecision(16) << x << ',' << y << "," - << wn_true << std::endl; - } - } -} - -TEST(primal_2d_paper_figure_data, graphical_abstract) -{ - return; - std::cout << "Running: \"graphical_abstract\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\graphical_abstract\\"; -// "E:\\Code\\winding_number\\figures\\\\"; - - int npts_x = 1000; - int npts_y = 1000; - - std::string name = "butterfly"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - for(int i = 0; i < shape.size(); ++i) - { - shape_out << shape[i] << std::endl; - } - - // Replace curve 100 with a point - - CurveArray exploded_shape; - std::ofstream exploded_shape_out(data_dir + name + "_exploded.txt"); - std::ofstream exploded_shape_cn_out(data_dir + name + "_exploded_cn.csv"); - std::ofstream exploded_shape_wn_out(data_dir + name + "_exploded_wn.csv"); - primal::convert_from_svg(data_dir + name + "_exploded.svg", exploded_shape); - - for(int i = 0; i < exploded_shape.size(); ++i) - { - exploded_shape_out << exploded_shape[i] << std::endl; - } - - BoundingBox shape_bbox = curves_bbox(shape, 1.1, false); - - // Get winding number and crossing number for the exploded shape - BoundingBox exploded_shape_bbox = curves_bbox(exploded_shape, 1.1, false); - - for(double x = exploded_shape_bbox.getMin()[0]; - x <= exploded_shape_bbox.getMax()[0]; - x += (exploded_shape_bbox.getMax()[0] - exploded_shape_bbox.getMin()[0]) / - npts_x) - { - std::cout << x << "/" << exploded_shape_bbox.getMax()[0] << std::endl; - - for(double y = exploded_shape_bbox.getMin()[1]; - y <= exploded_shape_bbox.getMax()[1]; - y += (exploded_shape_bbox.getMax()[1] - exploded_shape_bbox.getMin()[1]) / - npts_y) - { - primal::Point query({x, y}); - - int crossing_number = - primal::crossing_number(query, exploded_shape, exploded_shape_bbox); - double winding_number = primal::winding_number(query, exploded_shape); - - exploded_shape_cn_out << std::setprecision(16) << x << ',' << y << "," - << crossing_number << std::endl; - - exploded_shape_wn_out << std::setprecision(16) << x << ',' << y << "," - << winding_number << std::endl; - } - } - - // Get a linearized version of the shape - std::ofstream linearized_shape_out(data_dir + name + "_linearized.txt"); - - // Construct the linear approximation with the given level of refinement - CurveArray linearized_shape; - for(int i = 0; i < exploded_shape.size(); ++i) - { - int n = 5; - for(double j = 0; j < n; ++j) - { - primal::BezierCurve line(1); - line[0] = exploded_shape[i].evaluate(j / n); - line[1] = exploded_shape[i].evaluate((j + 1) / n); - - linearized_shape.push_back(line); - } - } - - std::ofstream linearized_shape_wn_out(data_dir + name + "_linearized_wn.csv"); - - for(int i = 0; i < linearized_shape.size(); ++i) - { - linearized_shape_out << linearized_shape[i] << std::endl; - } - - // Zoom in around curve 106 - std::string zoomed_name = "zoomed"; - - primal::Point zoomed_center = exploded_shape[106].evaluate(0.85); - double zoomed_radius = 400.0; - BoundingBox zoomed_bbox; - zoomed_bbox.addPoint( - primal::Point {zoomed_center[0] + zoomed_radius, - zoomed_center[1] + zoomed_radius}); - zoomed_bbox.addPoint( - primal::Point {zoomed_center[0] - zoomed_radius, - zoomed_center[1] - zoomed_radius}); - - std::ofstream zoomed_true_out(data_dir + zoomed_name + "_true.csv"); - std::ofstream zoomed_linearized_out(data_dir + zoomed_name + - "_linearized.csv"); - std::ofstream zoomed_quadrature_out(data_dir + zoomed_name + - "_quadrature.csv"); - - for(double x = zoomed_bbox.getMin()[0]; x <= zoomed_bbox.getMax()[0]; - x += (zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0]) / npts_x) - { - std::cout << x << "/" << exploded_shape_bbox.getMax()[0] << std::endl; - - for(double y = zoomed_bbox.getMin()[1]; y <= zoomed_bbox.getMax()[1]; - y += (zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double true_wn = 0.0; - double linearized = 0.0; - double quadrature = 0.0; - - for(int i = 0; i < exploded_shape.size(); ++i) - { - true_wn += primal::winding_number(query, exploded_shape[i], nevals); - quadrature += primal::winding_number_quad(query, exploded_shape[i], 30); - } - - for(int i = 0; i < linearized_shape.size(); ++i) - { - linearized += primal::winding_number(query, linearized_shape[i], nevals); - } - - zoomed_true_out << std::setprecision(16) << x << ',' << y << "," - << true_wn << std::endl; - zoomed_linearized_out << std::setprecision(16) << x << ',' << y << "," - << linearized << std::endl; - zoomed_quadrature_out << std::setprecision(16) << x << ',' << y << "," - << quadrature << std::endl; - } - } -} - -TEST(primal_2d_paper_figure_data, graphical_abstract_trumpet) -{ - return; - - std::cout << "Running: \"graphical_abstract_trumpet\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\graphical_abstract\\"; - //"E:\\Code\\winding_number\\figures\\graphical_abstract\\"; - - int npts_x = 1000; - int npts_y = 1000; - - CurveArray closed_trumpet; - std::ofstream closed_trumpet_out(data_dir + "closed_trumpet.txt"); - primal::convert_from_svg(data_dir + "closed_trumpet.svg", closed_trumpet); - for(int i = 0; i < closed_trumpet.size(); ++i) - { - closed_trumpet_out << closed_trumpet[i] << std::endl; - } - BoundingBox closed_bbox = curves_bbox(closed_trumpet, 1.1, false); - - CurveArray open_trumpet; - std::ofstream open_trumpet_out(data_dir + "open_trumpet.txt"); - primal::convert_from_svg(data_dir + "open_trumpet.svg", open_trumpet); - for(int i = 0; i < open_trumpet.size(); ++i) - { - open_trumpet_out << open_trumpet[i] << std::endl; - } - BoundingBox open_bbox = curves_bbox(open_trumpet, 1.1, false); - - std::ofstream closed_trumpet_wn_out(data_dir + "closed_trumpet_wn.csv"); - std::ofstream closed_trumpet_cn_out(data_dir + "closed_trumpet_cn.csv"); - std::ofstream open_trumpet_wn_out(data_dir + "open_trumpet_wn.csv"); - std::ofstream open_trumpet_cn_out(data_dir + "open_trumpet_cn.csv"); - - // Compute winding numbers and crossing numbers for both trumpets - for(double x = open_bbox.getMin()[0]; x <= open_bbox.getMax()[0]; - x += (open_bbox.getMax()[0] - open_bbox.getMin()[0]) / npts_x) - { - std::cout << x << "/" << open_bbox.getMax()[0] << std::endl; - - for(double y = open_bbox.getMin()[1]; y <= open_bbox.getMax()[1]; - y += (open_bbox.getMax()[1] - open_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int crossing_number = - primal::crossing_number(query, open_trumpet, open_bbox); - double winding_number = primal::winding_number(query, open_trumpet); - - open_trumpet_wn_out << std::setprecision(16) << x << ',' << y << "," - << winding_number << std::endl; - open_trumpet_cn_out << std::setprecision(16) << x << ',' << y << "," - << crossing_number << std::endl; - } - } - - for(double x = closed_bbox.getMin()[0]; x <= closed_bbox.getMax()[0]; - x += (closed_bbox.getMax()[0] - closed_bbox.getMin()[0]) / npts_x) - { - std::cout << x << "/" << closed_bbox.getMax()[0] << std::endl; - - for(double y = closed_bbox.getMin()[1]; y <= closed_bbox.getMax()[1]; - y += (closed_bbox.getMax()[1] - closed_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int crossing_number = - primal::crossing_number(query, closed_trumpet, closed_bbox); - double winding_number = primal::winding_number(query, closed_trumpet); - - closed_trumpet_wn_out << std::setprecision(16) << x << ',' << y << "," - << winding_number << std::endl; - closed_trumpet_cn_out << std::setprecision(16) << x << ',' << y << "," - << crossing_number << std::endl; - } - } - - // Get a linearized version of the shape - std::ofstream linearized_shape_out(data_dir + "zoomed_linearized.txt"); - - // Construct the linear approximation with the given level of refinement - CurveArray linearized_shape; - for(int i = 0; i < open_trumpet.size(); ++i) - { - int n = 5; - for(double j = 0; j < n; ++j) - { - primal::BezierCurve line(1); - line[0] = open_trumpet[i].evaluate(j / n); - line[1] = open_trumpet[i].evaluate((j + 1) / n); - - linearized_shape.push_back(line); - } - } - - for(int i = 0; i < linearized_shape.size(); ++i) - { - linearized_shape_out << linearized_shape[i] << std::endl; - } - - primal::Point zoomed_center = - open_trumpet[open_trumpet.size() - 1].evaluate(0.21); - double zoomed_radius = 22.0; - BoundingBox zoomed_bbox; - zoomed_bbox.addPoint( - primal::Point {zoomed_center[0] + zoomed_radius, - zoomed_center[1] + zoomed_radius}); - zoomed_bbox.addPoint( - primal::Point {zoomed_center[0] - zoomed_radius, - zoomed_center[1] - zoomed_radius}); - - std::ofstream zoomed_true_out(data_dir + "zoomed_true.csv"); - std::ofstream zoomed_linearized_out(data_dir + "zoomed_linearized.csv"); - std::ofstream zoomed_quadrature_out(data_dir + "zoomed_quadrature.csv"); - - npts_x = 1000; - npts_y = 1000; - - for(double x = zoomed_bbox.getMin()[0]; x <= zoomed_bbox.getMax()[0]; - x += (zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0]) / npts_x) - { - std::cout << x << "/" << zoomed_bbox.getMax()[0] << std::endl; - - for(double y = zoomed_bbox.getMin()[1]; y <= zoomed_bbox.getMax()[1]; - y += (zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]) / npts_y) - { - primal::Point query({x, y}); - - int nevals = 0; - - double true_wn = 0.0; - double linearized = 0.0; - double quadrature = 0.0; - - for(int i = 0; i < open_trumpet.size(); ++i) - { - true_wn += primal::winding_number(query, open_trumpet[i], nevals); - quadrature += primal::winding_number_quad(query, open_trumpet[i], 30); - } - - for(int i = 0; i < linearized_shape.size(); ++i) - { - linearized += primal::winding_number(query, linearized_shape[i], nevals); - } - - zoomed_true_out << std::setprecision(16) << x << ',' << y << "," - << true_wn << std::endl; - zoomed_linearized_out << std::setprecision(16) << x << ',' << y << "," - << linearized << std::endl; - zoomed_quadrature_out << std::setprecision(16) << x << ',' << y << "," - << quadrature << std::endl; - } - } -} - -TEST(primal_2d_paper_figure_data, animation_still) -{ - //return; - std::cout << "Running: \"animation_still\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\animation\\"; - - int npts_x = 200 * 11; - int npts_y = 200 * 8.5; - - std::string name = "all_logos"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - for(int i = 0; i < shape.size(); ++i) - { - shape_out << shape[i] << std::endl; - } - - BoundingBox bbox = curves_bbox(shape, 1.2, false); - // Scale the box so that it has a ratio of 11x8.5 - primal::Point min_pt = bbox.getMin(); - primal::Point max_pt = bbox.getMax(); - double ratio = 11.0 / 8.5; - //double ratio = 8.5 / 11.0; - double width = max_pt[0] - min_pt[0]; - double height = max_pt[1] - min_pt[1]; - if(width / height > ratio) - { - double new_height = width / ratio; - min_pt[1] -= (new_height - height) / 2.0; - max_pt[1] += (new_height - height) / 2.0; - } - else - { - double new_width = height * ratio; - min_pt[0] -= (new_width - width) / 2.0; - max_pt[0] += (new_width - width) / 2.0; - } - simple_grid_test(shape, bbox, npts_x, npts_y, wn_out); -} - -TEST(primal_2d_paper_figure_data, background) -{ - return; - std::cout << "Running: \"background\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = "E:\\Code\\winding_number\\figures\\animation\\"; - - int npts_x = 400 * 16; - int npts_y = 400 * 9; - - std::string name = "background"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - for(int i = 0; i < shape.size(); ++i) - { - shape_out << shape[i] << std::endl; - } - - BoundingBox bbox = curves_bbox(shape, 1.0, false); - - simple_grid_test(shape, bbox, npts_x, npts_y, wn_out); -} - -TEST(primal_2d_paper_figure_data, animation) -{ - return; - std::cout << "Running: \"animation\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = "E:\\Code\\winding_number\\figures\\animation\\"; - - int permutation[] = { - 111, 32, 88, 120, 101, 66, 68, 7, 54, 113, 65, 117, 129, 67, 110, - 125, 92, 85, 95, 8, 23, 83, 122, 26, 64, 3, 104, 51, 22, 30, - 63, 123, 84, 0, 91, 99, 62, 40, 61, 42, 94, 102, 33, 20, 100, - 126, 49, 53, 69, 107, 56, 78, 58, 71, 127, 108, 13, 130, 41, 43, - 19, 5, 31, 15, 86, 80, 79, 128, 90, 11, 24, 9, 16, 121, 116, - 14, 89, 74, 4, 25, 28, 57, 97, 47, 10, 82, 87, 133, 46, 44, - 36, 55, 1, 81, 119, 114, 35, 21, 27, 77, 98, 124, 6, 38, 45, - 34, 106, 29, 76, 96, 52, 48, 39, 105, 17, 72, 73, 37, 75, 103, - 70, 109, 2, 18, 132, 131, 12, 59, 60, 93, 112, 50, 118, 115}; - - int npts_x = 500; - int npts_y = 500; - - std::string name = "butterfly"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream shuffled_shape_out(data_dir + name + "shuffled_.txt"); - std::ofstream wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - CurveArray shuffled_shape; - primal::convert_from_svg(data_dir + name + "_simple_bonus_edger.svg", - shuffled_shape); - - BoundingBox bbox = curves_bbox(shape, 1.25, true); - primal::Point min_pt = bbox.getMin(); - primal::Point max_pt = bbox.getMax(); - auto length = bbox.getMax()[0] - bbox.getMin()[0]; - min_pt[0] -= length * 7.0 / 9.0 * 0.9; - max_pt[0] += length * 7.0 / 9.0 * 0.1; - bbox.addPoint(min_pt); - bbox.addPoint(max_pt); - - for(int i = 0; i < shape.size(); ++i) - { - shape_out << shape[i] << std::endl; - shuffled_shape_out << shuffled_shape[i] << std::endl; - } - - // Curves one at a time - if(false) - { - CurveArray shape_increment; - - // Print them all to the file - for(int i = 0; i < shape.size(); ++i) - { - shape_increment.push_back(shape[permutation[i]]); - std::ofstream increment_shape_out(data_dir + "animation1_frames\\" + - name + "_" + std::to_string(i) + ".txt"); - std::ofstream increment_wn_out(data_dir + "animation1_frames\\" + name + - "_" + std::to_string(i) + "_wn.csv"); - - for(int j = 0; j < shape_increment.size(); ++j) - { - increment_shape_out << shape_increment[j] << std::endl; - } - - simple_grid_test(shape_increment, bbox, npts_x, npts_y, increment_wn_out); - } - } - - // Explode n Back - if(false) - { - auto interp_curve = [](const primal::BezierCurve& curve1, - const primal::BezierCurve& curve2, - double t) -> primal::BezierCurve { - primal::BezierCurve result(curve1.getOrder()); - for(int i = 0; i <= curve1.getOrder(); ++i) - { - result[i][0] = curve1[i][0] * (1.0 - t) + curve2[i][0] * t; - result[i][1] = curve1[i][1] * (1.0 - t) + curve2[i][1] * t; - } - - return result; - }; - - double a = 10; - auto speed_func = [&a](double t) -> double { - double g0 = 1.0 / (1 + std::exp(0.5 * a)); - return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); - }; - - for(int frame = 0; frame <= 100; ++frame) - { - double t; - if(frame < 50) - { - t = speed_func(frame / 50.0); - } - else - { - t = speed_func((100 - frame) / 50.0); - } - - CurveArray midshift_shape; - std::ofstream midshift_shape_out(data_dir + "animation3_frames\\frame_" + - std::to_string(frame) + "_shape.txt"); - std::ofstream midshift_wn_out(data_dir + "animation3_frames\\frame_" + - std::to_string(frame) + "_wn.csv"); - - for(int i = 0; i < shape.size(); ++i) - { - primal::BezierCurve interp = - interp_curve(shape[i], shuffled_shape[i], t); - midshift_shape_out << interp << std::endl; - midshift_shape.push_back(interp); - } - - simple_grid_test(midshift_shape, bbox, npts_x, npts_y, midshift_wn_out); - } - } - - // Slide a curve across the screen - if(true) - { - auto interp_curve = [](const primal ::BezierCurve& curve1, - const primal::BezierCurve& curve2, - double t) -> primal::BezierCurve { - primal::BezierCurve result(curve1.getOrder()); - for(int i = 0; i <= curve1.getOrder(); ++i) - { - result[i][0] = curve1[i][0] * (1.0 - t) + curve2[i][0] * t; - result[i][1] = curve1[i][1] * (1.0 - t) + curve2[i][1] * t; - } - - return result; - }; - - double a = 5; - auto speed1 = [&a](double t) -> double { - double g0 = 1.0 / (1 + std::exp(0.5 * a)); - return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); - }; - - auto speed2 = [](double t) -> double { - if(t < 0.5) - return (std::pow(20, t) - 1) / (std::sqrt(20) - 1); - else - return (std::pow(20, 1 - t) - 1) / (std::sqrt(20) - 1); - }; - - shuffled_shape[103].reverseOrientation(); - primal::BezierCurve midtwist_curve(3); - midtwist_curve[0] = primal::Point {1417.0, 67.0}; - midtwist_curve[1] = primal::Point {1920.88, -95.604}; - midtwist_curve[2] = primal::Point {1920.88, -95.604}; - midtwist_curve[3] = primal::Point {2473.0, -240.0}; - - double nframes = 150; - for(int frame = 0; frame <= nframes; ++frame) - { - double t = speed1(frame / nframes); - - CurveArray midshift_shape; - std::ofstream midshift_shape_out(data_dir + "animation3_frames\\frame_" + - std::to_string(frame) + "_shape.txt"); - std::ofstream midshift_wn_out(data_dir + "animation3_frames\\frame_" + - std::to_string(frame) + "_wn.csv"); - - primal::BezierCurve motion_curve1 = - shuffled_shape[shuffled_shape.size() - 2]; - primal::BezierCurve motion_curve2 = - shuffled_shape[shuffled_shape.size() - 1]; - - primal::Point new_point, reference_point = shape[0][0]; - - if(frame < (nframes / 2)) - new_point = motion_curve1.evaluate(speed2(frame / nframes)); - else - new_point = motion_curve2.evaluate(1.0 - speed2(frame / nframes)); - - primal::Point the_shift {new_point[0] - reference_point[0], - new_point[1] - reference_point[1]}; - - primal::BezierCurve new_curve_1(3), new_curve_2(3); - - for(int i = 0; i <= 3; ++i) - { - new_curve_1[i][0] = shape[0][i][0] + the_shift[0]; - new_curve_1[i][1] = shape[0][i][1] + the_shift[1]; - - new_curve_2[i][0] = shape[1][i][0] + the_shift[0]; - new_curve_2[i][1] = shape[1][i][1] + the_shift[1]; - } - - midshift_shape_out << new_curve_1 << std::endl; - midshift_shape_out << new_curve_2 << std::endl; - - midshift_shape.push_back(new_curve_1); - midshift_shape.push_back(new_curve_2); - - //shuffled_shape[103][0] = shape[103][3]; - //shuffled_shape[103][1][0] += 2.5; - //shuffled_shape[103][2][0] += 2.5; - //shuffled_shape[103][3] = shape[103][0]; - - for(int i = 2; i < shape.size(); ++i) - { - primal::BezierCurve interp(3); - - if(i == 103) - interp = - interp_curve(interp_curve(shape[i], midtwist_curve, t), - interp_curve(midtwist_curve, shuffled_shape[i], t), - t); - else - interp = interp_curve(shape[i], shuffled_shape[i], t); - midshift_shape_out << interp << std::endl; - midshift_shape.push_back(interp); - } - - simple_grid_test(midshift_shape, bbox, npts_x, npts_y, midshift_wn_out); - - if(frame == nframes) shape = midshift_shape; - } - } - - // zoom in - if(false) - { - int idx = 109; - auto zoom_pt = shape[idx][shape[idx].getOrder()]; - zoom_pt[1] -= 0.5; - double zoom_r = 1; - BoundingBox zoomed_bbox; - zoomed_bbox.addPoint( - primal::Point {zoom_pt[0] + zoom_r, zoom_pt[1] + zoom_r}); - zoomed_bbox.addPoint( - primal::Point {zoom_pt[0] - zoom_r, zoom_pt[1] - zoom_r}); - - primal::Point zoomed_min_pt = zoomed_bbox.getMin(); - primal::Point zoomed_max_pt = zoomed_bbox.getMax(); - - zoomed_max_pt[0] += 2 * zoom_r * 7.0 / 9.0 * 0.1; - zoomed_min_pt[0] -= 2 * zoom_r * 7.0 / 9.0 * 0.9; - zoomed_bbox.addPoint(zoomed_min_pt); - zoomed_bbox.addPoint(zoomed_max_pt); - - double a = 15; - auto speed_func = [&a](double t) -> double { - double g0 = 1.0 / (1 + std::exp(0.5 * a)); - return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); - }; - - auto interp_box = [](const primal::BoundingBox& box1, - const primal::BoundingBox& box2, - double t) -> primal::BoundingBox { - primal::BoundingBox result; - result.addPoint(primal::Point { - box1.getMin()[0] * (1.0 - t) + box2.getMin()[0] * t, - box1.getMin()[1] * (1.0 - t) + box2.getMin()[1] * t}); - - result.addPoint(primal::Point { - box1.getMax()[0] * (1.0 - t) + box2.getMax()[0] * t, - box1.getMax()[1] * (1.0 - t) + box2.getMax()[1] * t}); - - return result; - }; - - double nframes = 150; - for(int frame = 0; frame <= nframes; ++frame) - { - double t = speed_func(frame / nframes); - - std::ofstream zoomed_wn_out(data_dir + "animation2_frames\\frame_" + - std::to_string(frame) + "_wn.csv"); - - auto semizoomed_bbox = interp_box(bbox, zoomed_bbox, t); - simple_grid_test(shape, semizoomed_bbox, npts_x, npts_y, zoomed_wn_out); - } - } -} - -TEST(primal_2d_paper_figure_data, fish_zoom) -{ - return; - std::cout << "Running: \"fish zoom\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\animation\\"; - - int npts_x = 500; - int npts_y = 500; - - std::string name = "fish"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - // Jiggle the curves, and print them all to the file - srand(100); - - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - - shape[i][0][0] += (rand() % 30) - 15; - shape[i][0][1] += (rand() % 30) - 15; - - shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; - shape[i][shape[i].getOrder()][0] += (rand() % 30) - 15; - - shape_out << shape[i] << std::endl; - } - - BoundingBox bbox = curves_bbox(shape, 1.1, true); - - // zoom in - BoundingBox zoomed_bbox(shape[240].boundingBox()); - double max_dim = - axom::utilities::max(zoomed_bbox.getMax()[0] - zoomed_bbox.getMin()[0], - zoomed_bbox.getMax()[1] - zoomed_bbox.getMin()[1]); - zoomed_bbox.addPoint( - primal::Point {zoomed_bbox.getMin()[0] + max_dim, - zoomed_bbox.getMin()[1] + max_dim}); - zoomed_bbox.scale(12.0); - - double a = 15; - auto speed_func = [&a](double t) -> double { - double g0 = 1.0 / (1 + std::exp(0.5 * a)); - return (g0 - 1.0 / (1 + std::exp(-a * (t - 0.5)))) / (2 * g0 - 1); - }; - - auto interp_box = [](const primal::BoundingBox& box1, - const primal::BoundingBox& box2, - double t) -> primal::BoundingBox { - primal::BoundingBox result; - result.addPoint(primal::Point { - box1.getMin()[0] * (1.0 - t) + box2.getMin()[0] * t, - box1.getMin()[1] * (1.0 - t) + box2.getMin()[1] * t}); - - result.addPoint(primal::Point { - box1.getMax()[0] * (1.0 - t) + box2.getMax()[0] * t, - box1.getMax()[1] * (1.0 - t) + box2.getMax()[1] * t}); - - return result; - }; - - double nframes = 150; - for(int frame = 0; frame <= nframes; ++frame) - { - double t = speed_func(frame / nframes); - - std::ofstream zoomed_wn_out(data_dir + "fish_animation\\frame_" + - std::to_string(frame) + "_wn.csv"); - - auto semizoomed_bbox = interp_box(bbox, zoomed_bbox, t); - simple_grid_test(shape, semizoomed_bbox, npts_x, npts_y, zoomed_wn_out); - } -} - -TEST(primal_2d_paper_figure_data, axom_trace) -{ - return; - std::cout << "Running: \"axom trace\"" << std::endl; - - using CurveArray = axom::Array>; - using BoundingBox = primal::BoundingBox; - - std::string data_dir = - "C:\\Users\\Fireh\\Code\\winding_number_code\\figures\\axom_animation\\"; - - int npts_x = 500; - int npts_y = 500; - - std::string name = "siggraph_logo"; - CurveArray shape; - std::ofstream shape_out(data_dir + name + ".txt"); - std::ofstream wn_out(data_dir + name + "_wn.csv"); - primal::convert_from_svg(data_dir + name + ".svg", shape); - - for(int i = 0; i < shape.size(); ++i) - { - shape[i].reverseOrientation(); - shape_out << shape[i] << std::endl; - } - - BoundingBox bbox = curves_bbox(shape, 1.2, false); - - double nframes = 200; - for(int frame = 0; frame <= nframes; ++frame) - { - double t = frame / nframes; - - std::ofstream traced_wn_out(data_dir + "frames_2\\frame_" + - std::to_string(frame) + "_wn.csv"); - std::ofstream traced_shape_out(data_dir + +"frames_2\\frame_" + - std::to_string(frame) + "_curves.csv"); - - CurveArray traced_shape; - - primal::BezierCurve real_curve, dummy; - for(int i = 0; i < shape.size(); ++i) - { - shape[i].split(t, real_curve, dummy); - traced_shape_out << real_curve << std::endl; - - traced_shape.push_back(real_curve); - } - - simple_grid_test(traced_shape, bbox, npts_x, npts_y, traced_wn_out); - } -} - -int main(int argc, char* argv[]) -{ - int result = 0; - - ::testing::InitGoogleTest(&argc, argv); - - axom::slic::SimpleLogger logger; - - result = RUN_ALL_TESTS(); - - return result; -} From 1a396d26077bd470b60993d1bb8cbb3031a65a36 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 13:01:45 -0600 Subject: [PATCH 03/25] Cleanup/Comment out some figure code --- .../operators/detail/winding_number_impl.hpp | 560 ++++++------------ src/axom/primal/operators/winding_number.hpp | 464 +++++---------- src/axom/primal/tests/CMakeLists.txt | 1 - 3 files changed, 322 insertions(+), 703 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index 1fd8289dca..5bdbc6d3e5 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -104,6 +104,7 @@ double convex_endpoint_winding_number(const Point& q, const BezierCurve& c, double edge_tol) { + const int ord = c.getOrder(); if(ord == 1) { @@ -139,6 +140,11 @@ double convex_endpoint_winding_number(const Point& q, } Vector V2(q, c[idx]); + std::cout << std::setprecision(16) << std::endl; + std::cout << c << std::endl; + std::cout << V1 << std::endl; + std::cout << V2 << std::endl; + // clang-format off // Measures the signed area of the triangle spanned by V1 and V2 double tri_area = axom::numerics::determinant(V1[0] - V2[0], V2[0], @@ -268,209 +274,177 @@ double curve_winding_number_recursive(const Point& q, curve_winding_number_recursive(q, c2, isConvexControlPolygon, nevals, edge_tol); } -template -double approxogon_winding_number(const Point& q, - const Polygon& approxogon, - double edge_tol) -{ - bool isOnEdge = false; - const int n = approxogon.numVertices(); - - // Catch some early edge cases - if(n <= 1) return 0.0; - - int integer_wn = winding_number(q, approxogon, isOnEdge, false, PRIMAL_TINY); - double closure_wn = - linear_winding_number(q, approxogon[n - 1], approxogon[0], edge_tol); - - if(isOnEdge) - { - // If on edge, can't use integer winding number. Have to compute it - // through winding number of each edge - - //std::cout << "on edge" << std::endl; - - // Main leg is oriented in the reverse - double wn = -closure_wn; - for(int i = 1; i < n; ++i) - { - wn += detail::linear_winding_number(q, - approxogon[i - 1], - approxogon[i], - edge_tol); - } - - return wn; - } - - return integer_wn - closure_wn; -} - -template -struct BezierCurveMemo -{ - bool isConvexControlPolygon; - BezierCurve curve; -}; - -struct PairHash -{ - using result_type = std::size_t; - - size_t operator()(const std::pair& p) const - { +// template +// struct BezierCurveMemo +// { + // bool isConvexControlPolygon; + // BezierCurve curve; +// }; + +// struct PairHash +// { + // using result_type = std::size_t; +// + // size_t operator()(const std::pair& p) const + // { // Combine hashes of the two integers - size_t hash1 = std::hash()(p.first); - size_t hash2 = std::hash()(p.second); - return hash1 ^ (hash2 << 1); // Simple combining function - } -}; + // size_t hash1 = std::hash()(p.first); + // size_t hash2 = std::hash()(p.second); + // return hash1 ^ (hash2 << 1); // Simple combining function + // } +// }; //bool operator==(const std::pair& lhs, const std::pair& rhs) //{ // return lhs.first == rhs.first && lhs.second == rhs.second; //} -template -void winding_number_adaptive_linear_memoized( - Point q, - const std::vector>>& array_memo, - axom::FlatMap, BezierCurveMemo, PairHash>& hash_memo, - double edge_tol, - double linear_tol, - Polygon& approxogon, - std::stack>& curve_stack, - double& end_wn) -{ - constexpr int LEVEL = 3; - const int ord = array_memo[0][0].curve.getOrder(); - - //std::stack> curve_stack; - curve_stack.push(std::make_pair(0, 0)); - - while(!curve_stack.empty()) - { - std::pair the_pair = curve_stack.top(); - curve_stack.pop(); - - //std::cout << the_pair.first << std::endl; - - //std::cout << the_pair.first << " " << the_pair.second << std::endl; - BezierCurveMemo curve_memo; - if(the_pair.first < LEVEL) - curve_memo = array_memo[the_pair.first][the_pair.second]; - else - curve_memo = hash_memo[the_pair]; - - //if(curve_memo.isLinear) - //{ - // approxogon.addVertex(curve_memo.curve[0]); - // continue; - //} - - BoundingBox bBox(curve_memo.curve.boundingBox()); - if(!bBox.contains(q)) - { - approxogon.addVertex(curve_memo.curve[0]); - continue; - } - - if(curve_memo.isConvexControlPolygon) - { - constexpr bool includeBoundary = true; - constexpr bool useNonzeroRule = true; - - if(!in_polygon(q, - Polygon(curve_memo.curve.getControlPoints()), - includeBoundary, - useNonzeroRule, - PRIMAL_TINY)) - { - approxogon.addVertex(curve_memo.curve[0]); - - continue; - } - - if((squared_distance(q, curve_memo.curve[0]) <= edge_tol * edge_tol) || - (squared_distance(q, curve_memo.curve[ord]) <= edge_tol * edge_tol)) - { - end_wn += approxogon_winding_number(q, approxogon, edge_tol); - approxogon.clear(); - - end_wn += convex_endpoint_winding_number(q, curve_memo.curve, edge_tol); - approxogon.addVertex(curve_memo.curve[ord]); - continue; - } - } - - auto ref1 = std::make_pair(the_pair.first + 1, 2 * the_pair.second); - auto ref2 = std::make_pair(the_pair.first + 1, 2 * the_pair.second + 1); - - if(the_pair.first >= LEVEL - 1 && hash_memo.find(ref1) == hash_memo.end()) - { - BezierCurve c1, c2; - curve_memo.curve.split(0.5, c1, c2); - - hash_memo[ref1] = { - //c1.isLinear(linear_tol), - curve_memo.isConvexControlPolygon || - is_convex(Polygon(c1.getControlPoints()), PRIMAL_TINY), - c1}; - - hash_memo[ref2] = { - //c2.isLinear(linear_tol), - curve_memo.isConvexControlPolygon || - is_convex(Polygon(c2.getControlPoints()), PRIMAL_TINY), - c2}; - } - - curve_stack.push(ref2); - curve_stack.push(ref1); - } -} +// template +// void winding_number_adaptive_linear_memoized( +// Point q, +// const std::vector>>& array_memo, +// axom::FlatMap, BezierCurveMemo, PairHash>& hash_memo, +// double edge_tol, +// double linear_tol, +// Polygon& approxogon, +// std::stack>& curve_stack, +// double& end_wn) +// { +// constexpr int LEVEL = 3; +// const int ord = array_memo[0][0].curve.getOrder(); + +// //std::stack> curve_stack; +// curve_stack.push(std::make_pair(0, 0)); + +// while(!curve_stack.empty()) +// { +// std::pair the_pair = curve_stack.top(); +// curve_stack.pop(); + +// //std::cout << the_pair.first << std::endl; + +// //std::cout << the_pair.first << " " << the_pair.second << std::endl; +// BezierCurveMemo curve_memo; +// if(the_pair.first < LEVEL) +// curve_memo = array_memo[the_pair.first][the_pair.second]; +// else +// curve_memo = hash_memo[the_pair]; + +// //if(curve_memo.isLinear) +// //{ +// // approxogon.addVertex(curve_memo.curve[0]); +// // continue; +// //} + +// BoundingBox bBox(curve_memo.curve.boundingBox()); +// if(!bBox.contains(q)) +// { +// approxogon.addVertex(curve_memo.curve[0]); +// continue; +// } + +// if(curve_memo.isConvexControlPolygon) +// { +// constexpr bool includeBoundary = true; +// constexpr bool useNonzeroRule = true; + +// if(!in_polygon(q, +// Polygon(curve_memo.curve.getControlPoints()), +// includeBoundary, +// useNonzeroRule, +// PRIMAL_TINY)) +// { +// approxogon.addVertex(curve_memo.curve[0]); + +// continue; +// } + +// if((squared_distance(q, curve_memo.curve[0]) <= edge_tol * edge_tol) || +// (squared_distance(q, curve_memo.curve[ord]) <= edge_tol * edge_tol)) +// { +// end_wn += approxogon_winding_number(q, approxogon, edge_tol); +// approxogon.clear(); + +// end_wn += convex_endpoint_winding_number(q, curve_memo.curve, edge_tol); +// approxogon.addVertex(curve_memo.curve[ord]); +// continue; +// } +// } + +// auto ref1 = std::make_pair(the_pair.first + 1, 2 * the_pair.second); +// auto ref2 = std::make_pair(the_pair.first + 1, 2 * the_pair.second + 1); + +// if(the_pair.first >= LEVEL - 1 && hash_memo.find(ref1) == hash_memo.end()) +// { +// BezierCurve c1, c2; +// curve_memo.curve.split(0.5, c1, c2); + +// hash_memo[ref1] = { +// //c1.isLinear(linear_tol), +// curve_memo.isConvexControlPolygon || +// is_convex(Polygon(c1.getControlPoints()), PRIMAL_TINY), +// c1}; + +// hash_memo[ref2] = { +// //c2.isLinear(linear_tol), +// curve_memo.isConvexControlPolygon || +// is_convex(Polygon(c2.getControlPoints()), PRIMAL_TINY), +// c2}; +// } + +// curve_stack.push(ref2); +// curve_stack.push(ref1); +// } +// } +/*! + * \brief Recursively construct a polygon with the same *integer* winding number + * as the closed original Bezier curve at a given query point. + * + * \param [in] q The query point at which to compute winding number + * \param [in] c A BezierCurve subcurve of the curve along which to compute the winding number + * \param [in] isConvexControlPolygon Boolean flag if the input Bezier subcurve + is already convex + * \param [in] edge_tol The physical distance level at which objects are + * considered indistinguishable + * \param [in] EPS Miscellaneous numerical tolerance for nonphysical distances, used in + * isLinear, isNearlyZero, in_polygon, is_convex + * \param [out] approximating_polygon The Polygon that, by termination of recursion, + * has the same integer winding number as the original closed curve + * \param [out] endpoint_gwn A running sum for the exact GWN if the point is at the + * endpoint of a subcurve + * + * By the termination of the recursive algorithm, `approximating_polygon` contains + * a polygon that has the same *integer* winding number as the original curve. + * + * Upon entering this algorithm, the closing line of `c` is already an + * edge of the approximating polygon. + * If q is outside a convex shape that contains the entire curve, the + * integer winding number for the *closed* curve `c` is zero, + * and the algorithm terminates. + * If the shape is not convex or we're inside it, instead add the midpoint + * as a vertex and repeat the algorithm. + */ template -void winding_number_adaptive_linear(const Point& q, +void construct_approximating_polygon(const Point& q, const BezierCurve& c, bool isConvexControlPolygon, - int& num_evals, double edge_tol, - double linear_tol, - Polygon& approxogon, - double& end_wn) + double EPS, + Polygon& approximating_polygon, + double& endpoint_gwn) { const int ord = c.getOrder(); - // If q is outside a convex shape that contains the entire curve, the winding - // number for the shape connected at the endpoints with straight lines is zero. - // We then subtract the contribution of this line segment. // Simplest convex shape containing c is its bounding box - BoundingBox bBox(c.boundingBox()); - if(!bBox.contains(q)) + if(!c.boundingBox().contains(q)) { - // Don't need to bisect any further - //desmos_print(c); return; } - // Use linearity as base case for recursion. - if(c.isLinear(linear_tol)) + // Use linearity as base case for recursion + if(c.isLinear(EPS)) { - // todo: why do we need to start over if we hit this case? - - //std::cout << "is linear case" << std::endl; - //std::cout << approxogon.numVertices() << std::endl; - //desmos_print(c); - - // Don't need to bisect any further, but we do need - // to start the polygon over, - //end_wn += approxogon_winding_number(q, approxogon, edge_tol); - //approxogon.clear(); - - // and use the direct formula for the line segment - //end_wn += linear_winding_number(q, c[0], c[ord], edge_tol); - //++num_evals; - //approxogon.addVertex(c[ord]); return; } @@ -491,243 +465,41 @@ void winding_number_adaptive_linear(const Point& q, // Bezier curves are always contained in their convex control polygon if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, PRIMAL_TINY)) { - // Don't need to bisect any further - //desmos_print(c); return; } - // If the query point is at either endpoint, use direct formula + // If the query point is at either endpoint... if(squared_distance(q, c[0]) <= edge_tol * edge_tol || squared_distance(q, c[ord]) <= edge_tol * edge_tol) { - //std::cout << approxogon << std::endl; + // ...we can use a direct formula... + // endpoint_gwn += approxogon_winding_number(q, approximating_polygon, edge_tol); - // Need to start the polygon over, - end_wn += approxogon_winding_number(q, approxogon, edge_tol); - std::cout << "using endpoint formula" << std::endl; - approxogon.clear(); + // ...but need to reconstru... + // approxogon.clear(); // and use the direct formula for the endpoint - end_wn += convex_endpoint_winding_number(q, c, edge_tol); + endpoint_gwn += convex_endpoint_winding_number(q, c, edge_tol); - approxogon.addVertex(c[ord]); + // approxogon.addVertex(c[ord]); //desmos_print(c); return; } } - // Do a single iteration of Newton's Method to get a better guess than 0.5 - //double split_val = 0.5; - //Point eval; - //Vector Dt, DtDt; - - //for(int i = 0; i < 15; ++i) - //{ - // c.evaluate_second_derivative(split_val, eval, Dt, DtDt); - - // Vector q_eval(q, eval); - // if(q_eval.squared_norm() < edge_tol * edge_tol) break; - - // split_val = axom::utilities::clampVal( - // split_val - Dt.dot(q_eval) / (Dt.dot(Dt) + DtDt.dot(q_eval)), - // 0.0, - // 1.0); - //} - // Recursively split curve until query is outside some known convex region BezierCurve c1, c2; c.split(0.5, c1, c2); // clang-format off - winding_number_adaptive_linear(q, c1, isConvexControlPolygon, num_evals, edge_tol, linear_tol, approxogon, end_wn); - - //std::cout << t1 << " " << c2[0] << c.evaluate( t1 ) << std::endl; - approxogon.addVertex(c2[0]); - ++num_evals; - - - winding_number_adaptive_linear(q, c2, isConvexControlPolygon, num_evals, edge_tol, linear_tol, approxogon, end_wn); + construct_approximating_polygon(q, c1, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn); + approximating_polygon.addVertex(c2[0]); + construct_approximating_polygon(q, c2, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn); // clang-format on return; } -template -int ray_casting_bezier_clipping(const BezierCurve& curve, - const Ray& ray, - int& nevals, - double tol = 1E-8) -{ - const int ord = curve.getOrder(); - const bool isRational = curve.isRational(); - - // Make an array of curves to append to later - axom::Array> curves(0); - curves.push_back(curve); - int inter = 0; - - while(!curves.empty()) - { - BezierCurve the_curve = curves[curves.size() - 1]; - curves.erase(curves.end() - 1); - - Point origin = ray.origin(); - Vector e1 = ray.direction().unitVector(); - Vector e2 = Vector {-e1[1], e1[0]}; - - // Iterate over the control points and find the coordinates - // in terms of e1 and e2 - - int8_t flag = ~0; - for(int p = 0; flag && p <= ord; ++p) - { - auto pt_vec = Vector(origin, the_curve[p]); - double alpha = pt_vec.dot(e1); - double beta = pt_vec.dot(e2); - - if(alpha > 0 && beta > 0) // quad 1 - { - // Turn off bits 1, 2, 3, 5, 6 - flag &= 0x89; - } - else if(alpha < 0 && beta > 0) // quad 2 - { - // Turn off bits 0, 2, 3, 6, 7 - flag &= 0x4C; - } - else if(alpha < 0 && beta < 0) - { - // Turn off bits 0, 1, 3, 4, 7 - flag &= 0x26; - } - else if(alpha > 0 && beta < 0) - { - // Turn off bits 0, 1, 2, 4, 5 - flag &= 0x13; - } - } - - if(flag == 0x0) - { - // Can't check intersections if the curve isn't convex - Polygon controlPolygon(the_curve.getControlPoints()); - if(!is_convex(controlPolygon, tol)) - { - BezierCurve c1, c2; - the_curve.split(0.5, c1, c2); - curves.push_back(c1); - curves.push_back(c2); - nevals += 1; - continue; - } - - BoundingBox bb = the_curve.boundingBox(); - double size = axom::utilities::max(bb.range()[0], bb.range()[1]); - - if(size < tol) - { - std::cout << "is On the curve lol" << std::endl; - return 1; - } - - double a = e1[1]; - double b = -e1[0]; - double c = e1[0] * origin[1] - e1[1] * origin[0]; - double u, tmp; - - // Do a very sloppy way of computing intersections with the convex hull - double umin = 1.0, umax = 0.0; - Ray axis(Point {0.0, 0.0}, Vector {1.0, 0.0}); - - // Find the maximum and minimum coordinates of all the intersections. - for(double p = 0; p < ord; ++p) - { - double R0 = a * the_curve[p][0] + b * the_curve[p][1] + c; - double R1 = a * the_curve[p + 1][0] + b * the_curve[p + 1][1] + c; - - if(R0 * R1 > 0) continue; - - if(the_curve.isRational()) - { - R0 *= the_curve.getWeight(p); - R1 *= the_curve.getWeight(p + 1); - } - - // Compute intersection between the ray and the segment - Segment seg(Point {p / ord, R0}, - Point {(p + 1) / ord, R1}); - intersect(axis, seg, u, tmp, 0.0); - - umin = axom::utilities::min(umin, u); - umax = axom::utilities::max(umax, u); - } - - // Do the final check to see if the ray intersects the curve - double R0 = a * the_curve[ord][0] + b * the_curve[ord][1] + c; - double R1 = a * the_curve[0][0] + b * the_curve[0][1] + c; - - if(R0 * R1 < 0) - { - if(the_curve.isRational()) - { - R0 *= the_curve.getWeight(ord); - R1 *= the_curve.getWeight(0); - } - - // Compute intersection between the ray and the segment - Segment seg(Point {1.0, R0}, Point {0.0, R1}); - intersect(axis, seg, u, tmp, 0.0); - - umin = axom::utilities::min(umin, u); - umax = axom::utilities::max(umax, u); - } - - // Make minor numerical adjustments - umin = 0.99 * umin; - umax = 0.99 * umax + 0.01; - - // Heuristic to account for multiple intersections - if(umax - umin > 0.8) - { - BezierCurve c1, c2; - the_curve.split(0.5, c1, c2); - curves.push_back(c1); - curves.push_back(c2); - nevals += 1; - } - else - { - // Now we have the min and max, so we can split the curve - BezierCurve c1, c2, c3; - the_curve.split(umin, c1, c2); - c2.split((umax - umin) / (1.0 - umin), c2, c3); - - nevals += 2; - curves.push_back(c1); - curves.push_back(c2); - curves.push_back(c3); - } - } - else if(flag == 0x1) - { - //std::cout << "Case B: Maybe intersections" << std::endl; - double alpha1 = Vector(origin, the_curve[0]).dot(e2); - double alpha2 = Vector(origin, the_curve[ord]).dot(e2); - - if(alpha1 * alpha2 < 0) - { - (alpha1 < 0) ? inter++ : inter--; - } - } - //else - //{ - // std::cout << "Case A: No intersections" << std::endl; - //} - } - - return inter; -} - /// Type to indicate orientation of singularities relative to surface enum class SingularityAxis { diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index b92b96219c..0f55f247aa 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -27,6 +27,7 @@ #include "axom/primal/geometry/CurvedPolygon.hpp" #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/OrientedBoundingBox.hpp" + #include "axom/primal/operators/detail/winding_number_impl.hpp" // C++ includes @@ -157,7 +158,7 @@ int winding_number(const Point& R, { // clang-format off double det = axom::numerics::determinant(P[i][0] - R[0], P[j][0] - R[0], - P[i][1] - R[1], P[j][1] - R[1]); + P[i][1] - R[1], P[j][1] - R[1]); // clang-format on // On edge @@ -219,233 +220,81 @@ int winding_number(const Point& R, } /*! - * \brief Computes the generalized winding number for a single 2D Bezier curve + * \brief Computes the generalized winding number (GWN) for a single 2D Bezier curve * * \param [in] query The query point to test * \param [in] c The Bezier curve object * \param [in] edge_tol The physical distance level at which objects are considered indistinguishable * \param [in] EPS Miscellaneous numerical tolerance level for nonphysical distances * - * Computes the winding number using a recursive, bisection algorithm, - * using nearly-linear Bezier curves as a base case. + * Computes the GWN using a recursive, bisection algorithm + * that constructs a polygon with the same *integer* WN as + * the curve closed with a linear segment. The *generalized* WN + * of the closing line is then subtracted from the integer WN to + * return the GWN of the original curve. (See ) + * + * Nearly-linear Bezier curves are the base case for recursion. + * + * See Algorithm 2 in + * Jacob Spainhour, David Gunderman, and Kenneth Weiss. 2024. + * Robust Containment Queries over Collections of Rational Parametric Curves via Generalized Winding Numbers. + * ACM Trans. Graph. 43, 4, Article 38 (July 2024) * * \return double the generalized winding number. */ template double winding_number(const Point& q, const BezierCurve& c, - int& nevals, double edge_tol = 1e-8, double EPS = 1e-8) { - return detail::curve_winding_number_recursive(q, c, false, nevals, edge_tol); const int ord = c.getOrder(); if(ord <= 0) return 0.0; - Polygon approxogon(2); - approxogon.addVertex(c[0]); - - int dummy_int = 0; - - double wn = 0.0; - //std::cout << "------------" << std::endl; - detail::winding_number_adaptive_linear(q, - c, - false, - dummy_int, - edge_tol, - edge_tol, - approxogon, - wn); - //std::cout << "------------" << std::endl; - approxogon.addVertex(c[ord]); - - //std::cout << approxogon.numVertices() << std::endl; - - return wn + detail::approxogon_winding_number(q, approxogon, edge_tol); -} - -template -double winding_number_quad(const Point& q, - const BezierCurve& c, - int npts) -{ - static mfem::IntegrationRules my_IntRules(0, mfem::Quadrature1D::GaussLegendre); - const mfem::IntegrationRule& quad = - my_IntRules.Get(mfem::Geometry::SEGMENT, 2 * npts - 1); - - double quad_result = 0.0; - for(int k = 0; k < quad.GetNPoints(); ++k) - { - Point x_quad = c.evaluate(quad.IntPoint(k).x); - Vector dx_quad = c.dt(quad.IntPoint(k).x); - - double query_dist = squared_distance(x_quad, q); - // clang-format off - double query_orient = axom::numerics::determinant(x_quad[0] - q[0], dx_quad[0], - x_quad[1] - q[1], dx_quad[1]); - // clang-format on - - quad_result += quad.IntPoint(k).weight * query_orient / query_dist; - } - - return 0.5 * M_1_PI * quad_result; -} - -template -double winding_number_clipping(const Point& q, - const BezierCurve& c, - int& nevals, - double edge_tol = 1e-8, - double EPS = 1e-8) -{ - const int ord = c.getOrder(); - Segment closure(c[ord], c[0]); - int inter = 0; - - BoundingBox bb = c.boundingBox(); - if(bb.contains(q)) - { - // Check the 4 edges of the bounding box to find the closest - double edge_dist = bb.getMax()[0] - q[0]; - Vector direction {1.0, 0.0}; - - if(bb.getMax()[1] - q[1] < edge_dist) - { - direction = Vector {0.0, 1.0}; - edge_dist = bb.getMax()[1] - q[1]; - } - if(q[0] - bb.getMin()[0] < edge_dist) - { - direction = Vector {-1.0, 0.0}; - edge_dist = q[0] - bb.getMin()[0]; - } - if(q[1] - bb.getMin()[1] < edge_dist) - { - direction = Vector {0.0, -1.0}; - } - - Ray ray(q, direction); - inter = detail::ray_casting_bezier_clipping(c, ray, nevals, edge_tol); - double u, tmp; - if(intersect(ray, closure, u, tmp, 0.0)) - { - Vector e1 = ray.direction().unitVector(); - Vector e2 = Vector {-e1[1], e1[0]}; - - double alpha = Vector(ray.origin(), c[ord]).dot(e2); - - (alpha < 0) ? inter++ : inter--; - } - } - // If not contained in the bounding box, just do a return - - return inter - winding_number(q, closure, edge_tol); -} - -template -double winding_number_bisection(const Point& q, - const BezierCurve& c, - int& nevals, - double edge_tol = 1e-8, - double EPS = 1e-8) -{ - const int ord = c.getOrder(); - Segment closure(c[ord], c[0]); - int crossing_num = 0; - int inter = 0; - - BoundingBox bb = c.boundingBox(); - if(bb.contains(q)) - { - // Check the 4 edges of the bounding box to find the closest - double edge_dist = bb.getMax()[0] - q[0]; - Vector direction {1.0, 0.0}; - - if(bb.getMax()[1] - q[1] < edge_dist) - { - direction = Vector {0.0, 1.0}; - edge_dist = bb.getMax()[1] - q[1]; - } - if(q[0] - bb.getMin()[0] < edge_dist) - { - direction = Vector {-1.0, 0.0}; - edge_dist = q[0] - bb.getMin()[0]; - } - if(q[1] - bb.getMin()[1] < edge_dist) - { - direction = Vector {0.0, -1.0}; - } - - Ray ray(q, direction); - std::vector cp; - std::vector rp; - intersect(c, ray, cp, rp, nevals, edge_tol); - - for(auto c0 : cp) - { - Vector e1 = ray.direction().unitVector(); - Vector e2 = c.dt(c0); - - double determinant = - axom::numerics::determinant(e1[0], e2[0], e1[1], e2[1]); - - (determinant > 0) ? crossing_num++ : crossing_num--; - } - double u, tmp; - if(intersect(ray, closure, u, tmp, edge_tol)) + // The first vertex of the polygon is the t=0 point of the curve + Polygon approximating_polygon(1); + approximating_polygon.addVertex(c[0]); + + // Need to keep a running total of the GWN to account for + // the winding number of coincident points + double gwn = 0.0; + detail::construct_approximating_polygon(q, + c, + false, + edge_tol, + EPS, + approximating_polygon, + gwn); + + // The last vertex of the polygon is the t=1 point of the curve + approximating_polygon.addVertex(c[ord]); + + int n = approximating_polygon.numVertices(); + + bool isOnEdge; + double closed_curve_wn = + winding_number(q, approximating_polygon, isOnEdge, false, PRIMAL_TINY); + + double closure_wn = detail::linear_winding_number(q, + approximating_polygon[n - 1], + approximating_polygon[0], + edge_tol); + + // If the point is on the edge of the approximating polygon (rare), then winding_number + // doesn't return the right half-integer. Have to go edge-by-edge + if(isOnEdge) + { + closed_curve_wn = closure_wn; + for(int i = 1; i < n; ++i) { - Vector e1 = ray.direction().unitVector(); - Vector e2 = Vector {-e1[1], e1[0]}; - - double alpha = Vector(q, c[ord]).dot(e2); - - (alpha < 0) ? crossing_num++ : crossing_num--; + closed_curve_wn += detail::linear_winding_number(q, + approximating_polygon[i - 1], + approximating_polygon[i], + edge_tol); } } - // If not contained in the bounding box, just do a return - return crossing_num - winding_number(q, closure, edge_tol); -} - -/// Compute the number of intersections that a ray makes with an array of Bezier curves -template -int crossing_number(const Point& q, - const axom::Array>& cs, - const BoundingBox& bb, - double edge_tol = 1e-8, - double EPS = 1e-8) -{ - int nevals = 0; - int inter = 0; - - double edge_dist = bb.getMax()[0] - q[0]; - Vector direction {1.0, 0.0}; - - // Uncomment this to get a reasonably fast method - if(bb.getMax()[1] - q[1] < edge_dist) - { - direction = Vector {0.0, 1.0}; - edge_dist = bb.getMax()[1] - q[1]; - } - if(q[0] - bb.getMin()[0] < edge_dist) - { - direction = Vector {-1.0, 0.0}; - edge_dist = q[0] - bb.getMin()[0]; - } - if(q[1] - bb.getMin()[1] < edge_dist) - { - direction = Vector {0.0, -1.0}; - } - - // Get a ray extending in a random direction - Ray ray(q, direction); - for(auto& c : cs) - { - inter += detail::ray_casting_bezier_clipping(c, ray, nevals, edge_tol); - } - - return inter; + return gwn + closed_curve_wn - closure_wn; } /*! @@ -469,113 +318,112 @@ double winding_number(const Point& q, double ret_val = 0.0; for(int i = 0; i < cpoly.numEdges(); i++) { - ret_val += - detail::curve_winding_number_recursive(q, cpoly[i], false, edge_tol, EPS); + ret_val += winding_number(q, cpoly[i], edge_tol, EPS ); } return ret_val; } -template -double winding_number_approxogon_memoized( - Point q, - const axom::Array>>>& array_memos, - axom::Array< - axom::FlatMap, detail::BezierCurveMemo, detail::PairHash>>& - hash_memos, - Polygon& temp_approxogon, - std::stack>& curve_stack, - double edge_tol = 1e-8, - double linear_tol = 1e-8) -{ - double wn = 0; - - for(int ci = 0; ci < array_memos.size(); ++ci) - { - auto& the_curve = array_memos[ci][0][0].curve; - - if(!the_curve.boundingBox().contains(q)) - { - wn += detail::linear_winding_number(q, - the_curve[0], - the_curve[the_curve.getOrder()], - edge_tol); - } - else - { - //temp_approxogon.addVertex(the_curve[0]); - detail::winding_number_adaptive_linear_memoized(q, - array_memos[ci], - hash_memos[ci], - edge_tol, - linear_tol, - temp_approxogon, - curve_stack, - wn); - - temp_approxogon.addVertex(the_curve[the_curve.getOrder()]); - - wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); - - //desmos_print(c); - //desmos_print(q); - //desmos_print(temp_approxogon); - - temp_approxogon.clear(); - //int xx = 0; - } - } - - return wn; -} - -template -double winding_number_approxogon(const Point& q, - const axom::Array>& carray, - Polygon& temp_approxogon, - int& num_evals, - int& max_depth, - double edge_tol = 1e-8, - double linear_tol = 1e-8) -{ - double wn = 0; - - for(int i = 0; i < carray.size(); i++) - { - // Check exterior bounding box - if(!carray[i].boundingBox().contains(q)) - { - wn += detail::linear_winding_number(q, - carray[i][0], - carray[i][carray[i].getOrder()], - edge_tol); - } - else - { - //wn += detail::curve_winding_number_recursive(q, - // carray[i], - // false, - // dummy_val, - // edge_tol); - temp_approxogon.addVertex(carray[i][0]); - detail::winding_number_adaptive_linear(q, - carray[i], - false, - num_evals, - edge_tol, - linear_tol, - temp_approxogon, - wn); - temp_approxogon.addVertex(carray[i][carray[i].getOrder()]); - max_depth = - axom::utilities::max(max_depth, temp_approxogon.numVertices() - 2); - wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); - temp_approxogon.clear(); - } - } - - return wn; -} +// template +// double winding_number_approxogon_memoized( +// Point q, +// const axom::Array>>>& array_memos, +// axom::Array< +// axom::FlatMap, detail::BezierCurveMemo, detail::PairHash>>& +// hash_memos, +// Polygon& temp_approxogon, +// std::stack>& curve_stack, +// double edge_tol = 1e-8, +// double linear_tol = 1e-8) +// { +// double wn = 0; + +// for(int ci = 0; ci < array_memos.size(); ++ci) +// { +// auto& the_curve = array_memos[ci][0][0].curve; + +// if(!the_curve.boundingBox().contains(q)) +// { +// wn += detail::linear_winding_number(q, +// the_curve[0], +// the_curve[the_curve.getOrder()], +// edge_tol); +// } +// else +// { +// //temp_approxogon.addVertex(the_curve[0]); +// detail::winding_number_adaptive_linear_memoized(q, +// array_memos[ci], +// hash_memos[ci], +// edge_tol, +// linear_tol, +// temp_approxogon, +// curve_stack, +// wn); + +// temp_approxogon.addVertex(the_curve[the_curve.getOrder()]); + +// wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); + +// //desmos_print(c); +// //desmos_print(q); +// //desmos_print(temp_approxogon); + +// temp_approxogon.clear(); +// //int xx = 0; +// } +// } + +// return wn; +// } + +// template +// double winding_number_approxogon(const Point& q, +// const axom::Array>& carray, +// Polygon& temp_approxogon, +// int& num_evals, +// int& max_depth, +// double edge_tol = 1e-8, +// double linear_tol = 1e-8) +// { +// double wn = 0; + +// for(int i = 0; i < carray.size(); i++) +// { +// // Check exterior bounding box +// if(!carray[i].boundingBox().contains(q)) +// { +// wn += detail::linear_winding_number(q, +// carray[i][0], +// carray[i][carray[i].getOrder()], +// edge_tol); +// } +// else +// { +// //wn += detail::curve_winding_number_recursive(q, +// // carray[i], +// // false, +// // dummy_val, +// // edge_tol); +// temp_approxogon.addVertex(carray[i][0]); +// detail::winding_number_adaptive_linear(q, +// carray[i], +// false, +// num_evals, +// edge_tol, +// linear_tol, +// temp_approxogon, +// wn); +// temp_approxogon.addVertex(carray[i][carray[i].getOrder()]); +// max_depth = +// axom::utilities::max(max_depth, temp_approxogon.numVertices() - 2); +// wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); +// temp_approxogon.clear(); +// } +// } + +// return wn; +// } template double winding_number(const Point& q, diff --git a/src/axom/primal/tests/CMakeLists.txt b/src/axom/primal/tests/CMakeLists.txt index 05819cd9d4..bf5d2aaa20 100644 --- a/src/axom/primal/tests/CMakeLists.txt +++ b/src/axom/primal/tests/CMakeLists.txt @@ -7,7 +7,6 @@ #------------------------------------------------------------------------------ set( primal_tests - primal_2d_paper_figure_data.cpp primal_bezier_curve.cpp primal_bezier_intersect.cpp primal_bezier_patch.cpp From 54bd98e72658ecbc0796b66ebfd0ece584a37c60 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 13:02:04 -0600 Subject: [PATCH 04/25] Save ray-intersection code for later PR --- .../detail/intersect_bezier_impl.hpp | 88 +-------- .../operators/detail/intersect_patch_impl.hpp | 179 ++++++++++++++++++ 2 files changed, 182 insertions(+), 85 deletions(-) create mode 100644 src/axom/primal/operators/detail/intersect_patch_impl.hpp diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index db0d25fc5f..5808eeb78d 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -69,19 +69,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, double s_offset, double s_scale, double t_offset, - double t_scale, - int &nevals); - -template -bool intersect_ray_bezier(const BezierCurve &c, - const BezierCurve &r, - std::vector &cp, - std::vector &rp, - double sq_tol, - int order, - double c_offset, - double c_scale, - int &nevals); + double t_scale); /*! * \brief Tests intersection of two line segments defined by @@ -131,8 +119,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, double s_offset, double s_scale, double t_offset, - double t_scale, - int &nevals) + double t_scale) { using BCurve = BezierCurve; @@ -162,7 +149,6 @@ bool intersect_bezier_curves(const BezierCurve &c1, BCurve c3(order1); BCurve c4(order1); c1.split(splitVal, c3, c4); - nevals += 1; s_scale *= scaleFac; // Note: we want to find all intersections, so don't short-circuit @@ -191,75 +177,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, t_offset, t_scale, s_offset + s_scale, - s_scale, - nevals)) - { - foundIntersection = true; - } - } - - return foundIntersection; -} - -template -bool intersect_ray_bezier(const BezierCurve &c, - const Ray &r, - std::vector &cp, - std::vector &rp, - double sq_tol, - int order, - double c_offset, - double c_scale, - int &nevals) -{ - using BCurve = BezierCurve; - - // Check bounding boxe to short-circuit the intersection - T r0, s0, c0; - Point ip; - if(!intersect(r, c.boundingBox(), ip)) - { - return false; - } - - bool foundIntersection = false; - //desmos_print(c); - if(c.isLinear(sq_tol)) - { - Segment seg(c[0], c[order]); - - if(intersect(r, seg, r0, s0) && s0 <= 1.0 - 1e-8) - { - rp.push_back(r0); - cp.push_back(c_offset + c_scale * s0); - foundIntersection = true; - } - } - else - { - constexpr double splitVal = 0.5; - constexpr double scaleFac = 0.5; - - BCurve c1(order); - BCurve c2(order); - c.split(splitVal, c1, c2); - nevals += 1; - c_scale *= scaleFac; - - // Note: we want to find all intersections, so don't short-circuit - if(intersect_ray_bezier(c1, r, cp, rp, sq_tol, order, c_offset, c_scale, nevals)) - { - foundIntersection = true; - } - if(intersect_ray_bezier(c2, - r, - cp, - rp, - sq_tol, - order, - c_offset + c_scale, - c_scale, - nevals)) + s_scale)) { foundIntersection = true; } diff --git a/src/axom/primal/operators/detail/intersect_patch_impl.hpp b/src/axom/primal/operators/detail/intersect_patch_impl.hpp new file mode 100644 index 0000000000..3f772c69f1 --- /dev/null +++ b/src/axom/primal/operators/detail/intersect_patch_impl.hpp @@ -0,0 +1,179 @@ +// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \file intersect_bezier_impl.hpp + * + * This file provides helper functions for testing the intersection + * of Bezier curves + */ + +#ifndef AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ +#define AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ + +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Polygon.hpp" +#include "axom/primal/geometry/BoundingBox.hpp" +#include "axom/primal/geometry/BezierPatch.hpp" + +#include "axom/primal/operators/intersect.hpp" +#include "axom/primal/operators/detail/intersect_impl.hpp" +#include "axom/primal/operators/detail/intersect_ray_impl.hpp" + +#include + +namespace axom +{ +namespace primal +{ +namespace detail +{ +//---------------------------- FUNCTION DECLARATIONS --------------------------- + +template +bool intersect_ray_patch(const BezierPatch &p, + const Ray &r, + std::vector &up, + std::vector &vp, + std::vector &rp, + double sq_tol, + int order_u, + int order_v, + double u_offset, + double u_scale, + double v_offset, + double v_scale); + + +//------------------------------ IMPLEMENTATIONS ------------------------------ + +template +bool intersect_ray_patch(const BezierPatch &p, + const Ray &r, + std::vector &up, + std::vector &vp, + std::vector &rp, + double sq_tol, + int order_u, + int order_v, + double u_offset, + double u_scale, + double v_offset, + double v_scale) +{ + using BPatch = BezierPatch; + + // Check bounding box to short-circuit the intersection + T r0, s0, c0; + Point ip; + if(!intersect(r, p.boundingBox(), ip)) + { + return false; + } + + bool foundIntersection = false; + + if(p.isPlanar(sq_tol)) + { + // Add this later + Polygon(axom::Array>( + {p(0, 0), p(order_u, 0), p(order_u, order_v), p(0, order_v)})); + + + if(intersect(r, seg, r0, s0) && s0 <= 1.0 - 1e-8) + { + rp.push_back(r0); + cp.push_back(c_offset + c_scale * s0); + foundIntersection = true; + } + } + else + { + constexpr double splitVal = 0.5; + constexpr double scaleFac = 0.5; + + BPatch p1(order_u, order_v), p2(order_u, order_v), p3(order_u, order_v), + p4(order_u, order_v); + + p.split(splitVal, splitVal, p1, p2, p3, p4); + nevals += 1; + u_scale *= scaleFac; + v_scale *= scaleFac; + + // Note: we want to find all intersections, so don't short-circuit + if(intersect_ray_patch(p1, + r, + up, + vp, + rp, + sq_tol, + order_u, + order_v, + u_offset, + u_scale, + v_offset, + v_scale, + nevals)) + { + foundIntersection = true; + } + if(intersect_ray_patch(p2, + r, + up, + vp, + rp, + sq_tol, + order_u, + order_v, + u_offset + u_scale, + u_scale, + v_offset, + v_scale, + nevals)) + { + foundIntersection = true; + } + if(intersect_ray_patch(p3, + r, + up, + vp, + rp, + sq_tol, + order_u, + order_v, + u_offset, + u_scale, + v_offset + v_scale, + v_scale, + nevals)) + { + foundIntersection = true; + } + if(intersect_ray_patch(p4, + r, + up, + vp, + rp, + sq_tol, + order_u, + order_v, + u_offset + u_scale, + u_scale, + v_offset + v_scale, + v_scale, + nevals)) + { + foundIntersection = true; + } + } + + return foundIntersection; +} + +} // end namespace detail +} // end namespace primal +} // end namespace axom + +#endif // AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ From c106e42165ffde9908cccb89f2bc35a45a57fa2f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 13:20:28 -0600 Subject: [PATCH 05/25] Add better catch for coincident points --- .../operators/detail/winding_number_impl.hpp | 16 +++++++++------- src/axom/primal/operators/winding_number.hpp | 10 ++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index 5bdbc6d3e5..f02e5e4c98 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -140,10 +140,10 @@ double convex_endpoint_winding_number(const Point& q, } Vector V2(q, c[idx]); - std::cout << std::setprecision(16) << std::endl; - std::cout << c << std::endl; - std::cout << V1 << std::endl; - std::cout << V2 << std::endl; + // std::cout << std::setprecision(16); + // std::cout << c << std::endl; + // std::cout << V1 << std::endl; + // std::cout << V2 << std::endl; // clang-format off // Measures the signed area of the triangle spanned by V1 and V2 @@ -432,7 +432,8 @@ void construct_approximating_polygon(const Point& q, double edge_tol, double EPS, Polygon& approximating_polygon, - double& endpoint_gwn) + double& endpoint_gwn, + bool& isCoincident) { const int ord = c.getOrder(); @@ -480,6 +481,7 @@ void construct_approximating_polygon(const Point& q, // and use the direct formula for the endpoint endpoint_gwn += convex_endpoint_winding_number(q, c, edge_tol); + isCoincident = true; // approxogon.addVertex(c[ord]); //desmos_print(c); @@ -492,9 +494,9 @@ void construct_approximating_polygon(const Point& q, c.split(0.5, c1, c2); // clang-format off - construct_approximating_polygon(q, c1, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn); + construct_approximating_polygon(q, c1, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn, isCoincident); approximating_polygon.addVertex(c2[0]); - construct_approximating_polygon(q, c2, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn); + construct_approximating_polygon(q, c2, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn, isCoincident); // clang-format on return; diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 0f55f247aa..6bed314c89 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -258,20 +258,21 @@ double winding_number(const Point& q, // Need to keep a running total of the GWN to account for // the winding number of coincident points double gwn = 0.0; + bool isCoincident = false; detail::construct_approximating_polygon(q, c, false, edge_tol, EPS, approximating_polygon, - gwn); + gwn, isCoincident); // The last vertex of the polygon is the t=1 point of the curve approximating_polygon.addVertex(c[ord]); int n = approximating_polygon.numVertices(); - bool isOnEdge; + bool isOnEdge = false; double closed_curve_wn = winding_number(q, approximating_polygon, isOnEdge, false, PRIMAL_TINY); @@ -280,9 +281,10 @@ double winding_number(const Point& q, approximating_polygon[0], edge_tol); - // If the point is on the edge of the approximating polygon (rare), then winding_number + // If the point is on the boundary of the approximating polygon (rare), + // or coincident with the curve, then winding_number // doesn't return the right half-integer. Have to go edge-by-edge - if(isOnEdge) + if(isCoincident || isOnEdge) { closed_curve_wn = closure_wn; for(int i = 1; i < n; ++i) From 71c31bc3ad8d58f22e14938ca1c846b37e8ae298 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 15:26:58 -0600 Subject: [PATCH 06/25] Remove the rest of the comments --- .../operators/detail/winding_number_impl.hpp | 263 ++---------------- src/axom/primal/operators/winding_number.hpp | 112 +------- 2 files changed, 30 insertions(+), 345 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index f02e5e4c98..e16e886ca5 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -104,7 +104,6 @@ double convex_endpoint_winding_number(const Point& q, const BezierCurve& c, double edge_tol) { - const int ord = c.getOrder(); if(ord == 1) { @@ -140,11 +139,6 @@ double convex_endpoint_winding_number(const Point& q, } Vector V2(q, c[idx]); - // std::cout << std::setprecision(16); - // std::cout << c << std::endl; - // std::cout << V1 << std::endl; - // std::cout << V2 << std::endl; - // clang-format off // Measures the signed area of the triangle spanned by V1 and V2 double tri_area = axom::numerics::determinant(V1[0] - V2[0], V2[0], @@ -184,219 +178,6 @@ double convex_endpoint_winding_number(const Point& q, return 0.5 * M_1_PI * acos(dotprod) * ((tri_area > 0) ? 1 : -1); } -/*! - * \brief Recursively compute the winding number for a query point with respect - * to a single Bezier curve. - * - * \param [in] q The query point at which to compute winding number - * \param [in] c The BezierCurve object along which to compute the winding number - * \param [in] isConvexControlPolygon Boolean flag if the input Bezier curve - is already convex - * \param [in] edge_tol The physical distance level at which objects are - * considered indistinguishable - * \param [in] EPS Miscellaneous numerical tolerance for nonphysical distances, used in - * isLinear, isNearlyZero, in_polygon, is_convex - * - * Use a recursive algorithm that checks if the query point is exterior to - * some convex shape containing the Bezier curve, in which case we have a direct - * formula for the winding number. If not, we bisect our curve and run the algorithm on - * each half. Use the proximity of the query point to endpoints and approximate - * linearity of the Bezier curve as base cases. - * - * \return double The winding number. - */ -template -double curve_winding_number_recursive(const Point& q, - const BezierCurve& c, - bool isConvexControlPolygon, - int& nevals, - double edge_tol = 1e-8) -{ - const int ord = c.getOrder(); - if(ord <= 0) - { - return 0.0; // Catch degenerate cases - } - - // If q is outside a convex shape that contains the entire curve, the winding - // number for the shape connected at the endpoints with straight lines is zero. - // We then subtract the contribution of this line segment. - - // Simplest convex shape containing c is its bounding box - BoundingBox bBox(c.boundingBox()); - if(!bBox.contains(q)) - { - return 0.0 - linear_winding_number(q, c[ord], c[0], edge_tol); - } - - // Use linearity as base case for recursion. - if(c.isLinear(edge_tol)) - { - return linear_winding_number(q, c[0], c[ord], edge_tol); - } - - // Check if our control polygon is convex. - // If so, all subsequent control polygons will be convex as well - Polygon controlPolygon(c.getControlPoints()); - const bool includeBoundary = true; - const bool useNonzeroRule = true; - - if(!isConvexControlPolygon) - { - isConvexControlPolygon = is_convex(controlPolygon, PRIMAL_TINY); - } - else // Formulas for winding number only work if shape is convex - { - // Bezier curves are always contained in their convex control polygon - if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, PRIMAL_TINY)) - { - return 0.0 - linear_winding_number(q, c[ord], c[0], edge_tol); - } - - // If the query point is at either endpoint, use direct formula - if((squared_distance(q, c[0]) <= edge_tol * edge_tol) || - (squared_distance(q, c[ord]) <= edge_tol * edge_tol)) - { - return convex_endpoint_winding_number(q, c, edge_tol); - } - } - - // Recursively split curve until query is outside some known convex region - BezierCurve c1, c2; - c.split(0.5, c1, c2); - nevals += 1; - - return curve_winding_number_recursive(q, - c1, - isConvexControlPolygon, - nevals, - edge_tol) + - curve_winding_number_recursive(q, c2, isConvexControlPolygon, nevals, edge_tol); -} - -// template -// struct BezierCurveMemo -// { - // bool isConvexControlPolygon; - // BezierCurve curve; -// }; - -// struct PairHash -// { - // using result_type = std::size_t; -// - // size_t operator()(const std::pair& p) const - // { - // Combine hashes of the two integers - // size_t hash1 = std::hash()(p.first); - // size_t hash2 = std::hash()(p.second); - // return hash1 ^ (hash2 << 1); // Simple combining function - // } -// }; - -//bool operator==(const std::pair& lhs, const std::pair& rhs) -//{ -// return lhs.first == rhs.first && lhs.second == rhs.second; -//} - -// template -// void winding_number_adaptive_linear_memoized( -// Point q, -// const std::vector>>& array_memo, -// axom::FlatMap, BezierCurveMemo, PairHash>& hash_memo, -// double edge_tol, -// double linear_tol, -// Polygon& approxogon, -// std::stack>& curve_stack, -// double& end_wn) -// { -// constexpr int LEVEL = 3; -// const int ord = array_memo[0][0].curve.getOrder(); - -// //std::stack> curve_stack; -// curve_stack.push(std::make_pair(0, 0)); - -// while(!curve_stack.empty()) -// { -// std::pair the_pair = curve_stack.top(); -// curve_stack.pop(); - -// //std::cout << the_pair.first << std::endl; - -// //std::cout << the_pair.first << " " << the_pair.second << std::endl; -// BezierCurveMemo curve_memo; -// if(the_pair.first < LEVEL) -// curve_memo = array_memo[the_pair.first][the_pair.second]; -// else -// curve_memo = hash_memo[the_pair]; - -// //if(curve_memo.isLinear) -// //{ -// // approxogon.addVertex(curve_memo.curve[0]); -// // continue; -// //} - -// BoundingBox bBox(curve_memo.curve.boundingBox()); -// if(!bBox.contains(q)) -// { -// approxogon.addVertex(curve_memo.curve[0]); -// continue; -// } - -// if(curve_memo.isConvexControlPolygon) -// { -// constexpr bool includeBoundary = true; -// constexpr bool useNonzeroRule = true; - -// if(!in_polygon(q, -// Polygon(curve_memo.curve.getControlPoints()), -// includeBoundary, -// useNonzeroRule, -// PRIMAL_TINY)) -// { -// approxogon.addVertex(curve_memo.curve[0]); - -// continue; -// } - -// if((squared_distance(q, curve_memo.curve[0]) <= edge_tol * edge_tol) || -// (squared_distance(q, curve_memo.curve[ord]) <= edge_tol * edge_tol)) -// { -// end_wn += approxogon_winding_number(q, approxogon, edge_tol); -// approxogon.clear(); - -// end_wn += convex_endpoint_winding_number(q, curve_memo.curve, edge_tol); -// approxogon.addVertex(curve_memo.curve[ord]); -// continue; -// } -// } - -// auto ref1 = std::make_pair(the_pair.first + 1, 2 * the_pair.second); -// auto ref2 = std::make_pair(the_pair.first + 1, 2 * the_pair.second + 1); - -// if(the_pair.first >= LEVEL - 1 && hash_memo.find(ref1) == hash_memo.end()) -// { -// BezierCurve c1, c2; -// curve_memo.curve.split(0.5, c1, c2); - -// hash_memo[ref1] = { -// //c1.isLinear(linear_tol), -// curve_memo.isConvexControlPolygon || -// is_convex(Polygon(c1.getControlPoints()), PRIMAL_TINY), -// c1}; - -// hash_memo[ref2] = { -// //c2.isLinear(linear_tol), -// curve_memo.isConvexControlPolygon || -// is_convex(Polygon(c2.getControlPoints()), PRIMAL_TINY), -// c2}; -// } - -// curve_stack.push(ref2); -// curve_stack.push(ref1); -// } -// } - /*! * \brief Recursively construct a polygon with the same *integer* winding number * as the closed original Bezier curve at a given query point. @@ -427,13 +208,13 @@ double curve_winding_number_recursive(const Point& q, */ template void construct_approximating_polygon(const Point& q, - const BezierCurve& c, - bool isConvexControlPolygon, - double edge_tol, - double EPS, - Polygon& approximating_polygon, - double& endpoint_gwn, - bool& isCoincident) + const BezierCurve& c, + bool isConvexControlPolygon, + double edge_tol, + double EPS, + Polygon& approximating_polygon, + double& endpoint_gwn, + bool& isCoincident) { const int ord = c.getOrder(); @@ -473,18 +254,10 @@ void construct_approximating_polygon(const Point& q, if(squared_distance(q, c[0]) <= edge_tol * edge_tol || squared_distance(q, c[ord]) <= edge_tol * edge_tol) { - // ...we can use a direct formula... - // endpoint_gwn += approxogon_winding_number(q, approximating_polygon, edge_tol); - - // ...but need to reconstru... - // approxogon.clear(); - - // and use the direct formula for the endpoint + // ...we can use a direct formula for the GWN at the endpoint endpoint_gwn += convex_endpoint_winding_number(q, c, edge_tol); isCoincident = true; - // approxogon.addVertex(c[ord]); - //desmos_print(c); return; } } @@ -493,11 +266,23 @@ void construct_approximating_polygon(const Point& q, BezierCurve c1, c2; c.split(0.5, c1, c2); - // clang-format off - construct_approximating_polygon(q, c1, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn, isCoincident); + construct_approximating_polygon(q, + c1, + isConvexControlPolygon, + edge_tol, + EPS, + approximating_polygon, + endpoint_gwn, + isCoincident); approximating_polygon.addVertex(c2[0]); - construct_approximating_polygon(q, c2, isConvexControlPolygon, edge_tol, EPS, approximating_polygon, endpoint_gwn, isCoincident); - // clang-format on + construct_approximating_polygon(q, + c2, + isConvexControlPolygon, + edge_tol, + EPS, + approximating_polygon, + endpoint_gwn, + isCoincident); return; } diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 6bed314c89..5fdec42aaf 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -270,19 +270,20 @@ double winding_number(const Point& q, // The last vertex of the polygon is the t=1 point of the curve approximating_polygon.addVertex(c[ord]); - int n = approximating_polygon.numVertices(); - + // Compute the integer winding numberof the closed curve bool isOnEdge = false; double closed_curve_wn = - winding_number(q, approximating_polygon, isOnEdge, false, PRIMAL_TINY); + winding_number(q, approximating_polygon, isOnEdge, false, edge_tol); + // Compute the fractional value of the closed curve + int n = approximating_polygon.numVertices(); double closure_wn = detail::linear_winding_number(q, approximating_polygon[n - 1], approximating_polygon[0], edge_tol); - // If the point is on the boundary of the approximating polygon (rare), - // or coincident with the curve, then winding_number + // If the point is on the boundary of the approximating polygon, + // or coincident with the curve (rare), then winding_number // doesn't return the right half-integer. Have to go edge-by-edge if(isCoincident || isOnEdge) { @@ -326,107 +327,6 @@ double winding_number(const Point& q, return ret_val; } -// template -// double winding_number_approxogon_memoized( -// Point q, -// const axom::Array>>>& array_memos, -// axom::Array< -// axom::FlatMap, detail::BezierCurveMemo, detail::PairHash>>& -// hash_memos, -// Polygon& temp_approxogon, -// std::stack>& curve_stack, -// double edge_tol = 1e-8, -// double linear_tol = 1e-8) -// { -// double wn = 0; - -// for(int ci = 0; ci < array_memos.size(); ++ci) -// { -// auto& the_curve = array_memos[ci][0][0].curve; - -// if(!the_curve.boundingBox().contains(q)) -// { -// wn += detail::linear_winding_number(q, -// the_curve[0], -// the_curve[the_curve.getOrder()], -// edge_tol); -// } -// else -// { -// //temp_approxogon.addVertex(the_curve[0]); -// detail::winding_number_adaptive_linear_memoized(q, -// array_memos[ci], -// hash_memos[ci], -// edge_tol, -// linear_tol, -// temp_approxogon, -// curve_stack, -// wn); - -// temp_approxogon.addVertex(the_curve[the_curve.getOrder()]); - -// wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); - -// //desmos_print(c); -// //desmos_print(q); -// //desmos_print(temp_approxogon); - -// temp_approxogon.clear(); -// //int xx = 0; -// } -// } - -// return wn; -// } - -// template -// double winding_number_approxogon(const Point& q, -// const axom::Array>& carray, -// Polygon& temp_approxogon, -// int& num_evals, -// int& max_depth, -// double edge_tol = 1e-8, -// double linear_tol = 1e-8) -// { -// double wn = 0; - -// for(int i = 0; i < carray.size(); i++) -// { -// // Check exterior bounding box -// if(!carray[i].boundingBox().contains(q)) -// { -// wn += detail::linear_winding_number(q, -// carray[i][0], -// carray[i][carray[i].getOrder()], -// edge_tol); -// } -// else -// { -// //wn += detail::curve_winding_number_recursive(q, -// // carray[i], -// // false, -// // dummy_val, -// // edge_tol); -// temp_approxogon.addVertex(carray[i][0]); -// detail::winding_number_adaptive_linear(q, -// carray[i], -// false, -// num_evals, -// edge_tol, -// linear_tol, -// temp_approxogon, -// wn); -// temp_approxogon.addVertex(carray[i][carray[i].getOrder()]); -// max_depth = -// axom::utilities::max(max_depth, temp_approxogon.numVertices() - 2); -// wn += detail::approxogon_winding_number(q, temp_approxogon, edge_tol); -// temp_approxogon.clear(); -// } -// } - -// return wn; -// } - template double winding_number(const Point& q, const axom::Array>& carray, From 1f05eb44a0bd6596f8e7767ad88bae9c0dc0af79 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:06:46 -0600 Subject: [PATCH 07/25] Trying to fix a strange difference from develop --- src/axom/primal/operators/clip.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/primal/operators/clip.hpp b/src/axom/primal/operators/clip.hpp index 38b15ea4e1..70067e390d 100644 --- a/src/axom/primal/operators/clip.hpp +++ b/src/axom/primal/operators/clip.hpp @@ -119,7 +119,7 @@ Polygon clip(const Triangle& tri, const BoundingBox& bbox) * * \return A polygon of the subject polygon clipped against the clip polygon. * - * \note Function is based off the Sutherland�Hodgman algorithm. + * \note Function is based off the Sutherland-Hodgman algorithm. * * \warning Polygons with static array types must have enough vertices * preallocated for the output polygon. It is mandatory that From ac1fc828bcd36133f81e2ff879f5784cd09e1aad Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:18:01 -0600 Subject: [PATCH 08/25] Another attempt to fix the commit --- src/axom/primal/operators/clip.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/primal/operators/clip.hpp b/src/axom/primal/operators/clip.hpp index 70067e390d..9e3fd0473b 100644 --- a/src/axom/primal/operators/clip.hpp +++ b/src/axom/primal/operators/clip.hpp @@ -119,7 +119,7 @@ Polygon clip(const Triangle& tri, const BoundingBox& bbox) * * \return A polygon of the subject polygon clipped against the clip polygon. * - * \note Function is based off the Sutherland-Hodgman algorithm. + * \note Function is based off the Sutherland–Hodgman algorithm. * * \warning Polygons with static array types must have enough vertices * preallocated for the output polygon. It is mandatory that From 2bb1bf59e698dcc219ee95112b5f758f316480bb Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:19:43 -0600 Subject: [PATCH 09/25] Remove another file planned for another PR --- .../operators/detail/intersect_patch_impl.hpp | 179 ------------------ 1 file changed, 179 deletions(-) delete mode 100644 src/axom/primal/operators/detail/intersect_patch_impl.hpp diff --git a/src/axom/primal/operators/detail/intersect_patch_impl.hpp b/src/axom/primal/operators/detail/intersect_patch_impl.hpp deleted file mode 100644 index 3f772c69f1..0000000000 --- a/src/axom/primal/operators/detail/intersect_patch_impl.hpp +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) 2017-2023, Lawrence Livermore National Security, LLC and -// other Axom Project Developers. See the top-level LICENSE file for details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -/*! - * \file intersect_bezier_impl.hpp - * - * This file provides helper functions for testing the intersection - * of Bezier curves - */ - -#ifndef AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ -#define AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ - -#include "axom/primal/geometry/Point.hpp" -#include "axom/primal/geometry/Polygon.hpp" -#include "axom/primal/geometry/BoundingBox.hpp" -#include "axom/primal/geometry/BezierPatch.hpp" - -#include "axom/primal/operators/intersect.hpp" -#include "axom/primal/operators/detail/intersect_impl.hpp" -#include "axom/primal/operators/detail/intersect_ray_impl.hpp" - -#include - -namespace axom -{ -namespace primal -{ -namespace detail -{ -//---------------------------- FUNCTION DECLARATIONS --------------------------- - -template -bool intersect_ray_patch(const BezierPatch &p, - const Ray &r, - std::vector &up, - std::vector &vp, - std::vector &rp, - double sq_tol, - int order_u, - int order_v, - double u_offset, - double u_scale, - double v_offset, - double v_scale); - - -//------------------------------ IMPLEMENTATIONS ------------------------------ - -template -bool intersect_ray_patch(const BezierPatch &p, - const Ray &r, - std::vector &up, - std::vector &vp, - std::vector &rp, - double sq_tol, - int order_u, - int order_v, - double u_offset, - double u_scale, - double v_offset, - double v_scale) -{ - using BPatch = BezierPatch; - - // Check bounding box to short-circuit the intersection - T r0, s0, c0; - Point ip; - if(!intersect(r, p.boundingBox(), ip)) - { - return false; - } - - bool foundIntersection = false; - - if(p.isPlanar(sq_tol)) - { - // Add this later - Polygon(axom::Array>( - {p(0, 0), p(order_u, 0), p(order_u, order_v), p(0, order_v)})); - - - if(intersect(r, seg, r0, s0) && s0 <= 1.0 - 1e-8) - { - rp.push_back(r0); - cp.push_back(c_offset + c_scale * s0); - foundIntersection = true; - } - } - else - { - constexpr double splitVal = 0.5; - constexpr double scaleFac = 0.5; - - BPatch p1(order_u, order_v), p2(order_u, order_v), p3(order_u, order_v), - p4(order_u, order_v); - - p.split(splitVal, splitVal, p1, p2, p3, p4); - nevals += 1; - u_scale *= scaleFac; - v_scale *= scaleFac; - - // Note: we want to find all intersections, so don't short-circuit - if(intersect_ray_patch(p1, - r, - up, - vp, - rp, - sq_tol, - order_u, - order_v, - u_offset, - u_scale, - v_offset, - v_scale, - nevals)) - { - foundIntersection = true; - } - if(intersect_ray_patch(p2, - r, - up, - vp, - rp, - sq_tol, - order_u, - order_v, - u_offset + u_scale, - u_scale, - v_offset, - v_scale, - nevals)) - { - foundIntersection = true; - } - if(intersect_ray_patch(p3, - r, - up, - vp, - rp, - sq_tol, - order_u, - order_v, - u_offset, - u_scale, - v_offset + v_scale, - v_scale, - nevals)) - { - foundIntersection = true; - } - if(intersect_ray_patch(p4, - r, - up, - vp, - rp, - sq_tol, - order_u, - order_v, - u_offset + u_scale, - u_scale, - v_offset + v_scale, - v_scale, - nevals)) - { - foundIntersection = true; - } - } - - return foundIntersection; -} - -} // end namespace detail -} // end namespace primal -} // end namespace axom - -#endif // AXOM_PRIMAL_INTERSECT_PATCH_IMPL_HPP_ From 36073f81ae4328a273eb7e732ac37bfe9a413cfb Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:25:02 -0600 Subject: [PATCH 10/25] Undo some tweaking with tolerances --- .../operators/detail/winding_number_impl.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index e16e886ca5..5547aff858 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -19,9 +19,6 @@ // C++ includes #include -#include -#include -#include // MFEM includes #ifdef AXOM_USE_MFEM @@ -102,7 +99,8 @@ double linear_winding_number(const Point& q, template double convex_endpoint_winding_number(const Point& q, const BezierCurve& c, - double edge_tol) + double edge_tol, + double EPS) { const int ord = c.getOrder(); if(ord == 1) @@ -113,7 +111,7 @@ double convex_endpoint_winding_number(const Point& q, double edge_tol_sq = edge_tol * edge_tol; // Verify that the shape is convex, and that the query point is at an endpoint - SLIC_ASSERT(is_convex(Polygon(c.getControlPoints()), PRIMAL_TINY)); + SLIC_ASSERT(is_convex(Polygon(c.getControlPoints()), EPS)); SLIC_ASSERT((squared_distance(q, c[0]) <= edge_tol_sq) || (squared_distance(q, c[ord]) <= edge_tol_sq)); @@ -147,7 +145,7 @@ double convex_endpoint_winding_number(const Point& q, // This means the bounding vectors are anti-parallel. // Parallel tangents can't happen with nontrivial convex control polygons - if((ord > 3) && axom::utilities::isNearlyEqual(tri_area, 0.0, PRIMAL_TINY)) + if((ord > 3) && axom::utilities::isNearlyEqual(tri_area, 0.0, EPS)) { for(int i = 1; i < ord; ++i) { @@ -160,7 +158,7 @@ double convex_endpoint_winding_number(const Point& q, // clang-format on // Because we are convex, a single non-collinear vertex tells us the orientation - if(!axom::utilities::isNearlyEqual(tri_area, 0.0, PRIMAL_TINY)) + if(!axom::utilities::isNearlyEqual(tri_area, 0.0, EPS)) { return (tri_area > 0) ? 0.5 : -0.5; } @@ -255,7 +253,7 @@ void construct_approximating_polygon(const Point& q, squared_distance(q, c[ord]) <= edge_tol * edge_tol) { // ...we can use a direct formula for the GWN at the endpoint - endpoint_gwn += convex_endpoint_winding_number(q, c, edge_tol); + endpoint_gwn += convex_endpoint_winding_number(q, c, edge_tol, EPS); isCoincident = true; return; From b1c1e9bcd06aa45771c7ae56323b21f91d6a17b6 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:26:40 -0600 Subject: [PATCH 11/25] Remove extra stuff from a future commit --- src/axom/primal/operators/intersect.hpp | 54 ------------------------- 1 file changed, 54 deletions(-) diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 9d1cad446d..539e9bfaff 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -536,60 +536,6 @@ bool intersect(const BezierCurve& c1, nevals); } -/*! - * \brief Tests if Bezier Curve \a c and ray \a r intersect. - * \return status true iff \a c intersects \a r, otherwise false. - * - * \param [in] c the BezierCurve, parametrized in [0,1) - * \param [in] r the Ray, parametrized in [0,inf) - * \param [out] cp vector of parameter space intersection points for \a c - * \param [out] rp vector of parameter space intersection points for \a r - * \param [in] tol tolerance parameter for determining if a curve can - * be approximated by a line segment. - * \return True if the curve and line intersect, false otherwise. Intersection - * parameters are stored in \a sp and \a tp - * - * Finds all intersection points between the curve and the ray. - * - * \note This function assumes two dimensional curves and rays in a plane. - * - * \note This function assumes that the curves are in general position. - * Specifically, we assume that all intersections are at points and that - * the curves don't overlap. - * - * \note This function assumes the all intersections have multiplicity - * one, i.e. there are no points at which the curves and their derivatives - * both intersect. Thus, the function does not find tangencies. - * - * \note This function assumes that the curves are half-open, i.e. they - * contain their first endpoint, but not their last endpoint. Thus, the - * curves do not intersect at \f$ s==1 \f$ or at \f$ t==1 \f$. - */ -template -bool intersect(const BezierCurve& c, - const Ray& r, - std::vector& cp, - std::vector& rp, - int& nevals, - double tol = 1E-8) -{ - const double offset = 0.; - const double scale = 1.; - - // for efficiency, linearity check actually uses a squared tolerance - const double sq_tol = tol * tol; - - return detail::intersect_ray_bezier(c, - r, - cp, - rp, - sq_tol, - c.getOrder(), - offset, - scale, - nevals); -} - /// @} /// \name Plane Intersection Routines From 3d0848a0587bf80d210f32f7d0b7969de1830793 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:28:58 -0600 Subject: [PATCH 12/25] Revert to original --- src/axom/primal/operators/detail/intersect_bezier_impl.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 5808eeb78d..a87750c077 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -19,7 +19,6 @@ #include "axom/primal/operators/intersect.hpp" #include "axom/primal/operators/detail/intersect_impl.hpp" -#include "axom/primal/operators/detail/intersect_ray_impl.hpp" #include @@ -149,6 +148,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, BCurve c3(order1); BCurve c4(order1); c1.split(splitVal, c3, c4); + s_scale *= scaleFac; // Note: we want to find all intersections, so don't short-circuit @@ -162,8 +162,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, t_offset, t_scale, s_offset, - s_scale, - nevals)) + s_scale)) { foundIntersection = true; } From aea5a297c26d60a332b2e6f588c04d8e10e5fe1d Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:28:58 -0600 Subject: [PATCH 13/25] Revert to original --- src/axom/primal/operators/detail/intersect_bezier_impl.hpp | 5 ++--- src/axom/primal/operators/intersect.hpp | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 5808eeb78d..a87750c077 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -19,7 +19,6 @@ #include "axom/primal/operators/intersect.hpp" #include "axom/primal/operators/detail/intersect_impl.hpp" -#include "axom/primal/operators/detail/intersect_ray_impl.hpp" #include @@ -149,6 +148,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, BCurve c3(order1); BCurve c4(order1); c1.split(splitVal, c3, c4); + s_scale *= scaleFac; // Note: we want to find all intersections, so don't short-circuit @@ -162,8 +162,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, t_offset, t_scale, s_offset, - s_scale, - nevals)) + s_scale)) { foundIntersection = true; } diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 539e9bfaff..d7f5ef96e6 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -513,7 +513,6 @@ bool intersect(const BezierCurve& c1, const BezierCurve& c2, std::vector& sp, std::vector& tp, - int& nevals, double tol = 1E-8) { const double offset = 0.; @@ -532,8 +531,7 @@ bool intersect(const BezierCurve& c1, offset, scale, offset, - scale, - nevals); + scale); } /// @} From 6c8013397bd0b5d959c07cb4e51cb43dad4e52f9 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 6 Sep 2024 16:30:57 -0600 Subject: [PATCH 14/25] Apply formatting --- src/axom/primal/operators/winding_number.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 5fdec42aaf..f7acc0f7cb 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -265,7 +265,8 @@ double winding_number(const Point& q, edge_tol, EPS, approximating_polygon, - gwn, isCoincident); + gwn, + isCoincident); // The last vertex of the polygon is the t=1 point of the curve approximating_polygon.addVertex(c[ord]); @@ -274,7 +275,7 @@ double winding_number(const Point& q, bool isOnEdge = false; double closed_curve_wn = winding_number(q, approximating_polygon, isOnEdge, false, edge_tol); - + // Compute the fractional value of the closed curve int n = approximating_polygon.numVertices(); double closure_wn = detail::linear_winding_number(q, @@ -282,7 +283,7 @@ double winding_number(const Point& q, approximating_polygon[0], edge_tol); - // If the point is on the boundary of the approximating polygon, + // If the point is on the boundary of the approximating polygon, // or coincident with the curve (rare), then winding_number // doesn't return the right half-integer. Have to go edge-by-edge if(isCoincident || isOnEdge) @@ -290,10 +291,11 @@ double winding_number(const Point& q, closed_curve_wn = closure_wn; for(int i = 1; i < n; ++i) { - closed_curve_wn += detail::linear_winding_number(q, - approximating_polygon[i - 1], - approximating_polygon[i], - edge_tol); + closed_curve_wn += + detail::linear_winding_number(q, + approximating_polygon[i - 1], + approximating_polygon[i], + edge_tol); } } @@ -321,7 +323,7 @@ double winding_number(const Point& q, double ret_val = 0.0; for(int i = 0; i < cpoly.numEdges(); i++) { - ret_val += winding_number(q, cpoly[i], edge_tol, EPS ); + ret_val += winding_number(q, cpoly[i], edge_tol, EPS); } return ret_val; From 5f9999aa5dcd831c5dfe95d36a7efb8cf900ed6c Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Sun, 8 Sep 2024 14:37:34 -0600 Subject: [PATCH 15/25] Remove reference to old interface --- src/axom/primal/operators/winding_number.hpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index f7acc0f7cb..fcbc90912f 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -339,11 +339,7 @@ double winding_number(const Point& q, int dummy_val = 0; for(int i = 0; i < carray.size(); i++) { - ret_val += detail::curve_winding_number_recursive(q, - carray[i], - false, - dummy_val, - edge_tol); + ret_val += winding_number(q, carray[i], false, dummy_val, edge_tol); } return ret_val; From 70ef788b8db18d86d7b2a7689392da3a4d9a74d5 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Sun, 8 Sep 2024 14:59:52 -0600 Subject: [PATCH 16/25] Add early return for far-away points --- src/axom/primal/operators/winding_number.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index fcbc90912f..b3de9f37a1 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -251,6 +251,12 @@ double winding_number(const Point& q, const int ord = c.getOrder(); if(ord <= 0) return 0.0; + // Early return is possible for must points + curves + if(!c.boundingBox().contains(q)) + { + return 0.0 - detail::linear_winding_number(q, c[0], c[ord], edge_tol); + } + // The first vertex of the polygon is the t=0 point of the curve Polygon approximating_polygon(1); approximating_polygon.addVertex(c[0]); @@ -336,10 +342,9 @@ double winding_number(const Point& q, double EPS = 1e-8) { double ret_val = 0.0; - int dummy_val = 0; for(int i = 0; i < carray.size(); i++) { - ret_val += winding_number(q, carray[i], false, dummy_val, edge_tol); + ret_val += winding_number(q, carray[i], false, edge_tol); } return ret_val; From d89f6a16c7ded2f227cd194455f4e0289c2ffdce Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Sun, 8 Sep 2024 15:23:06 -0600 Subject: [PATCH 17/25] Remove some unneeded changes --- src/axom/primal/operators/detail/winding_number_impl.hpp | 5 ++--- src/axom/primal/operators/winding_number.hpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index 5547aff858..895e0cd5b2 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -15,7 +15,6 @@ #include "axom/primal/operators/in_polygon.hpp" #include "axom/primal/operators/is_convex.hpp" #include "axom/primal/operators/squared_distance.hpp" -#include "axom/primal/operators/intersect.hpp" // C++ includes #include @@ -236,14 +235,14 @@ void construct_approximating_polygon(const Point& q, if(!isConvexControlPolygon) { - isConvexControlPolygon = is_convex(controlPolygon, PRIMAL_TINY); + isConvexControlPolygon = is_convex(controlPolygon, EPS); } // Formulas for winding number only work if shape is convex if(isConvexControlPolygon) { // Bezier curves are always contained in their convex control polygon - if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, PRIMAL_TINY)) + if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, EPS)) { return; } diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index b3de9f37a1..86a30a6699 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -254,7 +254,7 @@ double winding_number(const Point& q, // Early return is possible for must points + curves if(!c.boundingBox().contains(q)) { - return 0.0 - detail::linear_winding_number(q, c[0], c[ord], edge_tol); + return detail::linear_winding_number(q, c[0], c[ord], edge_tol); } // The first vertex of the polygon is the t=0 point of the curve From c42dcbbf4fb0383ab7c90ec8744e548393d05816 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Mon, 9 Sep 2024 14:14:58 -0600 Subject: [PATCH 18/25] Update comments --- src/axom/primal/operators/detail/winding_number_impl.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index 895e0cd5b2..8f64b07655 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -31,7 +31,7 @@ namespace primal namespace detail { /* - * \brief Compute the winding number with respect to a line segment + * \brief Compute the generalized winding number with respect to a line segment * * \param [in] q The query point to test * \param [in] c0 The initial point of the line segment @@ -42,7 +42,7 @@ namespace detail * is the signed angle subtended by the query point to each endpoint. * Colinear points return 0 for their winding number. * - * \return double The winding number + * \return double The GWN */ template double linear_winding_number(const Point& q, From d3dfa4dcb89d9b10fb2ca313cfb4dfeb1b7840b6 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Wed, 11 Sep 2024 17:10:49 -0600 Subject: [PATCH 19/25] Fix polygon GWN methods --- src/axom/primal/operators/winding_number.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 86a30a6699..8aa0f36a23 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -111,8 +111,8 @@ template int winding_number(const Point& R, const Polygon& P, bool& isOnEdge, - bool includeBoundary = false, - double EPS = 1e-8) + bool includeBoundary, + double EPS) { const int nverts = P.numVertices(); isOnEdge = false; @@ -123,7 +123,7 @@ int winding_number(const Point& R, axom::utilities::isNearlyEqual(P[0][1], R[1], EPS)) { isOnEdge = true; - return includeBoundary; + return includeBoundary ? 1 : 0; // On vertex } int winding_num = 0; @@ -136,12 +136,12 @@ int winding_number(const Point& R, if(axom::utilities::isNearlyEqual(P[j][0], R[0], EPS)) { isOnEdge = true; - return includeBoundary; // On vertex + return includeBoundary ? 1 : 0; // On vertex } else if(P[i][1] == R[1] && ((P[j][0] > R[0]) == (P[i][0] < R[0]))) { isOnEdge = true; - return includeBoundary; // On horizontal edge + return includeBoundary ? 1 : 0; // On horizontal edge } } @@ -165,7 +165,7 @@ int winding_number(const Point& R, if(axom::utilities::isNearlyEqual(det, 0.0, EPS)) { isOnEdge = true; - return includeBoundary; + return includeBoundary ? 1 : 0; // On horizontal edge } // Check if edge intersects horitonal ray to the right of R @@ -188,7 +188,7 @@ int winding_number(const Point& R, if(axom::utilities::isNearlyEqual(det, 0.0, EPS)) { isOnEdge = true; - return includeBoundary; + return includeBoundary ? 1 : 0; // On horizontal edge } // Check if edge intersects horitonal ray to the right of R From 792d14107d35c0d6dceae075302f7cb3e6c64a01 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 12 Sep 2024 16:53:20 -0600 Subject: [PATCH 20/25] Fix comments to be more descriptive --- .../operators/detail/winding_number_impl.hpp | 31 +++-- src/axom/primal/operators/winding_number.hpp | 109 ++++++++++++------ 2 files changed, 93 insertions(+), 47 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index 8f64b07655..bd659544cc 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -31,18 +31,18 @@ namespace primal namespace detail { /* - * \brief Compute the generalized winding number with respect to a line segment + * \brief Compute the GWN at a 2D point wrt a 2D line segment * * \param [in] q The query point to test * \param [in] c0 The initial point of the line segment * \param [in] c1 The terminal point of the line segment * \param [in] edge_tol The tolerance at which a point is on the line * - * The winding number for a point with respect to a straight line + * The GWN for a 2D point with respect to a 2D straight line * is the signed angle subtended by the query point to each endpoint. - * Colinear points return 0 for their winding number. + * Colinear points return 0 for their GWN. * - * \return double The GWN + * \return The GWN */ template double linear_winding_number(const Point& q, @@ -75,8 +75,8 @@ double linear_winding_number(const Point& q, } /*! - * \brief Directly compute the winding number at either endpoint of a - * Bezier curve with a convex control polygon + * \brief Compute the GWN at either endpoint of a + * 2D Bezier curve with a convex control polygon * * \param [in] q The query point * \param [in] c The BezierCurve object to compute the winding number along @@ -86,14 +86,19 @@ double linear_winding_number(const Point& q, * \pre Control polygon for c must be convex * \pre The query point must be on one of the endpoints * - * The winding number for a Bezier curve with a convex control polygon is + * The GWN for a Bezier curve with a convex control polygon is * given by the signed angle between the tangent vector at that endpoint and * the vector in the direction of the other endpoint. * + * See Algorithm 2 in + * Jacob Spainhour, David Gunderman, and Kenneth Weiss. 2024. + * Robust Containment Queries over Collections of Rational Parametric Curves via Generalized Winding Numbers. + * ACM Trans. Graph. 43, 4, Article 38 (July 2024) + * * The query can be located on both endpoints if it is closed, in which case * the angle is that between the tangent lines at both endpoints * - * \return double The winding number + * \return The GWN */ template double convex_endpoint_winding_number(const Point& q, @@ -295,7 +300,8 @@ enum class SingularityAxis #ifdef AXOM_USE_MFEM /*! - * \brief Evaluates an "anti-curl" of the winding number along a curve + * \brief Evaluates the integral of the "anti-curl" of the GWN integrand + * (via Stokes' theorem) at a point wrt to a 3D Bezier curve * * \param [in] query The query point to test * \param [in] curve The BezierCurve object @@ -311,7 +317,7 @@ enum class SingularityAxis * \note This is only meant to be used for `winding_number()`, * and the result does not make sense outside of that context. * - * \return double One component of the winding number + * \return The value of the integral */ template double stokes_winding_number(const Point& query, @@ -400,7 +406,8 @@ double stokes_winding_number(const Point& query, #ifdef AXOM_USE_MFEM /*! - * \brief Recursively evaluates an "anti-curl" of the winding number on subcurves + * \brief Accurately evaluates the integral of the "anti-curl" of the GWN integrand + * (via Stokes' theorem) at a point wrt to a 3D Bezier curve via recursion * * \param [in] query The query point to test * \param [in] curve The BezierCurve object @@ -417,7 +424,7 @@ double stokes_winding_number(const Point& query, * \note This is only meant to be used for `winding_number()`, * and the result does not make sense outside of that context. * - * \return double One component of the winding number + * \return The value of the integral */ template double stokes_winding_number_adaptive(const Point& query, diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 8aa0f36a23..79bf5335a3 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -6,8 +6,8 @@ /*! * \file winding_number.hpp * - * \brief Consists of methods to compute winding numbers for points - * with respect to various geometric objects. + * \brief Consists of methods to compute the generalized winding number (GWN) + * for points with respect to various geometric objects. */ #ifndef AXOM_PRIMAL_WINDING_NUMBER_HPP_ @@ -46,13 +46,13 @@ namespace primal //! @name Winding number operations between 2D points and primitives /* - * \brief Compute the winding number with respect to a 2D line segment + * \brief Compute the GWN for a 2D point wrt a 2D line segment * * \param [in] q The query point to test * \param [in] s The line segment * \param [in] edge_tol The tolerance at which a point is on the line * - * \return double The generalized winding number + * \return The GWN */ template double winding_number(const Point& q, @@ -63,7 +63,7 @@ double winding_number(const Point& q, } /* - * \brief Compute the winding number with respect to a 2D triangle + * \brief Compute the winding number for a 2D point wrt a 2D triangle * * \param [in] q The query point to test * \param [in] tri The triangle @@ -72,7 +72,7 @@ double winding_number(const Point& q, * * The triangle is assumed to be closed, so the winding number is an integer * - * \return int The integer winding number + * \return The integer winding number */ template int winding_number(const Point& q, @@ -88,11 +88,12 @@ int winding_number(const Point& q, } /*! - * \brief Computes the winding number for a point and a 2D polygon + * \brief Computes the winding number for a 2D point wrt a 2D polygon * * \param [in] R The query point to test * \param [in] P The Polygon object to test for containment - * \param [in] includeBoundary If true, points on the boundary are considered interior. + * \param [in] includeBoundary If true, points on the boundary are considered interior + * \param [in] isOnEdge An optional return parameter if the point is on the boundary * \param [in] EPS The tolerance level for collinearity * * Uses an adapted ray-casting approach that counts quarter-rotation @@ -205,9 +206,17 @@ int winding_number(const Point& R, } /*! - * \brief Computes the solid angle winding number for a 2D polygon + * \brief Computes the winding number for a 2D point wrt a 2D polygon * - * Overload function without additional returning parameter + * \param [in] R The query point to test + * \param [in] P The Polygon object to test for containment + * \param [in] includeBoundary If true, points on the boundary are considered interior + * \param [in] EPS The tolerance level for collinearity + * + * Computes the integer winding number for a polygon without an additional + * return parameter for whether the point is on the boundary. + * + * \return The integer winding number */ template int winding_number(const Point& R, @@ -220,7 +229,7 @@ int winding_number(const Point& R, } /*! - * \brief Computes the generalized winding number (GWN) for a single 2D Bezier curve + * \brief Computes the GWN for a 2D point wrt a 2D Bezier curve * * \param [in] query The query point to test * \param [in] c The Bezier curve object @@ -240,7 +249,7 @@ int winding_number(const Point& R, * Robust Containment Queries over Collections of Rational Parametric Curves via Generalized Winding Numbers. * ACM Trans. Graph. 43, 4, Article 38 (July 2024) * - * \return double the generalized winding number. + * \return The GWN. */ template double winding_number(const Point& q, @@ -277,14 +286,14 @@ double winding_number(const Point& q, // The last vertex of the polygon is the t=1 point of the curve approximating_polygon.addVertex(c[ord]); - // Compute the integer winding numberof the closed curve + // Compute the integer winding number of the closed curve bool isOnEdge = false; double closed_curve_wn = winding_number(q, approximating_polygon, isOnEdge, false, edge_tol); // Compute the fractional value of the closed curve - int n = approximating_polygon.numVertices(); - double closure_wn = detail::linear_winding_number(q, + const int n = approximating_polygon.numVertices(); + const double closure_wn = detail::linear_winding_number(q, approximating_polygon[n - 1], approximating_polygon[0], edge_tol); @@ -309,16 +318,16 @@ double winding_number(const Point& q, } /*! - * \brief Computes the generalized winding number for a 2D curved polygon + * \brief Computes the GWN for a 2D point wrt to a 2D curved polygon * * \param [in] query The query point to test * \param [in] cpoly The CurvedPolygon object * \param [in] edge_tol The physical distance level at which objects are considered indistinguishable * \param [in] EPS Miscellaneous numerical tolerance level for nonphysical distances * - * Computes the winding number by summing the winding number for each curve + * Computes the GWN for the curved polygon by summing the GWN for each curved edge * - * \return double the generalized winding number. + * \return The GWN. */ template double winding_number(const Point& q, @@ -335,6 +344,18 @@ double winding_number(const Point& q, return ret_val; } +/*! + * \brief Computes the GWN for a 2D point wrt to a collection of 2D Bezier curves + * + * \param [in] query The query point to test + * \param [in] cpoly The CurvedPolygon object + * \param [in] edge_tol The physical distance level at which objects are considered indistinguishable + * \param [in] EPS Miscellaneous numerical tolerance level for nonphysical distances + * + * Sums the GWN at `query` for each curved edge + * + * \return The GWN. + */ template double winding_number(const Point& q, const axom::Array>& carray, @@ -355,7 +376,7 @@ double winding_number(const Point& q, //! @name Winding number operations between 3D points and primitives /*! - * \brief Computes the solid angle winding number for a 3D triangle + * \brief Computes the GWN for a 3D point wrt a 3D triangle * * \param [in] query The query point to test * \param [in] tri The 3D Triangle object @@ -363,12 +384,12 @@ double winding_number(const Point& q, * \param [in] edge_tol The physical distance level at which objects are considered indistinguishable * \param [in] EPS Miscellaneous numerical tolerance level for nonphysical distances * - * Computes the winding number using the formula from + * Computes the GWN as the solid angle modulo 4pi using the formula from * Oosterom, Strackee, "The Solid Angle of a Plane Triangle" * IEEE Transactions on Biomedical Engineering, Vol BME-30, No. 2, February 1983 * with extra adjustments if the triangle takes up a full octant * - * \return double the generalized winding number. + * \return The GWN. */ template double winding_number(const Point& q, @@ -405,7 +426,7 @@ double winding_number(const Point& q, return 0; } - const double denom = a_norm * b_norm * c_norm // + const double denom = a_norm * b_norm * c_norm + a_norm * b.dot(c) + b_norm * a.dot(c) + c_norm * a.dot(b); // Handle direct cases where argument to atan is undefined @@ -427,9 +448,16 @@ double winding_number(const Point& q, } /*! - * \brief Computes the solid angle winding number for a 3D triangle + * \brief Computes the GWN for a 3D point wrt a 3D triangle * - * Overload function without additional returning parameter + * \param [in] query The query point to test + * \param [in] tri The 3D Triangle object + * \param [in] edge_tol The physical distance level at which objects are considered indistinguishable + * \param [in] EPS Miscellaneous numerical tolerance level for nonphysical distances + * + * Computes the GWN for the triangle without an additional return parameter + * + * \return The GWN. */ template double winding_number(const Point& q, @@ -442,7 +470,7 @@ double winding_number(const Point& q, } /*! - * \brief Computes the solid angle winding number for a 3D planar polygon + * \brief Computes the GWN for a 3D point wrt a 3D planar polygon * * \param [in] query The query point to test * \param [in] poly The Polygon object @@ -453,9 +481,9 @@ double winding_number(const Point& q, * * \pre Assumes the polygon is planar. Otherwise, a meaningless value is returned. * - * Triangulates the polygon and computes the triangular solid angle for each part + * Triangulates the polygon and computes the triangular GWN for each component * - * \return double the generalized winding number. + * \return The GWN. */ template double winding_number(const Point& q, @@ -484,9 +512,19 @@ double winding_number(const Point& q, } /*! - * \brief Computes the solid angle winding number for a 3D planar polygon + * \brief Computes the GWN for a 3D point wrt a 3D planar polygon * - * Overload function without additional returning parameter + * \param [in] query The query point to test + * \param [in] poly The Polygon object + * \param [in] edge_tol The physical distance level at which objects are + * considered indistinguishable + * \param [in] EPS Miscellaneous numerical tolerance level for nonphysical distances + * + * \pre Assumes the polygon is planar. Otherwise, a meaningless value is returned. + * + * Computes the GWN for the polygon without an additional return parameter + * + * \return The GWN. */ template double winding_number(const Point& q, @@ -499,7 +537,7 @@ double winding_number(const Point& q, } /*! - * \brief Computes the solid angle winding number for a 3D convex polyhedron + * \brief Computes the winding number for a 3D point wrt a 3D convex polyhedron * * \param [in] query The query point to test * \param [in] poly The Polyhedron object @@ -510,9 +548,10 @@ double winding_number(const Point& q, * * \pre Expects the polyhedron to be convex and closed so that the returned value is an integer. * - * Computes the faces of the polyhedron and computes the winding number for each. - * - * \return int The integer winding number. + * Computes the faces of the polyhedron and computes the GWN for each. + * The sum is then rounded to the nearest integer, as the shape is assumed to be closed. + * + * \return The integer winding number. */ template int winding_number(const Point& query, @@ -556,7 +595,7 @@ int winding_number(const Point& query, #ifdef AXOM_USE_MFEM /* - * \brief Computes the solid angle winding number for a Bezier patch + * \brief Computes the GWN for a 3D point wrt a 3D Bezier patch * * \param [in] query The query point to test * \param [in] bPatch The Bezier patch object @@ -571,7 +610,7 @@ int winding_number(const Point& query, * \note Warning: This algorithm is only tested to high accuracy for queries within * 1e-5 of the surface. Otherwise, it will return less accurate results. * - * \return double The generalized winding number. + * \return The GWN. */ template double winding_number(const Point& query, From 4bef52aacde4fd277e9288be2997aae502ab5f6f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Thu, 12 Sep 2024 22:30:49 -0600 Subject: [PATCH 21/25] Apply formatting --- src/axom/primal/operators/winding_number.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 79bf5335a3..e2fd0510e8 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -293,10 +293,11 @@ double winding_number(const Point& q, // Compute the fractional value of the closed curve const int n = approximating_polygon.numVertices(); - const double closure_wn = detail::linear_winding_number(q, - approximating_polygon[n - 1], - approximating_polygon[0], - edge_tol); + const double closure_wn = + detail::linear_winding_number(q, + approximating_polygon[n - 1], + approximating_polygon[0], + edge_tol); // If the point is on the boundary of the approximating polygon, // or coincident with the curve (rare), then winding_number @@ -426,8 +427,8 @@ double winding_number(const Point& q, return 0; } - const double denom = a_norm * b_norm * c_norm - + a_norm * b.dot(c) + b_norm * a.dot(c) + c_norm * a.dot(b); + const double denom = a_norm * b_norm * c_norm + a_norm * b.dot(c) + + b_norm * a.dot(c) + c_norm * a.dot(b); // Handle direct cases where argument to atan is undefined if(axom::utilities::isNearlyEqual(denom, 0.0, EPS)) From 74c8c0cb79a1101c99703713a661c9845f6c2061 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 13 Sep 2024 11:37:28 -0600 Subject: [PATCH 22/25] Fix bug and add a test for it --- src/axom/primal/operators/winding_number.hpp | 6 ++++-- src/axom/primal/tests/primal_winding_number.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index e2fd0510e8..96988696a0 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -301,8 +301,10 @@ double winding_number(const Point& q, // If the point is on the boundary of the approximating polygon, // or coincident with the curve (rare), then winding_number - // doesn't return the right half-integer. Have to go edge-by-edge - if(isCoincident || isOnEdge) + // doesn't return the right half-integer. Have to go edge-by-edge. + // Do an extra check for the result of linear_winding_number to + // account for differing tolerances between the two methods. + if(isCoincident || isOnEdge || axom::utilities::isNearlyEqual(closure_wn, 0.0, EPS)) { closed_curve_wn = closure_wn; for(int i = 1; i < n; ++i) diff --git a/src/axom/primal/tests/primal_winding_number.cpp b/src/axom/primal/tests/primal_winding_number.cpp index d2eaa66798..2665294e68 100644 --- a/src/axom/primal/tests/primal_winding_number.cpp +++ b/src/axom/primal/tests/primal_winding_number.cpp @@ -166,6 +166,12 @@ TEST(primal_winding_number, closure_edge_cases) 0.0, abs_tol); + // Tests a potential issue where the query point is treated as being + // on the closure, but not on the edge of the approximating polygon. + EXPECT_NEAR(winding_number(Point2D({0, 1e-8}), quartic), + 0.500000031830989, + abs_tol); + // Flip the curve vertically quartic[2] = Point2D({0.0, -1.0}); EXPECT_NEAR(winding_number(Point2D({0, 0}), quartic, edge_tol, EPS), From 265b075e64aba3bb6962f4c907d2c8b8aaba7435 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 13 Sep 2024 12:25:56 -0600 Subject: [PATCH 23/25] Replace "EPS" tolerance in `winding_number` to consider distance --- .../operators/detail/winding_number_impl.hpp | 4 +- src/axom/primal/operators/in_polygon.hpp | 8 +-- src/axom/primal/operators/winding_number.hpp | 53 ++++--------------- .../primal/tests/primal_winding_number.cpp | 13 +++-- 4 files changed, 27 insertions(+), 51 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index bd659544cc..ee31285d4f 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -191,7 +191,7 @@ double convex_endpoint_winding_number(const Point& q, * \param [in] edge_tol The physical distance level at which objects are * considered indistinguishable * \param [in] EPS Miscellaneous numerical tolerance for nonphysical distances, used in - * isLinear, isNearlyZero, in_polygon, is_convex + * isLinear, isNearlyZero, is_convex * \param [out] approximating_polygon The Polygon that, by termination of recursion, * has the same integer winding number as the original closed curve * \param [out] endpoint_gwn A running sum for the exact GWN if the point is at the @@ -247,7 +247,7 @@ void construct_approximating_polygon(const Point& q, if(isConvexControlPolygon) { // Bezier curves are always contained in their convex control polygon - if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, EPS)) + if(!in_polygon(q, controlPolygon, includeBoundary, useNonzeroRule, edge_tol)) { return; } diff --git a/src/axom/primal/operators/in_polygon.hpp b/src/axom/primal/operators/in_polygon.hpp index f29205bdf3..2621459b78 100644 --- a/src/axom/primal/operators/in_polygon.hpp +++ b/src/axom/primal/operators/in_polygon.hpp @@ -36,7 +36,7 @@ namespace primal * \param [in] poly The Polygon object to test for containment * \param [in] includeBoundary If true, points on the boundary are considered interior. * \param [in] useNonzeroRule If false, use even/odd protocol for inclusion - * \param [in] EPS The tolerance level for collinearity + * \param [in] edge_tol The distance at which a point is considered on the boundary * * Determines containment using the winding number with respect to the * given polygon. @@ -51,11 +51,11 @@ bool in_polygon(const Point& query, const Polygon& poly, bool includeBoundary = false, bool useNonzeroRule = true, - double EPS = 1e-8) + double edge_tol = 1e-8) { return useNonzeroRule - ? winding_number(query, poly, includeBoundary, EPS) != 0 - : (winding_number(query, poly, includeBoundary, EPS) % 2) == 1; + ? winding_number(query, poly, includeBoundary, edge_tol) != 0 + : (winding_number(query, poly, includeBoundary, edge_tol) % 2) == 1; } } // namespace primal diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 96988696a0..7c10e2b51c 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -94,7 +94,7 @@ int winding_number(const Point& q, * \param [in] P The Polygon object to test for containment * \param [in] includeBoundary If true, points on the boundary are considered interior * \param [in] isOnEdge An optional return parameter if the point is on the boundary - * \param [in] EPS The tolerance level for collinearity + * \param [in] edge_tol The distance at wich a point is considered on the boundary * * Uses an adapted ray-casting approach that counts quarter-rotation * of vertices around the query point. Current policy is to return 1 on edges @@ -113,37 +113,22 @@ int winding_number(const Point& R, const Polygon& P, bool& isOnEdge, bool includeBoundary, - double EPS) + double edge_tol) { const int nverts = P.numVertices(); + const double edge_tol_2 = edge_tol * edge_tol; isOnEdge = false; - // If the query is a vertex, return a value interpreted - // as "inside" by evenodd or nonzero protocols - if(axom::utilities::isNearlyEqual(P[0][0], R[0], EPS) && - axom::utilities::isNearlyEqual(P[0][1], R[1], EPS)) - { - isOnEdge = true; - return includeBoundary ? 1 : 0; // On vertex - } - int winding_num = 0; for(int i = 0; i < nverts; i++) { int j = (i == nverts - 1) ? 0 : i + 1; - if(axom::utilities::isNearlyEqual(P[j][1], R[1], EPS)) + // Check if the point is on the edge up to some tolerance + if(squared_distance(R, Segment(P[i], P[j])) <= edge_tol_2) { - if(axom::utilities::isNearlyEqual(P[j][0], R[0], EPS)) - { - isOnEdge = true; - return includeBoundary ? 1 : 0; // On vertex - } - else if(P[i][1] == R[1] && ((P[j][0] > R[0]) == (P[i][0] < R[0]))) - { - isOnEdge = true; - return includeBoundary ? 1 : 0; // On horizontal edge - } + isOnEdge = true; + return includeBoundary ? 1 : 0; } // Check if edge crosses horizontal line @@ -162,13 +147,6 @@ int winding_number(const Point& R, P[i][1] - R[1], P[j][1] - R[1]); // clang-format on - // On edge - if(axom::utilities::isNearlyEqual(det, 0.0, EPS)) - { - isOnEdge = true; - return includeBoundary ? 1 : 0; // On horizontal edge - } - // Check if edge intersects horitonal ray to the right of R if((det > 0) == (P[j][1] > P[i][1])) { @@ -185,13 +163,6 @@ int winding_number(const Point& R, P[i][1] - R[1], P[j][1] - R[1]); // clang-format on - // On edge - if(axom::utilities::isNearlyEqual(det, 0.0, EPS)) - { - isOnEdge = true; - return includeBoundary ? 1 : 0; // On horizontal edge - } - // Check if edge intersects horitonal ray to the right of R if((det > 0) == (P[j][1] > P[i][1])) { @@ -211,7 +182,7 @@ int winding_number(const Point& R, * \param [in] R The query point to test * \param [in] P The Polygon object to test for containment * \param [in] includeBoundary If true, points on the boundary are considered interior - * \param [in] EPS The tolerance level for collinearity + * \param [in] edge_tol The distance at wich a point is considered on the boundary * * Computes the integer winding number for a polygon without an additional * return parameter for whether the point is on the boundary. @@ -222,10 +193,10 @@ template int winding_number(const Point& R, const Polygon& P, bool includeBoundary = false, - double EPS = 1e-8) + double edge_tol = 1e-8) { bool isOnEdge = false; - return winding_number(R, P, isOnEdge, includeBoundary, EPS); + return winding_number(R, P, isOnEdge, includeBoundary, edge_tol); } /*! @@ -302,9 +273,7 @@ double winding_number(const Point& q, // If the point is on the boundary of the approximating polygon, // or coincident with the curve (rare), then winding_number // doesn't return the right half-integer. Have to go edge-by-edge. - // Do an extra check for the result of linear_winding_number to - // account for differing tolerances between the two methods. - if(isCoincident || isOnEdge || axom::utilities::isNearlyEqual(closure_wn, 0.0, EPS)) + if(isCoincident || isOnEdge) { closed_curve_wn = closure_wn; for(int i = 1; i < n; ++i) diff --git a/src/axom/primal/tests/primal_winding_number.cpp b/src/axom/primal/tests/primal_winding_number.cpp index 2665294e68..91ee12baf7 100644 --- a/src/axom/primal/tests/primal_winding_number.cpp +++ b/src/axom/primal/tests/primal_winding_number.cpp @@ -168,9 +168,16 @@ TEST(primal_winding_number, closure_edge_cases) // Tests a potential issue where the query point is treated as being // on the closure, but not on the edge of the approximating polygon. - EXPECT_NEAR(winding_number(Point2D({0, 1e-8}), quartic), - 0.500000031830989, - abs_tol); + for(int i = 2; i < 15; ++i) + { + // In all cases, the winding number should be *near* 0.5. + // If the tolerances don't match, we would get an "off-by-0.5" error + + double diff = std::pow( 10, -i ); + EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 0.5 * diff, EPS), 0.5, 0.1); + EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 1.0 * diff, EPS), 0.5, 0.1); + EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 2.0 * diff, EPS), 0.5, 0.1); + } // Flip the curve vertically quartic[2] = Point2D({0.0, -1.0}); From b7915f800db970c34e13a4655109d92d5e0eea8f Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 13 Sep 2024 12:33:19 -0600 Subject: [PATCH 24/25] Fix formatting --- src/axom/primal/operators/winding_number.hpp | 2 +- src/axom/primal/tests/primal_winding_number.cpp | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index 7c10e2b51c..fd1f6cfb8a 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -128,7 +128,7 @@ int winding_number(const Point& R, if(squared_distance(R, Segment(P[i], P[j])) <= edge_tol_2) { isOnEdge = true; - return includeBoundary ? 1 : 0; + return includeBoundary ? 1 : 0; } // Check if edge crosses horizontal line diff --git a/src/axom/primal/tests/primal_winding_number.cpp b/src/axom/primal/tests/primal_winding_number.cpp index 91ee12baf7..275204bded 100644 --- a/src/axom/primal/tests/primal_winding_number.cpp +++ b/src/axom/primal/tests/primal_winding_number.cpp @@ -173,10 +173,16 @@ TEST(primal_winding_number, closure_edge_cases) // In all cases, the winding number should be *near* 0.5. // If the tolerances don't match, we would get an "off-by-0.5" error - double diff = std::pow( 10, -i ); - EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 0.5 * diff, EPS), 0.5, 0.1); - EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 1.0 * diff, EPS), 0.5, 0.1); - EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 2.0 * diff, EPS), 0.5, 0.1); + double diff = std::pow(10, -i); + EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 0.5 * diff, EPS), + 0.5, + 0.1); + EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 1.0 * diff, EPS), + 0.5, + 0.1); + EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 2.0 * diff, EPS), + 0.5, + 0.1); } // Flip the curve vertically From dfa761ac3b6fa8589768336b3dbd31183e9b0669 Mon Sep 17 00:00:00 2001 From: Jacob Spainhour Date: Fri, 13 Sep 2024 15:01:15 -0600 Subject: [PATCH 25/25] Make bounding box tolerances consistent, and add relevant test --- .../primal/operators/detail/winding_number_impl.hpp | 2 +- src/axom/primal/operators/winding_number.hpp | 6 +++--- src/axom/primal/tests/primal_winding_number.cpp | 10 ++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/operators/detail/winding_number_impl.hpp b/src/axom/primal/operators/detail/winding_number_impl.hpp index ee31285d4f..fca2796899 100644 --- a/src/axom/primal/operators/detail/winding_number_impl.hpp +++ b/src/axom/primal/operators/detail/winding_number_impl.hpp @@ -221,7 +221,7 @@ void construct_approximating_polygon(const Point& q, const int ord = c.getOrder(); // Simplest convex shape containing c is its bounding box - if(!c.boundingBox().contains(q)) + if(!c.boundingBox().expand(edge_tol).contains(q)) { return; } diff --git a/src/axom/primal/operators/winding_number.hpp b/src/axom/primal/operators/winding_number.hpp index fd1f6cfb8a..036e99567d 100644 --- a/src/axom/primal/operators/winding_number.hpp +++ b/src/axom/primal/operators/winding_number.hpp @@ -94,7 +94,7 @@ int winding_number(const Point& q, * \param [in] P The Polygon object to test for containment * \param [in] includeBoundary If true, points on the boundary are considered interior * \param [in] isOnEdge An optional return parameter if the point is on the boundary - * \param [in] edge_tol The distance at wich a point is considered on the boundary + * \param [in] edge_tol The distance at which a point is considered on the boundary * * Uses an adapted ray-casting approach that counts quarter-rotation * of vertices around the query point. Current policy is to return 1 on edges @@ -182,7 +182,7 @@ int winding_number(const Point& R, * \param [in] R The query point to test * \param [in] P The Polygon object to test for containment * \param [in] includeBoundary If true, points on the boundary are considered interior - * \param [in] edge_tol The distance at wich a point is considered on the boundary + * \param [in] edge_tol The distance at which a point is considered on the boundary * * Computes the integer winding number for a polygon without an additional * return parameter for whether the point is on the boundary. @@ -232,7 +232,7 @@ double winding_number(const Point& q, if(ord <= 0) return 0.0; // Early return is possible for must points + curves - if(!c.boundingBox().contains(q)) + if(!c.boundingBox().expand(edge_tol).contains(q)) { return detail::linear_winding_number(q, c[0], c[ord], edge_tol); } diff --git a/src/axom/primal/tests/primal_winding_number.cpp b/src/axom/primal/tests/primal_winding_number.cpp index 275204bded..f554d3b57a 100644 --- a/src/axom/primal/tests/primal_winding_number.cpp +++ b/src/axom/primal/tests/primal_winding_number.cpp @@ -183,6 +183,16 @@ TEST(primal_winding_number, closure_edge_cases) EXPECT_NEAR(winding_number(Point2D({0, diff}), quartic, 2.0 * diff, EPS), 0.5, 0.1); + + EXPECT_NEAR(winding_number(Point2D({0, -diff}), quartic, 0.5 * diff, EPS), + 0.5, + 0.1); + EXPECT_NEAR(winding_number(Point2D({0, -diff}), quartic, 1.0 * diff, EPS), + 0.5, + 0.1); + EXPECT_NEAR(winding_number(Point2D({0, -diff}), quartic, 2.0 * diff, EPS), + 0.5, + 0.1); } // Flip the curve vertically