From 79ec50252f810742d00d0c8b7f8fc571a9e58257 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 23 Jul 2019 12:07:24 -0700 Subject: [PATCH 01/38] Added intersection of curved polygons --- src/axom/primal/CMakeLists.txt | 1 + .../operators/intersect_curved_poly.hpp | 239 ++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 src/axom/primal/operators/intersect_curved_poly.hpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index c917281e1c..59e3ec95e9 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -45,6 +45,7 @@ set( primal_headers operators/compute_bounding_box.hpp operators/compute_moments.hpp operators/in_sphere.hpp + operators/intersect_curved_poly.hpp operators/split.hpp operators/detail/clip_impl.hpp diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/intersect_curved_poly.hpp new file mode 100644 index 0000000000..d5af56c067 --- /dev/null +++ b/src/axom/primal/operators/intersect_curved_poly.hpp @@ -0,0 +1,239 @@ +// Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \file intersect_curved_poly.hpp + * + * \brief Consists of functions to test intersection among geometric primitives. + */ + +#ifndef AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_HPP_ +#define AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_HPP_ + +#include "axom/primal/geometry/BoundingBox.hpp" +#include "axom/primal/geometry/OrientedBoundingBox.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Ray.hpp" +#include "axom/primal/geometry/Segment.hpp" +#include "axom/primal/geometry/Sphere.hpp" +#include "axom/primal/geometry/Triangle.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/CurvedPolygon.hpp" + +#include "axom/core/utilities/Utilities.hpp" + +#include "axom/primal/operators/squared_distance.hpp" +#include "axom/primal/operators/intersect.hpp" + +namespace axom +{ +namespace primal +{ +template +class IntersectionInfo; +/* +template +bool orient(BezierCurve c1, BezierCurve c2, T s, T t); +*/ +/*! + * \brief Tests if Bezier Curves c1 and c2 intersect. + * \return status true iff c1 intersects with c2, otherwise false. + * + * \param c1, c2 BezierCurve objects to intersect + * \param sp, tp vector of type T parameter space intersection points (t-values + * and s-values) for c1 and c2, respectively + */ +template +bool intersect_polygon(CurvedPolygon& p1, + CurvedPolygon& p2, + std::vector>& pnew) +{ + // Object to store intersections + std::vector>> E1IntData(p1.numEdges()); + std::vector>> E2IntData(p2.numEdges()); + + IntersectionInfo firstinter; + + // Find all intersections and store + int numinters = 0; + for(int i = 0; i < p1.numEdges(); ++i) + { + for(int j = 0; j < p2.numEdges(); ++j) + { + std::vector p1times; + std::vector p2times; + intersect(p1[i], p2[j], p1times, p2times); + for(int k = 0; k < static_cast(p1times.size()); ++k) + { + E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + k + 1}); + E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + k + 1}); + if(numinters == 0) + { + firstinter = {p1times[0], i, p2times[0], j, 1}; + } + } + numinters += p1times.size(); + } + } + for(int i = 0; i < p1.numEdges(); ++i) + { + std::sort(E1IntData[i].begin(), E1IntData[i].end()); + std::sort(E2IntData[i].begin(), E2IntData[i].end()); + } + + bool orientation = orient(p1[firstinter.myEdge], + p2[firstinter.otherEdge], + firstinter.myTime, + firstinter.otherTime); + std::vector edgelabels[2]; + + CurvedPolygon psplit[2]; + psplit[0] = p1; + psplit[1] = p2; + int addedints = 0; + for(int i = 0; i < static_cast(p1.numEdges()); ++i) + { + edgelabels[0].push_back(0); + for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) + { + psplit[0].splitEdge(i + addedints, E1IntData[i][j].myTime); + edgelabels[0].insert(edgelabels[0].begin() + i + addedints, + E1IntData[i][j].numinter); + addedints += 1; + for(int k = j + 1; k < static_cast(E1IntData[i].size()); ++k) + { + E1IntData[i][k].myTime = + (E1IntData[i][k].myTime - E1IntData[i][j].myTime) / + (1 - E1IntData[i][j].myTime); + } + } + } + /*std::cout << psplit[0] << std::endl; + for (int i=0; i < edgelabels[0].size(); ++i) + { + std::cout << edgelabels[0][i] << std::endl; + }*/ + + addedints = 0; + for(int i = 0; i < p2.numEdges(); ++i) + { + edgelabels[1].push_back(0); + for(int j = 0; j < static_cast(E2IntData[i].size()); ++j) + { + psplit[1].splitEdge(i + addedints, E2IntData[i][j].myTime); + edgelabels[1].insert(edgelabels[1].begin() + i + addedints, + E2IntData[i][j].numinter); + addedints += 1; + for(int k = j + 1; k < static_cast(E2IntData[i].size()); ++k) + { + E2IntData[i][k].myTime = + (E2IntData[i][k].myTime - E2IntData[i][j].myTime) / + (1 - E2IntData[i][j].myTime); + } + } + } + /*std::cout << psplit[1] << std::endl; + for (int i=0; i < edgelabels[1].size(); ++i) + { + std::cout << edgelabels[1][i] << std::endl; + }*/ + std::vector::iterator> usedlabels; + if(numinters == 0) + { + return false; + } // If there are no intersections, return false + else + { + bool addingcurves = true; + int startinter = 1; // Start at the first intersection + int nextinter; + bool currentelement = orientation; + int currentit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + startinter) - + edgelabels[currentelement].begin(); + int startit = currentit; + int nextit = (currentit + 1) % edgelabels[0].size(); + nextinter = edgelabels[currentelement][nextit]; + while(numinters > 0) + { + CurvedPolygon aPart; // To store the current intersection polygon (could be multiple) + while(!(nextit == startit && currentelement == orientation)) + { + if(nextit == currentit) + { + currentit = nextit; + nextit = (currentit + 1) % edgelabels[0].size(); + } + nextinter = edgelabels[currentelement][nextit]; + while(nextinter == 0) + { + currentit = nextit; + if(addingcurves) + { + aPart.addEdge(psplit[currentelement][nextit]); + } + nextit = (currentit + 1) % edgelabels[0].size(); + nextinter = edgelabels[currentelement][nextit]; + } + if(addingcurves) + { + aPart.addEdge(psplit[currentelement][nextit]); + currentelement = !currentelement; + currentit = nextit; + nextit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + nextinter) - + edgelabels[currentelement].begin(); + numinters -= 1; + } + else + { + addingcurves = true; + currentit = nextit; + nextit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + nextinter) - + edgelabels[currentelement].begin(); + } + std::cout << aPart << std::endl; + } + pnew.push_back(aPart); + } + addingcurves = false; + } + std::cout << pnew[0] << std::endl; + + return true; +} + +template +bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) +{ + Point c1val1 = c1.evaluate(s + (1e-13)); + Point c1val2 = c1.evaluate(s - (1e-13)); + Point c2val = c2.evaluate(t + (1e-13)); + + return ((-(c1val1[0] - c1val2[0]) * (c2val[1] - c1val2[1])) + + (c1val1[1] - c1val2[1]) * (c2val[0] - c1val2[0])) > 0; +} + +// A class for storing intersection points so they can be easily sorted by parameter value +template +class IntersectionInfo +{ +public: + T myTime; + int myEdge; + T otherTime; + int otherEdge; + int numinter; + bool operator<(IntersectionInfo other) { return myTime < other.myTime; } +}; + +} // namespace primal +} // namespace axom + +#endif // AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_HPP_ From 5891534b40f8070843712d313a18a9bbe317fe42 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 23 Jul 2019 14:42:48 -0700 Subject: [PATCH 02/38] Improves and debugs interesect_curved_polygon --- .../operators/intersect_curved_poly.hpp | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/intersect_curved_poly.hpp index d5af56c067..619f0cd2ed 100644 --- a/src/axom/primal/operators/intersect_curved_poly.hpp +++ b/src/axom/primal/operators/intersect_curved_poly.hpp @@ -38,12 +38,11 @@ template bool orient(BezierCurve c1, BezierCurve c2, T s, T t); */ /*! - * \brief Tests if Bezier Curves c1 and c2 intersect. - * \return status true iff c1 intersects with c2, otherwise false. + * \brief Test whether CurvedPolygons p1 and p2 intersect. + * \return status true iff p1 intersects with p2, otherwise false. * - * \param c1, c2 BezierCurve objects to intersect - * \param sp, tp vector of type T parameter space intersection points (t-values - * and s-values) for c1 and c2, respectively + * \param p1, p2 CurvedPolygon objects to intersect + * \param pnew vector of type CurvedPolygon holding intersection regions oriented as the original curves were. */ template bool intersect_polygon(CurvedPolygon& p1, @@ -53,8 +52,7 @@ bool intersect_polygon(CurvedPolygon& p1, // Object to store intersections std::vector>> E1IntData(p1.numEdges()); std::vector>> E2IntData(p2.numEdges()); - - IntersectionInfo firstinter; + IntersectionInfo firstinter; // Need to do orientation test on first intersection // Find all intersections and store int numinters = 0; @@ -83,17 +81,21 @@ bool intersect_polygon(CurvedPolygon& p1, std::sort(E2IntData[i].begin(), E2IntData[i].end()); } + // Orient the first intersection point to be sure we get the intersection bool orientation = orient(p1[firstinter.myEdge], p2[firstinter.otherEdge], firstinter.myTime, firstinter.otherTime); + + // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each + // intersection and zeros for corners of original polygons. std::vector edgelabels[2]; CurvedPolygon psplit[2]; psplit[0] = p1; psplit[1] = p2; int addedints = 0; - for(int i = 0; i < static_cast(p1.numEdges()); ++i) + for(int i = 0; i < p1.numEdges(); ++i) { edgelabels[0].push_back(0); for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) @@ -110,6 +112,8 @@ bool intersect_polygon(CurvedPolygon& p1, } } } + + // Debugging code /*std::cout << psplit[0] << std::endl; for (int i=0; i < edgelabels[0].size(); ++i) { @@ -134,11 +138,15 @@ bool intersect_polygon(CurvedPolygon& p1, } } } + + // Debugging code /*std::cout << psplit[1] << std::endl; for (int i=0; i < edgelabels[1].size(); ++i) { std::cout << edgelabels[1][i] << std::endl; }*/ + + // This performs the directional walking method using the completely split polygon std::vector::iterator> usedlabels; if(numinters == 0) { @@ -198,17 +206,19 @@ bool intersect_polygon(CurvedPolygon& p1, nextinter) - edgelabels[currentelement].begin(); } - std::cout << aPart << std::endl; + //std::cout << aPart << std::endl; } pnew.push_back(aPart); } addingcurves = false; } - std::cout << pnew[0] << std::endl; + //std::cout << pnew[0] << std::endl; return true; } +// This determines with curve is "more" counterclockwise using signed distance +// It could potentially be changed to use a cross product of tangents instead template bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) { From 17b2aeddaa3d18bffbc382818591b1b6821feeea Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 23 Jul 2019 17:52:09 -0700 Subject: [PATCH 03/38] More debugging of intersect curved polygons TODO: precision of curvedpolygon intersection appears to be only 10^-10 --- .../operators/intersect_curved_poly.hpp | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/intersect_curved_poly.hpp index 619f0cd2ed..d2052e2a4d 100644 --- a/src/axom/primal/operators/intersect_curved_poly.hpp +++ b/src/axom/primal/operators/intersect_curved_poly.hpp @@ -82,10 +82,10 @@ bool intersect_polygon(CurvedPolygon& p1, } // Orient the first intersection point to be sure we get the intersection - bool orientation = orient(p1[firstinter.myEdge], - p2[firstinter.otherEdge], - firstinter.myTime, - firstinter.otherTime); + bool orientation = !orient(p1[firstinter.myEdge], + p2[firstinter.otherEdge], + firstinter.myTime, + firstinter.otherTime); // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each // intersection and zeros for corners of original polygons. @@ -114,11 +114,11 @@ bool intersect_polygon(CurvedPolygon& p1, } // Debugging code - /*std::cout << psplit[0] << std::endl; - for (int i=0; i < edgelabels[0].size(); ++i) + std::cout << psplit[0] << std::endl; + for(int i = 0; i < static_cast(edgelabels[0].size()); ++i) { std::cout << edgelabels[0][i] << std::endl; - }*/ + } addedints = 0; for(int i = 0; i < p2.numEdges(); ++i) @@ -140,11 +140,11 @@ bool intersect_polygon(CurvedPolygon& p1, } // Debugging code - /*std::cout << psplit[1] << std::endl; - for (int i=0; i < edgelabels[1].size(); ++i) + std::cout << psplit[1] << std::endl; + for(int i = 0; i < static_cast(edgelabels[1].size()); ++i) { std::cout << edgelabels[1][i] << std::endl; - }*/ + } // This performs the directional walking method using the completely split polygon std::vector::iterator> usedlabels; @@ -172,7 +172,6 @@ bool intersect_polygon(CurvedPolygon& p1, { if(nextit == currentit) { - currentit = nextit; nextit = (currentit + 1) % edgelabels[0].size(); } nextinter = edgelabels[currentelement][nextit]; @@ -182,19 +181,21 @@ bool intersect_polygon(CurvedPolygon& p1, if(addingcurves) { aPart.addEdge(psplit[currentelement][nextit]); + std::cout << 0; } nextit = (currentit + 1) % edgelabels[0].size(); nextinter = edgelabels[currentelement][nextit]; + std::cout << 0 << std::endl; } if(addingcurves) { aPart.addEdge(psplit[currentelement][nextit]); currentelement = !currentelement; - currentit = nextit; nextit = std::find(edgelabels[currentelement].begin(), edgelabels[currentelement].end(), nextinter) - edgelabels[currentelement].begin(); + currentit = nextit; numinters -= 1; } else @@ -206,13 +207,14 @@ bool intersect_polygon(CurvedPolygon& p1, nextinter) - edgelabels[currentelement].begin(); } - //std::cout << aPart << std::endl; + std::cout << numinters; + std::cout << aPart << std::endl; } pnew.push_back(aPart); } addingcurves = false; } - //std::cout << pnew[0] << std::endl; + std::cout << pnew[0] << std::endl; return true; } From 7a550c887511d86b21f680d6463c592f95aa6760 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Thu, 25 Jul 2019 13:14:06 -0700 Subject: [PATCH 04/38] More debugging intersect_curved_polygon --- .../operators/intersect_curved_poly.hpp | 40 +++++-------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/intersect_curved_poly.hpp index d2052e2a4d..80004f0bfb 100644 --- a/src/axom/primal/operators/intersect_curved_poly.hpp +++ b/src/axom/primal/operators/intersect_curved_poly.hpp @@ -62,7 +62,7 @@ bool intersect_polygon(CurvedPolygon& p1, { std::vector p1times; std::vector p2times; - intersect(p1[i], p2[j], p1times, p2times); + intersect(p1[i], p2[j], p1times, p2times, 1e-15); for(int k = 0; k < static_cast(p1times.size()); ++k) { E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + k + 1}); @@ -113,13 +113,6 @@ bool intersect_polygon(CurvedPolygon& p1, } } - // Debugging code - std::cout << psplit[0] << std::endl; - for(int i = 0; i < static_cast(edgelabels[0].size()); ++i) - { - std::cout << edgelabels[0][i] << std::endl; - } - addedints = 0; for(int i = 0; i < p2.numEdges(); ++i) { @@ -139,19 +132,12 @@ bool intersect_polygon(CurvedPolygon& p1, } } - // Debugging code - std::cout << psplit[1] << std::endl; - for(int i = 0; i < static_cast(edgelabels[1].size()); ++i) - { - std::cout << edgelabels[1][i] << std::endl; - } - // This performs the directional walking method using the completely split polygon std::vector::iterator> usedlabels; if(numinters == 0) { - return false; - } // If there are no intersections, return false + return false; // No intersections so return early + } else { bool addingcurves = true; @@ -181,11 +167,9 @@ bool intersect_polygon(CurvedPolygon& p1, if(addingcurves) { aPart.addEdge(psplit[currentelement][nextit]); - std::cout << 0; } nextit = (currentit + 1) % edgelabels[0].size(); nextinter = edgelabels[currentelement][nextit]; - std::cout << 0 << std::endl; } if(addingcurves) { @@ -207,29 +191,23 @@ bool intersect_polygon(CurvedPolygon& p1, nextinter) - edgelabels[currentelement].begin(); } - std::cout << numinters; - std::cout << aPart << std::endl; } pnew.push_back(aPart); } addingcurves = false; } - std::cout << pnew[0] << std::endl; - return true; } -// This determines with curve is "more" counterclockwise using signed distance -// It could potentially be changed to use a cross product of tangents instead +// This determines with curve is "more" counterclockwise using the cross product of tangents template bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) { - Point c1val1 = c1.evaluate(s + (1e-13)); - Point c1val2 = c1.evaluate(s - (1e-13)); - Point c2val = c2.evaluate(t + (1e-13)); - - return ((-(c1val1[0] - c1val2[0]) * (c2val[1] - c1val2[1])) + - (c1val1[1] - c1val2[1]) * (c2val[0] - c1val2[0])) > 0; + Point dc1s = c1.dt(s); + Point dc2t = c2.dt(t); + Point origin = primal::Point::make_point(0.0, 0.0); + auto orientation = detail::twoDcross(dc1s, dc2t, origin); + return (orientation < 0); } // A class for storing intersection points so they can be easily sorted by parameter value From 87c6a5ddde5966e9c78090e211b96340a45a3943 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 29 Jul 2019 08:18:43 -0700 Subject: [PATCH 05/38] Beginnings of a field transfer application for high-order meshes The current example generates two mfem meshes and finds the pairs of possibly intersecting elements. --- .../detail/intersect_bezier_impl.hpp | 2 +- src/axom/quest/examples/CMakeLists.txt | 19 + .../quest/examples/quest_high_order_remap.cpp | 451 ++++++++++++++++++ 3 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 src/axom/quest/examples/quest_high_order_remap.cpp diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 66925002a8..4cc84c7930 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -88,7 +88,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, * As such, the we do not consider the lines to intersect if they do so * at the endpoints \a b or \d, respectively. * - * \note This function assumes the all intersections have multiplicity + * \note This function assumes that 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. * diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 353ab529af..4eaaf70c3c 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -209,3 +209,22 @@ if (ENABLE_FORTRAN) endif() endif() endif() + +if(MFEM_FOUND) + blt_add_executable( + NAME quest_high_order_remap_ex + SOURCES quest_high_order_remap.cpp + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON ${quest_example_depends} mfem fmt + FOLDER axom/quest/examples + ) + + #string(STRIP "${AXOM_DISABLE_UNUSED_PARAMETER_WARNINGS}" MFEM_COMPILE_FLAGS) + + #if (ENABLE_CUDA) + # set(MFEM_COMPILE_FLAGS "-Xcompiler=${MFEM_COMPILE_FLAGS}") + #endif() + + #blt_add_target_compile_flags( TO quest_high_order_remap_ex FLAGS "${MFEM_COMPILE_FLAGS}" ) + +endif() diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp new file mode 100644 index 0000000000..ef19a9956b --- /dev/null +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -0,0 +1,451 @@ +// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/*! + * \file quest_high_order_remap.cpp + * \brief Demonstrates conservative field remap on 2D high order meshes + */ + +// Axom includes +#include "axom/core.hpp" +#include "axom/slic.hpp" +#include "axom/primal.hpp" +#include "axom/spin.hpp" + +#ifdef AXOM_USE_MFEM + #include "mfem.hpp" +#else + #error "This example requires mfem" +#endif + +#include "axom/fmt.hpp" + +#include + +//namespace mint = axom::mint; +namespace primal = axom::primal; +namespace spin = axom::spin; + +/** + * \brief Wrapper for a 2D mfem mesh + * + * Helps with conversion to BezierCurves and CurvedPolygons + * + * The meshwrapper assumes ownership of the wrapped mesh + */ +class MeshWrapper +{ +public: + using BBox = primal::BoundingBox; + using Point = primal::Point; + + using CurvedPolygonType = primal::CurvedPolygon; + using BCurve = CurvedPolygonType::BezierCurveType; + +private: + /*! \brief Checks if the mesh's nodes are in the Bernstein basis */ + bool isBernsteinBasis() const + { + auto* fec = m_mesh->GetNodalFESpace()->FEColl(); + + if(fec == nullptr) + { + return false; + } + + if(const mfem::H1_FECollection* h1Fec = + dynamic_cast(fec)) + { + return h1Fec->GetBasisType() == mfem::BasisType::Positive; + } + + if(const mfem::L2_FECollection* l2Fec = + dynamic_cast(fec)) + { + return l2Fec->GetBasisType() == mfem::BasisType::Positive; + } + + if(dynamic_cast(fec) || + dynamic_cast(fec) || + dynamic_cast(fec)) + { + return true; + } + + return false; + } + +public: + MeshWrapper() : m_mesh(nullptr) { } + + MeshWrapper(mfem::Mesh* mesh) { setMesh(mesh); } + + ~MeshWrapper() + { + if(m_mesh != nullptr) + { + delete m_mesh; + m_mesh = nullptr; + } + } + + /*! + * Sets the mfem mesh pointer for this MeshWrapper instance + */ + void setMesh(mfem::Mesh* mesh) + { + SLIC_ASSERT(mesh != nullptr); + m_mesh = mesh; + + bool isHighOrder = + (m_mesh->GetNodalFESpace() != nullptr) && (m_mesh->GetNE() > 0); + SLIC_ASSERT_MSG(isHighOrder, "The mesh must be high order."); + + bool isBernstein = isBernsteinBasis(); + SLIC_ASSERT_MSG(isBernstein, "The mesh must be in the Bernstein basis"); + + const double tol = 1E-8; + computeBoundingBoxes(1 + tol); + } + + int numVertices() const { return m_mesh->GetNV(); } + int numElements() const { return m_mesh->GetNE(); } + + bool hasMesh() const { return m_mesh != nullptr; } + mfem::Mesh* getMesh() { return m_mesh; } + const mfem::Mesh* getMesh() const { return m_mesh; } + + const BBox& elementBoundingBox(int i) const { return m_boundingBoxes[i]; } + const BBox& meshBoundingBox() const { return m_meshBBox; } + + /*! + * \brief Transform the mfem element into a primal CurvedPolygon + * + * \param elemId The index of the element + * \return The element as a CurvedPolygon composed of BezierCurves + */ + CurvedPolygonType elemAsCurvedPolygon(int elemId) + { + SLIC_ASSERT(elemId >= 0 && elemId < numElements()); + + auto* fes = m_mesh->GetNodalFESpace(); + auto* nodes = m_mesh->GetNodes(); + + // Get the edge Ids for this element + mfem::Array edgeIds, edgeOrients; + m_mesh->GetElementEdges(elemId, edgeIds, edgeOrients); + const int nEdges = edgeIds.Size(); + + CurvedPolygonType poly(nEdges); + const int order = fes->GetOrder(0); + + // Get the grid function data associated with this edge + mfem::Array dofIndices; + BCurve curve(order); + for(int e = 0; e < nEdges; ++e) + { + // get the dof (degree of freedom) indices for this edge + fes->GetEdgeDofs(edgeIds[e], dofIndices); + + //SLIC_INFO("Elem " << elemId + // << " edge " << edgeIds[e] << " w/ orient " << edgeOrients[e] + // << " -- dof inds " + // << dofIndices[0] << " " << dofIndices[1] << " " << dofIndices[2] + // << " -- points " + // << spacePointFromDof(dofIndices[0], fes, nodes) << " " + // << spacePointFromDof(dofIndices[1], fes, nodes) << " " + // << spacePointFromDof(dofIndices[2], fes, nodes) + // ); + + // possibly reverse the dofs, based on the orientation + // Note: The dofs are ordered by vertices, then by edge + const bool bReverse = (edgeOrients[e] > 0); + if(bReverse) + { + curve[0] = spacePointFromDof(dofIndices[1], fes, nodes); + for(int p = 1; p < order; ++p) + { + curve[p] = spacePointFromDof(dofIndices[order - (p - 1)], fes, nodes); + } + curve[order] = spacePointFromDof(dofIndices[0], fes, nodes); + } + else + { + curve[0] = spacePointFromDof(dofIndices[0], fes, nodes); + for(int p = 1; p < order; ++p) + { + curve[p] = spacePointFromDof(dofIndices[p + 1], fes, nodes); + } + curve[order] = spacePointFromDof(dofIndices[1], fes, nodes); + } + + // Note: mfem's orientation is reversed w.r.t. primal's + //SLIC_INFO("Elem " << elemId << " edge " << e << " -- curve: " << curve); + poly[nEdges - e - 1] = curve; + } + + return poly; + } + +private: + /*! Get the coordinates of the point from the dof index */ + Point spacePointFromDof(int idx, + const mfem::FiniteElementSpace* fes, + const mfem::GridFunction* nodes) + { + return Point::make_point((*nodes)(fes->DofToVDof(idx, 0)), + (*nodes)(fes->DofToVDof(idx, 1))); + } + + /*! + * \brief Compute the element and mesh bounding boxes + * + * \param[in] bboxScaleFac Scale factor to increase the bounding boxes + * \pre bboxScaleFac >= 1. + */ + void computeBoundingBoxes(double bboxScaleFac) + { + SLIC_ASSERT(bboxScaleFac >= 1.); + + m_boundingBoxes.resize(numElements()); + m_meshBBox.clear(); + + auto* nodes = m_mesh->GetNodes(); + auto* fes = m_mesh->GetNodalFESpace(); + mfem::Array dofIndices; + + const int NE = numElements(); + for(int elem = 0; elem < NE; ++elem) + { + auto& bbox = m_boundingBoxes[elem]; + bbox.clear(); + + // Add each dof of the element to the bbox + // Note: positivity of Bernstein bases ensures that convex + // hull of element nodes contain entire element + fes->GetElementDofs(elem, dofIndices); + for(int i = 0; i < dofIndices.Size(); ++i) + { + int nIdx = dofIndices[i]; + bbox.addPoint(spacePointFromDof(nIdx, fes, nodes)); + } + + // Slightly scale the bbox to account for numerical noise + bbox.scale(bboxScaleFac); + + m_meshBBox.addBox(bbox); + } + } + +private: + mfem::Mesh* m_mesh; + + std::vector m_boundingBoxes; + BBox m_meshBBox; +}; + +struct Remapper +{ +private: + using GridType = spin::ImplicitGrid<2, axom::SEQ_EXEC, int>; + +public: + using CandidateList = std::vector; + +public: + Remapper() = default; + + ~Remapper() + { + fecMap.DeleteData(true); + fesMap.DeleteData(true); + } + + /*! Set up the source and target meshes */ + void setupMeshes() + { + const auto quadType = mfem::Element::QUADRILATERAL; + const int dim = 2; + + // paramters for target mesh -- quad mesh covering unit square + const int src_res = 2; + const int src_ord = 2; + + // paramters for target mesh -- quad mesh covering (part of) unit square + const int tgt_res = 3; + const int tgt_ord = 3; + const double tgt_scale = .9; + const double tgt_trans = .05; + + // create the source mesh + { + // create mfem mesh + auto* mesh = new mfem::Mesh(src_res, src_res, quadType, true); + + // create finite element collection for nodes + auto* fec = + new mfem::H1_FECollection(src_ord, dim, mfem::BasisType::Positive); + fecMap.Register("src_fec", fec, true); + + // create finite element space for nodes + auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); + fesMap.Register("src_fes", fes, true); + mesh->SetNodalFESpace(fes); + + SLIC_INFO("Writing to: " << axom::utilities::filesystem::getCWD()); + { + std::ofstream file; + file.open("source_mesh.mfem"); + mesh->Print(file); + } + + srcMesh.setMesh(mesh); + } + + // create the target mesh + { + auto* mesh = new mfem::Mesh(tgt_res, tgt_res, quadType, true); + xformMesh(mesh, tgt_scale, tgt_trans); + + auto* fec = + new mfem::H1_FECollection(tgt_ord, dim, mfem::BasisType::Positive); + fecMap.Register("tgt_fec", fec, true); + + auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); + fesMap.Register("tgt_fes", fes, true); + mesh->SetNodalFESpace(fes); + + { + std::ofstream file; + file.open("target_mesh.mfem"); + mesh->Print(file); + } + + tgtMesh.setMesh(mesh); + } + } + + /*! Setup the implicit grid spatial index over the source mesh */ + void setupGrid() + { + const int NE = srcMesh.numElements(); + grid.initialize(srcMesh.meshBoundingBox(), nullptr, NE); + + for(int i = 0; i < NE; ++i) + { + grid.insert(srcMesh.elementBoundingBox(i), i); + } + } + + /*! + * Computes the overlap areas between the elements of the target mesh + * to the elements of the source mesh + */ + void computeOverlapAreas() + { + const int nTargetElems = tgtMesh.numElements(); + for(int i = 0; i < nTargetElems; ++i) + { + // Finds the candidates from the source mesh that + // can intersect this target element + auto candidates = getSourceCandidates(i); + + if(candidates.empty()) break; + + auto tgtPoly = tgtMesh.elemAsCurvedPolygon(i); + SLIC_INFO("Target elem " << i << " -- area " << primal::area(tgtPoly) + //<< " -- bbox " << tgtMesh.elementBoundingBox(i) + ); + + for(int srcElem : candidates) + { + auto srcPoly = srcMesh.elemAsCurvedPolygon(srcElem); + SLIC_INFO("* Source elem " << srcElem << " -- area " + << primal::area(srcPoly) + //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) + ); + + // TODO: Compute intersections and areas for this pairs + // Note -- there can be more than one CurvedPolygon in the intersection + } + } + } + + /*! + * Gets the IDs of the candidate elements from the source mesh + * that might intersect with \a targetElemID from the target mesh + */ + CandidateList getSourceCandidates(int targetElemID) const + { + SLIC_ASSERT(targetElemID < tgtMesh.numElements()); + using BitsetType = GridType::BitsetType; + + CandidateList filteredCandidates; + + auto& targetBBox = tgtMesh.elementBoundingBox(targetElemID); + auto candidateBits = grid.getCandidates(targetBBox); + + // Filter the candidates; return as std vec + for(auto idx = candidateBits.find_first(); idx != BitsetType::npos; + idx = candidateBits.find_next(idx)) + { + if(primal::intersect(targetBBox, srcMesh.elementBoundingBox(idx))) + filteredCandidates.push_back(idx); + } + + return filteredCandidates; + } + +public: + MeshWrapper srcMesh; + MeshWrapper tgtMesh; + GridType grid; + +private: + // Named fields map manage memory associated with the + // finite element collection and spaces + mfem::NamedFieldsMap fecMap; + mfem::NamedFieldsMap fesMap; + +private: + // scale and translate the vertices of the given mesh + void xformMesh(mfem::Mesh* mesh, double sc, double off) + { + for(int v = 0; v < mesh->GetNV(); ++v) + { + double* pt = mesh->GetVertex(v); + pt[0] = sc * pt[0] + off; + pt[1] = sc * pt[1] + off; + } + } +}; + +//------------------------------------------------------------------------------ +int main(int argc, char** argv) +{ + AXOM_UNUSED_VAR(argc); + AXOM_UNUSED_VAR(argv); + + axom::slic::SimpleLogger logger; + + SLIC_INFO("The application conservatively maps fields from a source \n" + << "high order mesh to a target high order mesh!"); + + Remapper remap; + + // Setup the two meshes in the Bernstein basis + // The current implementation hard-codes the two meshes + // TODO: Read in two meshes from disk. + // In that case, we will need to convert the FEC to the Bernstein basis + remap.setupMeshes(); + + // Set up the spatial index + remap.setupGrid(); + + // Computes the overlaps between elements of the target and source meshes + remap.computeOverlapAreas(); + + return 0; +} From 697045429623dd9265d55c7deaead34f978adbe3 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Wed, 31 Jul 2019 16:44:35 -0700 Subject: [PATCH 06/38] Changed recursion base case to improve robustness. Added intersect to high order mesh intersection example. TODO: deal with case of total inclusion and separation --- .../detail/intersect_bezier_impl.hpp | 2 +- .../operators/intersect_curved_poly.hpp | 210 +++++++++--------- .../quest/examples/quest_high_order_remap.cpp | 11 + 3 files changed, 118 insertions(+), 105 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index 4cc84c7930..cfe8e2572a 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -130,7 +130,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, bool foundIntersection = false; - if(c1.isLinear(sq_tol) && c2.isLinear(sq_tol)) + if(s_scale * s_scale < sq_tol && t_scale * t_scale < sq_tol) { T s, t; if(intersect_2d_linear(c1[0], c1[order1], c2[0], c2[order2], s, t)) diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/intersect_curved_poly.hpp index 80004f0bfb..74f215a91e 100644 --- a/src/axom/primal/operators/intersect_curved_poly.hpp +++ b/src/axom/primal/operators/intersect_curved_poly.hpp @@ -75,139 +75,141 @@ bool intersect_polygon(CurvedPolygon& p1, numinters += p1times.size(); } } - for(int i = 0; i < p1.numEdges(); ++i) + if(numinters > 0) { - std::sort(E1IntData[i].begin(), E1IntData[i].end()); - std::sort(E2IntData[i].begin(), E2IntData[i].end()); - } - - // Orient the first intersection point to be sure we get the intersection - bool orientation = !orient(p1[firstinter.myEdge], - p2[firstinter.otherEdge], - firstinter.myTime, - firstinter.otherTime); - - // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each - // intersection and zeros for corners of original polygons. - std::vector edgelabels[2]; + for(int i = 0; i < p1.numEdges(); ++i) + { + std::sort(E1IntData[i].begin(), E1IntData[i].end()); + std::sort(E2IntData[i].begin(), E2IntData[i].end()); + } - CurvedPolygon psplit[2]; - psplit[0] = p1; - psplit[1] = p2; - int addedints = 0; - for(int i = 0; i < p1.numEdges(); ++i) - { - edgelabels[0].push_back(0); - for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) + // Orient the first intersection point to be sure we get the intersection + bool orientation = !orient(p1[firstinter.myEdge], + p2[firstinter.otherEdge], + firstinter.myTime, + firstinter.otherTime); + + // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each + // intersection and zeros for corners of original polygons. + std::vector edgelabels[2]; + + CurvedPolygon psplit[2]; + psplit[0] = p1; + psplit[1] = p2; + int addedints = 0; + for(int i = 0; i < p1.numEdges(); ++i) { - psplit[0].splitEdge(i + addedints, E1IntData[i][j].myTime); - edgelabels[0].insert(edgelabels[0].begin() + i + addedints, - E1IntData[i][j].numinter); - addedints += 1; - for(int k = j + 1; k < static_cast(E1IntData[i].size()); ++k) + edgelabels[0].push_back(0); + for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) { - E1IntData[i][k].myTime = - (E1IntData[i][k].myTime - E1IntData[i][j].myTime) / - (1 - E1IntData[i][j].myTime); + psplit[0].splitEdge(i + addedints, E1IntData[i][j].myTime); + edgelabels[0].insert(edgelabels[0].begin() + i + addedints, + E1IntData[i][j].numinter); + addedints += 1; + for(int k = j + 1; k < static_cast(E1IntData[i].size()); ++k) + { + E1IntData[i][k].myTime = + (E1IntData[i][k].myTime - E1IntData[i][j].myTime) / + (1 - E1IntData[i][j].myTime); + } } } - } - addedints = 0; - for(int i = 0; i < p2.numEdges(); ++i) - { - edgelabels[1].push_back(0); - for(int j = 0; j < static_cast(E2IntData[i].size()); ++j) + addedints = 0; + for(int i = 0; i < p2.numEdges(); ++i) { - psplit[1].splitEdge(i + addedints, E2IntData[i][j].myTime); - edgelabels[1].insert(edgelabels[1].begin() + i + addedints, - E2IntData[i][j].numinter); - addedints += 1; - for(int k = j + 1; k < static_cast(E2IntData[i].size()); ++k) + edgelabels[1].push_back(0); + for(int j = 0; j < static_cast(E2IntData[i].size()); ++j) { - E2IntData[i][k].myTime = - (E2IntData[i][k].myTime - E2IntData[i][j].myTime) / - (1 - E2IntData[i][j].myTime); + psplit[1].splitEdge(i + addedints, E2IntData[i][j].myTime); + edgelabels[1].insert(edgelabels[1].begin() + i + addedints, + E2IntData[i][j].numinter); + addedints += 1; + for(int k = j + 1; k < static_cast(E2IntData[i].size()); ++k) + { + E2IntData[i][k].myTime = + (E2IntData[i][k].myTime - E2IntData[i][j].myTime) / + (1 - E2IntData[i][j].myTime); + } } } - } - // This performs the directional walking method using the completely split polygon - std::vector::iterator> usedlabels; - if(numinters == 0) - { - return false; // No intersections so return early - } - else - { - bool addingcurves = true; - int startinter = 1; // Start at the first intersection - int nextinter; - bool currentelement = orientation; - int currentit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - startinter) - - edgelabels[currentelement].begin(); - int startit = currentit; - int nextit = (currentit + 1) % edgelabels[0].size(); - nextinter = edgelabels[currentelement][nextit]; - while(numinters > 0) + // This performs the directional walking method using the completely split polygon + std::vector::iterator> usedlabels; + if(numinters == 0) + { + return false; // No intersections so return early + } + else { - CurvedPolygon aPart; // To store the current intersection polygon (could be multiple) - while(!(nextit == startit && currentelement == orientation)) + bool addingcurves = true; + int startinter = 1; // Start at the first intersection + int nextinter; + bool currentelement = orientation; + int currentit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + startinter) - + edgelabels[currentelement].begin(); + int startit = currentit; + int nextit = (currentit + 1) % edgelabels[0].size(); + nextinter = edgelabels[currentelement][nextit]; + while(numinters > 0) { - if(nextit == currentit) - { - nextit = (currentit + 1) % edgelabels[0].size(); - } - nextinter = edgelabels[currentelement][nextit]; - while(nextinter == 0) + CurvedPolygon aPart; // To store the current intersection polygon (could be multiple) + while(!(nextit == startit && currentelement == orientation)) { - currentit = nextit; + if(nextit == currentit) + { + nextit = (currentit + 1) % edgelabels[0].size(); + } + nextinter = edgelabels[currentelement][nextit]; + while(nextinter == 0) + { + currentit = nextit; + if(addingcurves) + { + aPart.addEdge(psplit[currentelement][nextit]); + } + nextit = (currentit + 1) % edgelabels[0].size(); + nextinter = edgelabels[currentelement][nextit]; + } if(addingcurves) { aPart.addEdge(psplit[currentelement][nextit]); + currentelement = !currentelement; + nextit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + nextinter) - + edgelabels[currentelement].begin(); + currentit = nextit; + numinters -= 1; + } + else + { + addingcurves = true; + currentit = nextit; + nextit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + nextinter) - + edgelabels[currentelement].begin(); } - nextit = (currentit + 1) % edgelabels[0].size(); - nextinter = edgelabels[currentelement][nextit]; - } - if(addingcurves) - { - aPart.addEdge(psplit[currentelement][nextit]); - currentelement = !currentelement; - nextit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - nextinter) - - edgelabels[currentelement].begin(); - currentit = nextit; - numinters -= 1; - } - else - { - addingcurves = true; - currentit = nextit; - nextit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - nextinter) - - edgelabels[currentelement].begin(); } + pnew.push_back(aPart); } - pnew.push_back(aPart); + addingcurves = false; } - addingcurves = false; + return true; } - return true; + return false; } // This determines with curve is "more" counterclockwise using the cross product of tangents template bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) { - Point dc1s = c1.dt(s); - Point dc2t = c2.dt(t); - Point origin = primal::Point::make_point(0.0, 0.0); - auto orientation = detail::twoDcross(dc1s, dc2t, origin); - return (orientation < 0); + const auto orientation = + primal::Vector::cross_product(c1.dt(s), c2.dt(t))[2]; + return (orientation > 0); } // A class for storing intersection points so they can be easily sorted by parameter value diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index ef19a9956b..01dd17bfd6 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -359,6 +359,7 @@ struct Remapper //<< " -- bbox " << tgtMesh.elementBoundingBox(i) ); + double A = 0.0; for(int srcElem : candidates) { auto srcPoly = srcMesh.elemAsCurvedPolygon(srcElem); @@ -367,9 +368,19 @@ struct Remapper //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) ); + std::vector> pnew; + if(primal::intersect_polygon(tgtPoly, srcPoly, pnew)) + { + for(int i = 0; i < static_cast(pnew.size()); ++i) + { + A = A + primal::area(pnew[i]); + SLIC_INFO("** Intersection area :" << primal::area(pnew[i])); + } + } // TODO: Compute intersections and areas for this pairs // Note -- there can be more than one CurvedPolygon in the intersection } + SLIC_INFO("Calculated Area :" << A); } } From fb894900d8deb7b8c237b212cc5f5be24dd27f96 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Wed, 14 Aug 2019 16:23:39 -0700 Subject: [PATCH 07/38] Adds inclusion/separation test to intersect_polygon, reverts base case in intersect_bezier, and adds two region test for intersect_polygon --- .../detail/intersect_bezier_impl.hpp | 2 +- .../operators/intersect_curved_poly.hpp | 278 +++++++++++++++--- 2 files changed, 231 insertions(+), 49 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index cfe8e2572a..4cc84c7930 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -130,7 +130,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, bool foundIntersection = false; - if(s_scale * s_scale < sq_tol && t_scale * t_scale < sq_tol) + if(c1.isLinear(sq_tol) && c2.isLinear(sq_tol)) { T s, t; if(intersect_2d_linear(c1[0], c1[order1], c2[0], c2[order2], s, t)) diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/intersect_curved_poly.hpp index 74f215a91e..8b0541a807 100644 --- a/src/axom/primal/operators/intersect_curved_poly.hpp +++ b/src/axom/primal/operators/intersect_curved_poly.hpp @@ -37,8 +37,9 @@ class IntersectionInfo; template bool orient(BezierCurve c1, BezierCurve c2, T s, T t); */ + /*! - * \brief Test whether CurvedPolygons p1 and p2 intersect. + * \brief Test whether CurvedPolygons p1 and p2 intersect and find intersection points * \return status true iff p1 intersects with p2, otherwise false. * * \param p1, p2 CurvedPolygon objects to intersect @@ -84,18 +85,19 @@ bool intersect_polygon(CurvedPolygon& p1, } // Orient the first intersection point to be sure we get the intersection - bool orientation = !orient(p1[firstinter.myEdge], - p2[firstinter.otherEdge], - firstinter.myTime, - firstinter.otherTime); + bool orientation = orient(p1[firstinter.myEdge], + p2[firstinter.otherEdge], + firstinter.myTime, + firstinter.otherTime); // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each // intersection and zeros for corners of original polygons. - std::vector edgelabels[2]; - - CurvedPolygon psplit[2]; + std::vector edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points + CurvedPolygon psplit[2]; // The two completely split polygons will be stored in this array psplit[0] = p1; psplit[1] = p2; + + //split polygon 1 at all the intersection points and store as psplit[0] int addedints = 0; for(int i = 0; i < p1.numEdges(); ++i) { @@ -115,6 +117,7 @@ bool intersect_polygon(CurvedPolygon& p1, } } + //split polygon 2 at all the intersection points and store as psplit[1] addedints = 0; for(int i = 0; i < p2.numEdges(); ++i) { @@ -134,75 +137,254 @@ bool intersect_polygon(CurvedPolygon& p1, } } - // This performs the directional walking method using the completely split polygon + // This performs the directional walking method using the completely split polygons std::vector::iterator> usedlabels; - if(numinters == 0) - { - return false; // No intersections so return early - } - else + // When this is false, we are walking between intersection regions + bool addingcurves = true; + int startvertex = 1; // Start at the vertex with "unique id" 1 + int nextvertex; // The next vertex id is unknown at this time + // This variable allows us to switch between the two elements + bool currentelement = orientation; + // This is the iterator pointing to the end vertex of the edge of the completely split polygon we are on + int currentit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + startvertex) - + edgelabels[currentelement].begin(); + // This is the iterator to the end vertex of the starting edge on the starting polygon + int startit = currentit; + // This is the iterator to the end vertex of the next edge of whichever polygon we will be on next + int nextit = (currentit + 1) % edgelabels[0].size(); + nextvertex = edgelabels[currentelement][nextit]; // This is the next vertex id + while(numinters > 0) { - bool addingcurves = true; - int startinter = 1; // Start at the first intersection - int nextinter; - bool currentelement = orientation; - int currentit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - startinter) - - edgelabels[currentelement].begin(); - int startit = currentit; - int nextit = (currentit + 1) % edgelabels[0].size(); - nextinter = edgelabels[currentelement][nextit]; - while(numinters > 0) + CurvedPolygon aPart; // Object to store the current intersection polygon (could be multiple) + // Once the end vertex of the current edge is the start vertex, we need to switch regions + while(!(nextit == startit && currentelement == orientation) || + addingcurves == false) { - CurvedPolygon aPart; // To store the current intersection polygon (could be multiple) - while(!(nextit == startit && currentelement == orientation)) + if(nextit == currentit) { - if(nextit == currentit) - { - nextit = (currentit + 1) % edgelabels[0].size(); - } - nextinter = edgelabels[currentelement][nextit]; - while(nextinter == 0) + nextit = (currentit + 1) % edgelabels[0].size(); + } + nextvertex = edgelabels[currentelement][nextit]; + while(nextvertex == 0) + { + currentit = nextit; + if(addingcurves) { - currentit = nextit; - if(addingcurves) - { - aPart.addEdge(psplit[currentelement][nextit]); - } - nextit = (currentit + 1) % edgelabels[0].size(); - nextinter = edgelabels[currentelement][nextit]; + aPart.addEdge(psplit[currentelement][nextit]); } + nextit = (currentit + 1) % edgelabels[0].size(); + nextvertex = edgelabels[currentelement][nextit]; + } + if(edgelabels[currentelement][nextit] > 0) + { if(addingcurves) { aPart.addEdge(psplit[currentelement][nextit]); + edgelabels[currentelement][nextit] = + -edgelabels[currentelement][nextit]; currentelement = !currentelement; nextit = std::find(edgelabels[currentelement].begin(), edgelabels[currentelement].end(), - nextinter) - + nextvertex) - edgelabels[currentelement].begin(); + edgelabels[currentelement][nextit] = + -edgelabels[currentelement][nextit]; currentit = nextit; numinters -= 1; } else { addingcurves = true; + startit = nextit; currentit = nextit; - nextit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - nextinter) - - edgelabels[currentelement].begin(); + nextit = (currentit + 1) % edgelabels[0].size(); + orientation = currentelement; } } - pnew.push_back(aPart); + else + { + currentelement = !currentelement; + nextit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + nextvertex) - + edgelabels[currentelement].begin(); + edgelabels[currentelement][nextit] = + -edgelabels[currentelement][nextit]; + currentit = nextit; + } + } + pnew.push_back(aPart); + currentelement = !currentelement; + currentit = std::find(edgelabels[currentelement].begin(), + edgelabels[currentelement].end(), + -nextvertex) - + edgelabels[currentelement].begin(); + nextit = (currentit + 1) % edgelabels[0].size(); + if(numinters > 0) + { + addingcurves = false; } - addingcurves = false; } return true; } + else + { + int containment = isContained(p1, p2); + if(containment == 0) + { + return false; + } + else + { + if(containment == 1) + { + pnew.push_back(p1); + } + else + { + pnew.push_back(p2); + } + return true; + } + } + // If there are no intersections, return false return false; } +/*! + * \brief Checks if two polygons are mutually exclusive or if one includes the other, + * assuming that they have no intersection points + * + * \param [in] p1, p2 CurvedPolygons to be tested + * \return 0 if mutually exclusive, 1 if p1 is in p2, 2 if p2 is in p1 + */ +template +int isContained(const CurvedPolygon p1, const CurvedPolygon p2) +{ + const int NDIMS = 2; + using PointType = primal::Point; + using BCurve = BezierCurve; + int p1c = 0; + int p2c = 0; + T p1t = .5; + T p2t = .5; + PointType controlPoints[2] = {p1[p1c].evaluate(p1t), p2[p2c].evaluate(p2t)}; + BCurve LineGuess = BCurve(controlPoints, 1); + T line1s = 0.0; + T line2s = 0.0; + for(int j = 0; j < p1.numEdges(); ++j) + { + std::vector temps; + std::vector tempt; + intersect(LineGuess, p1[j], temps, tempt); + for(int i = 0; i < temps.size(); ++i) + { + if(temps[i] > line1s) + { + line1s = temps[i]; + p1c = j; + p1t = tempt[i]; + } + } + } + for(int j = 0; j < p2.numEdges(); ++j) + { + std::vector temps; + std::vector tempt; + intersect(LineGuess, p2[j], temps, tempt); + for(int i = 0; i < temps.size(); ++i) + { + if(temps[i] < line2s && temps[i] > line1s) + { + line2s = temps[i]; + p2c = j; + p2t = tempt[i]; + } + } + } + + using Vec3 = primal::Vector; + bool E1inE2 = Vec3::cross_product(p1[p1c].dt(p1t), LineGuess.dt(line1s))[2] < 0; + bool E2inE1 = Vec3::cross_product(p2[p2c].dt(p2t), LineGuess.dt(line2s))[2] < 0; + if(E1inE2 && E2inE1) + { + return 1; + } + else if(!E1inE2 && !E2inE1) + { + return 2; + } + else + { + return 0; + } +} + +/* +template +bool isContained(const CurvedPolygon, p1, const CurvedPolygon p2) +{ + BezierCurve lineGuess({p1[0].eval(.5), p2[0].eval(.5));a + T startTime=0.0; + T endTime=1.0; + std::vector lineInts; + for (int i=0; i otherInts; + std::vector tempLineInts; + intersect(lineGuess[i],p1[j],tempLineInts,otherInts,1e-15); + for (int j=0; j otherInts; + std::vector tempLineInts; + intersect(lineGuess[i],p2[j],tempLineInts,otherInts,1e-15); + for (int j=0; j dc1s = p1[0].dt(.5); + Point dc2t = p2[0].dt(.5); + Point dc1line = lineGuess.dt(0.0); + Point dc2line = lineGuess.dt(1.0); + Point origin = primal::Point< T, NDIMS >::make_point(0.0, 0.0); + bool contains12 = (detail::twoDcross(dc1s,dc1line,origin)>0); + bool contains21 = (detail::twoDcross(dc2t,dc2line,origin)>0); + } + else + { + Point dc1s = p1[lineInts[counterint].myEdge].dt(lineInts[counterint].otherTime); + Point dc2t = p2[lineInts[counterint+1].myEdge].dt(lineInts[counterint+1].otherTime); + Point dc1line = lineGuess.dt(lineInts[counterint].myTime); + Point dc2line = lineGuess.dt(lineInts[counterint+1].myTime); + Point origin = primal::Point< T, NDIMS >::make_point(0.0, 0.0); + bool contains12 = (detail::twoDcross(dc1s,dc1line,origin)>0); + bool contains21 = (detail::twoDcross(dc2t,dc2line,origin)>0); + } + return true; +} +*/ + // This determines with curve is "more" counterclockwise using the cross product of tangents template bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) From dc2c32702efcf4829f4c9c7cce83dccab5d42b7c Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 27 Aug 2019 09:31:59 -0700 Subject: [PATCH 08/38] Debugged quest high order remap example --- .../operators/intersect_curved_poly.hpp | 67 +---- .../quest/examples/quest_high_order_remap.cpp | 236 ++++++++++++++---- 2 files changed, 195 insertions(+), 108 deletions(-) diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/intersect_curved_poly.hpp index 8b0541a807..34b79c3b26 100644 --- a/src/axom/primal/operators/intersect_curved_poly.hpp +++ b/src/axom/primal/operators/intersect_curved_poly.hpp @@ -63,7 +63,7 @@ bool intersect_polygon(CurvedPolygon& p1, { std::vector p1times; std::vector p2times; - intersect(p1[i], p2[j], p1times, p2times, 1e-15); + intersect(p1[i], p2[j], p1times, p2times, 1e-10); for(int k = 0; k < static_cast(p1times.size()); ++k) { E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + k + 1}); @@ -273,7 +273,7 @@ int isContained(const CurvedPolygon p1, const CurvedPolygon p2) PointType controlPoints[2] = {p1[p1c].evaluate(p1t), p2[p2c].evaluate(p2t)}; BCurve LineGuess = BCurve(controlPoints, 1); T line1s = 0.0; - T line2s = 0.0; + T line2s = 1.0; for(int j = 0; j < p1.numEdges(); ++j) { std::vector temps; @@ -322,69 +322,6 @@ int isContained(const CurvedPolygon p1, const CurvedPolygon p2) } } -/* -template -bool isContained(const CurvedPolygon, p1, const CurvedPolygon p2) -{ - BezierCurve lineGuess({p1[0].eval(.5), p2[0].eval(.5));a - T startTime=0.0; - T endTime=1.0; - std::vector lineInts; - for (int i=0; i otherInts; - std::vector tempLineInts; - intersect(lineGuess[i],p1[j],tempLineInts,otherInts,1e-15); - for (int j=0; j otherInts; - std::vector tempLineInts; - intersect(lineGuess[i],p2[j],tempLineInts,otherInts,1e-15); - for (int j=0; j dc1s = p1[0].dt(.5); - Point dc2t = p2[0].dt(.5); - Point dc1line = lineGuess.dt(0.0); - Point dc2line = lineGuess.dt(1.0); - Point origin = primal::Point< T, NDIMS >::make_point(0.0, 0.0); - bool contains12 = (detail::twoDcross(dc1s,dc1line,origin)>0); - bool contains21 = (detail::twoDcross(dc2t,dc2line,origin)>0); - } - else - { - Point dc1s = p1[lineInts[counterint].myEdge].dt(lineInts[counterint].otherTime); - Point dc2t = p2[lineInts[counterint+1].myEdge].dt(lineInts[counterint+1].otherTime); - Point dc1line = lineGuess.dt(lineInts[counterint].myTime); - Point dc2line = lineGuess.dt(lineInts[counterint+1].myTime); - Point origin = primal::Point< T, NDIMS >::make_point(0.0, 0.0); - bool contains12 = (detail::twoDcross(dc1s,dc1line,origin)>0); - bool contains21 = (detail::twoDcross(dc2t,dc2line,origin)>0); - } - return true; -} -*/ - // This determines with curve is "more" counterclockwise using the cross product of tangents template bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index 01dd17bfd6..3766e9f1c8 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -8,6 +8,8 @@ * \brief Demonstrates conservative field remap on 2D high order meshes */ +#include + // Axom includes #include "axom/core.hpp" #include "axom/slic.hpp" @@ -183,9 +185,10 @@ class MeshWrapper // Note: mfem's orientation is reversed w.r.t. primal's //SLIC_INFO("Elem " << elemId << " edge " << e << " -- curve: " << curve); + //curve.reverseOrientation(); poly[nEdges - e - 1] = curve; } - + // std::cout << poly << std::endl; return poly; } @@ -264,20 +267,20 @@ struct Remapper } /*! Set up the source and target meshes */ - void setupMeshes() + void setupMeshes(int res1, int res2, int order) { const auto quadType = mfem::Element::QUADRILATERAL; const int dim = 2; - // paramters for target mesh -- quad mesh covering unit square - const int src_res = 2; - const int src_ord = 2; + // paramters for source mesh -- quad mesh covering unit square + const int src_res = res2; + const int src_ord = order; // paramters for target mesh -- quad mesh covering (part of) unit square - const int tgt_res = 3; - const int tgt_ord = 3; - const double tgt_scale = .9; - const double tgt_trans = .05; + const int tgt_res = res1; + const int tgt_ord = order; + const double tgt_scale = .712378102150; + const double tgt_trans = .1345747181586; // create the source mesh { @@ -294,7 +297,7 @@ struct Remapper fesMap.Register("src_fes", fes, true); mesh->SetNodalFESpace(fes); - SLIC_INFO("Writing to: " << axom::utilities::filesystem::getCWD()); + // SLIC_INFO("Writing to: " << axom::utilities::filesystem::getCWD()); { std::ofstream file; file.open("source_mesh.mfem"); @@ -326,7 +329,77 @@ struct Remapper tgtMesh.setMesh(mesh); } } + void loadMeshes(int res2, int order) + { + // create the source mesh + const auto quadType = mfem::Element::QUADRILATERAL; + const int dim = 2; + + // paramters for target mesh -- quad mesh covering unit square + const int src_res = res2; + const int src_ord = order; + const double src_scale = 6.0; + const double src_trans = -3.001; + + // paramters for target mesh -- quad mesh covering (part of) unit square + const int tgt_ord = 2; + + { + // create mfem mesh + auto* mesh = new mfem::Mesh(src_res, src_res, quadType, true); + xformMesh(mesh, src_scale, src_trans); + + // create finite element collection for nodes + auto* fec = + new mfem::H1_FECollection(src_ord, dim, mfem::BasisType::Positive); + fecMap.Register("src_fec", fec, true); + + // create finite element space for nodes + auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); + fesMap.Register("src_fes", fes, true); + mesh->SetNodalFESpace(fes); + + // SLIC_INFO("Writing to: " << axom::utilities::filesystem::getCWD()); + { + std::ofstream file; + file.open("source_mesh.mfem"); + mesh->Print(file); + } + + srcMesh.setMesh(mesh); + } + // create the target mesh + { + auto* mesh = new mfem::Mesh("./disc-nurbs-80.mesh", 1, 1); + if(mesh->NURBSext) + { + int order = tgt_ord; + mesh->SetCurvature(order); + } + // xformMesh(mesh, tgt_scale, tgt_trans); + { + std::ofstream file; + file.open("target_mesh_orig.mfem"); + mesh->Print(file); + } + + auto* fec = + new mfem::H1_FECollection(tgt_ord, dim, mfem::BasisType::Positive); + fecMap.Register("tgt_fec", fec, true); + + auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); + fesMap.Register("tgt_fes", fes, true); + mesh->SetNodalFESpace(fes); + { + std::ofstream file; + file.open("target_mesh_set.mfem"); + mesh->Print(file); + } + std::cout << "Got here!" << std::endl; + tgtMesh.setMesh(mesh); + } + } /*! Setup the implicit grid spatial index over the source mesh */ void setupGrid() { @@ -343,45 +416,74 @@ struct Remapper * Computes the overlap areas between the elements of the target mesh * to the elements of the source mesh */ - void computeOverlapAreas() + double computeOverlapAreas() { + double totalArea = 0.0; + double correctArea = 0.0; const int nTargetElems = tgtMesh.numElements(); + // SLIC_INFO("Number of Target Elements: " << nTargetElems); + double calcE = 0.0; for(int i = 0; i < nTargetElems; ++i) { // Finds the candidates from the source mesh that // can intersect this target element auto candidates = getSourceCandidates(i); - if(candidates.empty()) break; auto tgtPoly = tgtMesh.elemAsCurvedPolygon(i); - SLIC_INFO("Target elem " << i << " -- area " << primal::area(tgtPoly) - //<< " -- bbox " << tgtMesh.elementBoundingBox(i) - ); + // SLIC_INFO("Target Element: " << tgtPoly); + correctArea += primal::area(tgtPoly); + //j SLIC_INFO("Target elem " << i + //j << " -- area " << primal::area(tgtPoly) + //j //<< " -- bbox " << tgtMesh.elementBoundingBox(i) + //j ); double A = 0.0; for(int srcElem : candidates) { auto srcPoly = srcMesh.elemAsCurvedPolygon(srcElem); - SLIC_INFO("* Source elem " << srcElem << " -- area " - << primal::area(srcPoly) - //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) - ); + // SLIC_INFO("*Source Element: " << srcPoly); + // SLIC_INFO("* Source elem " << srcElem + // << " -- area " << primal::area(srcPoly) + //// //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) + // ); std::vector> pnew; + tgtPoly.reverseOrientation(); + srcPoly.reverseOrientation(); if(primal::intersect_polygon(tgtPoly, srcPoly, pnew)) { for(int i = 0; i < static_cast(pnew.size()); ++i) { - A = A + primal::area(pnew[i]); - SLIC_INFO("** Intersection area :" << primal::area(pnew[i])); + A -= primal::area(pnew[i]); + // SLIC_INFO("** Intersection area :" << -primal::area(pnew[i]) + // ); } } - // TODO: Compute intersections and areas for this pairs - // Note -- there can be more than one CurvedPolygon in the intersection + srcPoly.reverseOrientation(); + tgtPoly.reverseOrientation(); + // SLIC_INFO("* Calculated area: " << srcElem + // << " -- area " << A + // //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) + // ); } - SLIC_INFO("Calculated Area :" << A); + calcE += abs(primal::area(tgtPoly) - A); + totalArea += A; + // SLIC_INFO("Calculated Area :" << A); } + // std::cout << inclusion << ", "; + // std::cout << calcE << ", "; + // const double tgt_scale = .999712378102150; + // const double tgt_trans = .0001345747181586; + // const double tgt_scale = .9999712378102150; + // const double tgt_trans = .00001345747181586; + // const double tgt_scale = .99999999999712378102150; + // const double tgt_trans = .000000000001345747181586; + // double trueError = (tgt_scale*tgt_scale-totalArea); + // std::cout << trueError << ", "; + std::cout << totalArea << std::endl; + std::cout << correctArea << std::endl; + return (totalArea - correctArea); } /*! @@ -424,39 +526,87 @@ struct Remapper // scale and translate the vertices of the given mesh void xformMesh(mfem::Mesh* mesh, double sc, double off) { + std::cout << "Transforming Mesh now" << std::endl; for(int v = 0; v < mesh->GetNV(); ++v) { double* pt = mesh->GetVertex(v); - pt[0] = sc * pt[0] + off; + // if (v==0) + // { + // std::cout << pt[0] << " , " << pt[1] << std::endl; + // } + // pt[0] = sc*pt[0] + off; + pt[0] = sc * pt[0] + off + .000147582957; pt[1] = sc * pt[1] + off; + + /* if (v%3==0) + { + pt[0] = pt[0]-.01748; + } + if (v%5==0) + { + pt[0] = pt[0]+.004; + } + if (v%7==0) + { + pt[1] = pt[1]+.0243; + }*/ + // if (v==0) + // { + // std::cout << pt[0] << " , " << pt[1] << std::endl; + // } } + // for (int e =0 ; e < mesh->GetNEdges() ; ++e) + // { + // mfem::array dofs(); + // GetEdgeDofs(e, dofs); + // } } }; //------------------------------------------------------------------------------ int main(int argc, char** argv) { - AXOM_UNUSED_VAR(argc); - AXOM_UNUSED_VAR(argv); - axom::slic::SimpleLogger logger; SLIC_INFO("The application conservatively maps fields from a source \n" << "high order mesh to a target high order mesh!"); - - Remapper remap; - - // Setup the two meshes in the Bernstein basis - // The current implementation hard-codes the two meshes - // TODO: Read in two meshes from disk. - // In that case, we will need to convert the FEC to the Bernstein basis - remap.setupMeshes(); - - // Set up the spatial index - remap.setupGrid(); - - // Computes the overlaps between elements of the target and source meshes - remap.computeOverlapAreas(); - + // int res1=1; + // Remapper remap; + // res1=5; + // int res2=res1+1; + // remap.loadMeshes(res1,2); + // remap.setupGrid(); + // double Area = remap.computeOverlapAreas(); + // std::cout << std::endl << Area << std::endl; + + // return 0; + + int res1 = 1; + std::cout << "["; + for(int i = 2; i <= 2; ++i) + { + Remapper remap; + + // res1= res1*2; + + res1 = 25; + int res2 = res1 + 2; + // Setup the two meshes in the Bernstein basis + // The current implementation hard-codes the two meshes + // TODO: Read in two meshes from disk. + // In that case, we will need to convert the FEC to the Bernstein basis + // remap.setupMeshes(res1, res2, i); + remap.loadMeshes(res1, i); + + // Set up the spatial index + remap.setupGrid(); + auto start = std::chrono::high_resolution_clock::now(); + // Computes the overlaps between elements of the target and source meshes + double Area = remap.computeOverlapAreas(); + auto finish = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = finish - start; + std::cout << i << ", " << Area << "," << elapsed.count() << std::endl; + } + std::cout << "]" << std::endl; return 0; } From b234e452258c3b3ab6a0536693ff4731316a9d7f Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 17 Sep 2019 16:03:15 -0700 Subject: [PATCH 09/38] Moved intersect curved poly into detail namespace --- src/axom/primal/CMakeLists.txt | 2 +- .../intersect_curved_poly_impl.hpp} | 82 ++++++++++++++----- src/axom/primal/operators/intersect.hpp | 40 +++++++++ .../quest/examples/quest_high_order_remap.cpp | 2 +- 4 files changed, 105 insertions(+), 21 deletions(-) rename src/axom/primal/operators/{intersect_curved_poly.hpp => detail/intersect_curved_poly_impl.hpp} (82%) diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 59e3ec95e9..d0cf59f466 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -45,13 +45,13 @@ set( primal_headers operators/compute_bounding_box.hpp operators/compute_moments.hpp operators/in_sphere.hpp - operators/intersect_curved_poly.hpp operators/split.hpp operators/detail/clip_impl.hpp operators/detail/compute_moments_impl.hpp operators/detail/intersect_bezier_impl.hpp operators/detail/intersect_bounding_box_impl.hpp + operators/detail/intersect_curved_poly_impl.hpp operators/detail/intersect_impl.hpp operators/detail/intersect_ray_impl.hpp diff --git a/src/axom/primal/operators/intersect_curved_poly.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp similarity index 82% rename from src/axom/primal/operators/intersect_curved_poly.hpp rename to src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 34b79c3b26..7fda704d93 100644 --- a/src/axom/primal/operators/intersect_curved_poly.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -4,13 +4,13 @@ // SPDX-License-Identifier: (BSD-3-Clause) /*! - * \file intersect_curved_poly.hpp + * \file intersect_curved_poly_impl.hpp * * \brief Consists of functions to test intersection among geometric primitives. */ -#ifndef AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_HPP_ -#define AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_HPP_ +#ifndef AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_IMPL_HPP_ +#define AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_IMPL_HPP_ #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/OrientedBoundingBox.hpp" @@ -25,18 +25,27 @@ #include "axom/core/utilities/Utilities.hpp" #include "axom/primal/operators/squared_distance.hpp" -#include "axom/primal/operators/intersect.hpp" +#include "axom/primal/operators/detail/intersect_bezier_impl.hpp" namespace axom { namespace primal { +namespace detail +{ template class IntersectionInfo; -/* + template -bool orient(BezierCurve c1, BezierCurve c2, T s, T t); -*/ +bool orient(const BezierCurve c1, + const BezierCurve c2, + T s, + T t); + +template +int isContained(const CurvedPolygon p1, + const CurvedPolygon p2, + double sq_tol = 1e-10); /*! * \brief Test whether CurvedPolygons p1 and p2 intersect and find intersection points @@ -48,7 +57,8 @@ bool orient(BezierCurve c1, BezierCurve c2, T s, T t); template bool intersect_polygon(CurvedPolygon& p1, CurvedPolygon& p2, - std::vector>& pnew) + std::vector>& pnew, + double sq_tol) { // Object to store intersections std::vector>> E1IntData(p1.numEdges()); @@ -63,7 +73,17 @@ bool intersect_polygon(CurvedPolygon& p1, { std::vector p1times; std::vector p2times; - intersect(p1[i], p2[j], p1times, p2times, 1e-10); + intersect_bezier_curves(p1[i], + p2[j], + p1times, + p2times, + sq_tol, + p1[i].getOrder(), + p2[j].getOrder(), + 1., + 0., + 1., + 0.); for(int k = 0; k < static_cast(p1times.size()); ++k) { E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + k + 1}); @@ -85,10 +105,10 @@ bool intersect_polygon(CurvedPolygon& p1, } // Orient the first intersection point to be sure we get the intersection - bool orientation = orient(p1[firstinter.myEdge], - p2[firstinter.otherEdge], - firstinter.myTime, - firstinter.otherTime); + bool orientation = detail::orient(p1[firstinter.myEdge], + p2[firstinter.otherEdge], + firstinter.myTime, + firstinter.otherTime); // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each // intersection and zeros for corners of original polygons. @@ -231,7 +251,7 @@ bool intersect_polygon(CurvedPolygon& p1, } else { - int containment = isContained(p1, p2); + int containment = isContained(p1, p2, sq_tol); if(containment == 0) { return false; @@ -249,7 +269,7 @@ bool intersect_polygon(CurvedPolygon& p1, return true; } } - // If there are no intersections, return false + // If there are no intersections return false; } @@ -261,7 +281,9 @@ bool intersect_polygon(CurvedPolygon& p1, * \return 0 if mutually exclusive, 1 if p1 is in p2, 2 if p2 is in p1 */ template -int isContained(const CurvedPolygon p1, const CurvedPolygon p2) +int isContained(const CurvedPolygon p1, + const CurvedPolygon p2, + double sq_tol) { const int NDIMS = 2; using PointType = primal::Point; @@ -278,7 +300,17 @@ int isContained(const CurvedPolygon p1, const CurvedPolygon p2) { std::vector temps; std::vector tempt; - intersect(LineGuess, p1[j], temps, tempt); + intersect_bezier_curves(LineGuess, + p1[j], + temps, + tempt, + sq_tol, + 1, + p1[j].getOrder(), + 1., + 0., + 1., + 0.); for(int i = 0; i < temps.size(); ++i) { if(temps[i] > line1s) @@ -293,7 +325,18 @@ int isContained(const CurvedPolygon p1, const CurvedPolygon p2) { std::vector temps; std::vector tempt; - intersect(LineGuess, p2[j], temps, tempt); + intersect_bezier_curves(LineGuess, + p2[j], + temps, + tempt, + sq_tol, + 1, + p2[j].getOrder(), + 1., + 0., + 1., + 0.); + intersect(LineGuess, p2[j], temps, tempt); // ? for(int i = 0; i < temps.size(); ++i) { if(temps[i] < line2s && temps[i] > line1s) @@ -344,7 +387,8 @@ class IntersectionInfo bool operator<(IntersectionInfo other) { return myTime < other.myTime; } }; +} // namespace detail } // namespace primal } // namespace axom -#endif // AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_HPP_ +#endif // AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_IMPL_HPP_ diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index f20eca17d0..7f25cea8c2 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -25,11 +25,13 @@ #include "axom/primal/geometry/Sphere.hpp" #include "axom/primal/geometry/Triangle.hpp" #include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/CurvedPolygon.hpp" #include "axom/primal/operators/detail/intersect_impl.hpp" #include "axom/primal/operators/detail/intersect_ray_impl.hpp" #include "axom/primal/operators/detail/intersect_bounding_box_impl.hpp" #include "axom/primal/operators/detail/intersect_bezier_impl.hpp" +#include "axom/primal/operators/detail/intersect_curved_poly_impl.hpp" namespace axom { @@ -477,6 +479,44 @@ bool intersect(const OrientedBoundingBox& b1, /// \name Bezier Curve Intersection Routines /// @{ +/*! + * \brief Tests if two CurvedPolygon \a p1 and \a p2 intersect. + * \return status true iff \a p1 intersects \a p2, otherwise false. + * + * \param [in] p1 the first CurvedPolygon + * \param [in] p2 the second CurvedPolygon + * \param [out] pnew vector of resulting CurvedPolygons + * \return True if the curvedPolygons intersect, false otherwise. + * + * Finds the set of curved polygons that bound the region of intersection between p1 and p2. + * + * \note This function assumes two dimensional curved polygons in a plane. + * + * \note This function assumes that the curved polygons are in general + * position. Specifically, we assume that all intersections are at points + * and that the component curves don't overlap. + * + * \note This function assumes the all intersections have multiplicity + * one, i.e. there are no points at which the component curves and their + * derivatives both intersect. Thus, the function does not find tangencies. + * + * \note This function assumes that the component curves are half-open, + * i.e. they contain their first endpoint, but not their last endpoint. + * Thus, component curves do not intersect at \f$ s==1 \f$ or at + * \f$ t==1 \f$. + */ +template +bool intersect(CurvedPolygon& p1, + CurvedPolygon& p2, + std::vector>& pnew, + double tol = 1e-7) +{ + // for efficiency, linearity check actually uses a squared tolerance + const double sq_tol = tol * tol; + + return detail::intersect_polygon(p1, p2, pnew, sq_tol); +} + /*! * \brief Tests if two Bezier Curves \a c1 and \a c2 intersect. * \return status true iff \a c1 intersects \a c2, otherwise false. diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index 3766e9f1c8..e5fb9b37f0 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -451,7 +451,7 @@ struct Remapper std::vector> pnew; tgtPoly.reverseOrientation(); srcPoly.reverseOrientation(); - if(primal::intersect_polygon(tgtPoly, srcPoly, pnew)) + if(primal::intersect(tgtPoly, srcPoly, pnew)) { for(int i = 0; i < static_cast(pnew.size()); ++i) { From 0f0308e013281a3f5168d0ccff4b63b2feefd842 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Wed, 18 Sep 2019 09:44:56 -0700 Subject: [PATCH 10/38] refactoring intersect_curved_polygon --- .../detail/intersect_curved_poly_impl.hpp | 158 ++++++++++++------ src/axom/primal/operators/intersect.hpp | 2 +- 2 files changed, 105 insertions(+), 55 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 7fda704d93..42b8feb155 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -1,5 +1,5 @@ // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and -// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// other Axom Project Developers. See the top-level LICENSE file for details. // // SPDX-License-Identifier: (BSD-3-Clause) @@ -48,11 +48,12 @@ int isContained(const CurvedPolygon p1, double sq_tol = 1e-10); /*! - * \brief Test whether CurvedPolygons p1 and p2 intersect and find intersection points + * \brief Test whether the regions within CurvedPolygons p1 and p2 intersect. * \return status true iff p1 intersects with p2, otherwise false. * - * \param p1, p2 CurvedPolygon objects to intersect - * \param pnew vector of type CurvedPolygon holding intersection regions oriented as the original curves were. + * \param [in] p1, p2 CurvedPolygon objects to intersect + * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curve + * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. */ template bool intersect_polygon(CurvedPolygon& p1, @@ -80,14 +81,15 @@ bool intersect_polygon(CurvedPolygon& p1, sq_tol, p1[i].getOrder(), p2[j].getOrder(), - 1., 0., 1., - 0.); + 0., + 1.); for(int k = 0; k < static_cast(p1times.size()); ++k) { E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + k + 1}); E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + k + 1}); + // std::cout << p1times[k] << ',' << p2times[k] << std::endl; if(numinters == 0) { firstinter = {p1times[0], i, p2times[0], j, 1}; @@ -110,52 +112,63 @@ bool intersect_polygon(CurvedPolygon& p1, firstinter.myTime, firstinter.otherTime); - // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each - // intersection and zeros for corners of original polygons. + // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each intersection and zeros for corners of original polygons. std::vector edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points CurvedPolygon psplit[2]; // The two completely split polygons will be stored in this array psplit[0] = p1; psplit[1] = p2; //split polygon 1 at all the intersection points and store as psplit[0] + /* int addedints = 0; for(int i = 0; i < p1.numEdges(); ++i) { edgelabels[0].push_back(0); for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) { - psplit[0].splitEdge(i + addedints, E1IntData[i][j].myTime); - edgelabels[0].insert(edgelabels[0].begin() + i + addedints, - E1IntData[i][j].numinter); - addedints += 1; - for(int k = j + 1; k < static_cast(E1IntData[i].size()); ++k) + edgelabels[0].push_back(0); + for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) { - E1IntData[i][k].myTime = - (E1IntData[i][k].myTime - E1IntData[i][j].myTime) / - (1 - E1IntData[i][j].myTime); + psplit[0].splitEdge(i + addedints, E1IntData[i][j].myTime); + edgelabels[0].insert(edgelabels[0].begin() + i + addedints, + E1IntData[i][j].numinter); + addedints += 1; + for(int k = j + 1; k < static_cast(E1IntData[i].size()); ++k) + { + E1IntData[i][k].myTime = + (E1IntData[i][k].myTime - E1IntData[i][j].myTime) / + (1 - E1IntData[i][j].myTime); + } } } } - - //split polygon 2 at all the intersection points and store as psplit[1] addedints = 0; for(int i = 0; i < p2.numEdges(); ++i) { edgelabels[1].push_back(0); for(int j = 0; j < static_cast(E2IntData[i].size()); ++j) { - psplit[1].splitEdge(i + addedints, E2IntData[i][j].myTime); - edgelabels[1].insert(edgelabels[1].begin() + i + addedints, - E2IntData[i][j].numinter); - addedints += 1; - for(int k = j + 1; k < static_cast(E2IntData[i].size()); ++k) + edgelabels[1].push_back(0); + for(int j = 0; j < static_cast(E2IntData[i].size()); ++j) { - E2IntData[i][k].myTime = - (E2IntData[i][k].myTime - E2IntData[i][j].myTime) / - (1 - E2IntData[i][j].myTime); + psplit[1].splitEdge(i + addedints, E2IntData[i][j].myTime); + edgelabels[1].insert(edgelabels[1].begin() + i + addedints, + E2IntData[i][j].numinter); + addedints += 1; + for(int k = j + 1; k < static_cast(E2IntData[i].size()); ++k) + { + E2IntData[i][k].myTime = + (E2IntData[i][k].myTime - E2IntData[i][j].myTime) / + (1 - E2IntData[i][j].myTime); + } } } } + */ + + splitPolygon(psplit[0], E1IntData, edgelabels[0]); + splitPolygon(psplit[1], E2IntData, edgelabels[1]); + //split polygon 2 at all the intersection points and store as psplit[1] // This performs the directional walking method using the completely split polygons std::vector::iterator> usedlabels; @@ -249,35 +262,28 @@ bool intersect_polygon(CurvedPolygon& p1, } return true; } - else + else // If there are no intersection points, check for containment { int containment = isContained(p1, p2, sq_tol); - if(containment == 0) + switch(containment) { + case 0: return false; - } - else - { - if(containment == 1) - { - pnew.push_back(p1); - } - else - { - pnew.push_back(p2); - } + case 1: + pnew.push_back(p1); + return true; + case 2: + pnew.push_back(p2); return true; } + return false; // Catch } - // If there are no intersections - return false; } -/*! - * \brief Checks if two polygons are mutually exclusive or if one includes the other, - * assuming that they have no intersection points +/*! \brief Checks if two polygons are mutually exclusive or if one includes the other, assuming that they have no intersection points * * \param [in] p1, p2 CurvedPolygons to be tested + * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curves * \return 0 if mutually exclusive, 1 if p1 is in p2, 2 if p2 is in p1 */ template @@ -311,7 +317,7 @@ int isContained(const CurvedPolygon p1, 0., 1., 0.); - for(int i = 0; i < temps.size(); ++i) + for(int i = 0; i < static_cast(temps.size()); ++i) { if(temps[i] > line1s) { @@ -336,8 +342,8 @@ int isContained(const CurvedPolygon p1, 0., 1., 0.); - intersect(LineGuess, p2[j], temps, tempt); // ? - for(int i = 0; i < temps.size(); ++i) + intersect(LineGuess, p2[j], temps, tempt); + for(int i = 0; i < static_cast(temps.size()); ++i) { if(temps[i] < line2s && temps[i] > line1s) { @@ -365,7 +371,44 @@ int isContained(const CurvedPolygon p1, } } -// This determines with curve is "more" counterclockwise using the cross product of tangents +/* + * \brief Splits a CurvedPolygon p1 at every intersection point stored in IntersectionData + */ +template +void splitPolygon(CurvedPolygon& p1, + std::vector>>& IntersectionData, + std::vector& edgelabels) +{ + const int nEd = p1.numEdges(); + //split polygon 2 at all the intersection points and store as psplit[1] + int addedints = 0; + for(int i = 0; i < nEd; ++i) + { + edgelabels.push_back(0); + for(int j = 0; j < static_cast(IntersectionData[i].size()); ++j) + { + p1.splitEdge(i + addedints, IntersectionData[i][j].myTime); + edgelabels.insert(edgelabels.begin() + i + addedints, + IntersectionData[i][j].numinter); + addedints += 1; + for(int k = j + 1; k < static_cast(IntersectionData[i].size()); ++k) + { + IntersectionData[i][k].myTime = + (IntersectionData[i][k].myTime - IntersectionData[i][j].myTime) / + (1 - IntersectionData[i][j].myTime); + } + } + } +} + +/* \brief Determines orientation of a bezier curve c1 with respect to another bezier curve c2, given that they intersect at parameter values s and t + * + * \param [in] c1 the first bezier curve + * \param [in] c2 the second bezier curve + * \param [in] s the parameter value of intersection on c1 + * \param [in] t the parameter value of intersection on c2 + * \return True if the c1's positive direction is counterclockwise from c2's positive direction + */ template bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) { @@ -374,16 +417,23 @@ bool orient(const BezierCurve c1, const BezierCurve c2, T s, return (orientation > 0); } -// A class for storing intersection points so they can be easily sorted by parameter value +/*! \class IntersectionInfo + * + * \brief For storing intersection points between CurvedPolygons so they can be easily sorted by parameter value using std::sort + */ template class IntersectionInfo { public: - T myTime; - int myEdge; - T otherTime; - int otherEdge; - int numinter; + T myTime; // parameter value of intersection on curve on first CurvePolygon + int myEdge; // index of curve on first CurvedPolygon + T otherTime; // parameter value of intersection on curve on second CurvedPolygon + int otherEdge; // index of curve on second CurvedPolygon + int numinter; // unique intersection point identifier + + /*! + * \brief Comparison operator for sorting by parameter value + */ bool operator<(IntersectionInfo other) { return myTime < other.myTime; } }; diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 7f25cea8c2..6242684866 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -509,7 +509,7 @@ template bool intersect(CurvedPolygon& p1, CurvedPolygon& p2, std::vector>& pnew, - double tol = 1e-7) + double tol = 1e-8) { // for efficiency, linearity check actually uses a squared tolerance const double sq_tol = tol * tol; From c97f0e9cdbe1e51d407f8680b855bb950c498609 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 1 Oct 2019 11:45:39 -0700 Subject: [PATCH 11/38] Adds interection tests for curvedpolygon Note (KW): This consolidates tests from several commits that were originally in the `primal::CurvedPolygon` tests. They were refactored when splitting a larger feature branch into separate features for adding a curved polygon and another for intersecting curved polygons. --- src/axom/primal/tests/CMakeLists.txt | 1 + .../tests/primal_curved_polygon_intersect.cpp | 515 ++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 src/axom/primal/tests/primal_curved_polygon_intersect.cpp diff --git a/src/axom/primal/tests/CMakeLists.txt b/src/axom/primal/tests/CMakeLists.txt index 6b11631efd..992874d297 100644 --- a/src/axom/primal/tests/CMakeLists.txt +++ b/src/axom/primal/tests/CMakeLists.txt @@ -15,6 +15,7 @@ set( primal_tests primal_compute_bounding_box.cpp primal_compute_moments.cpp primal_curved_polygon.cpp + primal_curved_polygon_intersect.cpp primal_in_sphere.cpp primal_intersect.cpp primal_intersect_impl.cpp diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp new file mode 100644 index 0000000000..cd9383c0a9 --- /dev/null +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -0,0 +1,515 @@ +// Copyright (c) 2017-2021, 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_curved_polygon_intersect.cpp + * /brief This file tests intersections of CurvedPolygon instances + */ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/primal.hpp" + +namespace primal = axom::primal; + +/** + * Helper function to compute the area and centroid of a curved polygon and to check that they match expectations, stored in \a expArea and \a expCentroid. Areas and Moments are computed within tolerance \a eps and checks use \a test_eps. + */ +template +void checkMoments(const primal::CurvedPolygon& bPolygon, + const CoordType expArea, + const primal::Point& expMoment, + double eps, + double test_eps) +{ + EXPECT_NEAR(expArea, primal::area(bPolygon, eps), test_eps); + for(int i = 0; i < DIM; ++i) + { + EXPECT_NEAR(expMoment[i], primal::centroid(bPolygon, eps)[i], test_eps); + } +} + +template +primal::CurvedPolygon createPolygon( + std::vector> ControlPoints, + std::vector orders) +{ + using PointType = primal::Point; + using CurvedPolygonType = primal::CurvedPolygon; + using BezierCurveType = primal::BezierCurve; + + const int num_edges = orders.size(); + const int num_unique_control_points = ControlPoints.size(); + + //std::cout << num_edges << ", " << num_unique_control_points << std::endl; + //std::cout << ControlPoints << std::endl; + for(int i = 0; i < num_edges; ++i) + { + std::cout << orders[i] << std::endl; + } + + //checks if the orders and control points given will give a valid polygon + EXPECT_EQ(accumulate(orders.begin(), orders.end(), 0) + 1, + num_unique_control_points); + + CurvedPolygonType bPolygon; + int iter = 0; + for(int j = 0; j < num_edges; ++j) + { + std::vector subCP; + subCP.assign(ControlPoints.begin() + iter, + ControlPoints.begin() + iter + orders[j] + 1); + BezierCurveType addCurve(subCP, orders[j]); + bPolygon.addEdge(addCurve); + iter += (orders[j]); + } + std::cout << bPolygon << std::endl; + return bPolygon; +} + +TEST(primal_curvedpolygon, intersection_triangle_linear) +{ + const int DIM = 2; + const int order = 1; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test intersecting two linear triangular CurvedPolygons (single region)."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, order); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, order); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, order); + bPolygon.addEdge(bCurve3); + CurvedPolygonType bPolygon2 = bPolygon; + for(int i = 0; i < DIM; ++i) + { + for(int j = 0; j <= order; ++j) + { + for(int k = 0; k < bPolygon2.numEdges(); ++k) + { + bPolygon2[k][j][i] += .11; + } + } + } + + PointType expcontrolPoints[order + 1] = { + PointType::make_point(0.3091666666666666666666, 1.9755555555555555555), + PointType::make_point(0.11, 1.71)}; + + PointType expcontrolPoints2[order + 1] = { + PointType::make_point(0.11, 1.71), + PointType::make_point(0.5083333333333333333, 1.44444444444444444444)}; + + PointType expcontrolPoints3[order + 1] = { + PointType::make_point(0.5083333333333333333, 1.44444444444444444444), + PointType::make_point(0.3091666666666666666666, 1.9755555555555555555)}; + + CurvedPolygonType expbPolygon; + + BezierCurveType expbCurve(expcontrolPoints, order); + expbPolygon.addEdge(expbCurve); + BezierCurveType expbCurve2(expcontrolPoints2, order); + expbPolygon.addEdge(expbCurve2); + BezierCurveType expbCurve3(expcontrolPoints3, order); + expbPolygon.addEdge(expbCurve3); + + std::vector bPolygons3; + bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); + EXPECT_TRUE(didIntersect); + std::cout << bPolygons3.size() << std::endl; + // int nEd= bPolygons3.size(); + for(int i = 0; i < static_cast(bPolygons3.size()); ++i) + { + std::cout << bPolygons3[i] << std::endl; + } + for(int i = 0; i < DIM; ++i) + { + for(int j = 0; j <= order; ++j) + { + for(int idxcurve = 0; idxcurve < static_cast(bPolygons3.size()); + ++idxcurve) + { + for(int k = 0; k < bPolygons3[idxcurve].numEdges(); ++k) + { + EXPECT_NEAR(expbPolygon[k][j][i], bPolygons3[idxcurve][k][j][i], 1e-10); + /*EXPECT_TRUE(axom::utilities::isNearlyEqual(expbPolygon[k][j][i],bPolygons3[idxcurve][k][j][i],2.));*/ + } + } + } + } +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, intersection_triangle_quadratic) +{ + const int DIM = 2; + const int order = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test intersecting two quadratic triangular CurvedPolygons (single " + "region)."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.4, 1.3), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.27, 1.5), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.1, 1.5), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, order); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, order); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, order); + bPolygon.addEdge(bCurve3); + CurvedPolygonType bPolygon2 = bPolygon; + for(int i = 0; i < DIM; ++i) + { + for(int j = 0; j <= order; ++j) + { + for(int k = 0; k < bPolygon2.numEdges(); ++k) + { + bPolygon2[k][j][i] += .11; + } + } + } + + PointType expcontrolPoints[order + 1] = { + PointType::make_point(0.335956890729522, 1.784126953773395), + PointType::make_point(0.297344765794753, 1.718171485335525), + PointType::make_point(0.239567753301698, 1.700128235793372)}; + + PointType expcontrolPoints2[order + 1] = { + PointType::make_point(0.2395677533016981, 1.700128235793371), + PointType::make_point(0.221884203146682, 1.662410644580941), + PointType::make_point(0.199328465398189, 1.636873522352205)}; + + PointType expcontrolPoints3[order + 1] = { + PointType::make_point(0.199328465398188, 1.636873522352206), + PointType::make_point(0.277429214338182, 1.579562422716502), + PointType::make_point(0.408882616650578, 1.495574996394597)}; + + PointType expcontrolPoints4[order + 1] = { + PointType::make_point(0.408882616650588, 1.495574996394586), + PointType::make_point(0.368520120719339, 1.616453177259694), + PointType::make_point(0.335956890729522, 1.784126953773394)}; + + CurvedPolygonType expbPolygon; + + BezierCurveType expbCurve(expcontrolPoints, order); + expbPolygon.addEdge(expbCurve); + BezierCurveType expbCurve2(expcontrolPoints2, order); + expbPolygon.addEdge(expbCurve2); + BezierCurveType expbCurve3(expcontrolPoints3, order); + expbPolygon.addEdge(expbCurve3); + BezierCurveType expbCurve4(expcontrolPoints4, order); + expbPolygon.addEdge(expbCurve4); + + std::vector bPolygons3; + bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); + EXPECT_TRUE(didIntersect); + + for(int i = 0; i < DIM; ++i) + { + for(int j = 0; j <= order; ++j) + { + for(int idxcurve = 0; idxcurve < static_cast(bPolygons3.size()); + ++idxcurve) + { + for(int k = 0; k < bPolygons3[idxcurve].numEdges(); ++k) + { + EXPECT_TRUE(axom::utilities::isNearlyEqual(expbPolygon[k][j][i], + bPolygons3[idxcurve][k][j][i], + 2.)); + } + } + } + } +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, area_intersection_triangle_linear) +{ + const int DIM = 2; + const int order = 1; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test finding area of intersection two linear triangular CurvedPolygons " + "(single region)."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, order); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, order); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, order); + bPolygon.addEdge(bCurve3); + CurvedPolygonType bPolygon2 = bPolygon; + for(int i = 0; i < DIM; ++i) + { + for(int j = 0; j <= order; ++j) + { + for(int k = 0; k < bPolygon2.numEdges(); ++k) + { + bPolygon2[k][j][i] += .11; + } + } + } + + std::vector bPolygons3; + bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); + EXPECT_TRUE(didIntersect); + + CoordType A = 0.0; + for(int i = 0; i < static_cast(bPolygons3.size()); ++i) + { + A += primal::area(bPolygons3[i], 1e-14); + } + CoordType expA = -0.0793347222222222222; + EXPECT_NEAR(A, expA, 1e-10); +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, area_intersection_triangle_quadratic) +{ + const int DIM = 2; + const int order = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test intersecting two quadratic triangular CurvedPolygons (single " + "region)."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.4, 1.3), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.27, 1.5), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.1, 1.5), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, order); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, order); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, order); + bPolygon.addEdge(bCurve3); + CurvedPolygonType bPolygon2 = bPolygon; + for(int i = 0; i < DIM; ++i) + { + for(int j = 0; j <= order; ++j) + { + for(int k = 0; k < bPolygon2.numEdges(); ++k) + { + bPolygon2[k][j][i] += .11; + } + } + } + + std::vector bPolygons3; + bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3, 1e-14); + EXPECT_TRUE(didIntersect); + + CoordType A = 0.0; + for(int i = 0; i < static_cast(bPolygons3.size()); ++i) + { + A += primal::area(bPolygons3[i], 1e-8); + } + CoordType expA = -0.024649833203616; + EXPECT_NEAR(A, expA, 1e-10); +} + +TEST(primal_curvedpolygon, area_intersection_triangle_quadratic_two_regions) +{ + const int DIM = 2; + const int order = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test intersecting two quadratic triangular CurvedPolygons (two regions)."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.4, 1.3), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.27, 1.5), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.1, 1.5), + PointType::make_point(0.6, 1.2)}; + + PointType controlPoints4[order + 1] = {PointType::make_point(1.0205, 1.6699), + PointType::make_point(0.8339, 1.5467), + PointType::make_point(0.1777, 1.8101)}; + + PointType controlPoints5[order + 1] = {PointType::make_point(0.1777, 1.8101), + PointType::make_point(0.5957, 1.5341), + PointType::make_point(0.3741, 1.3503)}; + + PointType controlPoints6[order + 1] = {PointType::make_point(0.3741, 1.3503), + PointType::make_point(0.5107, 1.3869), + PointType::make_point(1.0205, 1.6699)}; + CurvedPolygonType bPolygon2; + + BezierCurveType bCurve(controlPoints, order); + BezierCurveType bCurve4(controlPoints4, order); + bPolygon2.addEdge(bCurve4); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, order); + BezierCurveType bCurve5(controlPoints5, order); + bPolygon2.addEdge(bCurve5); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, order); + BezierCurveType bCurve6(controlPoints6, order); + bPolygon2.addEdge(bCurve6); + bPolygon.addEdge(bCurve3); + + std::vector bPolygons3; + bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); + EXPECT_TRUE(didIntersect); + EXPECT_EQ(bPolygons3.size(), 2); +} + +TEST(primal_curvedpolygon, area_intersection_triangle_inclusion) +{ + const int DIM = 2; + const int order = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test intersecting two quadratic triangular CurvedPolygons (inclusion)."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[order + 1] = {PointType::make_point(0.0, 0.0), + PointType::make_point(0.5, 0.0), + PointType::make_point(1.0, 0.0)}; + + PointType controlPoints2[order + 1] = {PointType::make_point(1.0, 0.0), + PointType::make_point(0.5, 0.5), + PointType::make_point(0.0, 1.0)}; + + PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.0), + PointType::make_point(0.0, 0.5), + PointType::make_point(0.0, 0.0)}; + + BezierCurveType bCurve(controlPoints, order); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, order); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, order); + bPolygon.addEdge(bCurve3); + + CurvedPolygonType bPolygon2 = bPolygon; + for(int i = 0; i < DIM; ++i) + { + for(int j = 0; j <= order; ++j) + { + for(int k = 0; k < bPolygon2.numEdges(); ++k) + { + bPolygon2[k][j][i] = bPolygon2[k][j][i] * .5 + .05; + } + } + } + + std::vector bPolygons3; + bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); + bPolygons3.clear(); + bool didIntersect2 = intersect(bPolygon2, bPolygon, bPolygons3); + EXPECT_TRUE(didIntersect); + EXPECT_TRUE(didIntersect2); + EXPECT_EQ(bPolygons3.size(), 1); +} +//---------------------------------------------------------------------------------- +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} From d0a89a17f7a893d88db14340088af1100c88de6e Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Wed, 2 Oct 2019 09:04:27 -0700 Subject: [PATCH 12/38] Refactored CurvedPolygon intersection tests --- .../detail/intersect_curved_poly_impl.hpp | 54 +- src/axom/primal/operators/intersect.hpp | 6 +- .../tests/primal_curved_polygon_intersect.cpp | 531 +++++------------- 3 files changed, 155 insertions(+), 436 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 42b8feb155..df2a0439af 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -56,8 +56,8 @@ int isContained(const CurvedPolygon p1, * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. */ template -bool intersect_polygon(CurvedPolygon& p1, - CurvedPolygon& p2, +bool intersect_polygon(const CurvedPolygon& p1, + const CurvedPolygon& p2, std::vector>& pnew, double sq_tol) { @@ -89,7 +89,6 @@ bool intersect_polygon(CurvedPolygon& p1, { E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + k + 1}); E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + k + 1}); - // std::cout << p1times[k] << ',' << p2times[k] << std::endl; if(numinters == 0) { firstinter = {p1times[0], i, p2times[0], j, 1}; @@ -118,57 +117,8 @@ bool intersect_polygon(CurvedPolygon& p1, psplit[0] = p1; psplit[1] = p2; - //split polygon 1 at all the intersection points and store as psplit[0] - /* - int addedints = 0; - for(int i = 0; i < p1.numEdges(); ++i) - { - edgelabels[0].push_back(0); - for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) - { - edgelabels[0].push_back(0); - for(int j = 0; j < static_cast(E1IntData[i].size()); ++j) - { - psplit[0].splitEdge(i + addedints, E1IntData[i][j].myTime); - edgelabels[0].insert(edgelabels[0].begin() + i + addedints, - E1IntData[i][j].numinter); - addedints += 1; - for(int k = j + 1; k < static_cast(E1IntData[i].size()); ++k) - { - E1IntData[i][k].myTime = - (E1IntData[i][k].myTime - E1IntData[i][j].myTime) / - (1 - E1IntData[i][j].myTime); - } - } - } - } - addedints = 0; - for(int i = 0; i < p2.numEdges(); ++i) - { - edgelabels[1].push_back(0); - for(int j = 0; j < static_cast(E2IntData[i].size()); ++j) - { - edgelabels[1].push_back(0); - for(int j = 0; j < static_cast(E2IntData[i].size()); ++j) - { - psplit[1].splitEdge(i + addedints, E2IntData[i][j].myTime); - edgelabels[1].insert(edgelabels[1].begin() + i + addedints, - E2IntData[i][j].numinter); - addedints += 1; - for(int k = j + 1; k < static_cast(E2IntData[i].size()); ++k) - { - E2IntData[i][k].myTime = - (E2IntData[i][k].myTime - E2IntData[i][j].myTime) / - (1 - E2IntData[i][j].myTime); - } - } - } - } - */ - splitPolygon(psplit[0], E1IntData, edgelabels[0]); splitPolygon(psplit[1], E2IntData, edgelabels[1]); - //split polygon 2 at all the intersection points and store as psplit[1] // This performs the directional walking method using the completely split polygons std::vector::iterator> usedlabels; diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 6242684866..e9e8c3c29f 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -506,10 +506,10 @@ bool intersect(const OrientedBoundingBox& b1, * \f$ t==1 \f$. */ template -bool intersect(CurvedPolygon& p1, - CurvedPolygon& p2, +bool intersect(const CurvedPolygon& p1, + const CurvedPolygon& p2, std::vector>& pnew, - double tol = 1e-8) + double tol = 1E-8) { // for efficiency, linearity check actually uses a squared tolerance const double sq_tol = tol * tol; diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index cd9383c0a9..9de7feb467 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -15,26 +15,52 @@ namespace primal = axom::primal; /** - * Helper function to compute the area and centroid of a curved polygon and to check that they match expectations, stored in \a expArea and \a expCentroid. Areas and Moments are computed within tolerance \a eps and checks use \a test_eps. + * Helper function to compute the set of intersection polygons given two input polygons and to check that they match expectations, stored in \a expbPolygon. Intersection polygon is computed to within tolerance \a eps and checks use \a test_eps. */ template -void checkMoments(const primal::CurvedPolygon& bPolygon, - const CoordType expArea, - const primal::Point& expMoment, - double eps, - double test_eps) +void checkIntersection( + const primal::CurvedPolygon& bPolygon1, + const primal::CurvedPolygon& bPolygon2, + const std::vector> expbPolygon, + const double eps = 1e-15, + const double test_eps = 1e-13) { - EXPECT_NEAR(expArea, primal::area(bPolygon, eps), test_eps); + using CurvedPolygonType = primal::CurvedPolygon; + + std::vector intersectionPolys; + + //Compute intersection using algorithm with tolerance of eps + intersect(bPolygon1, bPolygon2, intersectionPolys, eps); + //Check that expected number of intersection regions are found + EXPECT_EQ(expbPolygon.size(), intersectionPolys.size()); + + //Check that expected intersection curves are found to within test_eps for(int i = 0; i < DIM; ++i) { - EXPECT_NEAR(expMoment[i], primal::centroid(bPolygon, eps)[i], test_eps); + int sz = intersectionPolys.size(); + for(int idxcurve = 0; idxcurve < sz; ++idxcurve) + { + int nEd = intersectionPolys[idxcurve].numEdges(); + for(int k = 0; k < nEd; ++k) + { + int ord = intersectionPolys[idxcurve][k].getOrder(); + for(int j = 0; j <= ord; ++j) + { + EXPECT_NEAR(expbPolygon[idxcurve][k][j][i], + intersectionPolys[idxcurve][k][j][i], + test_eps); + } + } + } } } +/* Helper function to create a CurvedPolygon from a list of control points and a list of orders of component curves. Control points should be given as a list of Points in order of orientation with no duplicates except that the first control point should also be the last control point (if the polygon is closed). Orders should be given as a list of ints in order of orientation, representing the orders of the component curves. + */ template primal::CurvedPolygon createPolygon( - std::vector> ControlPoints, - std::vector orders) + const std::vector> ControlPoints, + const std::vector orders) { using PointType = primal::Point; using CurvedPolygonType = primal::CurvedPolygon; @@ -43,17 +69,11 @@ primal::CurvedPolygon createPolygon( const int num_edges = orders.size(); const int num_unique_control_points = ControlPoints.size(); - //std::cout << num_edges << ", " << num_unique_control_points << std::endl; - //std::cout << ControlPoints << std::endl; - for(int i = 0; i < num_edges; ++i) - { - std::cout << orders[i] << std::endl; - } - - //checks if the orders and control points given will give a valid polygon + //checks if the orders and control points given will yield a valid curved polygon EXPECT_EQ(accumulate(orders.begin(), orders.end(), 0) + 1, num_unique_control_points); + //Converts the control points to BezierCurves of specified orders and stores them in a CurvedPolygon object. CurvedPolygonType bPolygon; int iter = 0; for(int j = 0; j < num_edges; ++j) @@ -65,110 +85,53 @@ primal::CurvedPolygon createPolygon( bPolygon.addEdge(addCurve); iter += (orders[j]); } - std::cout << bPolygon << std::endl; return bPolygon; } TEST(primal_curvedpolygon, intersection_triangle_linear) { const int DIM = 2; - const int order = 1; using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; SLIC_INFO( "Test intersecting two linear triangular CurvedPolygons (single region)."); - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; + std::vector CP = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6), + PointType::make_point(0.6, 1.2)}; + std::vector orders = {1, 1, 1}; - PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; + CurvedPolygonType bPolygon1 = createPolygon(CP, orders); - PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; + std::vector CP2 = {PointType::make_point(0.71, 1.31), + PointType::make_point(0.41, 2.11), + PointType::make_point(0.11, 1.71), + PointType::make_point(0.71, 1.31)}; - BezierCurveType bCurve(controlPoints, order); - bPolygon.addEdge(bCurve); + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); - BezierCurveType bCurve2(controlPoints2, order); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, order); - bPolygon.addEdge(bCurve3); - CurvedPolygonType bPolygon2 = bPolygon; - for(int i = 0; i < DIM; ++i) - { - for(int j = 0; j <= order; ++j) - { - for(int k = 0; k < bPolygon2.numEdges(); ++k) - { - bPolygon2[k][j][i] += .11; - } - } - } - - PointType expcontrolPoints[order + 1] = { + std::vector expCP = { PointType::make_point(0.3091666666666666666666, 1.9755555555555555555), - PointType::make_point(0.11, 1.71)}; - - PointType expcontrolPoints2[order + 1] = { PointType::make_point(0.11, 1.71), - PointType::make_point(0.5083333333333333333, 1.44444444444444444444)}; - - PointType expcontrolPoints3[order + 1] = { PointType::make_point(0.5083333333333333333, 1.44444444444444444444), PointType::make_point(0.3091666666666666666666, 1.9755555555555555555)}; + std::vector exporders = {1, 1, 1}; + CurvedPolygonType expbPolygon = createPolygon(expCP, exporders); - CurvedPolygonType expbPolygon; - - BezierCurveType expbCurve(expcontrolPoints, order); - expbPolygon.addEdge(expbCurve); - BezierCurveType expbCurve2(expcontrolPoints2, order); - expbPolygon.addEdge(expbCurve2); - BezierCurveType expbCurve3(expcontrolPoints3, order); - expbPolygon.addEdge(expbCurve3); - - std::vector bPolygons3; - bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); - EXPECT_TRUE(didIntersect); - std::cout << bPolygons3.size() << std::endl; - // int nEd= bPolygons3.size(); - for(int i = 0; i < static_cast(bPolygons3.size()); ++i) - { - std::cout << bPolygons3[i] << std::endl; - } - for(int i = 0; i < DIM; ++i) - { - for(int j = 0; j <= order; ++j) - { - for(int idxcurve = 0; idxcurve < static_cast(bPolygons3.size()); - ++idxcurve) - { - for(int k = 0; k < bPolygons3[idxcurve].numEdges(); ++k) - { - EXPECT_NEAR(expbPolygon[k][j][i], bPolygons3[idxcurve][k][j][i], 1e-10); - /*EXPECT_TRUE(axom::utilities::isNearlyEqual(expbPolygon[k][j][i],bPolygons3[idxcurve][k][j][i],2.));*/ - } - } - } - } + std::vector expbPolygons {expbPolygon}; + checkIntersection(bPolygon1, bPolygon2, expbPolygons); } //---------------------------------------------------------------------------------- TEST(primal_curvedpolygon, intersection_triangle_quadratic) { const int DIM = 2; - const int order = 2; using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; SLIC_INFO( "Test intersecting two quadratic triangular CurvedPolygons (single " @@ -177,328 +140,134 @@ TEST(primal_curvedpolygon, intersection_triangle_quadratic) CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); - PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.4, 1.3), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.27, 1.5), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.1, 1.5), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, order); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, order); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, order); - bPolygon.addEdge(bCurve3); - CurvedPolygonType bPolygon2 = bPolygon; - for(int i = 0; i < DIM; ++i) - { - for(int j = 0; j <= order; ++j) - { - for(int k = 0; k < bPolygon2.numEdges(); ++k) - { - bPolygon2[k][j][i] += .11; - } - } - } - - PointType expcontrolPoints[order + 1] = { + std::vector CP = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.4, 1.3), + PointType::make_point(0.3, 2.0), + PointType::make_point(0.27, 1.5), + PointType::make_point(0.0, 1.6), + PointType::make_point(0.1, 1.5), + PointType::make_point(0.6, 1.2)}; + std::vector orders = {2, 2, 2}; + CurvedPolygonType bPolygon1 = createPolygon(CP, orders); + + std::vector CP2 = {PointType::make_point(0.71, 1.31), + PointType::make_point(0.51, 1.41), + PointType::make_point(0.41, 2.11), + PointType::make_point(0.38, 1.61), + PointType::make_point(0.11, 1.71), + PointType::make_point(0.21, 1.61), + PointType::make_point(0.71, 1.31)}; + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + + std::vector expCP = { PointType::make_point(0.335956890729522, 1.784126953773395), PointType::make_point(0.297344765794753, 1.718171485335525), - PointType::make_point(0.239567753301698, 1.700128235793372)}; - - PointType expcontrolPoints2[order + 1] = { PointType::make_point(0.2395677533016981, 1.700128235793371), PointType::make_point(0.221884203146682, 1.662410644580941), - PointType::make_point(0.199328465398189, 1.636873522352205)}; - - PointType expcontrolPoints3[order + 1] = { - PointType::make_point(0.199328465398188, 1.636873522352206), + PointType::make_point(0.199328465398189, 1.636873522352205), PointType::make_point(0.277429214338182, 1.579562422716502), - PointType::make_point(0.408882616650578, 1.495574996394597)}; - - PointType expcontrolPoints4[order + 1] = { - PointType::make_point(0.408882616650588, 1.495574996394586), + PointType::make_point(0.408882616650578, 1.495574996394597), PointType::make_point(0.368520120719339, 1.616453177259694), PointType::make_point(0.335956890729522, 1.784126953773394)}; + std::vector exporders = {2, 2, 2, 2}; + CurvedPolygonType expbPolygon = createPolygon(expCP, exporders); + std::vector expbPolygons = {expbPolygon}; - CurvedPolygonType expbPolygon; - - BezierCurveType expbCurve(expcontrolPoints, order); - expbPolygon.addEdge(expbCurve); - BezierCurveType expbCurve2(expcontrolPoints2, order); - expbPolygon.addEdge(expbCurve2); - BezierCurveType expbCurve3(expcontrolPoints3, order); - expbPolygon.addEdge(expbCurve3); - BezierCurveType expbCurve4(expcontrolPoints4, order); - expbPolygon.addEdge(expbCurve4); - - std::vector bPolygons3; - bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); - EXPECT_TRUE(didIntersect); - - for(int i = 0; i < DIM; ++i) - { - for(int j = 0; j <= order; ++j) - { - for(int idxcurve = 0; idxcurve < static_cast(bPolygons3.size()); - ++idxcurve) - { - for(int k = 0; k < bPolygons3[idxcurve].numEdges(); ++k) - { - EXPECT_TRUE(axom::utilities::isNearlyEqual(expbPolygon[k][j][i], - bPolygons3[idxcurve][k][j][i], - 2.)); - } - } - } - } -} - -//---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, area_intersection_triangle_linear) -{ - const int DIM = 2; - const int order = 1; - using CoordType = double; - using CurvedPolygonType = primal::CurvedPolygon; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - SLIC_INFO( - "Test finding area of intersection two linear triangular CurvedPolygons " - "(single region)."); - - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, order); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, order); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, order); - bPolygon.addEdge(bCurve3); - CurvedPolygonType bPolygon2 = bPolygon; - for(int i = 0; i < DIM; ++i) - { - for(int j = 0; j <= order; ++j) - { - for(int k = 0; k < bPolygon2.numEdges(); ++k) - { - bPolygon2[k][j][i] += .11; - } - } - } - - std::vector bPolygons3; - bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); - EXPECT_TRUE(didIntersect); - - CoordType A = 0.0; - for(int i = 0; i < static_cast(bPolygons3.size()); ++i) - { - A += primal::area(bPolygons3[i], 1e-14); - } - CoordType expA = -0.0793347222222222222; - EXPECT_NEAR(A, expA, 1e-10); + checkIntersection(bPolygon1, bPolygon2, expbPolygons, 1e-15, 1e-13); } //---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, area_intersection_triangle_quadratic) +TEST(primal_curvedpolygon, intersection_triangle_quadratic_two_regions) { const int DIM = 2; - const int order = 2; using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - SLIC_INFO( - "Test intersecting two quadratic triangular CurvedPolygons (single " - "region)."); - - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.4, 1.3), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.27, 1.5), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.1, 1.5), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, order); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, order); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, order); - bPolygon.addEdge(bCurve3); - CurvedPolygonType bPolygon2 = bPolygon; - for(int i = 0; i < DIM; ++i) - { - for(int j = 0; j <= order; ++j) - { - for(int k = 0; k < bPolygon2.numEdges(); ++k) - { - bPolygon2[k][j][i] += .11; - } - } - } - - std::vector bPolygons3; - bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3, 1e-14); - EXPECT_TRUE(didIntersect); - - CoordType A = 0.0; - for(int i = 0; i < static_cast(bPolygons3.size()); ++i) - { - A += primal::area(bPolygons3[i], 1e-8); - } - CoordType expA = -0.024649833203616; - EXPECT_NEAR(A, expA, 1e-10); -} - -TEST(primal_curvedpolygon, area_intersection_triangle_quadratic_two_regions) -{ - const int DIM = 2; - const int order = 2; - using CoordType = double; - using CurvedPolygonType = primal::CurvedPolygon; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; SLIC_INFO( "Test intersecting two quadratic triangular CurvedPolygons (two regions)."); - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[order + 1] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.4, 1.3), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[order + 1] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.27, 1.5), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.1, 1.5), - PointType::make_point(0.6, 1.2)}; - - PointType controlPoints4[order + 1] = {PointType::make_point(1.0205, 1.6699), - PointType::make_point(0.8339, 1.5467), - PointType::make_point(0.1777, 1.8101)}; - - PointType controlPoints5[order + 1] = {PointType::make_point(0.1777, 1.8101), - PointType::make_point(0.5957, 1.5341), - PointType::make_point(0.3741, 1.3503)}; - - PointType controlPoints6[order + 1] = {PointType::make_point(0.3741, 1.3503), - PointType::make_point(0.5107, 1.3869), - PointType::make_point(1.0205, 1.6699)}; - CurvedPolygonType bPolygon2; - - BezierCurveType bCurve(controlPoints, order); - BezierCurveType bCurve4(controlPoints4, order); - bPolygon2.addEdge(bCurve4); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, order); - BezierCurveType bCurve5(controlPoints5, order); - bPolygon2.addEdge(bCurve5); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, order); - BezierCurveType bCurve6(controlPoints6, order); - bPolygon2.addEdge(bCurve6); - bPolygon.addEdge(bCurve3); - - std::vector bPolygons3; - bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); - EXPECT_TRUE(didIntersect); - EXPECT_EQ(bPolygons3.size(), 2); + std::vector CP1 = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.4, 1.3), + PointType::make_point(0.3, 2.0), + PointType::make_point(0.27, 1.5), + PointType::make_point(0.0, 1.6), + PointType::make_point(0.1, 1.5), + PointType::make_point(0.6, 1.2)}; + + std::vector CP2 = {PointType::make_point(1.0205, 1.6699), + PointType::make_point(0.8339, 1.5467), + PointType::make_point(0.1777, 1.8101), + PointType::make_point(0.5957, 1.5341), + PointType::make_point(0.3741, 1.3503), + PointType::make_point(0.5107, 1.3869), + PointType::make_point(1.0205, 1.6699)}; + std::vector orders = {2, 2, 2}; + + std::vector expCP1 = { + PointType::make_point(0.343364196589264, 1.747080669655736), + PointType::make_point(0.305984025190458, 1.760433098612141), + PointType::make_point(0.266743999290327, 1.775316659915674), + PointType::make_point(0.263419346128088, 1.763343410502168), + PointType::make_point(0.259796003065908, 1.752116885838515), + PointType::make_point(0.320641367919239, 1.705796408318085), + PointType::make_point(0.362111919147859, 1.662268860466508), + PointType::make_point(0.352450139541348, 1.702947255097842), + PointType::make_point(0.343364196589264, 1.747080669655736), + }; + + std::vector expCP2 = { + PointType::make_point(0.454478985809487, 1.379250566393211), + PointType::make_point(0.444689566319939, 1.400290430035245), + PointType::make_point(0.435276730907216, 1.423589798138227), + PointType::make_point(0.416268597450954, 1.385275578571685), + PointType::make_point(0.374100000000000, 1.350300000000000), + PointType::make_point(0.404839872482010, 1.358536305511285), + PointType::make_point(0.454478985809487, 1.379250566393211)}; + + std::vector exporder1 = {2, 2, 2, 2}; + std::vector exporder2 = {2, 2, 2}; + CurvedPolygonType bPolygon1 = createPolygon(CP1, orders); + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + CurvedPolygonType expbPolygon1 = createPolygon(expCP1, exporder1); + CurvedPolygonType expbPolygon2 = createPolygon(expCP2, exporder2); + std::vector expIntersections = {expbPolygon1, expbPolygon2}; + + checkIntersection(bPolygon1, bPolygon2, expIntersections); } TEST(primal_curvedpolygon, area_intersection_triangle_inclusion) { const int DIM = 2; - const int order = 2; using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; SLIC_INFO( "Test intersecting two quadratic triangular CurvedPolygons (inclusion)."); - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[order + 1] = {PointType::make_point(0.0, 0.0), - PointType::make_point(0.5, 0.0), - PointType::make_point(1.0, 0.0)}; - - PointType controlPoints2[order + 1] = {PointType::make_point(1.0, 0.0), - PointType::make_point(0.5, 0.5), - PointType::make_point(0.0, 1.0)}; - - PointType controlPoints3[order + 1] = {PointType::make_point(0.0, 1.0), - PointType::make_point(0.0, 0.5), - PointType::make_point(0.0, 0.0)}; - - BezierCurveType bCurve(controlPoints, order); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, order); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, order); - bPolygon.addEdge(bCurve3); - - CurvedPolygonType bPolygon2 = bPolygon; - for(int i = 0; i < DIM; ++i) - { - for(int j = 0; j <= order; ++j) - { - for(int k = 0; k < bPolygon2.numEdges(); ++k) - { - bPolygon2[k][j][i] = bPolygon2[k][j][i] * .5 + .05; - } - } - } - - std::vector bPolygons3; - bool didIntersect = intersect(bPolygon, bPolygon2, bPolygons3); - bPolygons3.clear(); - bool didIntersect2 = intersect(bPolygon2, bPolygon, bPolygons3); - EXPECT_TRUE(didIntersect); - EXPECT_TRUE(didIntersect2); - EXPECT_EQ(bPolygons3.size(), 1); + std::vector CP1 = {PointType::make_point(0.0, 0.0), + PointType::make_point(0.5, 0.0), + PointType::make_point(1.0, 0.0), + PointType::make_point(0.5, 0.5), + PointType::make_point(0.0, 1.0), + PointType::make_point(0.0, 0.5), + PointType::make_point(0.0, 0.0)}; + + std::vector CP2 = {PointType::make_point(0.05, 0.05), + PointType::make_point(0.30, 0.05), + PointType::make_point(0.55, 0.05), + PointType::make_point(0.30, 0.30), + PointType::make_point(0.05, 0.55), + PointType::make_point(0.05, 0.30), + PointType::make_point(0.05, 0.05)}; + std::vector orders = {2, 2, 2}; + CurvedPolygonType bPolygon1 = createPolygon(CP1, orders); + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + std::vector expIntersections = {bPolygon2}; + + checkIntersection(bPolygon1, bPolygon2, expIntersections); + checkIntersection(bPolygon2, bPolygon1, expIntersections); } //---------------------------------------------------------------------------------- int main(int argc, char* argv[]) From 9b3f36dc563ee5e1743571ed558a07464432a587 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Tue, 8 Oct 2019 10:32:33 -0700 Subject: [PATCH 13/38] Bugfixes for curved polygon tests in MSVC --- src/axom/primal/tests/primal_curved_polygon.cpp | 2 ++ src/axom/primal/tests/primal_curved_polygon_intersect.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index ef0ef95373..9a4aab6efa 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -23,6 +23,8 @@ #include #include +#include + namespace primal = axom::primal; /*! diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index 9de7feb467..b73dc86023 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -12,6 +12,8 @@ #include "axom/slic.hpp" #include "axom/primal.hpp" +#include + namespace primal = axom::primal; /** @@ -70,8 +72,10 @@ primal::CurvedPolygon createPolygon( const int num_unique_control_points = ControlPoints.size(); //checks if the orders and control points given will yield a valid curved polygon - EXPECT_EQ(accumulate(orders.begin(), orders.end(), 0) + 1, - num_unique_control_points); + { + const int sum_of_orders = std::accumulate(orders.begin(), orders.end(), 0); + EXPECT_EQ(sum_of_orders + 1, num_unique_control_points); + } //Converts the control points to BezierCurves of specified orders and stores them in a CurvedPolygon object. CurvedPolygonType bPolygon; From ab1b1eea0de9551c7ad70608891c609948492d06 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Tue, 8 Oct 2019 11:00:55 -0700 Subject: [PATCH 14/38] Rely on Axom data submodule for mesh files in quest high order remap example --- src/axom/quest/examples/quest_high_order_remap.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index e5fb9b37f0..ebac0db8f7 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -1,5 +1,5 @@ -// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and -// other Axom Project Developers. See the top-level COPYRIGHT file for details. +// Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and +// other Axom Project Developers. See the top-level LICENSE file for details. // // SPDX-License-Identifier: (BSD-3-Clause) @@ -370,7 +370,11 @@ struct Remapper } // create the target mesh { - auto* mesh = new mfem::Mesh("./disc-nurbs-80.mesh", 1, 1); + // NOTE (KW): For now, assume we have AXOM_DATA_DIR + namespace fs = axom::utilities::filesystem; + std::string fname = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs.mesh"); + + auto* mesh = new mfem::Mesh(fname.c_str(), 1, 1); if(mesh->NURBSext) { int order = tgt_ord; From 86bde1c4b30b6b82a7ce6a12faa46abf540deae5 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Tue, 8 Oct 2019 11:39:03 -0700 Subject: [PATCH 15/38] Updates related to primal's BezierCurve::dt returning a Vector --- .../detail/intersect_curved_poly_impl.hpp | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index df2a0439af..e318b42c3f 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -36,15 +36,12 @@ namespace detail template class IntersectionInfo; -template -bool orient(const BezierCurve c1, - const BezierCurve c2, - T s, - T t); +template +bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t); template -int isContained(const CurvedPolygon p1, - const CurvedPolygon p2, +int isContained(const CurvedPolygon& p1, + const CurvedPolygon& p2, double sq_tol = 1e-10); /*! @@ -237,13 +234,14 @@ bool intersect_polygon(const CurvedPolygon& p1, * \return 0 if mutually exclusive, 1 if p1 is in p2, 2 if p2 is in p1 */ template -int isContained(const CurvedPolygon p1, - const CurvedPolygon p2, +int isContained(const CurvedPolygon& p1, + const CurvedPolygon& p2, double sq_tol) { const int NDIMS = 2; - using PointType = primal::Point; using BCurve = BezierCurve; + using PointType = typename BCurve::PointType; + int p1c = 0; int p2c = 0; T p1t = .5; @@ -267,6 +265,7 @@ int isContained(const CurvedPolygon p1, 0., 1., 0.); + for(int i = 0; i < static_cast(temps.size()); ++i) { if(temps[i] > line1s) @@ -305,8 +304,10 @@ int isContained(const CurvedPolygon p1, } using Vec3 = primal::Vector; - bool E1inE2 = Vec3::cross_product(p1[p1c].dt(p1t), LineGuess.dt(line1s))[2] < 0; - bool E2inE1 = Vec3::cross_product(p2[p2c].dt(p2t), LineGuess.dt(line2s))[2] < 0; + const bool E1inE2 = + Vec3::cross_product(p1[p1c].dt(p1t), LineGuess.dt(line1s))[2] < 0; + const bool E2inE1 = + Vec3::cross_product(p2[p2c].dt(p2t), LineGuess.dt(line2s))[2] < 0; if(E1inE2 && E2inE1) { return 1; @@ -359,8 +360,8 @@ void splitPolygon(CurvedPolygon& p1, * \param [in] t the parameter value of intersection on c2 * \return True if the c1's positive direction is counterclockwise from c2's positive direction */ -template -bool orient(const BezierCurve c1, const BezierCurve c2, T s, T t) +template +bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t) { const auto orientation = primal::Vector::cross_product(c1.dt(s), c2.dt(t))[2]; From da8479d6c2659b5d0d879618dfc09bfd8b7be124 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 22 Oct 2019 12:52:28 -0700 Subject: [PATCH 16/38] Attempting to fix macos error --- .../primal/operators/detail/intersect_curved_poly_impl.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index e318b42c3f..8e039114cc 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -385,7 +385,10 @@ class IntersectionInfo /*! * \brief Comparison operator for sorting by parameter value */ - bool operator<(IntersectionInfo other) { return myTime < other.myTime; } + bool operator<(const IntersectionInfo& other) const + { + return myTime < other.myTime; + } }; } // namespace detail From 94ab7e345a37f18153fe347fcbb810a6458ca57e Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 29 Oct 2019 15:34:18 -0700 Subject: [PATCH 17/38] Added conversion to positive basis, transform for high order mesh. Changed source mesh to high order mesh. --- .../detail/intersect_curved_poly_impl.hpp | 4 +- .../quest/examples/quest_high_order_remap.cpp | 237 +++++++++++++----- 2 files changed, 173 insertions(+), 68 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 8e039114cc..9a14766309 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -261,10 +261,10 @@ int isContained(const CurvedPolygon& p1, sq_tol, 1, p1[j].getOrder(), - 1., 0., 1., - 0.); + 0., + 1.); for(int i = 0; i < static_cast(temps.size()); ++i) { diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index ebac0db8f7..ac47c2b4ce 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -266,6 +266,92 @@ struct Remapper fesMap.DeleteData(true); } + mfem::GridFunction* project_to_pos_basis(const mfem::GridFunction* gf, + bool& is_new) + { + mfem::GridFunction* out_pos_gf = nullptr; + is_new = false; + + SLIC_ASSERT(gf != nullptr); + + const mfem::FiniteElementSpace* nodal_fe_space = gf->FESpace(); + if(nodal_fe_space == nullptr) + { + std::cerr << "project_to_pos_basis(): nodal_fe_space is NULL!" << std::endl; + } + + const mfem::FiniteElementCollection* nodal_fe_coll = nodal_fe_space->FEColl(); + if(nodal_fe_coll == nullptr) + { + std::cerr << "project_to_pos_basis(): nodal_fe_coll is NULL!" << std::endl; + } + + mfem::Mesh* gf_mesh = nodal_fe_space->GetMesh(); + if(gf_mesh == nullptr) + { + std::cerr << "project_to_pos_basis(): gf_mesh is NULL!" << std::endl; + } + + int order = nodal_fe_space->GetOrder(0); + int dim = gf_mesh->Dimension(); + mfem::Geometry::Type geom_type = gf_mesh->GetElementBaseGeometry(0); + int map_type = (nodal_fe_coll != nullptr) + ? nodal_fe_coll->FiniteElementForGeometry(geom_type)->GetMapType() + : static_cast(mfem::FiniteElement::VALUE); + + auto* pos_fe_coll = + new mfem::H1_FECollection(5, dim, mfem::BasisType::Positive); + // fecMap.Register("src_fec", fec, true); + // mfem::FiniteElementCollection pos_fe_coll = *nodal_fe_coll; + // detail::get_pos_fec(nodal_fe_coll, + // order, + // dim, + // map_type); + + //SLIC_ASSERT_MSG( + // pos_fe_coll != AXOM_NULLPTR, + // "Problem generating a positive finite element collection " + // << "corresponding to the mesh's '"<< nodal_fe_coll->Name() + // << "' finite element collection."); + + if(pos_fe_coll != nullptr) + { + //DEBUG + //std::cerr << "Good so far... pos_fe_coll is not null. Making FESpace and GridFunction." << std::endl; + const int dims = nodal_fe_space->GetVDim(); + // Create a positive (Bernstein) grid function for the nodes + mfem::FiniteElementSpace* pos_fe_space = + new mfem::FiniteElementSpace(gf_mesh, pos_fe_coll, dims); + mfem::GridFunction* pos_nodes = new mfem::GridFunction(pos_fe_space); + + // m_pos_nodes takes ownership of pos_fe_coll's memory (and pos_fe_space's memory) + pos_nodes->MakeOwner(pos_fe_coll); + + // Project the nodal grid function onto this + pos_nodes->ProjectGridFunction(*gf); + + out_pos_gf = pos_nodes; + is_new = true; + } + //DEBUG + else + std::cerr + << "BAD... pos_fe_coll is NULL. Could not make FESpace or GridFunction." + << std::endl; + + //DEBUG + if(!out_pos_gf) + { + std::cerr + << "project_to_pos_basis(): Construction failed; out_pos_gf is NULL!" + << std::endl; + } + + // } + + return out_pos_gf; + } + /*! Set up the source and target meshes */ void setupMeshes(int res1, int res2, int order) { @@ -280,7 +366,8 @@ struct Remapper const int tgt_res = res1; const int tgt_ord = order; const double tgt_scale = .712378102150; - const double tgt_trans = .1345747181586; + const double tgt_trans1 = .1345747181586; + const double tgt_trans2 = .1345747181586; // create the source mesh { @@ -310,7 +397,7 @@ struct Remapper // create the target mesh { auto* mesh = new mfem::Mesh(tgt_res, tgt_res, quadType, true); - xformMesh(mesh, tgt_scale, tgt_trans); + xformMesh(mesh, tgt_scale, tgt_trans1, tgt_trans2); auto* fec = new mfem::H1_FECollection(tgt_ord, dim, mfem::BasisType::Positive); @@ -335,65 +422,100 @@ struct Remapper const auto quadType = mfem::Element::QUADRILATERAL; const int dim = 2; - // paramters for target mesh -- quad mesh covering unit square + // parameters for target mesh -- quad mesh covering unit square const int src_res = res2; const int src_ord = order; - const double src_scale = 6.0; - const double src_trans = -3.001; + const double src_scale = 1.5; + const double src_trans1 = .01517288412347; + const double src_trans2 = .02571238506182; - // paramters for target mesh -- quad mesh covering (part of) unit square + // parameters for target mesh -- quad mesh covering (part of) unit square const int tgt_ord = 2; { - // create mfem mesh - auto* mesh = new mfem::Mesh(src_res, src_res, quadType, true); - xformMesh(mesh, src_scale, src_trans); - - // create finite element collection for nodes - auto* fec = - new mfem::H1_FECollection(src_ord, dim, mfem::BasisType::Positive); - fecMap.Register("src_fec", fec, true); - - // create finite element space for nodes - auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); - fesMap.Register("src_fes", fes, true); - mesh->SetNodalFESpace(fes); + // NOTE (KW): For now, assume we have AXOM_DATA_DIR + namespace fs = axom::utilities::filesystem; + std::string fname = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); - // SLIC_INFO("Writing to: " << axom::utilities::filesystem::getCWD()); + auto* mesh = new mfem::Mesh(fname.c_str(), 1, 1); + xformMesh(mesh, src_scale, src_trans1, src_trans2); + if(mesh->NURBSext) + { + int order = src_ord; + mesh->SetCurvature(5); + } + // xformMesh(mesh, tgt_scale, tgt_trans); { std::ofstream file; - file.open("source_mesh.mfem"); + file.open("src_mesh_orig.mfem"); mesh->Print(file); } + bool is_mesh_gf_new; + mfem::GridFunction* mesh_nodes = mesh->GetNodes(); + mfem::GridFunction* pos_mesh_nodes_ptr = + project_to_pos_basis(mesh_nodes, is_mesh_gf_new); + mfem::GridFunction& pos_mesh_nodes = + (is_mesh_gf_new ? *pos_mesh_nodes_ptr : *mesh_nodes); + mesh->NewNodes(pos_mesh_nodes, true); + + //auto* fec = new mfem::H1_FECollection(tgt_ord, dim, + // mfem::BasisType::Positive); + //fecMap.Register("tgt_fec", fec, true); + + //auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); + //fesMap.Register("tgt_fes", fes, true); + //mesh->SetNodalFESpace(fes); + std::cout << mesh->GetNV() << std::endl; + { + std::ofstream file; + file.open("src_mesh_set.mfem"); + mesh->Print(file); + } + //std::cout << "Got here!" << std::endl; srcMesh.setMesh(mesh); } + // create the target mesh { // NOTE (KW): For now, assume we have AXOM_DATA_DIR namespace fs = axom::utilities::filesystem; - std::string fname = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs.mesh"); + std::string fname = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); auto* mesh = new mfem::Mesh(fname.c_str(), 1, 1); if(mesh->NURBSext) { int order = tgt_ord; - mesh->SetCurvature(order); + mesh->SetCurvature(5); } + // xformMesh(mesh, tgt_scale, tgt_trans); + const double tgt_scale = 1.0; + const double tgt_trans1 = .001237586; + const double tgt_trans2 = -.06172376; + xformMesh(mesh, tgt_scale, tgt_trans1, tgt_trans2); + { std::ofstream file; file.open("target_mesh_orig.mfem"); mesh->Print(file); } - auto* fec = - new mfem::H1_FECollection(tgt_ord, dim, mfem::BasisType::Positive); - fecMap.Register("tgt_fec", fec, true); + bool is_mesh_gf_new; + mfem::GridFunction* mesh_nodes = mesh->GetNodes(); + mfem::GridFunction* pos_mesh_nodes_ptr = + project_to_pos_basis(mesh_nodes, is_mesh_gf_new); + mfem::GridFunction& pos_mesh_nodes = + (is_mesh_gf_new ? *pos_mesh_nodes_ptr : *mesh_nodes); + mesh->NewNodes(pos_mesh_nodes, true); - auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); - fesMap.Register("tgt_fes", fes, true); - mesh->SetNodalFESpace(fes); + //auto* fec = new mfem::H1_FECollection(tgt_ord, dim, + // mfem::BasisType::Positive); + //fecMap.Register("tgt_fec", fec, true); + + //auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); + //fesMap.Register("tgt_fes", fes, true); + //mesh->SetNodalFESpace(fes); { std::ofstream file; @@ -455,7 +577,7 @@ struct Remapper std::vector> pnew; tgtPoly.reverseOrientation(); srcPoly.reverseOrientation(); - if(primal::intersect(tgtPoly, srcPoly, pnew)) + if(primal::intersect(tgtPoly, srcPoly, pnew, 1e-8)) { for(int i = 0; i < static_cast(pnew.size()); ++i) { @@ -485,8 +607,9 @@ struct Remapper // const double tgt_trans = .000000000001345747181586; // double trueError = (tgt_scale*tgt_scale-totalArea); // std::cout << trueError << ", "; - std::cout << totalArea << std::endl; - std::cout << correctArea << std::endl; + std::cout << "Calculated area (supermesh): " << std::fixed << totalArea + << std::endl; + std::cout << "Calculated area (target mesh): " << correctArea << std::endl; return (totalArea - correctArea); } @@ -528,42 +651,23 @@ struct Remapper private: // scale and translate the vertices of the given mesh - void xformMesh(mfem::Mesh* mesh, double sc, double off) + void xformMesh(mfem::Mesh* mesh, double sc, double off1, double off2) { - std::cout << "Transforming Mesh now" << std::endl; - for(int v = 0; v < mesh->GetNV(); ++v) + // std::cout << "Transforming Mesh now" << std::endl; + //for (int v = 0 ; v < NumVerts ; ++v) + // { + // double pt* = mesh->GetVertex(v); + // pt[0] = sc*pt[0] + off1; + // pt[1] = sc*pt[1] + off2; + // } + mfem::GridFunction* mesh_nodes = mesh->GetNodes(); + //std::cout << *mesh_nodes[0] << std::endl; + int NumDofs = mesh_nodes->Size(); + for(int e = 0; e < (NumDofs / 2); ++e) { - double* pt = mesh->GetVertex(v); - // if (v==0) - // { - // std::cout << pt[0] << " , " << pt[1] << std::endl; - // } - // pt[0] = sc*pt[0] + off; - pt[0] = sc * pt[0] + off + .000147582957; - pt[1] = sc * pt[1] + off; - - /* if (v%3==0) - { - pt[0] = pt[0]-.01748; - } - if (v%5==0) - { - pt[0] = pt[0]+.004; - } - if (v%7==0) - { - pt[1] = pt[1]+.0243; - }*/ - // if (v==0) - // { - // std::cout << pt[0] << " , " << pt[1] << std::endl; - // } + (*mesh_nodes)[2 * e] = sc * (*mesh_nodes)[2 * e] + off1; + (*mesh_nodes)[2 * e + 1] = sc * (*mesh_nodes)[2 * e + 1] + off2; } - // for (int e =0 ; e < mesh->GetNEdges() ; ++e) - // { - // mfem::array dofs(); - // GetEdgeDofs(e, dofs); - // } } }; @@ -591,9 +695,10 @@ int main(int argc, char** argv) { Remapper remap; + std::cout.precision(16); // res1= res1*2; - res1 = 25; + res1 = i; int res2 = res1 + 2; // Setup the two meshes in the Bernstein basis // The current implementation hard-codes the two meshes From 6da61f1f1c15e661cd5825caef0d2e4db96b2189 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 11 Jun 2021 17:09:16 -0700 Subject: [PATCH 18/38] Fixes warnings from clang compiler --- src/axom/quest/examples/CMakeLists.txt | 1 + .../quest/examples/quest_high_order_remap.cpp | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 4eaaf70c3c..75d965c65e 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -228,3 +228,4 @@ if(MFEM_FOUND) #blt_add_target_compile_flags( TO quest_high_order_remap_ex FLAGS "${MFEM_COMPILE_FLAGS}" ) endif() + diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index ac47c2b4ce..ea43d2d3c1 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -301,6 +301,9 @@ struct Remapper auto* pos_fe_coll = new mfem::H1_FECollection(5, dim, mfem::BasisType::Positive); + + AXOM_UNUSED_VAR(map_type); + AXOM_UNUSED_VAR(order); // fecMap.Register("src_fec", fec, true); // mfem::FiniteElementCollection pos_fe_coll = *nodal_fe_coll; // detail::get_pos_fec(nodal_fe_coll, @@ -432,6 +435,11 @@ struct Remapper // parameters for target mesh -- quad mesh covering (part of) unit square const int tgt_ord = 2; + AXOM_UNUSED_VAR(quadType); + AXOM_UNUSED_VAR(src_res); + AXOM_UNUSED_VAR(src_ord); + AXOM_UNUSED_VAR(tgt_ord); + { // NOTE (KW): For now, assume we have AXOM_DATA_DIR namespace fs = axom::utilities::filesystem; @@ -441,8 +449,8 @@ struct Remapper xformMesh(mesh, src_scale, src_trans1, src_trans2); if(mesh->NURBSext) { - int order = src_ord; - mesh->SetCurvature(5); + int ord = 5; //src_ord; + mesh->SetCurvature(ord); } // xformMesh(mesh, tgt_scale, tgt_trans); { @@ -459,6 +467,7 @@ struct Remapper (is_mesh_gf_new ? *pos_mesh_nodes_ptr : *mesh_nodes); mesh->NewNodes(pos_mesh_nodes, true); + AXOM_UNUSED_VAR(dim); //auto* fec = new mfem::H1_FECollection(tgt_ord, dim, // mfem::BasisType::Positive); //fecMap.Register("tgt_fec", fec, true); @@ -485,8 +494,8 @@ struct Remapper auto* mesh = new mfem::Mesh(fname.c_str(), 1, 1); if(mesh->NURBSext) { - int order = tgt_ord; - mesh->SetCurvature(5); + int ord = 5; //tgt_ord; + mesh->SetCurvature(ord); } // xformMesh(mesh, tgt_scale, tgt_trans); @@ -674,6 +683,9 @@ struct Remapper //------------------------------------------------------------------------------ int main(int argc, char** argv) { + AXOM_UNUSED_VAR(argc); + AXOM_UNUSED_VAR(argv); + axom::slic::SimpleLogger logger; SLIC_INFO("The application conservatively maps fields from a source \n" @@ -700,6 +712,8 @@ int main(int argc, char** argv) res1 = i; int res2 = res1 + 2; + AXOM_UNUSED_VAR(res2); + // Setup the two meshes in the Bernstein basis // The current implementation hard-codes the two meshes // TODO: Read in two meshes from disk. From 6f66294c61c3e4e25691318c7e94e3e9c749cf5d Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 11 Jun 2021 17:56:58 -0700 Subject: [PATCH 19/38] Minor cleanup of CurvedPolygon intersection tests --- .../detail/intersect_curved_poly_impl.hpp | 22 ++++++------ .../tests/primal_curved_polygon_intersect.cpp | 34 +++++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 9a14766309..1484e49cde 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -6,7 +6,7 @@ /*! * \file intersect_curved_poly_impl.hpp * - * \brief Consists of functions to test intersection among geometric primitives. + * \brief Consists of functions to test intersections among curved polygons */ #ifndef AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_IMPL_HPP_ @@ -45,8 +45,8 @@ int isContained(const CurvedPolygon& p1, double sq_tol = 1e-10); /*! - * \brief Test whether the regions within CurvedPolygons p1 and p2 intersect. - * \return status true iff p1 intersects with p2, otherwise false. + * \brief Test whether the regions bounded by CurvedPolygons \a p1 and \a p2 intersect + * \return status true iff \a p1 intersects with \a p2, otherwise false. * * \param [in] p1, p2 CurvedPolygon objects to intersect * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curve @@ -352,13 +352,15 @@ void splitPolygon(CurvedPolygon& p1, } } -/* \brief Determines orientation of a bezier curve c1 with respect to another bezier curve c2, given that they intersect at parameter values s and t +/*! + * \brief Determines orientation of a bezier curve \a c1 with respect to another bezier curve \a c2, + * given that they intersect at parameter values \a s and \a t, respectively * * \param [in] c1 the first bezier curve * \param [in] c2 the second bezier curve - * \param [in] s the parameter value of intersection on c1 - * \param [in] t the parameter value of intersection on c2 - * \return True if the c1's positive direction is counterclockwise from c2's positive direction + * \param [in] s the parameter value of intersection on \a c1 + * \param [in] t the parameter value of intersection on \a c2 + * \return True if \a c1's positive direction is counterclockwise from \a c2's positive direction */ template bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t) @@ -370,7 +372,7 @@ bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t) /*! \class IntersectionInfo * - * \brief For storing intersection points between CurvedPolygons so they can be easily sorted by parameter value using std::sort + * \brief For storing intersection points between \a CurvedPolygon instances so they can be easily sorted by parameter value using std::sort */ template class IntersectionInfo @@ -382,9 +384,7 @@ class IntersectionInfo int otherEdge; // index of curve on second CurvedPolygon int numinter; // unique intersection point identifier - /*! - * \brief Comparison operator for sorting by parameter value - */ + /// \brief Comparison operator for sorting by parameter value bool operator<(const IntersectionInfo& other) const { return myTime < other.myTime; diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index b73dc86023..fea940649d 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -3,8 +3,9 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -/* /file primal_curved_polygon_intersect.cpp - * /brief This file tests intersections of CurvedPolygon instances +/** + * \file primal_curved_polygon_intersect.cpp + * \brief This file tests intersections of CurvedPolygon instances */ #include "gtest/gtest.h" @@ -16,8 +17,10 @@ namespace primal = axom::primal; -/** - * Helper function to compute the set of intersection polygons given two input polygons and to check that they match expectations, stored in \a expbPolygon. Intersection polygon is computed to within tolerance \a eps and checks use \a test_eps. +/*! + * Helper function to compute the set of intersection polygons given two input polygons + * and to check that they match expectations, stored in \a expbPolygon. + * Intersection polygon is computed to within tolerance \a eps and checks use \a test_eps. */ template void checkIntersection( @@ -57,7 +60,12 @@ void checkIntersection( } } -/* Helper function to create a CurvedPolygon from a list of control points and a list of orders of component curves. Control points should be given as a list of Points in order of orientation with no duplicates except that the first control point should also be the last control point (if the polygon is closed). Orders should be given as a list of ints in order of orientation, representing the orders of the component curves. +/*! + * Helper function to create a CurvedPolygon from a list of control points and a list + * of orders of component curves. Control points should be given as a list of Points + * in order of orientation with no duplicates except that the first control point + * should also be the last control point (if the polygon is closed). Orders should + * be given as a list of ints in order of orientation, representing the orders of the component curves. */ template primal::CurvedPolygon createPolygon( @@ -92,6 +100,8 @@ primal::CurvedPolygon createPolygon( return bPolygon; } +//---------------------------------------------------------------------------------- + TEST(primal_curvedpolygon, intersection_triangle_linear) { const int DIM = 2; @@ -99,8 +109,7 @@ TEST(primal_curvedpolygon, intersection_triangle_linear) using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - SLIC_INFO( - "Test intersecting two linear triangular CurvedPolygons (single region)."); + SLIC_INFO("Test intersection of two linear triangles (single region)"); std::vector CP = {PointType::make_point(0.6, 1.2), PointType::make_point(0.3, 2.0), @@ -137,9 +146,7 @@ TEST(primal_curvedpolygon, intersection_triangle_quadratic) using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - SLIC_INFO( - "Test intersecting two quadratic triangular CurvedPolygons (single " - "region)."); + SLIC_INFO("Test intersecting two quadratic triangles (single region)"); CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); @@ -188,8 +195,7 @@ TEST(primal_curvedpolygon, intersection_triangle_quadratic_two_regions) using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - SLIC_INFO( - "Test intersecting two quadratic triangular CurvedPolygons (two regions)."); + SLIC_INFO("Test intersection of two quadratic triangles (two regions)"); std::vector CP1 = {PointType::make_point(0.6, 1.2), PointType::make_point(0.4, 1.3), @@ -247,8 +253,7 @@ TEST(primal_curvedpolygon, area_intersection_triangle_inclusion) using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - SLIC_INFO( - "Test intersecting two quadratic triangular CurvedPolygons (inclusion)."); + SLIC_INFO("Test intersection of two quadratic triangles (inclusion)"); std::vector CP1 = {PointType::make_point(0.0, 0.0), PointType::make_point(0.5, 0.0), @@ -273,6 +278,7 @@ TEST(primal_curvedpolygon, area_intersection_triangle_inclusion) checkIntersection(bPolygon1, bPolygon2, expIntersections); checkIntersection(bPolygon2, bPolygon1, expIntersections); } + //---------------------------------------------------------------------------------- int main(int argc, char* argv[]) { From 3ea2dc22182ce650a365f952aa626fe22f6efb9f Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 11 Jun 2021 18:29:17 -0700 Subject: [PATCH 20/38] Use initializer lists to construct points and vectors for Bezier curve tests --- .../primal/tests/primal_curved_polygon.cpp | 8 +- .../tests/primal_curved_polygon_intersect.cpp | 163 +++++++++--------- 2 files changed, 82 insertions(+), 89 deletions(-) diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 9a4aab6efa..93ab37e28b 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -12,13 +12,7 @@ #include "axom/config.hpp" #include "axom/slic.hpp" -#include "axom/primal/geometry/Point.hpp" -#include "axom/primal/geometry/Segment.hpp" -#include "axom/primal/geometry/CurvedPolygon.hpp" -#include "axom/primal/geometry/OrientationResult.hpp" -#include "axom/primal/operators/intersect.hpp" -#include "axom/primal/operators/compute_moments.hpp" -#include "axom/primal/operators/orientation.hpp" +#include "axom/primal.hpp" #include #include diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index fea940649d..468e189450 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -111,26 +111,26 @@ TEST(primal_curvedpolygon, intersection_triangle_linear) SLIC_INFO("Test intersection of two linear triangles (single region)"); - std::vector CP = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; + std::vector CP = {PointType {0.6, 1.2}, + PointType {0.3, 2.0}, + PointType {0.0, 1.6}, + PointType {0.6, 1.2}}; std::vector orders = {1, 1, 1}; CurvedPolygonType bPolygon1 = createPolygon(CP, orders); - std::vector CP2 = {PointType::make_point(0.71, 1.31), - PointType::make_point(0.41, 2.11), - PointType::make_point(0.11, 1.71), - PointType::make_point(0.71, 1.31)}; + std::vector CP2 = {PointType {0.71, 1.31}, + PointType {0.41, 2.11}, + PointType {0.11, 1.71}, + PointType {0.71, 1.31}}; CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); std::vector expCP = { - PointType::make_point(0.3091666666666666666666, 1.9755555555555555555), - PointType::make_point(0.11, 1.71), - PointType::make_point(0.5083333333333333333, 1.44444444444444444444), - PointType::make_point(0.3091666666666666666666, 1.9755555555555555555)}; + PointType {0.3091666666666666666666, 1.9755555555555555555}, + PointType {0.11, 1.71}, + PointType {0.5083333333333333333, 1.44444444444444444444}, + PointType {0.3091666666666666666666, 1.9755555555555555555}}; std::vector exporders = {1, 1, 1}; CurvedPolygonType expbPolygon = createPolygon(expCP, exporders); @@ -151,35 +151,35 @@ TEST(primal_curvedpolygon, intersection_triangle_quadratic) CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); - std::vector CP = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.4, 1.3), - PointType::make_point(0.3, 2.0), - PointType::make_point(0.27, 1.5), - PointType::make_point(0.0, 1.6), - PointType::make_point(0.1, 1.5), - PointType::make_point(0.6, 1.2)}; + std::vector CP = {PointType {0.6, 1.2}, + PointType {0.4, 1.3}, + PointType {0.3, 2.0}, + PointType {0.27, 1.5}, + PointType {0.0, 1.6}, + PointType {0.1, 1.5}, + PointType {0.6, 1.2}}; std::vector orders = {2, 2, 2}; CurvedPolygonType bPolygon1 = createPolygon(CP, orders); - std::vector CP2 = {PointType::make_point(0.71, 1.31), - PointType::make_point(0.51, 1.41), - PointType::make_point(0.41, 2.11), - PointType::make_point(0.38, 1.61), - PointType::make_point(0.11, 1.71), - PointType::make_point(0.21, 1.61), - PointType::make_point(0.71, 1.31)}; + std::vector CP2 = {PointType {0.71, 1.31}, + PointType {0.51, 1.41}, + PointType {0.41, 2.11}, + PointType {0.38, 1.61}, + PointType {0.11, 1.71}, + PointType {0.21, 1.61}, + PointType {0.71, 1.31}}; CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); std::vector expCP = { - PointType::make_point(0.335956890729522, 1.784126953773395), - PointType::make_point(0.297344765794753, 1.718171485335525), - PointType::make_point(0.2395677533016981, 1.700128235793371), - PointType::make_point(0.221884203146682, 1.662410644580941), - PointType::make_point(0.199328465398189, 1.636873522352205), - PointType::make_point(0.277429214338182, 1.579562422716502), - PointType::make_point(0.408882616650578, 1.495574996394597), - PointType::make_point(0.368520120719339, 1.616453177259694), - PointType::make_point(0.335956890729522, 1.784126953773394)}; + PointType {0.335956890729522, 1.784126953773395}, + PointType {0.297344765794753, 1.718171485335525}, + PointType {0.2395677533016981, 1.700128235793371}, + PointType {0.221884203146682, 1.662410644580941}, + PointType {0.199328465398189, 1.636873522352205}, + PointType {0.277429214338182, 1.579562422716502}, + PointType {0.408882616650578, 1.495574996394597}, + PointType {0.368520120719339, 1.616453177259694}, + PointType {0.335956890729522, 1.784126953773394}}; std::vector exporders = {2, 2, 2, 2}; CurvedPolygonType expbPolygon = createPolygon(expCP, exporders); std::vector expbPolygons = {expbPolygon}; @@ -197,43 +197,42 @@ TEST(primal_curvedpolygon, intersection_triangle_quadratic_two_regions) SLIC_INFO("Test intersection of two quadratic triangles (two regions)"); - std::vector CP1 = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.4, 1.3), - PointType::make_point(0.3, 2.0), - PointType::make_point(0.27, 1.5), - PointType::make_point(0.0, 1.6), - PointType::make_point(0.1, 1.5), - PointType::make_point(0.6, 1.2)}; - - std::vector CP2 = {PointType::make_point(1.0205, 1.6699), - PointType::make_point(0.8339, 1.5467), - PointType::make_point(0.1777, 1.8101), - PointType::make_point(0.5957, 1.5341), - PointType::make_point(0.3741, 1.3503), - PointType::make_point(0.5107, 1.3869), - PointType::make_point(1.0205, 1.6699)}; + std::vector CP1 = {PointType {0.6, 1.2}, + PointType {0.4, 1.3}, + PointType {0.3, 2.0}, + PointType {0.27, 1.5}, + PointType {0.0, 1.6}, + PointType {0.1, 1.5}, + PointType {0.6, 1.2}}; + + std::vector CP2 = {PointType {1.0205, 1.6699}, + PointType {0.8339, 1.5467}, + PointType {0.1777, 1.8101}, + PointType {0.5957, 1.5341}, + PointType {0.3741, 1.3503}, + PointType {0.5107, 1.3869}, + PointType {1.0205, 1.6699}}; std::vector orders = {2, 2, 2}; std::vector expCP1 = { - PointType::make_point(0.343364196589264, 1.747080669655736), - PointType::make_point(0.305984025190458, 1.760433098612141), - PointType::make_point(0.266743999290327, 1.775316659915674), - PointType::make_point(0.263419346128088, 1.763343410502168), - PointType::make_point(0.259796003065908, 1.752116885838515), - PointType::make_point(0.320641367919239, 1.705796408318085), - PointType::make_point(0.362111919147859, 1.662268860466508), - PointType::make_point(0.352450139541348, 1.702947255097842), - PointType::make_point(0.343364196589264, 1.747080669655736), - }; + PointType {0.343364196589264, 1.747080669655736}, + PointType {0.305984025190458, 1.760433098612141}, + PointType {0.266743999290327, 1.775316659915674}, + PointType {0.263419346128088, 1.763343410502168}, + PointType {0.259796003065908, 1.752116885838515}, + PointType {0.320641367919239, 1.705796408318085}, + PointType {0.362111919147859, 1.662268860466508}, + PointType {0.352450139541348, 1.702947255097842}, + PointType {0.343364196589264, 1.747080669655736}}; std::vector expCP2 = { - PointType::make_point(0.454478985809487, 1.379250566393211), - PointType::make_point(0.444689566319939, 1.400290430035245), - PointType::make_point(0.435276730907216, 1.423589798138227), - PointType::make_point(0.416268597450954, 1.385275578571685), - PointType::make_point(0.374100000000000, 1.350300000000000), - PointType::make_point(0.404839872482010, 1.358536305511285), - PointType::make_point(0.454478985809487, 1.379250566393211)}; + PointType {0.454478985809487, 1.379250566393211}, + PointType {0.444689566319939, 1.400290430035245}, + PointType {0.435276730907216, 1.423589798138227}, + PointType {0.416268597450954, 1.385275578571685}, + PointType {0.374100000000000, 1.350300000000000}, + PointType {0.404839872482010, 1.358536305511285}, + PointType {0.454478985809487, 1.379250566393211}}; std::vector exporder1 = {2, 2, 2, 2}; std::vector exporder2 = {2, 2, 2}; @@ -255,21 +254,21 @@ TEST(primal_curvedpolygon, area_intersection_triangle_inclusion) SLIC_INFO("Test intersection of two quadratic triangles (inclusion)"); - std::vector CP1 = {PointType::make_point(0.0, 0.0), - PointType::make_point(0.5, 0.0), - PointType::make_point(1.0, 0.0), - PointType::make_point(0.5, 0.5), - PointType::make_point(0.0, 1.0), - PointType::make_point(0.0, 0.5), - PointType::make_point(0.0, 0.0)}; - - std::vector CP2 = {PointType::make_point(0.05, 0.05), - PointType::make_point(0.30, 0.05), - PointType::make_point(0.55, 0.05), - PointType::make_point(0.30, 0.30), - PointType::make_point(0.05, 0.55), - PointType::make_point(0.05, 0.30), - PointType::make_point(0.05, 0.05)}; + std::vector CP1 = {PointType {0.0, 0.0}, + PointType {0.5, 0.0}, + PointType {1.0, 0.0}, + PointType {0.5, 0.5}, + PointType {0.0, 1.0}, + PointType {0.0, 0.5}, + PointType {0.0, 0.0}}; + + std::vector CP2 = {PointType {0.05, 0.05}, + PointType {0.30, 0.05}, + PointType {0.55, 0.05}, + PointType {0.30, 0.30}, + PointType {0.05, 0.55}, + PointType {0.05, 0.30}, + PointType {0.05, 0.05}}; std::vector orders = {2, 2, 2}; CurvedPolygonType bPolygon1 = createPolygon(CP1, orders); CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); From 8f5d3b2caa14efae8634e3abe409107856de507c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 11 Jun 2021 19:13:26 -0700 Subject: [PATCH 21/38] Preliminary cleanup for quest's high order field transfer example --- .../quest/examples/quest_high_order_remap.cpp | 207 ++++++++---------- 1 file changed, 86 insertions(+), 121 deletions(-) diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index ea43d2d3c1..3e6ea83512 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -8,8 +8,6 @@ * \brief Demonstrates conservative field remap on 2D high order meshes */ -#include - // Axom includes #include "axom/core.hpp" #include "axom/slic.hpp" @@ -26,7 +24,6 @@ #include -//namespace mint = axom::mint; namespace primal = axom::primal; namespace spin = axom::spin; @@ -40,10 +37,11 @@ namespace spin = axom::spin; class MeshWrapper { public: - using BBox = primal::BoundingBox; - using Point = primal::Point; + constexpr static int DIM = 2; + using BBox = primal::BoundingBox; + using SpacePoint = primal::Point; - using CurvedPolygonType = primal::CurvedPolygon; + using CurvedPolygonType = primal::CurvedPolygon; using BCurve = CurvedPolygonType::BezierCurveType; private: @@ -93,9 +91,7 @@ class MeshWrapper } } - /*! - * Sets the mfem mesh pointer for this MeshWrapper instance - */ + /// Sets the mfem mesh pointer for this MeshWrapper instance void setMesh(mfem::Mesh* mesh) { SLIC_ASSERT(mesh != nullptr); @@ -194,12 +190,12 @@ class MeshWrapper private: /*! Get the coordinates of the point from the dof index */ - Point spacePointFromDof(int idx, - const mfem::FiniteElementSpace* fes, - const mfem::GridFunction* nodes) + SpacePoint spacePointFromDof(int idx, + const mfem::FiniteElementSpace* fes, + const mfem::GridFunction* nodes) { - return Point::make_point((*nodes)(fes->DofToVDof(idx, 0)), - (*nodes)(fes->DofToVDof(idx, 1))); + return SpacePoint {(*nodes)(fes->DofToVDof(idx, 0)), + (*nodes)(fes->DofToVDof(idx, 1))}; } /*! @@ -251,12 +247,13 @@ class MeshWrapper struct Remapper { -private: - using GridType = spin::ImplicitGrid<2, axom::SEQ_EXEC, int>; - public: + constexpr static int DIM = 2; using CandidateList = std::vector; +private: + using GridType = spin::ImplicitGrid<2, axom::SEQ_EXEC, int>; + public: Remapper() = default; @@ -275,83 +272,60 @@ struct Remapper SLIC_ASSERT(gf != nullptr); const mfem::FiniteElementSpace* nodal_fe_space = gf->FESpace(); - if(nodal_fe_space == nullptr) - { - std::cerr << "project_to_pos_basis(): nodal_fe_space is NULL!" << std::endl; - } + SLIC_ERROR_IF(nodal_fe_space == nullptr, + "project_to_pos_basis(): nodal_fe_space is NULL!"); const mfem::FiniteElementCollection* nodal_fe_coll = nodal_fe_space->FEColl(); - if(nodal_fe_coll == nullptr) - { - std::cerr << "project_to_pos_basis(): nodal_fe_coll is NULL!" << std::endl; - } + SLIC_ERROR_IF(nodal_fe_coll == nullptr, + "project_to_pos_basis(): nodal_fe_coll is NULL!"); mfem::Mesh* gf_mesh = nodal_fe_space->GetMesh(); - if(gf_mesh == nullptr) - { - std::cerr << "project_to_pos_basis(): gf_mesh is NULL!" << std::endl; - } + SLIC_ERROR_IF(gf_mesh == nullptr, + "project_to_pos_basis(): gf_mesh is NULL!"); - int order = nodal_fe_space->GetOrder(0); + int order = 5; // nodal_fe_space->GetOrder(0); int dim = gf_mesh->Dimension(); - mfem::Geometry::Type geom_type = gf_mesh->GetElementBaseGeometry(0); - int map_type = (nodal_fe_coll != nullptr) - ? nodal_fe_coll->FiniteElementForGeometry(geom_type)->GetMapType() - : static_cast(mfem::FiniteElement::VALUE); auto* pos_fe_coll = - new mfem::H1_FECollection(5, dim, mfem::BasisType::Positive); - - AXOM_UNUSED_VAR(map_type); - AXOM_UNUSED_VAR(order); - // fecMap.Register("src_fec", fec, true); - // mfem::FiniteElementCollection pos_fe_coll = *nodal_fe_coll; - // detail::get_pos_fec(nodal_fe_coll, - // order, - // dim, - // map_type); - - //SLIC_ASSERT_MSG( - // pos_fe_coll != AXOM_NULLPTR, - // "Problem generating a positive finite element collection " - // << "corresponding to the mesh's '"<< nodal_fe_coll->Name() - // << "' finite element collection."); - - if(pos_fe_coll != nullptr) - { - //DEBUG - //std::cerr << "Good so far... pos_fe_coll is not null. Making FESpace and GridFunction." << std::endl; - const int dims = nodal_fe_space->GetVDim(); - // Create a positive (Bernstein) grid function for the nodes - mfem::FiniteElementSpace* pos_fe_space = - new mfem::FiniteElementSpace(gf_mesh, pos_fe_coll, dims); - mfem::GridFunction* pos_nodes = new mfem::GridFunction(pos_fe_space); - - // m_pos_nodes takes ownership of pos_fe_coll's memory (and pos_fe_space's memory) - pos_nodes->MakeOwner(pos_fe_coll); - - // Project the nodal grid function onto this - pos_nodes->ProjectGridFunction(*gf); - - out_pos_gf = pos_nodes; - is_new = true; - } - //DEBUG - else - std::cerr - << "BAD... pos_fe_coll is NULL. Could not make FESpace or GridFunction." - << std::endl; - - //DEBUG - if(!out_pos_gf) - { - std::cerr - << "project_to_pos_basis(): Construction failed; out_pos_gf is NULL!" - << std::endl; - } + new mfem::H1_FECollection(order, dim, mfem::BasisType::Positive); + + // { + // mfem::Geometry::Type geom_type = gf_mesh->GetElementBaseGeometry(0); + // int map_type = (nodal_fe_coll != nullptr) + // ? nodal_fe_coll->FiniteElementForGeometry(geom_type)->GetMapType() + // : static_cast(mfem::FiniteElement::VALUE); + // fecMap.Register("src_fec", fec, true); + // mfem::FiniteElementCollection pos_fe_coll = *nodal_fe_coll; + // detail::get_pos_fec(nodal_fe_coll, order, dim, map_type); // } + SLIC_ERROR_IF(pos_fe_coll == nullptr, + "Problem generating a positive finite element collection " + << "corresponding to the mesh's '" << nodal_fe_coll->Name() + << "' finite element collection."); + + //SLIC_INFO("Good so far... pos_fe_coll is not null. Making FESpace and GridFunction."); + const int dims = nodal_fe_space->GetVDim(); + + // Create a positive (Bernstein) grid function for the nodes + mfem::FiniteElementSpace* pos_fe_space = + new mfem::FiniteElementSpace(gf_mesh, pos_fe_coll, dims); + mfem::GridFunction* pos_nodes = new mfem::GridFunction(pos_fe_space); + + // m_pos_nodes takes ownership of pos_fe_coll's memory (and pos_fe_space's memory) + pos_nodes->MakeOwner(pos_fe_coll); + + // Project the nodal grid function onto this + pos_nodes->ProjectGridFunction(*gf); + + out_pos_gf = pos_nodes; + is_new = true; + + SLIC_WARNING_IF( + out_pos_gf == nullptr, + "project_to_pos_basis(): Construction failed; out_pos_gf is NULL!"); + return out_pos_gf; } @@ -361,11 +335,11 @@ struct Remapper const auto quadType = mfem::Element::QUADRILATERAL; const int dim = 2; - // paramters for source mesh -- quad mesh covering unit square + // parameters for source mesh -- quad mesh covering unit square const int src_res = res2; const int src_ord = order; - // paramters for target mesh -- quad mesh covering (part of) unit square + // parameters for target mesh -- quad mesh covering (part of) unit square const int tgt_res = res1; const int tgt_ord = order; const double tgt_scale = .712378102150; @@ -387,10 +361,10 @@ struct Remapper fesMap.Register("src_fes", fes, true); mesh->SetNodalFESpace(fes); - // SLIC_INFO("Writing to: " << axom::utilities::filesystem::getCWD()); + //SLIC_DEBUG("Outputting mesh to: " << axom::utilities::filesystem::getCWD()); { std::ofstream file; - file.open("source_mesh.mfem"); + file.open("ho_field_xfer_source.mfem"); mesh->Print(file); } @@ -412,7 +386,7 @@ struct Remapper { std::ofstream file; - file.open("target_mesh.mfem"); + file.open("ho_field_xfer_target.mfem"); mesh->Print(file); } @@ -475,7 +449,7 @@ struct Remapper //auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); //fesMap.Register("tgt_fes", fes, true); //mesh->SetNodalFESpace(fes); - std::cout << mesh->GetNV() << std::endl; + SLIC_INFO(axom::fmt::format("Source mesh has {} vertices", mesh->GetNV())); { std::ofstream file; file.open("src_mesh_set.mfem"); @@ -531,7 +505,7 @@ struct Remapper file.open("target_mesh_set.mfem"); mesh->Print(file); } - std::cout << "Got here!" << std::endl; + //std::cout << "Got here!" << std::endl; tgtMesh.setMesh(mesh); } } @@ -568,10 +542,10 @@ struct Remapper auto tgtPoly = tgtMesh.elemAsCurvedPolygon(i); // SLIC_INFO("Target Element: " << tgtPoly); correctArea += primal::area(tgtPoly); - //j SLIC_INFO("Target elem " << i - //j << " -- area " << primal::area(tgtPoly) - //j //<< " -- bbox " << tgtMesh.elementBoundingBox(i) - //j ); + //SLIC_INFO("Target elem " << i + // << " -- area " << primal::area(tgtPoly) + // //<< " -- bbox " << tgtMesh.elementBoundingBox(i) + // ); double A = 0.0; for(int srcElem : candidates) @@ -580,7 +554,7 @@ struct Remapper // SLIC_INFO("*Source Element: " << srcPoly); // SLIC_INFO("* Source elem " << srcElem // << " -- area " << primal::area(srcPoly) - //// //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) + // //// //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) // ); std::vector> pnew; @@ -690,46 +664,37 @@ int main(int argc, char** argv) SLIC_INFO("The application conservatively maps fields from a source \n" << "high order mesh to a target high order mesh!"); - // int res1=1; - // Remapper remap; - // res1=5; - // int res2=res1+1; - // remap.loadMeshes(res1,2); - // remap.setupGrid(); - // double Area = remap.computeOverlapAreas(); - // std::cout << std::endl << Area << std::endl; - - // return 0; - - int res1 = 1; - std::cout << "["; + + SLIC_INFO("Running intersection tests..."); for(int i = 2; i <= 2; ++i) { Remapper remap; - std::cout.precision(16); - // res1= res1*2; - - res1 = i; - int res2 = res1 + 2; - AXOM_UNUSED_VAR(res2); + int res1 = i; + int res2 = i; // Setup the two meshes in the Bernstein basis // The current implementation hard-codes the two meshes // TODO: Read in two meshes from disk. // In that case, we will need to convert the FEC to the Bernstein basis // remap.setupMeshes(res1, res2, i); - remap.loadMeshes(res1, i); + remap.loadMeshes(res1, res2); // Set up the spatial index remap.setupGrid(); - auto start = std::chrono::high_resolution_clock::now(); + axom::utilities::Timer timer(true); + // Computes the overlaps between elements of the target and source meshes - double Area = remap.computeOverlapAreas(); - auto finish = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = finish - start; - std::cout << i << ", " << Area << "," << elapsed.count() << std::endl; + double area = remap.computeOverlapAreas(); + + timer.stop(); + std::cout.precision(16); + SLIC_INFO(axom::fmt::format( + "Intersecting meshes at resolution {}: area: {}, time: {}", + i, + area, + timer.elapsed())); } - std::cout << "]" << std::endl; + return 0; } From 46094a6ee62f5c1dbd1c8d96c81e455462bbe418 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 13 Jun 2021 12:13:43 -0700 Subject: [PATCH 22/38] Adds cli11 to quest mesh intersection example Supports loading in source and target mfem meshes from file as well as some simple mesh transformations. --- src/axom/quest/examples/CMakeLists.txt | 2 +- .../quest/examples/quest_high_order_remap.cpp | 438 ++++++++++-------- 2 files changed, 233 insertions(+), 207 deletions(-) diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 75d965c65e..8e927d695c 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -215,7 +215,7 @@ if(MFEM_FOUND) NAME quest_high_order_remap_ex SOURCES quest_high_order_remap.cpp OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} - DEPENDS_ON ${quest_example_depends} mfem fmt + DEPENDS_ON ${quest_example_depends} mfem fmt cli11 FOLDER axom/quest/examples ) diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index 3e6ea83512..baf177db95 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -21,6 +21,7 @@ #endif #include "axom/fmt.hpp" +#include "axom/CLI11.hpp" #include @@ -45,7 +46,7 @@ class MeshWrapper using BCurve = CurvedPolygonType::BezierCurveType; private: - /*! \brief Checks if the mesh's nodes are in the Bernstein basis */ + /// \brief Checks if the mesh's nodes are in the Bernstein basis bool isBernsteinBasis() const { auto* fec = m_mesh->GetNodalFESpace()->FEColl(); @@ -94,15 +95,15 @@ class MeshWrapper /// Sets the mfem mesh pointer for this MeshWrapper instance void setMesh(mfem::Mesh* mesh) { - SLIC_ASSERT(mesh != nullptr); + SLIC_ERROR_IF(mesh == nullptr, "Mesh was null"); m_mesh = mesh; - bool isHighOrder = - (m_mesh->GetNodalFESpace() != nullptr) && (m_mesh->GetNE() > 0); - SLIC_ASSERT_MSG(isHighOrder, "The mesh must be high order."); + // Check that mesh is high order + SLIC_ERROR_IF(m_mesh->GetNodes() == nullptr, "The mesh must be high order."); - bool isBernstein = isBernsteinBasis(); - SLIC_ASSERT_MSG(isBernstein, "The mesh must be in the Bernstein basis"); + // Check that mesh nodes are using Bernstein basis + SLIC_ERROR_IF(!isBernsteinBasis(), + "The mesh must be in the Bernstein basis"); const double tol = 1E-8; computeBoundingBoxes(1 + tol); @@ -119,7 +120,7 @@ class MeshWrapper const BBox& meshBoundingBox() const { return m_meshBBox; } /*! - * \brief Transform the mfem element into a primal CurvedPolygon + * \brief Transform the mfem element into a primal::CurvedPolygon * * \param elemId The index of the element * \return The element as a CurvedPolygon composed of BezierCurves @@ -147,16 +148,6 @@ class MeshWrapper // get the dof (degree of freedom) indices for this edge fes->GetEdgeDofs(edgeIds[e], dofIndices); - //SLIC_INFO("Elem " << elemId - // << " edge " << edgeIds[e] << " w/ orient " << edgeOrients[e] - // << " -- dof inds " - // << dofIndices[0] << " " << dofIndices[1] << " " << dofIndices[2] - // << " -- points " - // << spacePointFromDof(dofIndices[0], fes, nodes) << " " - // << spacePointFromDof(dofIndices[1], fes, nodes) << " " - // << spacePointFromDof(dofIndices[2], fes, nodes) - // ); - // possibly reverse the dofs, based on the orientation // Note: The dofs are ordered by vertices, then by edge const bool bReverse = (edgeOrients[e] > 0); @@ -189,7 +180,7 @@ class MeshWrapper } private: - /*! Get the coordinates of the point from the dof index */ + /// Get the coordinates of the point from the dof index SpacePoint spacePointFromDof(int idx, const mfem::FiniteElementSpace* fes, const mfem::GridFunction* nodes) @@ -252,7 +243,7 @@ struct Remapper using CandidateList = std::vector; private: - using GridType = spin::ImplicitGrid<2, axom::SEQ_EXEC, int>; + using GridType = spin::ImplicitGrid; public: Remapper() = default; @@ -283,7 +274,7 @@ struct Remapper SLIC_ERROR_IF(gf_mesh == nullptr, "project_to_pos_basis(): gf_mesh is NULL!"); - int order = 5; // nodal_fe_space->GetOrder(0); + int order = nodal_fe_space->GetOrder(0); int dim = gf_mesh->Dimension(); auto* pos_fe_coll = @@ -329,188 +320,99 @@ struct Remapper return out_pos_gf; } - /*! Set up the source and target meshes */ - void setupMeshes(int res1, int res2, int order) + /*! + * \brief Loads a mesh (source or target) and applies some simple transformations + * + * \param isSource Determines is we're loading the source mesh (true) or target mesh (false) + * \param fname File pointing to an mfem mesh. If empty, we'll generate a Cartesian mesh over the unit square + * \param offset_x Offset for translating the mesh in the x direction + * \param offset_y Offset for translating the mesh in the y direction + * \param scale Factor for uniformly scaling the mesh + * \param mref Number of uniform refinements to apply to the mesh + * \param order Polynomial order for the mesh's nodal grid function + */ + void loadMesh(bool isSource, + const std::string& fname, + double offset_x, + double offset_y, + double scale, + int mref, + int order) { - const auto quadType = mfem::Element::QUADRILATERAL; - const int dim = 2; - - // parameters for source mesh -- quad mesh covering unit square - const int src_res = res2; - const int src_ord = order; - - // parameters for target mesh -- quad mesh covering (part of) unit square - const int tgt_res = res1; - const int tgt_ord = order; - const double tgt_scale = .712378102150; - const double tgt_trans1 = .1345747181586; - const double tgt_trans2 = .1345747181586; + mfem::Mesh* mesh = nullptr; - // create the source mesh + // Load mesh from file or as Cartesian mesh + if(!fname.empty()) { - // create mfem mesh - auto* mesh = new mfem::Mesh(src_res, src_res, quadType, true); - - // create finite element collection for nodes - auto* fec = - new mfem::H1_FECollection(src_ord, dim, mfem::BasisType::Positive); - fecMap.Register("src_fec", fec, true); - - // create finite element space for nodes - auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); - fesMap.Register("src_fes", fes, true); - mesh->SetNodalFESpace(fes); - - //SLIC_DEBUG("Outputting mesh to: " << axom::utilities::filesystem::getCWD()); - { - std::ofstream file; - file.open("ho_field_xfer_source.mfem"); - mesh->Print(file); - } - - srcMesh.setMesh(mesh); + mesh = new mfem::Mesh(fname.c_str(), 1, 1); } - - // create the target mesh + else { - auto* mesh = new mfem::Mesh(tgt_res, tgt_res, quadType, true); - xformMesh(mesh, tgt_scale, tgt_trans1, tgt_trans2); - - auto* fec = - new mfem::H1_FECollection(tgt_ord, dim, mfem::BasisType::Positive); - fecMap.Register("tgt_fec", fec, true); - - auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); - fesMap.Register("tgt_fes", fes, true); - mesh->SetNodalFESpace(fes); - - { - std::ofstream file; - file.open("ho_field_xfer_target.mfem"); - mesh->Print(file); - } - - tgtMesh.setMesh(mesh); + const auto quadType = mfem::Element::QUADRILATERAL; + const bool generateEdges = true; + const int res = 1; + mesh = new mfem::Mesh(res, res, quadType, generateEdges); } - } - void loadMeshes(int res2, int order) - { - // create the source mesh - const auto quadType = mfem::Element::QUADRILATERAL; - const int dim = 2; - // parameters for target mesh -- quad mesh covering unit square - const int src_res = res2; - const int src_ord = order; - const double src_scale = 1.5; - const double src_trans1 = .01517288412347; - const double src_trans2 = .02571238506182; + // Ensure that mesh has high order nodes + mesh->SetCurvature(order); - // parameters for target mesh -- quad mesh covering (part of) unit square - const int tgt_ord = 2; - - AXOM_UNUSED_VAR(quadType); - AXOM_UNUSED_VAR(src_res); - AXOM_UNUSED_VAR(src_ord); - AXOM_UNUSED_VAR(tgt_ord); + // Scale and offset mesh + xformMesh(mesh, scale, offset_x, offset_y); + // Apply uniform refinement + for(int i = 0; i < mref; ++i) { - // NOTE (KW): For now, assume we have AXOM_DATA_DIR - namespace fs = axom::utilities::filesystem; - std::string fname = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); + mesh->UniformRefinement(); + } - auto* mesh = new mfem::Mesh(fname.c_str(), 1, 1); - xformMesh(mesh, src_scale, src_trans1, src_trans2); - if(mesh->NURBSext) - { - int ord = 5; //src_ord; - mesh->SetCurvature(ord); - } - // xformMesh(mesh, tgt_scale, tgt_trans); - { - std::ofstream file; - file.open("src_mesh_orig.mfem"); - mesh->Print(file); - } + // dump original mesh + { + std::ofstream file; + file.open(axom::fmt::format("{}_mesh_orig.mfem", isSource ? "src" : "tgt")); + mesh->Print(file); + } - bool is_mesh_gf_new; + // project nodes to Berstein basis, if necessary + { + bool is_mesh_gf_new {false}; mfem::GridFunction* mesh_nodes = mesh->GetNodes(); mfem::GridFunction* pos_mesh_nodes_ptr = project_to_pos_basis(mesh_nodes, is_mesh_gf_new); + mfem::GridFunction& pos_mesh_nodes = (is_mesh_gf_new ? *pos_mesh_nodes_ptr : *mesh_nodes); mesh->NewNodes(pos_mesh_nodes, true); + } - AXOM_UNUSED_VAR(dim); - //auto* fec = new mfem::H1_FECollection(tgt_ord, dim, - // mfem::BasisType::Positive); - //fecMap.Register("tgt_fec", fec, true); + // dump modified mesh + { + std::ofstream file; + file.open(axom::fmt::format("{}_mesh_set.mfem", isSource ? "src" : "tgt")); + mesh->Print(file); + } - //auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); - //fesMap.Register("tgt_fes", fes, true); - //mesh->SetNodalFESpace(fes); - SLIC_INFO(axom::fmt::format("Source mesh has {} vertices", mesh->GetNV())); - { - std::ofstream file; - file.open("src_mesh_set.mfem"); - mesh->Print(file); - } - //std::cout << "Got here!" << std::endl; + // set as active source/target mesh + if(isSource) + { srcMesh.setMesh(mesh); } - - // create the target mesh + else { - // NOTE (KW): For now, assume we have AXOM_DATA_DIR - namespace fs = axom::utilities::filesystem; - std::string fname = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); - - auto* mesh = new mfem::Mesh(fname.c_str(), 1, 1); - if(mesh->NURBSext) - { - int ord = 5; //tgt_ord; - mesh->SetCurvature(ord); - } - - // xformMesh(mesh, tgt_scale, tgt_trans); - const double tgt_scale = 1.0; - const double tgt_trans1 = .001237586; - const double tgt_trans2 = -.06172376; - xformMesh(mesh, tgt_scale, tgt_trans1, tgt_trans2); - - { - std::ofstream file; - file.open("target_mesh_orig.mfem"); - mesh->Print(file); - } - - bool is_mesh_gf_new; - mfem::GridFunction* mesh_nodes = mesh->GetNodes(); - mfem::GridFunction* pos_mesh_nodes_ptr = - project_to_pos_basis(mesh_nodes, is_mesh_gf_new); - mfem::GridFunction& pos_mesh_nodes = - (is_mesh_gf_new ? *pos_mesh_nodes_ptr : *mesh_nodes); - mesh->NewNodes(pos_mesh_nodes, true); - - //auto* fec = new mfem::H1_FECollection(tgt_ord, dim, - // mfem::BasisType::Positive); - //fecMap.Register("tgt_fec", fec, true); - - //auto* fes = new mfem::FiniteElementSpace(mesh, fec, dim); - //fesMap.Register("tgt_fes", fes, true); - //mesh->SetNodalFESpace(fes); - - { - std::ofstream file; - file.open("target_mesh_set.mfem"); - mesh->Print(file); - } - //std::cout << "Got here!" << std::endl; tgtMesh.setMesh(mesh); } + + SLIC_INFO(axom::fmt::format( + "Loaded {} mesh from {} w/ {} elements." + "\n\t(Slightly inflated) mesh bounding box: {}", + isSource ? "source" : "target", + fname.empty() ? "Cartesian grid" : fname, + mesh->GetNE(), + isSource ? srcMesh.meshBoundingBox() : tgtMesh.meshBoundingBox())); } - /*! Setup the implicit grid spatial index over the source mesh */ - void setupGrid() + + /// Setup the implicit grid spatial index over the source mesh + void initSpatialIndex() { const int NE = srcMesh.numElements(); grid.initialize(srcMesh.meshBoundingBox(), nullptr, NE); @@ -534,8 +436,7 @@ struct Remapper double calcE = 0.0; for(int i = 0; i < nTargetElems; ++i) { - // Finds the candidates from the source mesh that - // can intersect this target element + // Finds the candidates from the source mesh that can intersect this target element auto candidates = getSourceCandidates(i); if(candidates.empty()) break; @@ -654,47 +555,172 @@ struct Remapper } }; +/// Simple struct to hold properties for initializing a mesh instance +struct MeshProps +{ + std::string file; + std::vector offset; + double scale {1.}; + int order {2}; + int refinement {0}; + + bool isDefault() const { return file.empty() && offset.empty(); } + bool hasOffset() const { return offset.size() == 2; } + + friend std::ostream& operator<<(std::ostream& os, const MeshProps& props) + { + os << axom::fmt::format( + "{{\n" + " file: {} \n" + " offset({}): {} \n" + " order: {} \n" + " scale: {} \n" + " refinement: {} \n" + "}}", + props.file.empty() ? "<>" : axom::fmt::format("'{}'", props.file), + props.offset.size(), + axom::fmt::join(props.offset, " "), + props.order, + props.scale, + props.refinement); + return os; + } +}; + //------------------------------------------------------------------------------ int main(int argc, char** argv) { - AXOM_UNUSED_VAR(argc); - AXOM_UNUSED_VAR(argv); - axom::slic::SimpleLogger logger; SLIC_INFO("The application conservatively maps fields from a source \n" << "high order mesh to a target high order mesh!"); - SLIC_INFO("Running intersection tests..."); - for(int i = 2; i <= 2; ++i) + MeshProps srcMesh; + MeshProps tgtMesh; + + // Setup default meshes if user doesn't pass in data +#ifdef AXOM_DATA_DIR + // Use a mesh from a file if we have the data directory + namespace fs = axom::utilities::filesystem; + MeshProps defaultSrcMesh; + defaultSrcMesh.file = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); + defaultSrcMesh.scale = 1.5; + defaultSrcMesh.offset = {.01517288412347, .02571238506182}; + defaultSrcMesh.order = 3; + + MeshProps defaultTgtMesh; + defaultTgtMesh.file = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); + defaultTgtMesh.offset = {.001237586, -.06172376}; + defaultTgtMesh.order = 3; +#else + // parameters for a Cartesian mesh + MeshProps defaultSrcMesh; + defaultSrcMesh.refinement = 1; + defaultSrcMesh.order = 5; + + // quad mesh partially covering unit square + MeshProps defaultTgtMesh; + defaultSrcMesh.refinement = 1; + defaultTgtMesh.scale = 712378102150; + defaultTgtMesh.offset = {.1345747181586, .1345747181586}; + defaultTgtMesh.order = 5; +#endif + + // Set up and parse command line args + axom::CLI::App app {"High order mesh intersection application"}; { - Remapper remap; + app.add_option("--srcFile", srcMesh.file) + ->description("mfem mesh file for source mesh") + ->check(axom::CLI::ExistingFile); + app.add_option("--srcOffset", srcMesh.offset) + ->description("offset for source mesh") + ->expected(2); + app.add_option("--srcScale", srcMesh.scale) + ->description("scale for source mesh") + ->capture_default_str(); + app.add_option("--srcOrder", srcMesh.order) + ->description("polynomial order for source mesh") + ->capture_default_str(); + app.add_option("--srcRef", srcMesh.refinement) + ->description("refinement levels for source mesh") + ->capture_default_str(); + + app.add_option("--tgtFile", tgtMesh.file) + ->description("mfem mesh file for source mesh") + ->check(axom::CLI::ExistingFile); + app.add_option("--tgtOffset", tgtMesh.offset) + ->description("offset for target mesh") + ->expected(2); + app.add_option("--tgtScale", tgtMesh.scale) + ->description("scale for target mesh") + ->capture_default_str(); + app.add_option("--tgtOrder", tgtMesh.order) + ->description("polynomial order for target mesh") + ->capture_default_str(); + app.add_option("--tgtRef", tgtMesh.refinement) + ->description("refinement levels for target mesh") + ->capture_default_str(); + } + CLI11_PARSE(app, argc, argv); - int res1 = i; - int res2 = i; + SLIC_INFO("Running intersection tests..."); + axom::utilities::Timer timer(false); + + Remapper remap; - // Setup the two meshes in the Bernstein basis - // The current implementation hard-codes the two meshes - // TODO: Read in two meshes from disk. - // In that case, we will need to convert the FEC to the Bernstein basis - // remap.setupMeshes(res1, res2, i); - remap.loadMeshes(res1, res2); + // Load the source mesh + { + const bool isSource = true; + if(srcMesh.isDefault()) + { + srcMesh = defaultSrcMesh; + } + if(!srcMesh.hasOffset()) + { + srcMesh.offset = {0, 0}; + } + SLIC_INFO("Loading source mesh: " << srcMesh); + remap.loadMesh(isSource, + srcMesh.file, + srcMesh.offset[0], + srcMesh.offset[1], + srcMesh.scale, + srcMesh.refinement, + srcMesh.order); + } + // Load the target mesh + { + const bool isSource = false; + if(tgtMesh.isDefault()) + { + tgtMesh = defaultTgtMesh; + } + if(!tgtMesh.hasOffset()) + { + tgtMesh.offset = {0, 0}; + } + SLIC_INFO("Loading target mesh: " << tgtMesh); + remap.loadMesh(isSource, + tgtMesh.file, + tgtMesh.offset[0], + tgtMesh.offset[1], + tgtMesh.scale, + tgtMesh.refinement, + tgtMesh.order); + } - // Set up the spatial index - remap.setupGrid(); - axom::utilities::Timer timer(true); + // Set up the spatial index + remap.initSpatialIndex(); - // Computes the overlaps between elements of the target and source meshes - double area = remap.computeOverlapAreas(); + // Computes the overlaps between elements of the target and source meshes + timer.start(); + double area = remap.computeOverlapAreas(); + timer.stop(); - timer.stop(); - std::cout.precision(16); - SLIC_INFO(axom::fmt::format( - "Intersecting meshes at resolution {}: area: {}, time: {}", - i, - area, - timer.elapsed())); - } + std::cout.precision(16); + SLIC_INFO(axom::fmt::format("Intersecting meshes: area: {}, time: {}", + area, + timer.elapsed())); return 0; } From af3bf65626b87bddae04e1c44809f76c418e18a9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 14 Jun 2021 09:33:35 -0700 Subject: [PATCH 23/38] Adds a function to output the high order intersections as SVG in quest example Also fixes a bug in loading the input mesh: We need to refine before transforming the mesh. --- .../quest/examples/quest_high_order_remap.cpp | 184 ++++++++++++++++-- 1 file changed, 168 insertions(+), 16 deletions(-) diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index baf177db95..4b29c66621 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -354,18 +354,18 @@ struct Remapper mesh = new mfem::Mesh(res, res, quadType, generateEdges); } - // Ensure that mesh has high order nodes - mesh->SetCurvature(order); - - // Scale and offset mesh - xformMesh(mesh, scale, offset_x, offset_y); - // Apply uniform refinement for(int i = 0; i < mref; ++i) { mesh->UniformRefinement(); } + // Ensure that mesh has high order nodes + mesh->SetCurvature(order); + + // Scale and offset mesh + xformMesh(mesh, scale, offset_x, offset_y); + // dump original mesh { std::ofstream file; @@ -432,16 +432,19 @@ struct Remapper double totalArea = 0.0; double correctArea = 0.0; const int nTargetElems = tgtMesh.numElements(); - // SLIC_INFO("Number of Target Elements: " << nTargetElems); + double calcE = 0.0; for(int i = 0; i < nTargetElems; ++i) { // Finds the candidates from the source mesh that can intersect this target element auto candidates = getSourceCandidates(i); - if(candidates.empty()) break; + if(candidates.empty()) + { + continue; + } auto tgtPoly = tgtMesh.elemAsCurvedPolygon(i); - // SLIC_INFO("Target Element: " << tgtPoly); + //SLIC_INFO("Target Element: " << tgtPoly); correctArea += primal::area(tgtPoly); //SLIC_INFO("Target elem " << i // << " -- area " << primal::area(tgtPoly) @@ -452,11 +455,11 @@ struct Remapper for(int srcElem : candidates) { auto srcPoly = srcMesh.elemAsCurvedPolygon(srcElem); - // SLIC_INFO("*Source Element: " << srcPoly); - // SLIC_INFO("* Source elem " << srcElem - // << " -- area " << primal::area(srcPoly) - // //// //<< " -- bbox " << srcMesh.elementBoundingBox(srcElem) - // ); + //SLIC_INFO("\tSource Element: " << srcPoly); + //SLIC_INFO( + // "\tSource elem \n\t\t" << srcElem << "\n\t\t -- area " << primal::area(srcPoly) + // << " -- bbox " << srcMesh.elementBoundingBox(srcElem) + //); std::vector> pnew; tgtPoly.reverseOrientation(); @@ -522,6 +525,153 @@ struct Remapper return filteredCandidates; } + void outputAsSVG() + { + std::string header = R"html()html"; + std::string footer = ""; + + // lambda to convert a CurvedPolygon to an SVG path string + auto cpToSVG = [](const MeshWrapper::CurvedPolygonType& cp) { + axom::fmt::memory_buffer out; + bool is_first = true; + + for(auto& curve : cp.getEdges()) + { + // Only write out first point for first edge + if(is_first) + { + axom::fmt::format_to(out, "M {} {} ", curve[0][0], curve[0][1]); + is_first = false; + } + + switch(curve.getOrder()) + { + case 1: + axom::fmt::format_to(out, "L {} {} ", curve[1][0], curve[1][1]); + break; + case 2: + axom::fmt::format_to(out, + "Q {} {}, {} {} ", + curve[1][0], + curve[1][1], + curve[2][0], + curve[2][1]); + break; + case 3: + axom::fmt::format_to(out, + "C {} {}, {} {}, {} {} ", + curve[1][0], + curve[1][1], + curve[2][0], + curve[2][1], + curve[3][0], + curve[3][1]); + break; + default: + SLIC_WARNING( + "Unsupported case: can only output up to cubic curves as SVG."); + } + } + return axom::fmt::format(" \n", + axom::fmt::to_string(out)); + }; + + std::string srcGroup; + std::string tgtGroup; + std::string intersectionGroup; + + // output src mesh + { + axom::fmt::memory_buffer out; + axom::fmt::format_to( + out, + " \n"); + auto& meshWrapper = srcMesh; + for(int i = 0; i < meshWrapper.numElements(); ++i) + { + auto cp = meshWrapper.elemAsCurvedPolygon(i); + axom::fmt::format_to(out, cpToSVG(cp)); + } + axom::fmt::format_to(out, " \n"); + srcGroup = axom::fmt::to_string(out); + } + + //output tgt mesh + { + axom::fmt::memory_buffer out; + axom::fmt::format_to( + out, + " \n"); + auto& meshWrapper = tgtMesh; + for(int i = 0; i < meshWrapper.numElements(); ++i) + { + auto cp = meshWrapper.elemAsCurvedPolygon(i); + axom::fmt::format_to(out, cpToSVG(cp)); + } + axom::fmt::format_to(out, " \n"); + tgtGroup = axom::fmt::to_string(out); + } + + //output intersection elements + { + double EPS = 1e-8; + axom::fmt::memory_buffer out; + axom::fmt::format_to( + out, + " \n"); + + // foreach target element, find src intersections and add to group + const int nTargetElems = tgtMesh.numElements(); + for(int i = 0; i < nTargetElems; ++i) + { + auto candidates = getSourceCandidates(i); + if(candidates.empty()) + { + continue; + } + + auto tgtPoly = tgtMesh.elemAsCurvedPolygon(i); + for(int srcElem : candidates) + { + auto srcPoly = srcMesh.elemAsCurvedPolygon(srcElem); + + srcPoly.reverseOrientation(); + tgtPoly.reverseOrientation(); + + std::vector pnew; + if(primal::intersect(tgtPoly, srcPoly, pnew, EPS)) + { + for(auto& cp : pnew) + { + axom::fmt::format_to(out, cpToSVG(cp)); + } + } + + srcPoly.reverseOrientation(); + tgtPoly.reverseOrientation(); + } + } + + axom::fmt::format_to(out, " \n"); + intersectionGroup = axom::fmt::to_string(out); + } + + // Write the file + { + std::string fname = "high_order_intersections.svg"; + std::ofstream fs(fname); + fs << header << std::endl; + fs << srcGroup << std::endl; + fs << tgtGroup << std::endl; + fs << intersectionGroup << std::endl; + fs << footer << std::endl; + } + } + public: MeshWrapper srcMesh; MeshWrapper tgtMesh; @@ -603,13 +753,13 @@ int main(int argc, char** argv) // Use a mesh from a file if we have the data directory namespace fs = axom::utilities::filesystem; MeshProps defaultSrcMesh; - defaultSrcMesh.file = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); + defaultSrcMesh.file = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs.mesh"); defaultSrcMesh.scale = 1.5; defaultSrcMesh.offset = {.01517288412347, .02571238506182}; defaultSrcMesh.order = 3; MeshProps defaultTgtMesh; - defaultTgtMesh.file = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs-80.mesh"); + defaultTgtMesh.file = fs::joinPath(AXOM_DATA_DIR, "mfem/disc-nurbs.mesh"); defaultTgtMesh.offset = {.001237586, -.06172376}; defaultTgtMesh.order = 3; #else @@ -717,6 +867,8 @@ int main(int argc, char** argv) double area = remap.computeOverlapAreas(); timer.stop(); + remap.outputAsSVG(); + std::cout.precision(16); SLIC_INFO(axom::fmt::format("Intersecting meshes: area: {}, time: {}", area, From aa5271dfe1ffd9bfa779577e98cc35eebdf7dfed Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 14 Jun 2021 20:41:55 -0700 Subject: [PATCH 24/38] Refactoring primal's intersect_polygon Breaks out function that inserts all intersection vertices into the polygons. --- .../detail/intersect_curved_poly_impl.hpp | 122 +++++++++++++----- 1 file changed, 89 insertions(+), 33 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 1484e49cde..010dab1b17 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -27,6 +27,8 @@ #include "axom/primal/operators/squared_distance.hpp" #include "axom/primal/operators/detail/intersect_bezier_impl.hpp" +#include "axom/fmt.hpp" + namespace axom { namespace primal @@ -34,7 +36,7 @@ namespace primal namespace detail { template -class IntersectionInfo; +class EdgeIntersectionInfo; template bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t); @@ -45,23 +47,24 @@ int isContained(const CurvedPolygon& p1, double sq_tol = 1e-10); /*! - * \brief Test whether the regions bounded by CurvedPolygons \a p1 and \a p2 intersect - * \return status true iff \a p1 intersects with \a p2, otherwise false. - * - * \param [in] p1, p2 CurvedPolygon objects to intersect - * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curve - * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. + * Splits edges of each polygon based on the intersections with the other polygon + * When the two polygons intersect, the split polygons are returned in \a psplit + * The edges are labeled by the types of intersections in \a edgelabels + * and \a orientation contains the relative orientation of the first intersection */ -template -bool intersect_polygon(const CurvedPolygon& p1, - const CurvedPolygon& p2, - std::vector>& pnew, - double sq_tol) + +template +int splitPolygonsAlongIntersections(const CurvedPolygonType& p1, + const CurvedPolygonType& p2, + double sq_tol, + CurvedPolygonType psplit[2], + std::vector edgelabels[2], + bool& orientation) { // Object to store intersections - std::vector>> E1IntData(p1.numEdges()); - std::vector>> E2IntData(p2.numEdges()); - IntersectionInfo firstinter; // Need to do orientation test on first intersection + std::vector>> E1IntData(p1.numEdges()); + std::vector>> E2IntData(p2.numEdges()); + EdgeIntersectionInfo firstinter; // Need to do orientation test on first intersection // Find all intersections and store int numinters = 0; @@ -82,41 +85,93 @@ bool intersect_polygon(const CurvedPolygon& p1, 1., 0., 1.); - for(int k = 0; k < static_cast(p1times.size()); ++k) + const int edgeIntersections = p1times.size(); + if(edgeIntersections > 0) { - E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + k + 1}); - E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + k + 1}); if(numinters == 0) { firstinter = {p1times[0], i, p2times[0], j, 1}; } + + for(int k = 0; k < edgeIntersections; ++k, ++numinters) + { + E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + 1}); + E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + 1}); + + SLIC_INFO(fmt::format( + "Found intersection {} -- on edge {} of polygon1 at t={}" + " and edge {} of polygon2 at t={}; intersection point {}", + numinters + 1, + i, + p1times[k], + j, + p2times[k], + p1[i].evaluate(p1times[k]))); + } } - numinters += p1times.size(); } } + if(numinters > 0) { + // Orient the first intersection point to be sure we get the intersection + orientation = detail::orient(p1[firstinter.myEdge], + p2[firstinter.otherEdge], + firstinter.myTime, + firstinter.otherTime); + for(int i = 0; i < p1.numEdges(); ++i) { std::sort(E1IntData[i].begin(), E1IntData[i].end()); + } + for(int i = 0; i < p2.numEdges(); ++i) + { std::sort(E2IntData[i].begin(), E2IntData[i].end()); } - // Orient the first intersection point to be sure we get the intersection - bool orientation = detail::orient(p1[firstinter.myEdge], - p2[firstinter.otherEdge], - firstinter.myTime, - firstinter.otherTime); - - // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each intersection and zeros for corners of original polygons. - std::vector edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points - CurvedPolygon psplit[2]; // The two completely split polygons will be stored in this array psplit[0] = p1; psplit[1] = p2; + SLIC_INFO("Poly1 before split: " << psplit[0]); splitPolygon(psplit[0], E1IntData, edgelabels[0]); + SLIC_INFO("Poly1 after split: " << psplit[0]); + + SLIC_INFO("Poly2 before split: " << psplit[1]); splitPolygon(psplit[1], E2IntData, edgelabels[1]); + SLIC_INFO("Poly2 after split: " << psplit[1]); + } + + return numinters; +} +/*! + * \brief Test whether the regions bounded by CurvedPolygons \a p1 and \a p2 intersect + * \return status true iff \a p1 intersects with \a p2, otherwise false. + * + * \param [in] p1, p2 CurvedPolygon objects to intersect + * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curve + * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. + */ +template +bool intersect_polygon(const CurvedPolygon& p1, + const CurvedPolygon& p2, + std::vector>& pnew, + double sq_tol) +{ + // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each intersection and zeros for corners of original polygons. + std::vector edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points + CurvedPolygon psplit[2]; // The two completely split polygons will be stored in this array + bool orientation {false}; + + int numinters = splitPolygonsAlongIntersections(p1, + p2, + sq_tol, + psplit, + edgelabels, + orientation); + + if(numinters > 0) + { // This performs the directional walking method using the completely split polygons std::vector::iterator> usedlabels; // When this is false, we are walking between intersection regions @@ -138,6 +193,7 @@ bool intersect_polygon(const CurvedPolygon& p1, while(numinters > 0) { CurvedPolygon aPart; // Object to store the current intersection polygon (could be multiple) + // Once the end vertex of the current edge is the start vertex, we need to switch regions while(!(nextit == startit && currentelement == orientation) || addingcurves == false) @@ -327,7 +383,7 @@ int isContained(const CurvedPolygon& p1, */ template void splitPolygon(CurvedPolygon& p1, - std::vector>>& IntersectionData, + std::vector>>& IntersectionData, std::vector& edgelabels) { const int nEd = p1.numEdges(); @@ -370,12 +426,12 @@ bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t) return (orientation > 0); } -/*! \class IntersectionInfo +/*! \class EdgeIntersectionInfo * - * \brief For storing intersection points between \a CurvedPolygon instances so they can be easily sorted by parameter value using std::sort + * \brief For storing intersection points between edges of \a CurvedPolygon instances so they can be easily sorted by parameter value using std::sort */ template -class IntersectionInfo +class EdgeIntersectionInfo { public: T myTime; // parameter value of intersection on curve on first CurvePolygon @@ -385,7 +441,7 @@ class IntersectionInfo int numinter; // unique intersection point identifier /// \brief Comparison operator for sorting by parameter value - bool operator<(const IntersectionInfo& other) const + bool operator<(const EdgeIntersectionInfo& other) const { return myTime < other.myTime; } From c8f494ed30dace16fdd84b151e4761ca6246ed32 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 14 Jun 2021 21:45:32 -0700 Subject: [PATCH 25/38] Cleans up splitPolygon algorithm for primal's intersect_polygon algorithm --- .../detail/intersect_curved_poly_impl.hpp | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 010dab1b17..7cf9718106 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -132,6 +132,9 @@ int splitPolygonsAlongIntersections(const CurvedPolygonType& p1, psplit[0] = p1; psplit[1] = p2; + edgelabels[0].reserve(p1.numEdges() + numinters); + edgelabels[1].reserve(p2.numEdges() + numinters); + SLIC_INFO("Poly1 before split: " << psplit[0]); splitPolygon(psplit[0], E1IntData, edgelabels[0]); SLIC_INFO("Poly1 after split: " << psplit[0]); @@ -378,31 +381,49 @@ int isContained(const CurvedPolygon& p1, } } -/* - * \brief Splits a CurvedPolygon p1 at every intersection point stored in IntersectionData - */ +/// Splits a CurvedPolygon \a p1 at every intersection point stored in \a IntersectionData +/// \a edgeLabels stores intersection id for new vertices and 0 for original vertices template void splitPolygon(CurvedPolygon& p1, std::vector>>& IntersectionData, std::vector& edgelabels) { - const int nEd = p1.numEdges(); - //split polygon 2 at all the intersection points and store as psplit[1] - int addedints = 0; - for(int i = 0; i < nEd; ++i) + using axom::utilities::isNearlyEqual; + + int addedIntersections = 0; + const int numEdges = p1.numEdges(); + for(int i = 0; i < numEdges; ++i) // foreach edge { - edgelabels.push_back(0); - for(int j = 0; j < static_cast(IntersectionData[i].size()); ++j) + edgelabels.push_back(0); // mark start current vertex as 'original' + const int nIntersect = IntersectionData[i].size(); + for(int j = 0; j < nIntersect; ++j) // foreach intersection on this edge { - p1.splitEdge(i + addedints, IntersectionData[i][j].myTime); - edgelabels.insert(edgelabels.begin() + i + addedints, - IntersectionData[i][j].numinter); - addedints += 1; - for(int k = j + 1; k < static_cast(IntersectionData[i].size()); ++k) + // split edge at parameter t_j + const double t_j = IntersectionData[i][j].myTime; + const int edgeIndex = i + addedIntersections; + p1.splitEdge(edgeIndex, t_j); + + // update edge label + const int label = IntersectionData[i][j].numinter; + edgelabels.insert(edgelabels.begin() + edgeIndex, label); + + ++addedIntersections; + + // update remaining intersections on this edge; special case if already at end of curve + if(!isNearlyEqual(1., t_j)) { - IntersectionData[i][k].myTime = - (IntersectionData[i][k].myTime - IntersectionData[i][j].myTime) / - (1 - IntersectionData[i][j].myTime); + for(int k = j + 1; k < nIntersect; ++k) + { + const double t_k = IntersectionData[i][k].myTime; + IntersectionData[i][k].myTime = (t_k - t_j) / (1.0 - t_j); + } + } + else + { + for(int k = j + 1; k < nIntersect; ++k) + { + IntersectionData[i][k].myTime = 1.; + } } } } From 5c1f8377e104a180ccf8be0693f9a7c96c4c117b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 14 Jun 2021 23:11:08 -0700 Subject: [PATCH 26/38] Refactors internals of primal's intersect_polygon into new DirectionalWalk class --- .../detail/intersect_curved_poly_impl.hpp | 384 +++++++++--------- 1 file changed, 201 insertions(+), 183 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 7cf9718106..de2f423a54 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -35,9 +35,6 @@ namespace primal { namespace detail { -template -class EdgeIntersectionInfo; - template bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t); @@ -46,137 +43,145 @@ int isContained(const CurvedPolygon& p1, const CurvedPolygon& p2, double sq_tol = 1e-10); -/*! - * Splits edges of each polygon based on the intersections with the other polygon - * When the two polygons intersect, the split polygons are returned in \a psplit - * The edges are labeled by the types of intersections in \a edgelabels - * and \a orientation contains the relative orientation of the first intersection - */ - -template -int splitPolygonsAlongIntersections(const CurvedPolygonType& p1, - const CurvedPolygonType& p2, - double sq_tol, - CurvedPolygonType psplit[2], - std::vector edgelabels[2], - bool& orientation) +template +class DirectionalWalk { - // Object to store intersections - std::vector>> E1IntData(p1.numEdges()); - std::vector>> E2IntData(p2.numEdges()); - EdgeIntersectionInfo firstinter; // Need to do orientation test on first intersection - - // Find all intersections and store - int numinters = 0; - for(int i = 0; i < p1.numEdges(); ++i) +public: + using CurvedPolygonType = CurvedPolygon; + using EdgeLabels = std::vector; + +public: + /*! \class EdgeIntersectionInfo + * + * \brief For storing intersection points between edges of \a CurvedPolygon instances so they can be easily sorted by parameter value using std::sort + */ + class EdgeIntersectionInfo { - for(int j = 0; j < p2.numEdges(); ++j) + public: + T myTime; // parameter value of intersection on curve on first CurvePolygon + int myEdge; // index of curve on first CurvedPolygon + T otherTime; // parameter value of intersection on curve on second CurvedPolygon + int otherEdge; // index of curve on second CurvedPolygon + int numinter; // unique intersection point identifier + + /// \brief Comparison operator for sorting by parameter value + bool operator<(const EdgeIntersectionInfo& other) const { - std::vector p1times; - std::vector p2times; - intersect_bezier_curves(p1[i], - p2[j], - p1times, - p2times, - sq_tol, - p1[i].getOrder(), - p2[j].getOrder(), - 0., - 1., - 0., - 1.); - const int edgeIntersections = p1times.size(); - if(edgeIntersections > 0) - { - if(numinters == 0) - { - firstinter = {p1times[0], i, p2times[0], j, 1}; - } - - for(int k = 0; k < edgeIntersections; ++k, ++numinters) - { - E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + 1}); - E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + 1}); - - SLIC_INFO(fmt::format( - "Found intersection {} -- on edge {} of polygon1 at t={}" - " and edge {} of polygon2 at t={}; intersection point {}", - numinters + 1, - i, - p1times[k], - j, - p2times[k], - p1[i].evaluate(p1times[k]))); - } - } + return myTime < other.myTime; } - } + }; - if(numinters > 0) +public: + explicit DirectionalWalk(bool verbose = false) : m_verbose(verbose) { } + + /*! + * Splits edges of each polygon based on the intersections with the other polygon + * When the two polygons intersect, the split polygons are returned in \a psplit + * The edges are labeled by the types of intersections in \a edgelabels + * and \a orientation contains the relative orientation of the first intersection + */ + int splitPolygonsAlongIntersections(const CurvedPolygonType& p1, + const CurvedPolygonType& p2, + double sq_tol) { - // Orient the first intersection point to be sure we get the intersection - orientation = detail::orient(p1[firstinter.myEdge], - p2[firstinter.otherEdge], - firstinter.myTime, - firstinter.otherTime); + // We store intersection information for each edge in EdgeIntersectionInfo structures + std::vector> E1IntData(p1.numEdges()); + std::vector> E2IntData(p2.numEdges()); + EdgeIntersectionInfo firstinter; // Need to do orientation test on first intersection + // Find all intersections and store + numinters = 0; for(int i = 0; i < p1.numEdges(); ++i) { - std::sort(E1IntData[i].begin(), E1IntData[i].end()); + for(int j = 0; j < p2.numEdges(); ++j) + { + std::vector p1times; + std::vector p2times; + intersect_bezier_curves(p1[i], + p2[j], + p1times, + p2times, + sq_tol, + p1[i].getOrder(), + p2[j].getOrder(), + 0., + 1., + 0., + 1.); + const int edgeIntersections = p1times.size(); + if(edgeIntersections > 0) + { + if(numinters == 0) + { + firstinter = {p1times[0], i, p2times[0], j, 1}; + } + + for(int k = 0; k < edgeIntersections; ++k, ++numinters) + { + E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + 1}); + E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + 1}); + + if(m_verbose) + { + SLIC_INFO(fmt::format( + "Found intersection {} -- on edge {} of polygon1 at t={}" + " and edge {} of polygon2 at t={}; intersection point {}", + numinters + 1, + i, + p1times[k], + j, + p2times[k], + p1[i].evaluate(p1times[k]))); + } + } + } + } } - for(int i = 0; i < p2.numEdges(); ++i) + + if(numinters > 0) { - std::sort(E2IntData[i].begin(), E2IntData[i].end()); - } + // Orient the first intersection point to be sure we get the intersection + orientation = detail::orient(p1[firstinter.myEdge], + p2[firstinter.otherEdge], + firstinter.myTime, + firstinter.otherTime); - psplit[0] = p1; - psplit[1] = p2; + for(int i = 0; i < p1.numEdges(); ++i) + { + std::sort(E1IntData[i].begin(), E1IntData[i].end()); + } + for(int i = 0; i < p2.numEdges(); ++i) + { + std::sort(E2IntData[i].begin(), E2IntData[i].end()); + } - edgelabels[0].reserve(p1.numEdges() + numinters); - edgelabels[1].reserve(p2.numEdges() + numinters); + psplit[0] = p1; + psplit[1] = p2; - SLIC_INFO("Poly1 before split: " << psplit[0]); - splitPolygon(psplit[0], E1IntData, edgelabels[0]); - SLIC_INFO("Poly1 after split: " << psplit[0]); + edgelabels[0].reserve(p1.numEdges() + numinters); + edgelabels[1].reserve(p2.numEdges() + numinters); - SLIC_INFO("Poly2 before split: " << psplit[1]); - splitPolygon(psplit[1], E2IntData, edgelabels[1]); - SLIC_INFO("Poly2 after split: " << psplit[1]); - } + if(m_verbose) + { + SLIC_INFO("Poly1 before split: " << psplit[0]); + SLIC_INFO("Poly2 before split: " << psplit[1]); + } - return numinters; -} + splitPolygon(psplit[0], E1IntData, edgelabels[0]); + splitPolygon(psplit[1], E2IntData, edgelabels[1]); -/*! - * \brief Test whether the regions bounded by CurvedPolygons \a p1 and \a p2 intersect - * \return status true iff \a p1 intersects with \a p2, otherwise false. - * - * \param [in] p1, p2 CurvedPolygon objects to intersect - * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curve - * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. - */ -template -bool intersect_polygon(const CurvedPolygon& p1, - const CurvedPolygon& p2, - std::vector>& pnew, - double sq_tol) -{ - // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each intersection and zeros for corners of original polygons. - std::vector edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points - CurvedPolygon psplit[2]; // The two completely split polygons will be stored in this array - bool orientation {false}; + if(m_verbose) + { + SLIC_INFO("Poly1 after split: " << psplit[0]); + SLIC_INFO("Poly2 after split: " << psplit[1]); + } + } - int numinters = splitPolygonsAlongIntersections(p1, - p2, - sq_tol, - psplit, - edgelabels, - orientation); + return numinters; + } - if(numinters > 0) + void findIntersectionRegions(std::vector& pnew) { - // This performs the directional walking method using the completely split polygons - std::vector::iterator> usedlabels; // When this is false, we are walking between intersection regions bool addingcurves = true; int startvertex = 1; // Start at the vertex with "unique id" 1 @@ -266,6 +271,88 @@ bool intersect_polygon(const CurvedPolygon& p1, addingcurves = false; } } + } + +private: + /// Splits a CurvedPolygon \a p1 at every intersection point stored in \a IntersectionData + /// \a edgeLabels stores intersection id for new vertices and 0 for original vertices + void splitPolygon(CurvedPolygon& p1, + std::vector>& IntersectionData, + std::vector& edgelabels) + { + using axom::utilities::isNearlyEqual; + + int addedIntersections = 0; + const int numEdges = p1.numEdges(); + for(int i = 0; i < numEdges; ++i) // foreach edge + { + edgelabels.push_back(0); // mark start current vertex as 'original' + const int nIntersect = IntersectionData[i].size(); + for(int j = 0; j < nIntersect; ++j) // foreach intersection on this edge + { + // split edge at parameter t_j + const double t_j = IntersectionData[i][j].myTime; + const int edgeIndex = i + addedIntersections; + p1.splitEdge(edgeIndex, t_j); + + // update edge label + const int label = IntersectionData[i][j].numinter; + edgelabels.insert(edgelabels.begin() + edgeIndex, label); + + ++addedIntersections; + + // update remaining intersections on this edge; special case if already at end of curve + if(!isNearlyEqual(1., t_j)) + { + for(int k = j + 1; k < nIntersect; ++k) + { + const double t_k = IntersectionData[i][k].myTime; + IntersectionData[i][k].myTime = (t_k - t_j) / (1.0 - t_j); + } + } + else + { + for(int k = j + 1; k < nIntersect; ++k) + { + IntersectionData[i][k].myTime = 1.; + } + } + } + } + } + +public: + // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each intersection and zeros for corners of original polygons. + CurvedPolygonType psplit[2]; // The two completely split polygons will be stored in this array + EdgeLabels edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points + int numinters {0}; + bool orientation {false}; + + bool m_verbose {false}; +}; + +/*! + * \brief Test whether the regions bounded by CurvedPolygons \a p1 and \a p2 intersect + * \return status true iff \a p1 intersects with \a p2, otherwise false. + * + * \param [in] p1, p2 CurvedPolygon objects to intersect + * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curve + * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. + */ +template +bool intersect_polygon(const CurvedPolygon& p1, + const CurvedPolygon& p2, + std::vector>& pnew, + double sq_tol) +{ + DirectionalWalk walk; + + int numinters = walk.splitPolygonsAlongIntersections(p1, p2, sq_tol); + + if(numinters > 0) + { + // This performs the directional walking method using the completely split polygons + walk.findIntersectionRegions(pnew); return true; } else // If there are no intersection points, check for containment @@ -381,54 +468,6 @@ int isContained(const CurvedPolygon& p1, } } -/// Splits a CurvedPolygon \a p1 at every intersection point stored in \a IntersectionData -/// \a edgeLabels stores intersection id for new vertices and 0 for original vertices -template -void splitPolygon(CurvedPolygon& p1, - std::vector>>& IntersectionData, - std::vector& edgelabels) -{ - using axom::utilities::isNearlyEqual; - - int addedIntersections = 0; - const int numEdges = p1.numEdges(); - for(int i = 0; i < numEdges; ++i) // foreach edge - { - edgelabels.push_back(0); // mark start current vertex as 'original' - const int nIntersect = IntersectionData[i].size(); - for(int j = 0; j < nIntersect; ++j) // foreach intersection on this edge - { - // split edge at parameter t_j - const double t_j = IntersectionData[i][j].myTime; - const int edgeIndex = i + addedIntersections; - p1.splitEdge(edgeIndex, t_j); - - // update edge label - const int label = IntersectionData[i][j].numinter; - edgelabels.insert(edgelabels.begin() + edgeIndex, label); - - ++addedIntersections; - - // update remaining intersections on this edge; special case if already at end of curve - if(!isNearlyEqual(1., t_j)) - { - for(int k = j + 1; k < nIntersect; ++k) - { - const double t_k = IntersectionData[i][k].myTime; - IntersectionData[i][k].myTime = (t_k - t_j) / (1.0 - t_j); - } - } - else - { - for(int k = j + 1; k < nIntersect; ++k) - { - IntersectionData[i][k].myTime = 1.; - } - } - } - } -} - /*! * \brief Determines orientation of a bezier curve \a c1 with respect to another bezier curve \a c2, * given that they intersect at parameter values \a s and \a t, respectively @@ -447,27 +486,6 @@ bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t) return (orientation > 0); } -/*! \class EdgeIntersectionInfo - * - * \brief For storing intersection points between edges of \a CurvedPolygon instances so they can be easily sorted by parameter value using std::sort - */ -template -class EdgeIntersectionInfo -{ -public: - T myTime; // parameter value of intersection on curve on first CurvePolygon - int myEdge; // index of curve on first CurvedPolygon - T otherTime; // parameter value of intersection on curve on second CurvedPolygon - int otherEdge; // index of curve on second CurvedPolygon - int numinter; // unique intersection point identifier - - /// \brief Comparison operator for sorting by parameter value - bool operator<(const EdgeIntersectionInfo& other) const - { - return myTime < other.myTime; - } -}; - } // namespace detail } // namespace primal } // namespace axom From 31df7120af7ec7fd9be5d02d306d6eb2751b0f0e Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 15 Jun 2021 12:40:14 -0700 Subject: [PATCH 27/38] Clean up and bugfix for directional walk algorithm It now properly handles the cases where the two polygons have different numbers of edges. --- .../detail/intersect_curved_poly_impl.hpp | 132 +++++++++++------- 1 file changed, 80 insertions(+), 52 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index de2f423a54..2ca05801b7 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -180,95 +180,123 @@ class DirectionalWalk return numinters; } +private: + using intersection_iterator = int; + + /// Finds the offset to \a intersectionID w.r.t. the active polygon + intersection_iterator update_iter(bool active, int intersectionID) const + { + const auto beg = edgelabels[active].begin(); + const auto end = edgelabels[active].end(); + return std::find(beg, end, intersectionID) - beg; + } + + void negate_label(bool active, intersection_iterator iter) + { + edgelabels[active][iter] = -edgelabels[active][iter]; + } + +public: + /*! + * Finds all intersection regions of the two input polygons + * + * This function must be called after splitPolygonsAlongIntersections + * which inserts the intersection points into each polygon and generates + * the \a edgelabels structure. + * + * The results will be a vector of polygons inserted into OUT parameter \a pnew + */ void findIntersectionRegions(std::vector& pnew) { + const int numEdges[2] = {static_cast(edgelabels[0].size()), + static_cast(edgelabels[1].size())}; + // When this is false, we are walking between intersection regions - bool addingcurves = true; - int startvertex = 1; // Start at the vertex with "unique id" 1 - int nextvertex; // The next vertex id is unknown at this time - // This variable allows us to switch between the two elements - bool currentelement = orientation; - // This is the iterator pointing to the end vertex of the edge of the completely split polygon we are on - int currentit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - startvertex) - - edgelabels[currentelement].begin(); + bool intersectionsRemaining = true; + + // This variable allows us to switch between the two polygons + bool active = orientation; + + // Start at the vertex with "unique id" 1 + int startvertex = 1; + + // This is the iterator pointing to the end vertex of the edge of the active polygon + intersection_iterator currentit = update_iter(active, startvertex); + // This is the iterator to the end vertex of the starting edge on the starting polygon - int startit = currentit; + intersection_iterator startit = currentit; + // This is the iterator to the end vertex of the next edge of whichever polygon we will be on next - int nextit = (currentit + 1) % edgelabels[0].size(); - nextvertex = edgelabels[currentelement][nextit]; // This is the next vertex id + intersection_iterator nextit = (currentit + 1) % numEdges[active]; + + // This is the next vertex id + int nextvertex = edgelabels[active][nextit]; + + // Find all connected regions of the intersection (we're done when we've found all intersections) while(numinters > 0) { - CurvedPolygon aPart; // Object to store the current intersection polygon (could be multiple) + CurvedPolygonType aPart; // Object to store the current intersection polygon (could be multiple) // Once the end vertex of the current edge is the start vertex, we need to switch regions - while(!(nextit == startit && currentelement == orientation) || - addingcurves == false) + while(!(nextit == startit && active == orientation) || + !intersectionsRemaining) { if(nextit == currentit) { - nextit = (currentit + 1) % edgelabels[0].size(); + nextit = (currentit + 1) % numEdges[active]; } - nextvertex = edgelabels[currentelement][nextit]; + nextvertex = edgelabels[active][nextit]; + + // Accept edges corresponding to original vertices while(nextvertex == 0) { currentit = nextit; - if(addingcurves) + if(intersectionsRemaining) { - aPart.addEdge(psplit[currentelement][nextit]); + aPart.addEdge(psplit[active][nextit]); } - nextit = (currentit + 1) % edgelabels[0].size(); - nextvertex = edgelabels[currentelement][nextit]; + nextit = (currentit + 1) % numEdges[active]; + nextvertex = edgelabels[active][nextit]; } - if(edgelabels[currentelement][nextit] > 0) + + // Handle intersection vertex + if(edgelabels[active][nextit] > 0) { - if(addingcurves) + if(intersectionsRemaining) { - aPart.addEdge(psplit[currentelement][nextit]); - edgelabels[currentelement][nextit] = - -edgelabels[currentelement][nextit]; - currentelement = !currentelement; - nextit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - nextvertex) - - edgelabels[currentelement].begin(); - edgelabels[currentelement][nextit] = - -edgelabels[currentelement][nextit]; + aPart.addEdge(psplit[active][nextit]); + + negate_label(active, nextit); + active = !active; + nextit = update_iter(active, nextvertex); + negate_label(active, nextit); currentit = nextit; - numinters -= 1; + --numinters; } else { - addingcurves = true; + intersectionsRemaining = true; startit = nextit; currentit = nextit; - nextit = (currentit + 1) % edgelabels[0].size(); - orientation = currentelement; + nextit = (currentit + 1) % numEdges[active]; + orientation = active; } } else { - currentelement = !currentelement; - nextit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - nextvertex) - - edgelabels[currentelement].begin(); - edgelabels[currentelement][nextit] = - -edgelabels[currentelement][nextit]; + active = !active; + nextit = update_iter(active, nextvertex); + negate_label(active, nextit); currentit = nextit; } } pnew.push_back(aPart); - currentelement = !currentelement; - currentit = std::find(edgelabels[currentelement].begin(), - edgelabels[currentelement].end(), - -nextvertex) - - edgelabels[currentelement].begin(); - nextit = (currentit + 1) % edgelabels[0].size(); + active = !active; + currentit = update_iter(active, -nextvertex); + nextit = (currentit + 1) % numEdges[active]; if(numinters > 0) { - addingcurves = false; + intersectionsRemaining = false; } } } From 3104c320072db5bd131c87bba73222235aa2a466 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 15 Jun 2021 12:41:54 -0700 Subject: [PATCH 28/38] Adds some additional tests for primal's intersect_polygon operation * A case where there are multiple intersections along an edge, and the polygons have different numbers of edges * A regression case over linear quads where the computed intersection was (and is still) incorrect. This case has an intersection between an edge an a vertex. --- .../tests/primal_curved_polygon_intersect.cpp | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index 468e189450..35f5e9ff80 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -278,6 +278,142 @@ TEST(primal_curvedpolygon, area_intersection_triangle_inclusion) checkIntersection(bPolygon2, bPolygon1, expIntersections); } +TEST(primal_curvedpolygon, doubleIntersection) +{ + const double EPS = 1e-8; + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + + SLIC_INFO("Tests multiple intersections along an edge"); + + { + // Unit square + std::vector CP1 = {PointType {0, 0}, + PointType {1, 0}, + PointType {1, 1}, + PointType {0, 1}, + PointType {0, 0}}; + std::vector orders1 = {1, 1, 1, 1}; + + // Bi-gon defined by a quadratic edge and a straight line + std::vector CP2 = {PointType {0.8, .25}, + PointType {2.0, .50}, + PointType {0.8, .75}, + PointType {0.8, .25}}; + std::vector orders2 = {2, 1}; + + CurvedPolygonType bPolygon1 = createPolygon(CP1, orders1); + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders2); + + // Find intersections using DirectionalWalk class + double walkIntersectionArea = 0.; + { + const bool verbose = true; + primal::detail::DirectionalWalk walk(verbose); + int numIntersections = + walk.splitPolygonsAlongIntersections(bPolygon1, bPolygon2, EPS * EPS); + + EXPECT_EQ(2, numIntersections); + EXPECT_NEAR(primal::area(bPolygon1), primal::area(walk.psplit[0]), EPS); + EXPECT_NEAR(primal::area(bPolygon2), primal::area(walk.psplit[1]), EPS); + + SLIC_INFO("Checking for intersections between " << bPolygon1 << " and " + << bPolygon2); + + std::vector regions; + walk.findIntersectionRegions(regions); + EXPECT_EQ(1, regions.size()); + + if(!regions.empty()) + { + EXPECT_EQ(4, regions[0].numEdges()); + walkIntersectionArea = primal::area(regions[0]); + } + else + { + FAIL() << "Expected the two polygons to intersect"; + } + + SLIC_INFO("Found intersections (directional walk): "); + for(auto cp : regions) + { + SLIC_INFO("\t" << cp); + } + } + + // Find interesections using primal::intersect + double directIntersectionArea = 0.; + { + std::vector regions; + bool intersects = intersect(bPolygon1, bPolygon2, regions, EPS); + EXPECT_TRUE(intersects); + EXPECT_EQ(1, regions.size()); + + if(!regions.empty()) + { + EXPECT_EQ(4, regions[0].numEdges()); + directIntersectionArea = primal::area(regions[0]); + } + else + { + FAIL() << "Expected the two polygons to intersect"; + } + + SLIC_INFO("Found intersections (direct): "); + for(auto cp : regions) + { + SLIC_INFO("\t" << cp); + } + } + + EXPECT_NEAR(walkIntersectionArea, directIntersectionArea, EPS); + } +} + +TEST(primal_curvedpolygon, regression) +{ + const double EPS = 1e-8; + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + + SLIC_INFO("Test intersection of pairs of polygons from regression data"); + + // First test: Intersecting a pair of linear quadrilaterals + // Note: One of the intersections happens betwen a vertex and edge + // and is not yet properly handled by primal::intersect + { + std::vector CP1 = {PointType {1, -2}, + PointType {0, -1}, + PointType {0, 1}, + PointType {1, 2}, + PointType {1, -2}}; + + std::vector CP2 = {PointType {-.9, -2}, + PointType {0.1, -1}, + PointType {2.1, -1}, + PointType {3.1, -2}, + PointType {-.9, -2}}; + std::vector orders = {1, 1, 1, 1}; + + CurvedPolygonType bPolygon1 = createPolygon(CP1, orders); + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + std::vector expIntersections; + + intersect(bPolygon1, bPolygon2, expIntersections, EPS); + + SLIC_INFO("There were " << expIntersections.size() + << " intersection polygons"); + for(auto cp : expIntersections) + { + SLIC_INFO("\t" << cp); + } + } +} + //---------------------------------------------------------------------------------- int main(int argc, char* argv[]) { From ddf1bd7f18e73a9d4eba840c20ae12dde10b7415 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 15:27:23 -0700 Subject: [PATCH 29/38] Adds utility function to primal's CurvedPolygon intersection tests to output polygons as SVG --- .../tests/primal_curved_polygon_intersect.cpp | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index 35f5e9ff80..916c4f7454 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -13,10 +13,138 @@ #include "axom/slic.hpp" #include "axom/primal.hpp" +#include "axom/fmt.hpp" + #include +#include namespace primal = axom::primal; +template +void outputAsSVG( + const std::string& filename, + const primal::CurvedPolygon& polygon1, + const primal::CurvedPolygon& polygon2, + const std::vector> intersectionPolygons) +{ + // Find the bounding box of the set of polygons + primal::BoundingBox bbox; + bbox.addBox(polygon1.boundingBox()); + bbox.addBox(polygon2.boundingBox()); + for(const auto& cp : intersectionPolygons) + { + bbox.addBox(cp.boundingBox()); + } + bbox.scale(1.1); + + std::string header = axom::fmt::format( + "", + bbox.getMin()[0], + bbox.getMin()[1], + bbox.range()[0], + bbox.range()[1]); + std::string footer = ""; + + // lambda to convert a CurvedPolygon to an SVG path string + auto cpToSVG = [](const primal::CurvedPolygon& cp) { + axom::fmt::memory_buffer out; + bool is_first = true; + + for(auto& curve : cp.getEdges()) + { + // Only write out first point for first edge + if(is_first) + { + axom::fmt::format_to(out, "M {} {} ", curve[0][0], curve[0][1]); + is_first = false; + } + + switch(curve.getOrder()) + { + case 1: + axom::fmt::format_to(out, "L {} {} ", curve[1][0], curve[1][1]); + break; + case 2: + axom::fmt::format_to(out, + "Q {} {}, {} {} ", + curve[1][0], + curve[1][1], + curve[2][0], + curve[2][1]); + break; + case 3: + axom::fmt::format_to(out, + "C {} {}, {} {}, {} {} ", + curve[1][0], + curve[1][1], + curve[2][0], + curve[2][1], + curve[3][0], + curve[3][1]); + break; + default: + SLIC_WARNING( + "Unsupported case: can only output up to cubic curves as SVG."); + } + } + return axom::fmt::format(" \n", + axom::fmt::to_string(out)); + }; + + std::string poly1Group; + std::string poly2Group; + std::string intersectionGroup; + + // render polygon1 as SVG + { + axom::fmt::memory_buffer out; + axom::fmt::format_to( + out, + " \n"); + axom::fmt::format_to(out, cpToSVG(polygon1)); + axom::fmt::format_to(out, " \n"); + poly1Group = axom::fmt::to_string(out); + } + + // render polygon2 as SVG + { + axom::fmt::memory_buffer out; + axom::fmt::format_to( + out, + " \n"); + axom::fmt::format_to(out, cpToSVG(polygon2)); + axom::fmt::format_to(out, " \n"); + poly2Group = axom::fmt::to_string(out); + } + + //render intersection polygons as SVG + { + axom::fmt::memory_buffer out; + axom::fmt::format_to( + out, + " \n"); + for(auto& cp : intersectionPolygons) + { + axom::fmt::format_to(out, cpToSVG(cp)); + } + axom::fmt::format_to(out, " \n"); + intersectionGroup = axom::fmt::to_string(out); + } + + // Write the file + { + std::ofstream fs(filename); + fs << header << std::endl; + fs << poly1Group << std::endl; + fs << poly2Group << std::endl; + fs << intersectionGroup << std::endl; + fs << footer << std::endl; + } +} + /*! * Helper function to compute the set of intersection polygons given two input polygons * and to check that they match expectations, stored in \a expbPolygon. From 614c52aa634b3e96d975c50b70a67a9e38987168 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 15:29:48 -0700 Subject: [PATCH 30/38] Adds a test case to primal's CurvedPolygon intersection tests for a pair of linear squares Also outputs some additional test cases as SVG. --- .../tests/primal_curved_polygon_intersect.cpp | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index 916c4f7454..f909b708e6 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -230,6 +230,60 @@ primal::CurvedPolygon createPolygon( //---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, intersection_squares) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + + SLIC_INFO("Test intersection of two squares (single region)"); + + std::vector orders = {1, 1, 1, 1}; + + // Unit square scaled by 2 + std::vector CP = {PointType {0, 0}, + PointType {2, 0}, + PointType {2, 2}, + PointType {0, 2}, + PointType {0, 0}}; + + CurvedPolygonType bPolygon1 = createPolygon(CP, orders); + + // Unit square scaled by 2 and offset by (-1,-1) + std::vector CP2 = {PointType {-1, -1}, + PointType {1, -1}, + PointType {1, 1}, + PointType {-1, 1}, + PointType {-1, -1}}; + + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + + // Intersection should be a unit square + std::vector expCP = {PointType {1, 0}, + PointType {1, 1}, + PointType {0, 1}, + PointType {0, 0}, + PointType {1, 0}}; + std::vector exporders = {1, 1, 1, 1}; + CurvedPolygonType expbPolygon = createPolygon(expCP, exporders); + + std::vector expbPolygons {expbPolygon}; + checkIntersection(bPolygon1, bPolygon2, expbPolygons); + + // Output intersections as SVG + { + std::vector intersections; + intersect(bPolygon1, bPolygon2, intersections, 1e-15); + outputAsSVG("curved_polygon_intersections_linear_squares.svg", + bPolygon1, + bPolygon2, + intersections); + } +} + +//---------------------------------------------------------------------------------- + TEST(primal_curvedpolygon, intersection_triangle_linear) { const int DIM = 2; @@ -264,6 +318,16 @@ TEST(primal_curvedpolygon, intersection_triangle_linear) std::vector expbPolygons {expbPolygon}; checkIntersection(bPolygon1, bPolygon2, expbPolygons); + + // Output intersections as SVG + { + std::vector intersections; + intersect(bPolygon1, bPolygon2, intersections, 1e-15); + outputAsSVG("curved_polygon_intersections_linear_triangles.svg", + bPolygon1, + bPolygon2, + intersections); + } } //---------------------------------------------------------------------------------- @@ -371,6 +435,17 @@ TEST(primal_curvedpolygon, intersection_triangle_quadratic_two_regions) std::vector expIntersections = {expbPolygon1, expbPolygon2}; checkIntersection(bPolygon1, bPolygon2, expIntersections); + + // Output intersections as SVG + { + std::vector intersections; + intersect(bPolygon1, bPolygon2, intersections, 1e-15); + outputAsSVG( + "curved_polygon_intersections_quadratic_triangles_two_regions.svg", + bPolygon1, + bPolygon2, + intersections); + } } TEST(primal_curvedpolygon, area_intersection_triangle_inclusion) From 0bb349e0a09643a734376873ab61734ad04462de Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 15:50:45 -0700 Subject: [PATCH 31/38] Improves checkIntersection() function in primal's CurvedPolygon intersection tests --- .../tests/primal_curved_polygon_intersect.cpp | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index f909b708e6..ad618438ba 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -159,6 +159,8 @@ void checkIntersection( const double test_eps = 1e-13) { using CurvedPolygonType = primal::CurvedPolygon; + using BezierCurveType = typename CurvedPolygonType::BezierCurveType; + using PointType = typename BezierCurveType::PointType; std::vector intersectionPolys; @@ -168,20 +170,31 @@ void checkIntersection( EXPECT_EQ(expbPolygon.size(), intersectionPolys.size()); //Check that expected intersection curves are found to within test_eps - for(int i = 0; i < DIM; ++i) + const int nPolygons = expbPolygon.size(); + for(int p = 0; p < nPolygons; ++p) { - int sz = intersectionPolys.size(); - for(int idxcurve = 0; idxcurve < sz; ++idxcurve) + const CurvedPolygonType& polyExp = expbPolygon[p]; + const CurvedPolygonType& polyActual = intersectionPolys[p]; + EXPECT_EQ(polyExp.numEdges(), polyActual.numEdges()); + + const int nEdges = polyExp.numEdges(); + for(int e = 0; e < nEdges; ++e) { - int nEd = intersectionPolys[idxcurve].numEdges(); - for(int k = 0; k < nEd; ++k) + const BezierCurveType& curveExp = polyExp[e]; + const BezierCurveType& curveActual = polyActual[e]; + EXPECT_EQ(curveExp.getOrder(), curveActual.getOrder()); + + const int nPts = curveExp.getOrder() + 1; + for(int idx = 0; idx < nPts; ++idx) { - int ord = intersectionPolys[idxcurve][k].getOrder(); - for(int j = 0; j <= ord; ++j) + const PointType& ptExp = curveExp[idx]; + const PointType& ptActual = curveActual[idx]; + + for(int d = 0; d < DIM; ++d) { - EXPECT_NEAR(expbPolygon[idxcurve][k][j][i], - intersectionPolys[idxcurve][k][j][i], - test_eps); + EXPECT_NEAR(ptExp[d], ptActual[d], test_eps) + << "Difference in polygon " << p << " edge " << e + << " control point " << idx << " dimension " << d; } } } From 92c6037f179ec32f78f88c5f6295b162ceaf06a3 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 21:19:25 -0700 Subject: [PATCH 32/38] Refactors primal's DirectionalWalking::findIntersectionRegions() algorithm Uses `PolygonEdge` and `Junction` helper classes to simplify the logic. --- .../detail/intersect_curved_poly_impl.hpp | 297 ++++++++++++------ 1 file changed, 209 insertions(+), 88 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 2ca05801b7..f44a884a16 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -71,6 +71,97 @@ class DirectionalWalk } }; + /// Enum to represent different Junction states + enum class JunctionState : int + { + UNINITIALIZED = -1, + NON_JUNCTION, /// Not a junction, e.g. a vertex of the original + APPLIED, /// Use after junction has been applied + CROSS, /// Typical case: edges of polygons intersect + GRAZE /// Atypical case: polygons intersecte at a terminal vertex + }; + + /*! + * Helper class for encoding information about the incident edges at an interection + */ + struct Junction + { + using JunctionIndex = int; + using EdgeIndex = int; + static const EdgeIndex INVALID_EDGE_INDEX = -1; + static const int INVALID_JUNCTION_INDEX = -1; + + // indices of polygon edges leading into junction + EdgeIndex edgeIndex[2] {INVALID_EDGE_INDEX, INVALID_EDGE_INDEX}; + // describes the junction type and status + JunctionState junctionState {JunctionState::UNINITIALIZED}; + JunctionIndex index {INVALID_JUNCTION_INDEX}; + + bool isActive() const { return junctionState > JunctionState::APPLIED; } + + EdgeIndex currentEdgeIndex(bool active) const { return edgeIndex[active]; } + EdgeIndex nextEdgeIndex(bool active) const { return edgeIndex[active] + 1; } + + bool operator==(const Junction& other) const + { + return index == other.index; + } + bool operator!=(const Junction& other) const { return !(*this == other); } + }; + + /*! + * Helper class for indexing an edge of a polygon + * Assumption is that the polygon is not empty + */ + class PolygonEdge + { + public: + using VertexIndex = int; + using EdgeIndex = int; + + PolygonEdge(int polygonID, const EdgeLabels& labels) + : m_polygonID(polygonID) + , m_labels(labels) + , m_numEdges(labels.size()) + { + SLIC_ASSERT_MSG(m_numEdges > 0, "Polygon " << polygonID << " was empty"); + } + + EdgeIndex getIndex() const { return m_index; } + void setIndex(EdgeIndex idx) { m_index = (idx % m_numEdges); } + + int getStartLabel() const { return m_labels[prevIndex()]; } + int getEndLabel() const { return m_labels[m_index]; } + void advance() { m_index = nextIndex(); } + + bool isJunction() const { return getEndLabel() > 0; } + + bool operator==(const PolygonEdge& other) + { + return m_index == other.m_index && m_polygonID == other.m_polygonID; + } + + bool operator!=(const PolygonEdge& other) { return !(*this == other); } + + private: + EdgeIndex lastIndex() const { return m_numEdges - 1; } + EdgeIndex nextIndex() const + { + return m_index < lastIndex() ? m_index + 1 : 0; + } + EdgeIndex prevIndex() const + { + return m_index > 0 ? m_index - 1 : lastIndex(); + } + + private: + EdgeIndex m_index; + + const int m_polygonID; + const EdgeLabels& m_labels; + const int m_numEdges; + }; + public: explicit DirectionalWalk(bool verbose = false) : m_verbose(verbose) { } @@ -161,14 +252,16 @@ class DirectionalWalk edgelabels[0].reserve(p1.numEdges() + numinters); edgelabels[1].reserve(p2.numEdges() + numinters); + junctions = std::vector(numinters + 1); + if(m_verbose) { SLIC_INFO("Poly1 before split: " << psplit[0]); SLIC_INFO("Poly2 before split: " << psplit[1]); } - splitPolygon(psplit[0], E1IntData, edgelabels[0]); - splitPolygon(psplit[1], E2IntData, edgelabels[1]); + splitPolygon(psplit[0], E1IntData, edgelabels[0], 0); + splitPolygon(psplit[1], E2IntData, edgelabels[1], 1); if(m_verbose) { @@ -180,22 +273,6 @@ class DirectionalWalk return numinters; } -private: - using intersection_iterator = int; - - /// Finds the offset to \a intersectionID w.r.t. the active polygon - intersection_iterator update_iter(bool active, int intersectionID) const - { - const auto beg = edgelabels[active].begin(); - const auto end = edgelabels[active].end(); - return std::find(beg, end, intersectionID) - beg; - } - - void negate_label(bool active, intersection_iterator iter) - { - edgelabels[active][iter] = -edgelabels[active][iter]; - } - public: /*! * Finds all intersection regions of the two input polygons @@ -208,110 +285,140 @@ class DirectionalWalk */ void findIntersectionRegions(std::vector& pnew) { - const int numEdges[2] = {static_cast(edgelabels[0].size()), - static_cast(edgelabels[1].size())}; - - // When this is false, we are walking between intersection regions - bool intersectionsRemaining = true; + PolygonEdge currentEdge[2] = {PolygonEdge(0, edgelabels[0]), + PolygonEdge(1, edgelabels[1])}; - // This variable allows us to switch between the two polygons - bool active = orientation; + // We use junctionIndex to loop through the active junctions starting with index 1 + int junctionIndex = 1; - // Start at the vertex with "unique id" 1 - int startvertex = 1; + // Find all connected regions of the intersection (we're done when we've found all junctions) + const int numJunctions = junctions.size(); + while(junctionIndex < numJunctions) + { + // Attempt to get an initial "active" junction for this polygon + Junction* startJunction = nullptr; + do + { + startJunction = (junctionIndex < numJunctions) + ? &(junctions[junctionIndex++]) + : nullptr; + } while(startJunction != nullptr && !startJunction->isActive()); - // This is the iterator pointing to the end vertex of the edge of the active polygon - intersection_iterator currentit = update_iter(active, startvertex); + // If we've found all the active junctions, we're done + if(startJunction == nullptr) + { + break; + } + else + { + startJunction->junctionState = JunctionState::APPLIED; + } - // This is the iterator to the end vertex of the starting edge on the starting polygon - intersection_iterator startit = currentit; + // This variable allows us to switch between the two polygons + bool active = orientation; - // This is the iterator to the end vertex of the next edge of whichever polygon we will be on next - intersection_iterator nextit = (currentit + 1) % numEdges[active]; + // Set the index of the active edge to that of the start index + PolygonEdge* activeEdge = &(currentEdge[active]); + const auto startEdgeIndex = startJunction->nextEdgeIndex(active); + activeEdge->setIndex(startEdgeIndex); - // This is the next vertex id - int nextvertex = edgelabels[active][nextit]; + if(m_verbose) + { + SLIC_INFO("" + << "Starting with junction " << startJunction->index + << "\n\t edges[0] {in: " << startJunction->currentEdgeIndex(0) + << " -- " << psplit[0][startJunction->currentEdgeIndex(0)] + << "; out: " << startJunction->nextEdgeIndex(0) << " -- " + << psplit[0][startJunction->nextEdgeIndex(0)] << "}" + << "\n\t edges[1] {in: " << startJunction->currentEdgeIndex(1) + << " -- " << psplit[1][startJunction->currentEdgeIndex(1)] + << "; out: " << startJunction->nextEdgeIndex(1) << " -- " + << psplit[1][startJunction->nextEdgeIndex(1)] << "}" + << "\n\t active: " << (active ? 1 : 0)); + } - // Find all connected regions of the intersection (we're done when we've found all intersections) - while(numinters > 0) - { - CurvedPolygonType aPart; // Object to store the current intersection polygon (could be multiple) + CurvedPolygonType aPart; // Tracks the edges of the current intersection polygon - // Once the end vertex of the current edge is the start vertex, we need to switch regions - while(!(nextit == startit && active == orientation) || - !intersectionsRemaining) + // Each polygon iterates until it returns to the startJunctions + Junction* junction = nullptr; + while(junction == nullptr || *junction != *startJunction) { - if(nextit == currentit) + // Add all edges until we find a junction edge + while(!activeEdge->isJunction()) { - nextit = (currentit + 1) % numEdges[active]; + const auto edgeIndex = activeEdge->getIndex(); + if(m_verbose) + { + SLIC_INFO("Adding edge (non-junction): " << psplit[active][edgeIndex]); + } + aPart.addEdge(psplit[active][edgeIndex]); + activeEdge->advance(); } - nextvertex = edgelabels[active][nextit]; - // Accept edges corresponding to original vertices - while(nextvertex == 0) + // Add last leg of previous segment { - currentit = nextit; - if(intersectionsRemaining) + const auto edgeIndex = activeEdge->getIndex(); + if(m_verbose) { - aPart.addEdge(psplit[active][nextit]); + SLIC_INFO("Adding edge (end of last): " << psplit[active][edgeIndex]); } - nextit = (currentit + 1) % numEdges[active]; - nextvertex = edgelabels[active][nextit]; + aPart.addEdge(psplit[active][edgeIndex]); } - // Handle intersection vertex - if(edgelabels[active][nextit] > 0) + // Handle junction + junction = &(junctions[activeEdge->getEndLabel()]); + SLIC_ASSERT(junction != nullptr); + + if(m_verbose) { - if(intersectionsRemaining) - { - aPart.addEdge(psplit[active][nextit]); - - negate_label(active, nextit); - active = !active; - nextit = update_iter(active, nextvertex); - negate_label(active, nextit); - currentit = nextit; - --numinters; - } - else - { - intersectionsRemaining = true; - startit = nextit; - currentit = nextit; - nextit = (currentit + 1) % numEdges[active]; - orientation = active; - } + SLIC_INFO("" + << "Swapped to junction " << junction->index + << "\n\t edges[0] {in: " << junction->currentEdgeIndex(0) + << " -- " << psplit[0][junction->currentEdgeIndex(0)] + << "; out: " << junction->nextEdgeIndex(0) << " -- " + << psplit[0][junction->nextEdgeIndex(0)] << "}" + << "\n\t edges[1] {in: " << junction->currentEdgeIndex(1) + << " -- " << psplit[1][junction->currentEdgeIndex(1)] + << "; out: " << junction->nextEdgeIndex(1) << " -- " + << psplit[1][junction->nextEdgeIndex(1)] << "}" + << "\n\t active: " << (active ? 1 : 0)); } - else + + if(junction->isActive()) { + // swap active edge using junction data active = !active; - nextit = update_iter(active, nextvertex); - negate_label(active, nextit); - currentit = nextit; + const auto nextEdgeIndex = junction->nextEdgeIndex(active); + activeEdge = &(currentEdge[active]); + activeEdge->setIndex(nextEdgeIndex); + + junction->junctionState = JunctionState::APPLIED; + + if(m_verbose) + { + SLIC_INFO("Swapped to other polygon, edge index: " + << nextEdgeIndex + << "; edge: " << psplit[active][activeEdge->getIndex()]); + } } } + // Finalize polygon pnew.push_back(aPart); - active = !active; - currentit = update_iter(active, -nextvertex); - nextit = (currentit + 1) % numEdges[active]; - if(numinters > 0) - { - intersectionsRemaining = false; - } } } private: /// Splits a CurvedPolygon \a p1 at every intersection point stored in \a IntersectionData /// \a edgeLabels stores intersection id for new vertices and 0 for original vertices - void splitPolygon(CurvedPolygon& p1, + void splitPolygon(CurvedPolygon& polygon, std::vector>& IntersectionData, - std::vector& edgelabels) + std::vector& edgelabels, + int polygonID) { using axom::utilities::isNearlyEqual; int addedIntersections = 0; - const int numEdges = p1.numEdges(); + const int numEdges = polygon.numEdges(); for(int i = 0; i < numEdges; ++i) // foreach edge { edgelabels.push_back(0); // mark start current vertex as 'original' @@ -321,11 +428,18 @@ class DirectionalWalk // split edge at parameter t_j const double t_j = IntersectionData[i][j].myTime; const int edgeIndex = i + addedIntersections; - p1.splitEdge(edgeIndex, t_j); + + // TODO: Handle case where t_j is nearly 0. + + polygon.splitEdge(edgeIndex, t_j); // update edge label const int label = IntersectionData[i][j].numinter; edgelabels.insert(edgelabels.begin() + edgeIndex, label); + junctions[label].edgeIndex[polygonID] = edgeIndex; + junctions[label].junctionState = + JunctionState::CROSS; // TODO: Figure out what junction type needs to be + junctions[label].index = label; ++addedIntersections; @@ -353,6 +467,7 @@ class DirectionalWalk // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each intersection and zeros for corners of original polygons. CurvedPolygonType psplit[2]; // The two completely split polygons will be stored in this array EdgeLabels edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points + std::vector junctions; int numinters {0}; bool orientation {false}; @@ -373,6 +488,12 @@ bool intersect_polygon(const CurvedPolygon& p1, std::vector>& pnew, double sq_tol) { + // Intersection is empty if either of the two polygons are empty + if(p1.empty() || p2.empty()) + { + return false; + } + DirectionalWalk walk; int numinters = walk.splitPolygonsAlongIntersections(p1, p2, sq_tol); From ea1b5b6bcf664f5bd8c9bc6df6c6fc230109f21e Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 21:20:20 -0700 Subject: [PATCH 33/38] Reorders vertices in a CurvedPolygon intersection test due to refactored intersection algorithm --- src/axom/primal/tests/primal_curved_polygon_intersect.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index ad618438ba..c76935c236 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -431,13 +431,13 @@ TEST(primal_curvedpolygon, intersection_triangle_quadratic_two_regions) PointType {0.343364196589264, 1.747080669655736}}; std::vector expCP2 = { - PointType {0.454478985809487, 1.379250566393211}, - PointType {0.444689566319939, 1.400290430035245}, PointType {0.435276730907216, 1.423589798138227}, PointType {0.416268597450954, 1.385275578571685}, PointType {0.374100000000000, 1.350300000000000}, PointType {0.404839872482010, 1.358536305511285}, - PointType {0.454478985809487, 1.379250566393211}}; + PointType {0.454478985809487, 1.379250566393211}, + PointType {0.444689566319939, 1.400290430035245}, + PointType {0.435276730907216, 1.423589798138227}}; std::vector exporder1 = {2, 2, 2, 2}; std::vector exporder2 = {2, 2, 2}; From bec8b3f54255b44988583dacdfda3b7fa8666a72 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 21:22:22 -0700 Subject: [PATCH 34/38] Adds a new set of polygon intersection test cases To help improve handling of degenerate edge-vertex intersection for primal's CurvedPolygon class. --- .../tests/primal_curved_polygon_intersect.cpp | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index c76935c236..f63d69acca 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -343,6 +343,174 @@ TEST(primal_curvedpolygon, intersection_triangle_linear) } } +//---------------------------------------------------------------------------------- + +TEST(primal_curvedpolygon, intersections_triangle_rectangle) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + + SLIC_INFO( + "Test several intersection cases b/w a linear triangle and rectangle"); + + // Rectangle with bounds, -5 <= x < 5 ; and 0 <= y <= 2 + std::vector rectanglePts = {PointType {-5, 0}, + PointType {5, 0}, + PointType {5, 2}, + PointType {-5, 2}, + PointType {-5, 0}}; + std::vector rectangleOrders = {1, 1, 1, 1}; + + // Equilateral triangle with base from -3 <= x <= 3 and height 1 + CurvedPolygonType bRectangle = createPolygon(rectanglePts, rectangleOrders); + + std::vector triPts = {PointType {-3, -2}, + PointType {3, -2}, + PointType {0, -1}, + PointType {-3, -2}}; + std::vector triOrders = {1, 1, 1}; + + const bool bVerbose = true; + const double EPS = 1e-7; + const double SQ_EPS = EPS * EPS; + + // No intersection case: Triangle is below the rectangle + { + CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); + primal::detail::DirectionalWalk walk(bVerbose); + int nIntersections = + walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); + EXPECT_EQ(0, nIntersections); + + std::vector intersections; + EXPECT_FALSE(primal::intersect(bRectangle, bTriangle, intersections, EPS)); + + outputAsSVG("intersection_test_tri_rect_none.svg", + bRectangle, + bTriangle, + intersections); + } + + // No intersection case: Triangle apex grazes rectangle + { + triPts[2][1] = 0; + CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); + + primal::detail::DirectionalWalk walk(bVerbose); + int nIntersections = + walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); + EXPECT_EQ(1, nIntersections); + + std::vector walkIntersections; + walk.findIntersectionRegions(walkIntersections); + + // EXPECT_EQ(0, walkIntersections.size()); // Warning: Not properly handled yet + + outputAsSVG("intersection_test_tri_rect_lower_graze.svg", + bRectangle, + bTriangle, + walkIntersections); + + std::vector intersections; + // Warning: Not properly handled yet + // EXPECT_FALSE( + primal::intersect(bRectangle, bTriangle, intersections, EPS) + //) + ; + } + + // Simple intersection case: Triangle apex inside rectangle + { + triPts[2][1] = 1; + CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); + + primal::detail::DirectionalWalk walk(bVerbose); + int nIntersections = + walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); + EXPECT_EQ(2, nIntersections); + + std::vector walkIntersections; + walk.findIntersectionRegions(walkIntersections); + EXPECT_EQ(1, walkIntersections.size()); + + outputAsSVG("intersection_test_tri_rect_intersect_tri.svg", + bRectangle, + bTriangle, + walkIntersections); + + std::vector expCP = {PointType {1, 0}, + PointType {0, 1}, + PointType {-1, 0}, + PointType {1, 0}}; + std::vector exporders = {1, 1, 1}; + + std::vector expbPolygons = { + createPolygon(expCP, exporders)}; + + checkIntersection(bRectangle, bTriangle, expbPolygons); + } + + // Grazing intersection case: Triangle apex intersects top + { + triPts[2][1] = 2; + CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); + + primal::detail::DirectionalWalk walk(bVerbose); + int nIntersections = + walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); + EXPECT_EQ(3, nIntersections); + + std::vector walkIntersections; + walk.findIntersectionRegions(walkIntersections); + EXPECT_EQ(1, walkIntersections.size()); + + //EXPECT_EQ(3, walkIntersections[0].numEdges()); // Warning: Not properly handled yet + + outputAsSVG("intersection_test_tri_rect_upper_graze.svg", + bRectangle, + bTriangle, + walkIntersections); + + std::vector intersections; + // Warning: Not properly handled yet + EXPECT_TRUE(primal::intersect(bRectangle, bTriangle, intersections, EPS)); + } + + // intersection case: Triangle apex above rectangle + { + triPts[2][1] = 3; + CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); + + primal::detail::DirectionalWalk walk(bVerbose); + int nIntersections = + walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); + EXPECT_EQ(4, nIntersections); + + std::vector walkIntersections; + walk.findIntersectionRegions(walkIntersections); + EXPECT_EQ(1, walkIntersections.size()); + + outputAsSVG("intersection_test_tri_rect_intersect_rect.svg", + bRectangle, + bTriangle, + walkIntersections); + + std::vector expCP = {PointType {1.8, 0}, + PointType {0.6, 2}, + PointType {-0.6, 2}, + PointType {-1.8, 0}, + PointType {1.8, 0}}; + std::vector exporders = {1, 1, 1, 1}; + + std::vector expbPolygons = { + createPolygon(expCP, exporders)}; + + checkIntersection(bRectangle, bTriangle, expbPolygons); + } +} + //---------------------------------------------------------------------------------- TEST(primal_curvedpolygon, intersection_triangle_quadratic) { From d70e34acca7be6599826a9c5856a950017e594c9 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Sun, 3 Jul 2022 17:22:59 -0700 Subject: [PATCH 35/38] Minor improvements to quest's intersection example --- src/axom/primal/tests/primal_curved_polygon.cpp | 2 -- src/axom/quest/examples/CMakeLists.txt | 2 +- src/axom/quest/examples/quest_high_order_remap.cpp | 11 +++++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 93ab37e28b..00a627c4bf 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -17,8 +17,6 @@ #include #include -#include - namespace primal = axom::primal; /*! diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 8e927d695c..67c855fed3 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -215,7 +215,7 @@ if(MFEM_FOUND) NAME quest_high_order_remap_ex SOURCES quest_high_order_remap.cpp OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} - DEPENDS_ON ${quest_example_depends} mfem fmt cli11 + DEPENDS_ON ${quest_example_depends} mfem FOLDER axom/quest/examples ) diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index 4b29c66621..68442a48ce 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -9,6 +9,7 @@ */ // Axom includes +#include "axom/config.hpp" #include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/primal.hpp" @@ -444,12 +445,11 @@ struct Remapper } auto tgtPoly = tgtMesh.elemAsCurvedPolygon(i); - //SLIC_INFO("Target Element: " << tgtPoly); + //SLIC_INFO("Target Element: \n\t" << tgtPoly); correctArea += primal::area(tgtPoly); - //SLIC_INFO("Target elem " << i - // << " -- area " << primal::area(tgtPoly) - // //<< " -- bbox " << tgtMesh.elementBoundingBox(i) - // ); + //SLIC_INFO("Target elem " << i << " -- area " << primal::area(tgtPoly) + // << " -- bbox " << tgtMesh.elementBoundingBox(i) + //); double A = 0.0; for(int srcElem : candidates) @@ -869,7 +869,6 @@ int main(int argc, char** argv) remap.outputAsSVG(); - std::cout.precision(16); SLIC_INFO(axom::fmt::format("Intersecting meshes: area: {}, time: {}", area, timer.elapsed())); From 49b40191149ee0a83c6d0a968e86d688f3d2b7ec Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 23 Nov 2021 14:57:40 -0800 Subject: [PATCH 36/38] Adds regression test for CurvedPolygon intersections along shared geometry Note: currently failing for overlapped vertex. --- .../tests/primal_curved_polygon_intersect.cpp | 135 +++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index f63d69acca..06dfafe334 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -112,7 +112,7 @@ void outputAsSVG( axom::fmt::memory_buffer out; axom::fmt::format_to( out, - " \n"); axom::fmt::format_to(out, cpToSVG(polygon2)); axom::fmt::format_to(out, " \n"); @@ -798,6 +798,139 @@ TEST(primal_curvedpolygon, regression) } } +TEST(primal_curvedpolygon, adjacent_squares) +{ + const double EPS = 1e-8; + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + + SLIC_INFO("Test intersection of pairs of adjacent squares"); + + // Sharing an edge + { + std::vector CP1 = {PointType {-1, 0}, + PointType {0, 0}, + PointType {0, 1}, + PointType {-1, 1}, + PointType {-1, 0}}; + + std::vector CP2 = {PointType {0, 0}, + PointType {0, 1}, + PointType {1, 1}, + PointType {1, 0}, + PointType {0, 0}}; + std::vector orders = {1, 1, 1, 1}; + + CurvedPolygonType bPolygon1 = createPolygon(CP1, orders); + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + + { + std::vector expIntersections; + intersect(bPolygon1, bPolygon2, expIntersections, EPS); + + EXPECT_TRUE(expIntersections.empty()); + + if(!expIntersections.empty()) + { + SLIC_INFO("There were " << expIntersections.size() + << " intersection polygons"); + for(auto cp : expIntersections) + { + SLIC_INFO("\t" << cp); + } + outputAsSVG("adjacent_squares_edge_LR.svg", + bPolygon1, + bPolygon2, + expIntersections); + } + } + + { + std::vector expIntersections; + intersect(bPolygon2, bPolygon1, expIntersections, EPS); + + EXPECT_TRUE(expIntersections.empty()); + + if(!expIntersections.empty()) + { + SLIC_INFO("There were " << expIntersections.size() + << " intersection polygons"); + for(auto cp : expIntersections) + { + SLIC_INFO("\t" << cp); + } + outputAsSVG("adjacent_squares_edge_RL.svg", + bPolygon2, + bPolygon1, + expIntersections); + } + } + } + + // Sharing a vertex + { + std::vector CP1 = {PointType {-1, 0}, + PointType {0, 0}, + PointType {0, 1}, + PointType {-1, 1}, + PointType {-1, 0}}; + + std::vector CP2 = {PointType {0, 1}, + PointType {0, 2}, + PointType {1, 2}, + PointType {1, 1}, + PointType {0, 1}}; + std::vector orders = {1, 1, 1, 1}; + + CurvedPolygonType bPolygon1 = createPolygon(CP1, orders); + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + + { + std::vector expIntersections; + intersect(bPolygon1, bPolygon2, expIntersections, EPS); + + EXPECT_TRUE(expIntersections.empty()); + + if(!expIntersections.empty()) + { + SLIC_INFO("There were " << expIntersections.size() + << " intersection polygons"); + for(auto cp : expIntersections) + { + SLIC_INFO("\t" << cp); + } + outputAsSVG("adjacent_squares_corner_LR.svg", + bPolygon1, + bPolygon2, + expIntersections); + } + } + + { + std::vector expIntersections; + intersect(bPolygon2, bPolygon1, expIntersections, EPS); + + EXPECT_TRUE(expIntersections.empty()); + + if(!expIntersections.empty()) + { + SLIC_INFO("There were " << expIntersections.size() + << " intersection polygons"); + for(auto cp : expIntersections) + { + SLIC_INFO("\t" << cp); + } + outputAsSVG("adjacent_squares_corner_RL.svg", + bPolygon2, + bPolygon1, + expIntersections); + } + } + } +} + //---------------------------------------------------------------------------------- int main(int argc, char* argv[]) { From 3e656d48c617ef94e6ec76512520e5d1ea2ff50a Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Mon, 29 Nov 2021 09:04:04 -0800 Subject: [PATCH 37/38] WIP: Fixing curved polygon intersection algorithm for special cases --- .../detail/intersect_curved_poly_impl.hpp | 360 ++++++++++++++---- src/axom/primal/operators/intersect.hpp | 8 +- .../tests/primal_curved_polygon_intersect.cpp | 91 +++-- 3 files changed, 345 insertions(+), 114 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index f44a884a16..3a5f8982c1 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -12,6 +12,8 @@ #ifndef AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_IMPL_HPP_ #define AXOM_PRIMAL_INTERSECTION_CURVED_POLYGON_IMPL_HPP_ +#include "axom/core.hpp" + #include "axom/primal/geometry/BoundingBox.hpp" #include "axom/primal/geometry/OrientedBoundingBox.hpp" #include "axom/primal/geometry/Point.hpp" @@ -22,9 +24,8 @@ #include "axom/primal/geometry/BezierCurve.hpp" #include "axom/primal/geometry/CurvedPolygon.hpp" -#include "axom/core/utilities/Utilities.hpp" - #include "axom/primal/operators/squared_distance.hpp" +#include "axom/primal/operators/orientation.hpp" #include "axom/primal/operators/detail/intersect_bezier_impl.hpp" #include "axom/fmt.hpp" @@ -43,21 +44,21 @@ int isContained(const CurvedPolygon& p1, const CurvedPolygon& p2, double sq_tol = 1e-10); -template +template class DirectionalWalk { public: + static constexpr int NDIMS = 2; using CurvedPolygonType = CurvedPolygon; - using EdgeLabels = std::vector; + using IndexArray = std::vector; public: /*! \class EdgeIntersectionInfo * * \brief For storing intersection points between edges of \a CurvedPolygon instances so they can be easily sorted by parameter value using std::sort */ - class EdgeIntersectionInfo + struct EdgeIntersectionInfo { - public: T myTime; // parameter value of intersection on curve on first CurvePolygon int myEdge; // index of curve on first CurvedPolygon T otherTime; // parameter value of intersection on curve on second CurvedPolygon @@ -78,7 +79,7 @@ class DirectionalWalk NON_JUNCTION, /// Not a junction, e.g. a vertex of the original APPLIED, /// Use after junction has been applied CROSS, /// Typical case: edges of polygons intersect - GRAZE /// Atypical case: polygons intersecte at a terminal vertex + GRAZE /// Atypical case: polygons intersect at a terminal vertex }; /*! @@ -88,30 +89,38 @@ class DirectionalWalk { using JunctionIndex = int; using EdgeIndex = int; - static const EdgeIndex INVALID_EDGE_INDEX = -1; - static const int INVALID_JUNCTION_INDEX = -1; - - // indices of polygon edges leading into junction - EdgeIndex edgeIndex[2] {INVALID_EDGE_INDEX, INVALID_EDGE_INDEX}; - // describes the junction type and status - JunctionState junctionState {JunctionState::UNINITIALIZED}; - JunctionIndex index {INVALID_JUNCTION_INDEX}; + static constexpr EdgeIndex INVALID_EDGE_INDEX = -1; + static constexpr int INVALID_JUNCTION_INDEX = -1; + static constexpr int NON_JUNCTION_INDEX = 0; bool isActive() const { return junctionState > JunctionState::APPLIED; } - EdgeIndex currentEdgeIndex(bool active) const { return edgeIndex[active]; } - EdgeIndex nextEdgeIndex(bool active) const { return edgeIndex[active] + 1; } + EdgeIndex currentEdgeIndex(bool active) const + { + return incomingEdgeIndex[active]; + } + EdgeIndex nextEdgeIndex(bool active) const + { + return incomingEdgeIndex[active] + 1; + } bool operator==(const Junction& other) const { return index == other.index; } bool operator!=(const Junction& other) const { return !(*this == other); } + + public: + // indices of polygon edges leading into junction + EdgeIndex incomingEdgeIndex[2] {INVALID_EDGE_INDEX, INVALID_EDGE_INDEX}; + // describes the junction type and status + JunctionState junctionState {JunctionState::UNINITIALIZED}; + JunctionIndex index {INVALID_JUNCTION_INDEX}; }; /*! * Helper class for indexing an edge of a polygon - * Assumption is that the polygon is not empty + * Assumption is that the polygon is not empty */ class PolygonEdge { @@ -119,10 +128,10 @@ class DirectionalWalk using VertexIndex = int; using EdgeIndex = int; - PolygonEdge(int polygonID, const EdgeLabels& labels) + PolygonEdge(int polygonID, const IndexArray& endpointJunctionIndices) : m_polygonID(polygonID) - , m_labels(labels) - , m_numEdges(labels.size()) + , m_endpointJunctionIndices(endpointJunctionIndices) + , m_numEdges(endpointJunctionIndices.size()) { SLIC_ASSERT_MSG(m_numEdges > 0, "Polygon " << polygonID << " was empty"); } @@ -130,8 +139,8 @@ class DirectionalWalk EdgeIndex getIndex() const { return m_index; } void setIndex(EdgeIndex idx) { m_index = (idx % m_numEdges); } - int getStartLabel() const { return m_labels[prevIndex()]; } - int getEndLabel() const { return m_labels[m_index]; } + int getStartLabel() const { return m_endpointJunctionIndices[prevIndex()]; } + int getEndLabel() const { return m_endpointJunctionIndices[m_index]; } void advance() { m_index = nextIndex(); } bool isJunction() const { return getEndLabel() > 0; } @@ -158,17 +167,17 @@ class DirectionalWalk EdgeIndex m_index; const int m_polygonID; - const EdgeLabels& m_labels; + const IndexArray& m_endpointJunctionIndices; const int m_numEdges; }; public: - explicit DirectionalWalk(bool verbose = false) : m_verbose(verbose) { } + explicit DirectionalWalk(bool verbose = true) : m_verbose(verbose) { } /*! * Splits edges of each polygon based on the intersections with the other polygon * When the two polygons intersect, the split polygons are returned in \a psplit - * The edges are labeled by the types of intersections in \a edgelabels + * The edges are labeled by the types of intersections in \a endpointJunctionIndices * and \a orientation contains the relative orientation of the first intersection */ int splitPolygonsAlongIntersections(const CurvedPolygonType& p1, @@ -176,8 +185,10 @@ class DirectionalWalk double sq_tol) { // We store intersection information for each edge in EdgeIntersectionInfo structures - std::vector> E1IntData(p1.numEdges()); - std::vector> E2IntData(p2.numEdges()); + using EdgeIntersectionInfoArray = + std::vector>; + EdgeIntersectionInfoArray p1IntersectionData(p1.numEdges()); + EdgeIntersectionInfoArray p2IntersectionData(p2.numEdges()); EdgeIntersectionInfo firstinter; // Need to do orientation test on first intersection // Find all intersections and store @@ -209,8 +220,10 @@ class DirectionalWalk for(int k = 0; k < edgeIntersections; ++k, ++numinters) { - E1IntData[i].push_back({p1times[k], i, p2times[k], j, numinters + 1}); - E2IntData[j].push_back({p2times[k], j, p1times[k], i, numinters + 1}); + p1IntersectionData[i].push_back( + {p1times[k], i, p2times[k], j, numinters + 1}); + p2IntersectionData[j].push_back( + {p2times[k], j, p1times[k], i, numinters + 1}); if(m_verbose) { @@ -239,18 +252,18 @@ class DirectionalWalk for(int i = 0; i < p1.numEdges(); ++i) { - std::sort(E1IntData[i].begin(), E1IntData[i].end()); + std::sort(p1IntersectionData[i].begin(), p1IntersectionData[i].end()); } for(int i = 0; i < p2.numEdges(); ++i) { - std::sort(E2IntData[i].begin(), E2IntData[i].end()); + std::sort(p2IntersectionData[i].begin(), p2IntersectionData[i].end()); } psplit[0] = p1; psplit[1] = p2; - edgelabels[0].reserve(p1.numEdges() + numinters); - edgelabels[1].reserve(p2.numEdges() + numinters); + endpointJunctionIndices[0].reserve(p1.numEdges() + numinters); + endpointJunctionIndices[1].reserve(p2.numEdges() + numinters); junctions = std::vector(numinters + 1); @@ -260,8 +273,8 @@ class DirectionalWalk SLIC_INFO("Poly2 before split: " << psplit[1]); } - splitPolygon(psplit[0], E1IntData, edgelabels[0], 0); - splitPolygon(psplit[1], E2IntData, edgelabels[1], 1); + this->splitPolygon(0, p1IntersectionData); + this->splitPolygon(1, p2IntersectionData); if(m_verbose) { @@ -276,17 +289,35 @@ class DirectionalWalk public: /*! * Finds all intersection regions of the two input polygons - * + * * This function must be called after splitPolygonsAlongIntersections - * which inserts the intersection points into each polygon and generates - * the \a edgelabels structure. - * + * which inserts the intersection points into each polygon and generates + * the \a endpointJunctionIndices structure. + * * The results will be a vector of polygons inserted into OUT parameter \a pnew */ void findIntersectionRegions(std::vector& pnew) { - PolygonEdge currentEdge[2] = {PolygonEdge(0, edgelabels[0]), - PolygonEdge(1, edgelabels[1])}; + PolygonEdge currentEdge[2] = {PolygonEdge(0, endpointJunctionIndices[0]), + PolygonEdge(1, endpointJunctionIndices[1])}; + + if(m_verbose) + { + const int nJunctions = junctions.size(); + SLIC_INFO("At start of 'findIntersectionRegions', there are " + << nJunctions << " junctions:"); + for(int i = 0; i < nJunctions; ++i) + { + SLIC_INFO( + axom::fmt::format("\tJunction {}: incomingEdge[0]: {}, " + "incomingEdge[1]: {}, state: {}, index: {} ", + i, + junctions[i].incomingEdgeIndex[0], + junctions[i].incomingEdgeIndex[1], + junctions[i].junctionState, + junctions[i].index)); + } + } // We use junctionIndex to loop through the active junctions starting with index 1 int junctionIndex = 1; @@ -309,6 +340,15 @@ class DirectionalWalk { break; } + // switch(startJunction->junctionState) + // { + // case JunctionState::CROSS: + // break; + // case JunctionState::GRAZE: + // break; + // default: + // break; + // } else { startJunction->junctionState = JunctionState::APPLIED; @@ -324,17 +364,28 @@ class DirectionalWalk if(m_verbose) { - SLIC_INFO("" - << "Starting with junction " << startJunction->index - << "\n\t edges[0] {in: " << startJunction->currentEdgeIndex(0) - << " -- " << psplit[0][startJunction->currentEdgeIndex(0)] - << "; out: " << startJunction->nextEdgeIndex(0) << " -- " - << psplit[0][startJunction->nextEdgeIndex(0)] << "}" - << "\n\t edges[1] {in: " << startJunction->currentEdgeIndex(1) - << " -- " << psplit[1][startJunction->currentEdgeIndex(1)] - << "; out: " << startJunction->nextEdgeIndex(1) << " -- " - << psplit[1][startJunction->nextEdgeIndex(1)] << "}" - << "\n\t active: " << (active ? 1 : 0)); + const int p0Edges = psplit[0].numEdges(); + const int p1Edges = psplit[1].numEdges(); + const int p0CurrIdx = startJunction->currentEdgeIndex(0); + const int p0NextIdx = startJunction->nextEdgeIndex(0) % p0Edges; + const int p1CurrIdx = startJunction->currentEdgeIndex(1); + const int p1NextIdx = startJunction->nextEdgeIndex(1) % p1Edges; + + SLIC_INFO( + axom::fmt::format("Starting with junction {}" + "\n\t edges[0] {} -> {} {{in: {}; out: {} }}" + "\n\t edges[1] {} -> {} {{in: {}; out: {} }}" + "\n\t active: {}", + startJunction->index, + p0CurrIdx, + p0NextIdx, + psplit[0][p0CurrIdx], + psplit[0][p0NextIdx], + p1CurrIdx, + p1NextIdx, + psplit[1][p1CurrIdx], + psplit[1][p1NextIdx], + (active ? 1 : 0))); } CurvedPolygonType aPart; // Tracks the edges of the current intersection polygon @@ -408,65 +459,138 @@ class DirectionalWalk } private: - /// Splits a CurvedPolygon \a p1 at every intersection point stored in \a IntersectionData - /// \a edgeLabels stores intersection id for new vertices and 0 for original vertices - void splitPolygon(CurvedPolygon& polygon, - std::vector>& IntersectionData, - std::vector& edgelabels, - int polygonID) + /// Splits the polygon with id \a polygonID at every intersection point in \a edgeIntersections. + /// Uses internal array \a edgeLabels to store ids for vertices (junction indices or 0 for original vertices) + void splitPolygon( + int polygonID, + std::vector>& allEdgeIntersections) { using axom::utilities::isNearlyEqual; + CurvedPolygonType& polygon = psplit[polygonID]; + IndexArray& junctionIndices = endpointJunctionIndices[polygonID]; + + bool fixupBeginning = false; + int fixupBeginningJunctionIdx = Junction::INVALID_JUNCTION_INDEX; + int addedIntersections = 0; - const int numEdges = polygon.numEdges(); - for(int i = 0; i < numEdges; ++i) // foreach edge + const int numOrigEdges = polygon.numEdges(); + for(int i = 0; i < numOrigEdges; ++i) // foreach edge { - edgelabels.push_back(0); // mark start current vertex as 'original' - const int nIntersect = IntersectionData[i].size(); + // mark this edge's endpoint as 'original' + junctionIndices.push_back(Junction::NON_JUNCTION_INDEX); + double previous_tj = 0.; + auto& curEdgeIntersections = allEdgeIntersections[i]; + const int nIntersect = curEdgeIntersections.size(); for(int j = 0; j < nIntersect; ++j) // foreach intersection on this edge { // split edge at parameter t_j - const double t_j = IntersectionData[i][j].myTime; + const double t_j = curEdgeIntersections[j].myTime; const int edgeIndex = i + addedIntersections; + if(m_verbose) + { + SLIC_INFO( + fmt::format("i {}, j {}, added {}, t_j {}, previous t_j {}, " + "edgeIndex {} (sz: {}), polygon size {}", + i, + j, + addedIntersections, + t_j, + previous_tj, + edgeIndex, + junctionIndices.size(), + polygon.numEdges())); + } + + const bool nearlyZero = isNearlyEqual(t_j, 0.); + // TODO: Handle case where t_j is nearly 0. + if(!nearlyZero) + { + polygon.splitEdge(edgeIndex, t_j); + + // update edge label + const int idx = curEdgeIntersections[j].numinter; + junctionIndices.insert(junctionIndices.begin() + edgeIndex, idx); + junctions[idx].incomingEdgeIndex[polygonID] = edgeIndex; + //if(junctions[idx].junctionState == JunctionState::UNINITIALIZED) + //{ + junctions[idx].junctionState = JunctionState::CROSS; + //} + junctions[idx].index = idx; + + ++addedIntersections; + previous_tj = t_j; + } + else + { + const int idx = curEdgeIntersections[j].numinter; - polygon.splitEdge(edgeIndex, t_j); + if(edgeIndex > 0) + { + junctionIndices[edgeIndex] = idx; + junctions[idx].incomingEdgeIndex[polygonID] = edgeIndex; + } + else + { + fixupBeginning = true; + fixupBeginningJunctionIdx = idx; + } - // update edge label - const int label = IntersectionData[i][j].numinter; - edgelabels.insert(edgelabels.begin() + edgeIndex, label); - junctions[label].edgeIndex[polygonID] = edgeIndex; - junctions[label].junctionState = - JunctionState::CROSS; // TODO: Figure out what junction type needs to be - junctions[label].index = label; + junctions[idx].junctionState = + JunctionState::CROSS; //JunctionState::GRAZE; + junctions[idx].index = idx; + } - ++addedIntersections; + if(m_verbose) + { + SLIC_INFO(axom::fmt::format("junctionIndices: {}\n junctions:", + axom::fmt::join(junctionIndices, " "))); + for(const auto& jj : junctions) + { + SLIC_INFO(fmt::format( + "\t{{index: {}, state: {}, edgeIndex[0]: {}, edgeIndex[1]: {} }}", + jj.index, + jj.junctionState, + jj.incomingEdgeIndex[0], + jj.incomingEdgeIndex[1])); + } + } // update remaining intersections on this edge; special case if already at end of curve if(!isNearlyEqual(1., t_j)) { for(int k = j + 1; k < nIntersect; ++k) { - const double t_k = IntersectionData[i][k].myTime; - IntersectionData[i][k].myTime = (t_k - t_j) / (1.0 - t_j); + const double t_k = curEdgeIntersections[k].myTime; + curEdgeIntersections[k].myTime = (t_k - t_j) / (1.0 - t_j); } } else { for(int k = j + 1; k < nIntersect; ++k) { - IntersectionData[i][k].myTime = 1.; + curEdgeIntersections[k].myTime = 1.; } } } } + + // If the first vertex of the first edge was a junction, we need to fix it up using the current last edge + if(fixupBeginning) + { + const int edgeIndex = junctionIndices.size() - 1; + junctionIndices[edgeIndex] = fixupBeginningJunctionIdx; + junctions[fixupBeginningJunctionIdx].incomingEdgeIndex[polygonID] = + edgeIndex; + } } public: // Objects to store completely split polygons (split at every intersection point) and vector with unique id for each intersection and zeros for corners of original polygons. CurvedPolygonType psplit[2]; // The two completely split polygons will be stored in this array - EdgeLabels edgelabels[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points + IndexArray endpointJunctionIndices[2]; // 0 for curves that end in original vertices, unique id for curves that end in intersection points std::vector junctions; int numinters {0}; bool orientation {false}; @@ -480,12 +604,12 @@ class DirectionalWalk * * \param [in] p1, p2 CurvedPolygon objects to intersect * \param [in] sq_tol tolerance parameter for the base case of intersect_bezier_curve - * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. + * \param [out] pnew vector of type CurvedPolygon holding CurvedPolygon objects representing boundaries of intersection regions. */ -template -bool intersect_polygon(const CurvedPolygon& p1, - const CurvedPolygon& p2, - std::vector>& pnew, +template +bool intersect_polygon(const CurvedPolygon& p1, + const CurvedPolygon& p2, + std::vector>& pnew, double sq_tol) { // Intersection is empty if either of the two polygons are empty @@ -494,7 +618,7 @@ bool intersect_polygon(const CurvedPolygon& p1, return false; } - DirectionalWalk walk; + DirectionalWalk walk; int numinters = walk.splitPolygonsAlongIntersections(p1, p2, sq_tol); @@ -618,7 +742,7 @@ int isContained(const CurvedPolygon& p1, } /*! - * \brief Determines orientation of a bezier curve \a c1 with respect to another bezier curve \a c2, + * \brief Determines orientation of a bezier curve \a c1 with respect to another bezier curve \a c2, * given that they intersect at parameter values \a s and \a t, respectively * * \param [in] c1 the first bezier curve @@ -635,6 +759,74 @@ bool orient(const BezierCurve& c1, const BezierCurve& c2, T s, T t) return (orientation > 0); } +enum class JunctionIntersectionType : int +{ + Non_Intersection = -1, // + Cross, + Type1, + Type2, + Type3, + Type4 +}; + +// when interesection is at a vertex of the original polygon(s), +// determine if the in/out edges of second polygon is on the same +// side of the first polygon +template +JunctionIntersectionType getJunctionIntersectionType( + const BezierCurve& p0In, + const BezierCurve& p0Out, + const BezierCurve& p1In, + const BezierCurve& p1Out) +{ + using SegmentType = primal::Segment; + using VectorType = primal::Vector; + using PointType = primal::Point; + + // TODO: Error checking to ensure the four curves have a common intersection point + // P0In(1) == P0Out(0) == P1In(1) == P1Out(0) + // If not, return JunctionIntersectionType::Non_Intersection + + const VectorType p0Tangents[2] = {-p0In.dt(1.).unitVector(), + p0Out.dt(0.).unitVector()}; + const SegmentType p0Seg(PointType {p0Tangents[0][0], p0Tangents[0][1]}, + PointType {p0Tangents[1][0], p0Tangents[1][1]}); + + const VectorType p1Tangents[2] = {-p1In.dt(1.).unitVector(), + p1Out.dt(0.).unitVector()}; + const SegmentType p1Seg(PointType {p1Tangents[0][0], p1Tangents[0][1]}, + PointType {p1Tangents[1][0], p1Tangents[1][1]}); + + const int orientIn = primal::orientation(p1Seg[0], p0Seg); + const int orientOut = primal::orientation(p1Seg[1], p0Seg); + + if(orientIn == orientOut) + { + const int orient_p1_p0In = primal::orientation(p0Seg[0], p1Seg); + if(orientIn == ON_POSITIVE_SIDE) + { + return orient_p1_p0In == ON_POSITIVE_SIDE + ? JunctionIntersectionType::Type3 + : JunctionIntersectionType::Type1; + } + else + { + { + return orient_p1_p0In == ON_POSITIVE_SIDE + ? JunctionIntersectionType::Type4 + : JunctionIntersectionType::Type2; + } + } + } + else + { + return JunctionIntersectionType::Cross; + } +} + +template +constexpr int DirectionalWalk::Junction::NON_JUNCTION_INDEX; + } // namespace detail } // namespace primal } // namespace axom diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index e9e8c3c29f..2b66bfab9a 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -505,10 +505,10 @@ bool intersect(const OrientedBoundingBox& b1, * Thus, component curves do not intersect at \f$ s==1 \f$ or at * \f$ t==1 \f$. */ -template -bool intersect(const CurvedPolygon& p1, - const CurvedPolygon& p2, - std::vector>& pnew, +template +bool intersect(const CurvedPolygon& p1, + const CurvedPolygon& p2, + std::vector>& pnew, double tol = 1E-8) { // for efficiency, linearity check actually uses a squared tolerance diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index 06dfafe334..d0ce71b37f 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -20,15 +20,15 @@ namespace primal = axom::primal; -template +template void outputAsSVG( const std::string& filename, - const primal::CurvedPolygon& polygon1, - const primal::CurvedPolygon& polygon2, - const std::vector> intersectionPolygons) + const primal::CurvedPolygon& polygon1, + const primal::CurvedPolygon& polygon2, + const std::vector> intersectionPolygons) { // Find the bounding box of the set of polygons - primal::BoundingBox bbox; + primal::BoundingBox bbox; bbox.addBox(polygon1.boundingBox()); bbox.addBox(polygon2.boundingBox()); for(const auto& cp : intersectionPolygons) @@ -46,7 +46,7 @@ void outputAsSVG( std::string footer = ""; // lambda to convert a CurvedPolygon to an SVG path string - auto cpToSVG = [](const primal::CurvedPolygon& cp) { + auto cpToSVG = [](const primal::CurvedPolygon& cp) { axom::fmt::memory_buffer out; bool is_first = true; @@ -146,18 +146,19 @@ void outputAsSVG( } /*! - * Helper function to compute the set of intersection polygons given two input polygons - * and to check that they match expectations, stored in \a expbPolygon. + * Helper function to compute the set of intersection polygons given two input polygons + * and to check that they match expectations, stored in \a expbPolygon. * Intersection polygon is computed to within tolerance \a eps and checks use \a test_eps. */ -template +template void checkIntersection( - const primal::CurvedPolygon& bPolygon1, - const primal::CurvedPolygon& bPolygon2, - const std::vector> expbPolygon, + const primal::CurvedPolygon& bPolygon1, + const primal::CurvedPolygon& bPolygon2, + const std::vector> expbPolygon, const double eps = 1e-15, const double test_eps = 1e-13) { + constexpr int DIM = 2; using CurvedPolygonType = primal::CurvedPolygon; using BezierCurveType = typename CurvedPolygonType::BezierCurveType; using PointType = typename BezierCurveType::PointType; @@ -167,7 +168,7 @@ void checkIntersection( //Compute intersection using algorithm with tolerance of eps intersect(bPolygon1, bPolygon2, intersectionPolys, eps); //Check that expected number of intersection regions are found - EXPECT_EQ(expbPolygon.size(), intersectionPolys.size()); + ASSERT_EQ(expbPolygon.size(), intersectionPolys.size()); //Check that expected intersection curves are found to within test_eps const int nPolygons = expbPolygon.size(); @@ -204,18 +205,18 @@ void checkIntersection( /*! * Helper function to create a CurvedPolygon from a list of control points and a list * of orders of component curves. Control points should be given as a list of Points - * in order of orientation with no duplicates except that the first control point + * in order of orientation with no duplicates except that the first control point * should also be the last control point (if the polygon is closed). Orders should * be given as a list of ints in order of orientation, representing the orders of the component curves. */ -template -primal::CurvedPolygon createPolygon( - const std::vector> ControlPoints, +template +primal::CurvedPolygon createPolygon( + const std::vector> ControlPoints, const std::vector orders) { - using PointType = primal::Point; - using CurvedPolygonType = primal::CurvedPolygon; - using BezierCurveType = primal::BezierCurve; + using PointType = primal::Point; + using CurvedPolygonType = primal::CurvedPolygon; + using BezierCurveType = primal::BezierCurve; const int num_edges = orders.size(); const int num_unique_control_points = ControlPoints.size(); @@ -243,6 +244,42 @@ primal::CurvedPolygon createPolygon( //---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, detail_intersection_type) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + + using primal::detail::getJunctionIntersectionType; + using primal::detail::JunctionIntersectionType; + + SLIC_INFO("Tests various junction intersections"); + + // the first pair is a horizontal line from left to right + std::vector orders = {1, 1}; + std::vector CP = {PointType {-1, 0}, + PointType {0, 0}, + PointType {1, 0}}; + CurvedPolygonType bPolygon1 = createPolygon(CP, orders); + + // A type2 case + { + std::vector CP2 = {PointType {1, -1}, + PointType {0, 0}, + PointType {-1, -1}}; + CurvedPolygonType bPolygon2 = createPolygon(CP2, orders); + + auto xType = getJunctionIntersectionType(bPolygon1[0], + bPolygon1[1], + bPolygon2[0], + bPolygon2[1]); + EXPECT_EQ(JunctionIntersectionType::Type2, xType); + } +} + +//---------------------------------------------------------------------------------- + TEST(primal_curvedpolygon, intersection_squares) { const int DIM = 2; @@ -379,7 +416,7 @@ TEST(primal_curvedpolygon, intersections_triangle_rectangle) // No intersection case: Triangle is below the rectangle { CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); - primal::detail::DirectionalWalk walk(bVerbose); + primal::detail::DirectionalWalk walk(bVerbose); int nIntersections = walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); EXPECT_EQ(0, nIntersections); @@ -398,7 +435,7 @@ TEST(primal_curvedpolygon, intersections_triangle_rectangle) triPts[2][1] = 0; CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); - primal::detail::DirectionalWalk walk(bVerbose); + primal::detail::DirectionalWalk walk(bVerbose); int nIntersections = walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); EXPECT_EQ(1, nIntersections); @@ -426,7 +463,7 @@ TEST(primal_curvedpolygon, intersections_triangle_rectangle) triPts[2][1] = 1; CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); - primal::detail::DirectionalWalk walk(bVerbose); + primal::detail::DirectionalWalk walk(bVerbose); int nIntersections = walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); EXPECT_EQ(2, nIntersections); @@ -457,7 +494,7 @@ TEST(primal_curvedpolygon, intersections_triangle_rectangle) triPts[2][1] = 2; CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); - primal::detail::DirectionalWalk walk(bVerbose); + primal::detail::DirectionalWalk walk(bVerbose); int nIntersections = walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); EXPECT_EQ(3, nIntersections); @@ -483,7 +520,7 @@ TEST(primal_curvedpolygon, intersections_triangle_rectangle) triPts[2][1] = 3; CurvedPolygonType bTriangle = createPolygon(triPts, triOrders); - primal::detail::DirectionalWalk walk(bVerbose); + primal::detail::DirectionalWalk walk(bVerbose); int nIntersections = walk.splitPolygonsAlongIntersections(bRectangle, bTriangle, SQ_EPS); EXPECT_EQ(4, nIntersections); @@ -695,7 +732,7 @@ TEST(primal_curvedpolygon, doubleIntersection) double walkIntersectionArea = 0.; { const bool verbose = true; - primal::detail::DirectionalWalk walk(verbose); + primal::detail::DirectionalWalk walk(verbose); int numIntersections = walk.splitPolygonsAlongIntersections(bPolygon1, bPolygon2, EPS * EPS); @@ -870,6 +907,7 @@ TEST(primal_curvedpolygon, adjacent_squares) } // Sharing a vertex + if(false) { std::vector CP1 = {PointType {-1, 0}, PointType {0, 0}, @@ -908,6 +946,7 @@ TEST(primal_curvedpolygon, adjacent_squares) } } + if(false) { std::vector expIntersections; intersect(bPolygon2, bPolygon1, expIntersections, EPS); From d1c65b5d8d6329303388b071d20278753eceb9f5 Mon Sep 17 00:00:00 2001 From: Axom Shared User Date: Thu, 13 Jan 2022 15:45:36 -0800 Subject: [PATCH 38/38] Updates copyright year --- src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp | 2 +- src/axom/primal/tests/primal_curved_polygon_intersect.cpp | 2 +- src/axom/quest/examples/quest_high_order_remap.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp index 3a5f8982c1..9bdc394f87 100644 --- a/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_curved_poly_impl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and +// Copyright (c) 2017-2022, Lawrence Livermore National Security, LLC and // other Axom Project Developers. See the top-level LICENSE file for details. // // SPDX-License-Identifier: (BSD-3-Clause) diff --git a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp index d0ce71b37f..9611f63ee4 100644 --- a/src/axom/primal/tests/primal_curved_polygon_intersect.cpp +++ b/src/axom/primal/tests/primal_curved_polygon_intersect.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and +// Copyright (c) 2017-2022, Lawrence Livermore National Security, LLC and // other Axom Project Developers. See the top-level LICENSE file for details. // // SPDX-License-Identifier: (BSD-3-Clause) diff --git a/src/axom/quest/examples/quest_high_order_remap.cpp b/src/axom/quest/examples/quest_high_order_remap.cpp index 68442a48ce..462ed05cb1 100644 --- a/src/axom/quest/examples/quest_high_order_remap.cpp +++ b/src/axom/quest/examples/quest_high_order_remap.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and +// Copyright (c) 2017-2022, Lawrence Livermore National Security, LLC and // other Axom Project Developers. See the top-level LICENSE file for details. // // SPDX-License-Identifier: (BSD-3-Clause)