From 13e007d2100a5b75619fde8482d0d864621ba2ed Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Wed, 17 Jul 2019 14:31:13 -0700 Subject: [PATCH 01/38] Added sectorArea() to BezierCurve, CurvedPolygon class with area function --- src/axom/core/utilities/Utilities.cpp | 15 + src/axom/core/utilities/Utilities.hpp | 2 +- src/axom/primal/CMakeLists.txt | 1 + src/axom/primal/geometry/BezierCurve.hpp | 53 +++ src/axom/primal/geometry/CurvedPolygon.hpp | 212 +++++++++ src/axom/primal/tests/CMakeLists.txt | 1 + src/axom/primal/tests/primal_bezier_curve.cpp | 40 ++ .../primal/tests/primal_curved_polygon.cpp | 406 ++++++++++++++++++ 8 files changed, 729 insertions(+), 1 deletion(-) create mode 100644 src/axom/primal/geometry/CurvedPolygon.hpp create mode 100644 src/axom/primal/tests/primal_curved_polygon.cpp diff --git a/src/axom/core/utilities/Utilities.cpp b/src/axom/core/utilities/Utilities.cpp index d64b28363b..85d0bc9050 100644 --- a/src/axom/core/utilities/Utilities.cpp +++ b/src/axom/core/utilities/Utilities.cpp @@ -24,6 +24,21 @@ namespace axom { namespace utilities { +int binomial_coefficient(int n, int k) +{ + if(k > n - k) + { + k = n - k; + } + int val = 1; + for(int i = 1; i <= k; ++i) + { + val *= (n - k + i); + val /= i; + } + return val; +} + void processAbort() { #ifndef AXOM_USE_MPI diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index 1c7b86bcbf..542be49515 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -32,7 +32,7 @@ namespace utilities * \brief Gracefully aborts the application */ void processAbort(); - +int binomial_coefficient(int n, int k); /*! * \brief Returns the absolute value of x. * \accelerated diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 83a7a175eb..d0dfe7e98f 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -20,6 +20,7 @@ set( primal_headers ## geometry geometry/BezierCurve.hpp geometry/BoundingBox.hpp + geometry/CurvedPolygon.hpp geometry/OrientedBoundingBox.hpp geometry/OrientationResult.hpp geometry/NumericArray.hpp diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 0ce440dfd7..88d3a7c08e 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -39,6 +39,9 @@ class BezierCurve; template std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve); +// UedaAreaMats is where all of the area matrices from Ueda '99 are stored +extern std::map> UedaAreaMats; + /*! * \class BezierCurve * @@ -278,6 +281,56 @@ class BezierCurve return; } + /*! + * \brief Calculates the sector area (area between curve and origin) of a Bezier Curve + * + * \param [in] tol a tolerance parameter controlling definition of + * near-linearity + * \param [out] boolean TRUE if c1 is near-linear + */ + T sectorArea() const + { + T A = 0; + int ord = getOrder(); + if(UedaAreaMats.find(ord) == UedaAreaMats.end()) + { + std::vector newUedaAreaMat((ord + 1) * (ord + 1)); + int twonchoosen = axom::utilities::binomial_coefficient(2 * ord, ord); + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + if((i == 0 && j == 0) || (i == (ord) && j == (ord))) + { + newUedaAreaMat[i * (ord + 1) + j] = 0.0; + } + else + { + newUedaAreaMat[i * (ord + 1) + j] = ((1.0 * j - i) / 2) * + (2.0 * (ord) / (1.0 * twonchoosen)) * + (1.0 * axom::utilities::binomial_coefficient(i + j, i) / + (1.0 * i + j)) * + (1.0 * + axom::utilities::binomial_coefficient(2 * (ord)-i - j, (ord)-j) / + (1.0 * (ord)-j + (ord)-i)); + } + } + } + UedaAreaMats.insert( + std::pair>(ord, newUedaAreaMat)); + } + const std::vector& whicharea = (UedaAreaMats.find(ord)->second); + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + A += static_cast(whicharea[i * (ord + 1) + j]) * + m_controlPoints[i][1] * m_controlPoints[j][0]; + } + } + return A; + } + /*! * \brief Predicate to check if the Bezier curve is approximately linear * diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp new file mode 100644 index 0000000000..2da1214fa4 --- /dev/null +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -0,0 +1,212 @@ +// 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 CurvedPolygon.hpp + * + * \brief A CurvedPolygon primitive for primal based on Bezier Curves + */ + +#ifndef PRIMAL_CURVEDPOLYGON_HPP_ +#define PRIMAL_CURVEDPOLYGON_HPP_ + +#include "axom/slic.hpp" + +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/Vector.hpp" +#include "axom/primal/geometry/NumericArray.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/operators/intersect.hpp" + +#include +#include // for std::ostream + +namespace axom +{ +namespace primal +{ +// Forward declare the templated classes and operator functions +template +class CurvedPolygon; + +/*! \brief Overloaded output operator for polygons */ +template +std::ostream& operator<<(std::ostream& os, const CurvedPolygon& poly); + +/*! + * \class CurvedPolygon + * + * \brief Represents a curved polygon defined by a vector of BezierCurves + * \tparam T the coordinate type, e.g., double, float, etc. + * \tparam NDIMS the number of dimensions + * \note The component curves should be ordered in a counter clockwise + * orientation with respect to the polygon's desired normal vector + */ +template +class CurvedPolygon +{ +public: + using PointType = Point; + using VectorType = Vector; + using NumArrayType = NumericArray; + using BezierCurveType = BezierCurve; + +public: + /*! Default constructor for an empty polygon */ + CurvedPolygon() = default; + + /*! + * \brief Constructor for an empty CurvedPolygon that reserves space for + * the given number of Edges + * + * \param [in] numExpectedEdges number of edges for which to reserve space + * \pre numExpectedEdges is at least 1 + * + */ + CurvedPolygon(int nEdges) + { + SLIC_ASSERT(nEdges >= 1); + m_edges.reserve(nEdges); + m_edges.resize(nEdges); + } + + /*! Return the number of edges in the polygon */ + int numEdges() const { return m_edges.size(); } + + void setNumEdges(int ngon) + { + SLIC_ASSERT(ngon >= 0); + m_edges.resize(ngon); + } + + /* Checks equality of two Bezier Curve */ + friend inline bool operator==(const CurvedPolygon& lhs, + const CurvedPolygon& rhs) + { + return lhs.m_edges == rhs.m_edges; + } + + friend inline bool operator!=(const CurvedPolygon& lhs, + const CurvedPolygon& rhs) + { + return !(lhs == rhs); + } + + /*! Appends a BezierCurve to the list of edges */ + void addEdge(const BezierCurveType& c1) { m_edges.push_back(c1); } + + /*! Clears the list of edges */ + void clear() { m_edges.clear(); } + + std::vector> getEdges() const { return m_edges; } + + /*! Retrieves the Bezier Curve at index idx */ + BezierCurveType& operator[](int idx) { return m_edges[idx]; } + /*! Retrieves the vertex at index idx */ + const BezierCurveType& operator[](int idx) const { return m_edges[idx]; } + + /*! + * \brief Simple formatted print of a CurvedPolygon instance + * + * \param os The output stream to write to + * \return A reference to the modified ostream + */ + std::ostream& print(std::ostream& os) const + { + const int sz = numEdges(); + + os << "{" << sz << "-sided Bezier polygon:"; + for(int i = 0; i < sz - 1; ++i) + { + os << m_edges[i] << ","; + } + if(sz >= 2) + { + os << m_edges[sz - 1]; + } + os << "}"; + + return os; + } + + /*! + * \brief Check closedness of a CurvedPolygon + * + * Check is that the endpoint of each edge coincides with startpoint of next edge + * \return True, if the polygon is closed, False otherwise + */ + bool isClosed() const + { + const int ngon = numEdges(); + if(ngon <= 1) + { + return false; + } + else + { + for(int p = 0; p < NDIMS; ++p) + { + for(int i = 0; i < (ngon - 1); ++i) + { + if(!axom::utilities::isNearlyEqual(m_edges[i][m_edges[i].getOrder()][p], + m_edges[i + 1][0][p])) + { + return false; + } + } + if(!axom::utilities::isNearlyEqual( + m_edges[ngon - 1][m_edges[ngon - 1].getOrder()][p], + m_edges[0][0][p])) + { + return false; + } + } + } + return true; + } + + /*! + * \brief Check closedness of a CurvedPolygon + * + * Check is that the endpoint of each edge coincides with startpoint of next edge + * \return True, if the polygon is closed, False otherwise + */ + + T area() const + { + const int ngon = numEdges(); + T A = 0.0; + if(!isClosed()) + { + return A; + } + else + { + for(int ed = 0; ed < ngon; ++ed) + { + A += m_edges[ed].sectorArea(); + } + return A; + } + } + +private: + std::vector> m_edges; +}; + +//------------------------------------------------------------------------------ +/// Free functions implementing Polygon's operators +//------------------------------------------------------------------------------ +template +std::ostream& operator<<(std::ostream& os, const CurvedPolygon& poly) +{ + poly.print(os); + return os; +} + +} // namespace primal +} // namespace axom + +#endif // PRIMAL_CURVEDPOLYGON_HPP_ diff --git a/src/axom/primal/tests/CMakeLists.txt b/src/axom/primal/tests/CMakeLists.txt index b8d8667634..776f3daab4 100644 --- a/src/axom/primal/tests/CMakeLists.txt +++ b/src/axom/primal/tests/CMakeLists.txt @@ -13,6 +13,7 @@ set( primal_tests primal_clip.cpp primal_closest_point.cpp primal_compute_bounding_box.cpp + primal_curved_polygon.cpp primal_in_sphere.cpp primal_intersect.cpp primal_intersect_impl.cpp diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 9f95645ec5..391ed0b515 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -14,6 +14,7 @@ namespace primal = axom::primal; +std::map> primal::UedaAreaMats; //TODO: put this in the BezierCurve.cpp file //------------------------------------------------------------------------------ TEST(primal_beziercurve, constructor) { @@ -163,6 +164,45 @@ TEST(primal_beziercurve, evaluate) } } +//------------------------------------------------------------------------------ +TEST(primal_beziercurve, sector_area_cubic) +{ + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing Bezier sector area calculation for a cubic"); + const int order = 3; + PointType data[order + 1] = {PointType::make_point(0.6, 1.2), + PointType::make_point(1.3, 1.6), + PointType::make_point(2.9, 2.4), + PointType::make_point(3.2, 3.5)}; + + BezierCurveType bCurve(data, order); + EXPECT_TRUE(axom::utilities::isNearlyEqual(bCurve.sectorArea(), .1455)); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_beziercurve, sector_area_point) +{ + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing Bezier sector area calculation for a cubic"); + const int order = 0; + PointType data[order + 1] = {PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(data, order); + EXPECT_DOUBLE_EQ(bCurve.sectorArea(), 0.0); + } +} + //------------------------------------------------------------------------------ TEST(primal_beziercurve, split_cubic) { diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp new file mode 100644 index 0000000000..763bb1addb --- /dev/null +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -0,0 +1,406 @@ +// 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 bezier_test.cpp + * /brief This file tests the BezierCurve.hpp and eval_bezier.hpp files + */ + +#include "gtest/gtest.h" + +#include "axom/slic.hpp" +#include "axom/primal/geometry/CurvedPolygon.hpp" + +namespace primal = axom::primal; + +std::map> primal::UedaAreaMats; //TODO: put this in the BezierCurve.cpp file +//------------------------------------------------------------------------------ +TEST(primal_curvedpolygon, constructor) +{ + const int DIM = 3; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing default CurvedPolygon constructor "); + CurvedPolygonType bPolygon; + + int expNumEdges = 0; + EXPECT_EQ(expNumEdges, bPolygon.numEdges()); + EXPECT_EQ(expNumEdges, bPolygon.getEdges().size()); + EXPECT_EQ(std::vector(), bPolygon.getEdges()); + } + + { + SLIC_INFO("Testing CurvedPolygon order constructor "); + + CurvedPolygonType bPolygon(1); + int expNumEdges = 1; + EXPECT_EQ(expNumEdges, bPolygon.numEdges()); + EXPECT_EQ(expNumEdges, static_cast(bPolygon.getEdges().size())); + } +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, add_edges) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO("Test adding edges to empty CurvedPolygon"); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.0, 1.6)}; + + BezierCurveType bCurve(controlPoints, 1); + + bPolygon.addEdge(bCurve); + bPolygon.addEdge(bCurve); + + EXPECT_EQ(2, bPolygon.numEdges()); + for(int p = 0; p < bPolygon.numEdges(); ++p) + { + BezierCurveType& bc = bPolygon[p]; + for(int sz = 0; sz <= bc.getOrder(); ++sz) + { + auto& pt = bc[sz]; + for(int i = 0; i < DIM; ++i) + { + EXPECT_DOUBLE_EQ(controlPoints[sz][i], pt[i]); + } + } + } +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, is_Valid) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO("Test checking if CurvedPolygon is closed."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, 1); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, 1); + bPolygon.addEdge(bCurve2); + + EXPECT_EQ(2, bPolygon.numEdges()); + EXPECT_EQ(false, bPolygon.isClosed()); + + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); + + EXPECT_EQ(3, bPolygon.numEdges()); + EXPECT_EQ(true, bPolygon.isClosed()); +} + +//---------------------------------------------------------------------------------- +TEST(primal_beziercurve, area) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO("Test checking if CurvedPolygon is closed."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, 1); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, 1); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); + + CoordType A = bPolygon.area(); + CoordType trueA = .18; + + EXPECT_TRUE(axom::utilities::isNearlyEqual(trueA, A)); +} + +/* +//---------------------------------------------------------------------------------- +TEST( primal_beziercurve, coordinate_array_constructor ) +{ + SLIC_INFO("Testing coordinate array constructor"); + + const int DIM = 3; + using CoordType = double; + using BezierCurveType = primal::BezierCurve< CoordType, DIM >; + + // Note: Order of coordinates is by dimension + CoordType coords[6] = {0.6, 0.0, // x-coords for control points + 1.2, 1.6, // y-coords for control points + 1.0, 1.8}; // z-coords for control points + + BezierCurveType bCurve(coords,1); + EXPECT_EQ(1, bCurve.getOrder()); + + + EXPECT_DOUBLE_EQ(coords[0], bCurve[0][0]); + EXPECT_DOUBLE_EQ(coords[2], bCurve[0][1]); + EXPECT_DOUBLE_EQ(coords[4], bCurve[0][2]); + + EXPECT_DOUBLE_EQ(coords[1], bCurve[1][0]); + EXPECT_DOUBLE_EQ(coords[3], bCurve[1][1]); + EXPECT_DOUBLE_EQ(coords[5], bCurve[1][2]); +} + +//------------------------------------------------------------------------------ +TEST( primal_beziercurve, evaluate) +{ + SLIC_INFO("Testing Bezier evaluation"); + + const int DIM = 3; + using CoordType = double; + using PointType = primal::Point< CoordType, DIM >; + using BezierCurveType = primal::BezierCurve< CoordType, DIM >; + + const int order = 3; + PointType data[order+1] = { PointType::make_point(0.6, 1.2, 1.0), + PointType::make_point(1.3, 1.6, 1.8), + PointType::make_point(2.9, 2.4, 2.3), + PointType::make_point(3.2, 3.5, 3.0) }; + + BezierCurveType b2Curve(data, order); + + PointType midtval = PointType::make_point(2.05,2.0875,2.0375); + + // Evaluate the curve at several parameter values + // Curve should interpolate endpoints + PointType eval0 = b2Curve.evaluate(0.0); + PointType eval1 = b2Curve.evaluate(1.0); + PointType evalMid = b2Curve.evaluate(0.5); + + for ( int i=0 ; i; + using BezierCurveType = primal::BezierCurve< CoordType, DIM >; + + const int order = 3; + PointType data[order+1] = { PointType::make_point(0.6, 1.2, 1.0), + PointType::make_point(1.3, 1.6, 1.8), + PointType::make_point(2.9, 2.4, 2.3), + PointType::make_point(3.2, 3.5, 3.0) }; + BezierCurveType b2Curve(data, order); + + BezierCurveType b3Curve(order); // Checks split with order constructor + BezierCurveType b4Curve; // Checks split with default constructor + b2Curve.split(.5, b3Curve, b4Curve); + + CoordType b3Coords[12] = {0.6, .95, 1.525, 2.05, + 1.2, 1.4, 1.7, 2.0875, + 1.0, 1.4, 1.725, 2.0375}; + CoordType b4Coords[12] = {2.05, 2.575, 3.05, 3.2, + 2.0875, 2.475, 2.95, 3.5, + 2.0375, 2.35, 2.65, 3.0}; + BezierCurveType b3True(b3Coords,3); + BezierCurveType b4True(b4Coords,3); + for ( int i=0 ; i; + using BezierCurveType = primal::BezierCurve< CoordType, DIM >; + + // Test order-0 + { + const int order = 0; + PointType data[order+1] = { PointType::make_point(0.6, 1.2)}; + BezierCurveType b(data, order); + + BezierCurveType c1,c2; + b.split(0.5, c1, c2); + + for ( int i=0 ; i; + using BezierCurveType = primal::BezierCurve< CoordType, DIM >; + + const int order = 1; + PointType data[order+1] = { PointType::make_point(-1, -5), + PointType::make_point( 1, 5) }; + BezierCurveType b(data, order); + + { + BezierCurveType c1,c2; + b.split(0.5, c1, c2); + + EXPECT_DOUBLE_EQ(-1., c1[0][0]); + EXPECT_DOUBLE_EQ(-5., c1[0][1]); + EXPECT_DOUBLE_EQ( 0., c1[1][0]); + EXPECT_DOUBLE_EQ( 0., c1[1][1]); + + EXPECT_DOUBLE_EQ( 0., c2[0][0]); + EXPECT_DOUBLE_EQ( 0., c2[0][1]); + EXPECT_DOUBLE_EQ( 1., c2[1][0]); + EXPECT_DOUBLE_EQ( 5., c2[1][1]); + + } + + { + BezierCurveType c1,c2; + const double t = 0.25; + b.split(0.25, c1, c2); + + PointType interp = PointType::lerp(data[0], data[1], t); + + for ( int i=0 ; i; + using BezierCurveType = primal::BezierCurve< CoordType, DIM >; + + const double t = .42; + const int order = 2; + + // Control points for the three levels of the quadratic de Casteljau algorithm + PointType lev0[3] = { PointType::make_point(1.1, 1.1), + PointType::make_point( 5.5, 5.5), + PointType::make_point( 9.9, 2.2) }; + + PointType lev1[2] = { PointType::lerp(lev0[0], lev0[1], t), + PointType::lerp(lev0[1], lev0[2], t) }; + + PointType lev2[1] = { PointType::lerp(lev1[0], lev1[1], t) }; + + BezierCurveType b(lev0, order); + + // Define expected control points for curves 1 and 2 + BezierCurveType expC1(order); + expC1[0] = lev0[0]; + expC1[1] = lev1[0]; + expC1[2] = lev2[0]; + + BezierCurveType expC2(order); + expC2[0] = lev2[0]; + expC2[1] = lev1[1]; + expC2[2] = lev0[2]; + + // Split the curve + BezierCurveType c1,c2; + b.split(t, c1, c2); + + SLIC_INFO("" + <<"Original quadratic: "<< b + << "\nCurves after splitting at t = "<< t + << "\n\t c1: " << c1 + << "\n\t c2: " << c2); + + // Check values + for(int p=0 ; p <= order ; ++p) + { + for ( int i=0 ; i Date: Tue, 23 Jul 2019 12:07:24 -0700 Subject: [PATCH 02/38] Added some helper functions in BezierCurve and CurvedPolygon --- src/axom/primal/geometry/CurvedPolygon.hpp | 29 +- .../primal/tests/primal_curved_polygon.cpp | 264 +++--------------- 2 files changed, 69 insertions(+), 224 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 2da1214fa4..7809c2898e 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// 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) @@ -9,8 +9,8 @@ * \brief A CurvedPolygon primitive for primal based on Bezier Curves */ -#ifndef PRIMAL_CURVEDPOLYGON_HPP_ -#define PRIMAL_CURVEDPOLYGON_HPP_ +#ifndef AXOM_PRIMAL_CURVEDPOLYGON_HPP_ +#define AXOM_PRIMAL_CURVEDPOLYGON_HPP_ #include "axom/slic.hpp" @@ -72,6 +72,19 @@ class CurvedPolygon m_edges.resize(nEdges); } + CurvedPolygon(BezierCurveType* curves, int nEdges) + { + SLIC_ASSERT(curves != nullptr); + SLIC_ASSERT(nEdges >= 1); + + m_edges.reserve(nEdges); + + for(int e = 0; e < nEdges; ++e) + { + this->addEdge(curves[e]); + } + } + /*! Return the number of edges in the polygon */ int numEdges() const { return m_edges.size(); } @@ -97,6 +110,14 @@ class CurvedPolygon /*! Appends a BezierCurve to the list of edges */ void addEdge(const BezierCurveType& c1) { m_edges.push_back(c1); } + /*! Splits an edge "in place" */ + void splitEdge(int idx, T t) + { + m_edges.insert(m_edges.begin() + idx + 1, 1, m_edges[idx]); + BezierCurve csplit = m_edges[idx]; + csplit.split(t, m_edges[idx], m_edges[idx + 1]); + } + /*! Clears the list of edges */ void clear() { m_edges.clear(); } @@ -209,4 +230,4 @@ std::ostream& operator<<(std::ostream& os, const CurvedPolygon& poly) } // namespace primal } // namespace axom -#endif // PRIMAL_CURVEDPOLYGON_HPP_ +#endif // AXOM_PRIMAL_CURVEDPOLYGON_HPP_ diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 763bb1addb..aee50f466b 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2019, Lawrence Livermore National Security, LLC and +// 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) @@ -128,19 +128,19 @@ TEST(primal_beziercurve, area) using PointType = primal::Point; using BezierCurveType = primal::BezierCurve; - SLIC_INFO("Test checking if CurvedPolygon is closed."); + SLIC_INFO("Test checking CurvedPolygon area computation."); CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.0, 1.6)}; + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.6, 1.2)}; - PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.3, 2.0)}; + PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.6, 1.2)}; + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.3, 2.0)}; BezierCurveType bCurve(controlPoints, 1); bPolygon.addEdge(bCurve); @@ -152,246 +152,70 @@ TEST(primal_beziercurve, area) bPolygon.addEdge(bCurve3); CoordType A = bPolygon.area(); - CoordType trueA = .18; + CoordType trueA = -.18; EXPECT_TRUE(axom::utilities::isNearlyEqual(trueA, A)); } -/* //---------------------------------------------------------------------------------- -TEST( primal_beziercurve, coordinate_array_constructor ) -{ - SLIC_INFO("Testing coordinate array constructor"); - - const int DIM = 3; - using CoordType = double; - using BezierCurveType = primal::BezierCurve< CoordType, DIM >; - - // Note: Order of coordinates is by dimension - CoordType coords[6] = {0.6, 0.0, // x-coords for control points - 1.2, 1.6, // y-coords for control points - 1.0, 1.8}; // z-coords for control points - - BezierCurveType bCurve(coords,1); - EXPECT_EQ(1, bCurve.getOrder()); - - - EXPECT_DOUBLE_EQ(coords[0], bCurve[0][0]); - EXPECT_DOUBLE_EQ(coords[2], bCurve[0][1]); - EXPECT_DOUBLE_EQ(coords[4], bCurve[0][2]); - - EXPECT_DOUBLE_EQ(coords[1], bCurve[1][0]); - EXPECT_DOUBLE_EQ(coords[3], bCurve[1][1]); - EXPECT_DOUBLE_EQ(coords[5], bCurve[1][2]); -} - -//------------------------------------------------------------------------------ -TEST( primal_beziercurve, evaluate) -{ - SLIC_INFO("Testing Bezier evaluation"); - - const int DIM = 3; - using CoordType = double; - using PointType = primal::Point< CoordType, DIM >; - using BezierCurveType = primal::BezierCurve< CoordType, DIM >; - - const int order = 3; - PointType data[order+1] = { PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(1.3, 1.6, 1.8), - PointType::make_point(2.9, 2.4, 2.3), - PointType::make_point(3.2, 3.5, 3.0) }; - - BezierCurveType b2Curve(data, order); - - PointType midtval = PointType::make_point(2.05,2.0875,2.0375); - - // Evaluate the curve at several parameter values - // Curve should interpolate endpoints - PointType eval0 = b2Curve.evaluate(0.0); - PointType eval1 = b2Curve.evaluate(1.0); - PointType evalMid = b2Curve.evaluate(0.5); - - for ( int i=0 ; i; - using BezierCurveType = primal::BezierCurve< CoordType, DIM >; - - const int order = 3; - PointType data[order+1] = { PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(1.3, 1.6, 1.8), - PointType::make_point(2.9, 2.4, 2.3), - PointType::make_point(3.2, 3.5, 3.0) }; - BezierCurveType b2Curve(data, order); - - BezierCurveType b3Curve(order); // Checks split with order constructor - BezierCurveType b4Curve; // Checks split with default constructor - b2Curve.split(.5, b3Curve, b4Curve); - - CoordType b3Coords[12] = {0.6, .95, 1.525, 2.05, - 1.2, 1.4, 1.7, 2.0875, - 1.0, 1.4, 1.725, 2.0375}; - CoordType b4Coords[12] = {2.05, 2.575, 3.05, 3.2, - 2.0875, 2.475, 2.95, 3.5, - 2.0375, 2.35, 2.65, 3.0}; - BezierCurveType b3True(b3Coords,3); - BezierCurveType b4True(b4Coords,3); - for ( int i=0 ; i; - using BezierCurveType = primal::BezierCurve< CoordType, DIM >; - - // Test order-0 - { - const int order = 0; - PointType data[order+1] = { PointType::make_point(0.6, 1.2)}; - BezierCurveType b(data, order); + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; - BezierCurveType c1,c2; - b.split(0.5, c1, c2); + SLIC_INFO("Test checking CurvedPolygon edge split."); - for ( int i=0 ; i; - using BezierCurveType = primal::BezierCurve< CoordType, DIM >; + PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.3, 2.0)}; - const int order = 1; - PointType data[order+1] = { PointType::make_point(-1, -5), - PointType::make_point( 1, 5) }; - BezierCurveType b(data, order); + PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.6, 1.2)}; - { - BezierCurveType c1,c2; - b.split(0.5, c1, c2); + BezierCurveType bCurve(controlPoints, 1); + bPolygon.addEdge(bCurve); - EXPECT_DOUBLE_EQ(-1., c1[0][0]); - EXPECT_DOUBLE_EQ(-5., c1[0][1]); - EXPECT_DOUBLE_EQ( 0., c1[1][0]); - EXPECT_DOUBLE_EQ( 0., c1[1][1]); + BezierCurveType bCurve2(controlPoints2, 1); + bPolygon.addEdge(bCurve2); - EXPECT_DOUBLE_EQ( 0., c2[0][0]); - EXPECT_DOUBLE_EQ( 0., c2[0][1]); - EXPECT_DOUBLE_EQ( 1., c2[1][0]); - EXPECT_DOUBLE_EQ( 5., c2[1][1]); + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); - } + /* bPolygon.splitEdge(0,.5); + bCurve.split(.5,bCurve2,bCurve3); +*/ + CurvedPolygonType bPolygon2 = bPolygon; + std::vector bPolygon3; + EXPECT_EQ(bPolygon.numEdges(), 3); + for(int i = 0; i < bPolygon[0].getOrder(); ++i) { - BezierCurveType c1,c2; - const double t = 0.25; - b.split(0.25, c1, c2); - - PointType interp = PointType::lerp(data[0], data[1], t); - - for ( int i=0 ; i; - using BezierCurveType = primal::BezierCurve< CoordType, DIM >; - - const double t = .42; - const int order = 2; - - // Control points for the three levels of the quadratic de Casteljau algorithm - PointType lev0[3] = { PointType::make_point(1.1, 1.1), - PointType::make_point( 5.5, 5.5), - PointType::make_point( 9.9, 2.2) }; - - PointType lev1[2] = { PointType::lerp(lev0[0], lev0[1], t), - PointType::lerp(lev0[1], lev0[2], t) }; - - PointType lev2[1] = { PointType::lerp(lev1[0], lev1[1], t) }; - - BezierCurveType b(lev0, order); - - // Define expected control points for curves 1 and 2 - BezierCurveType expC1(order); - expC1[0] = lev0[0]; - expC1[1] = lev1[0]; - expC1[2] = lev2[0]; - - BezierCurveType expC2(order); - expC2[0] = lev2[0]; - expC2[1] = lev1[1]; - expC2[2] = lev0[2]; - - // Split the curve - BezierCurveType c1,c2; - b.split(t, c1, c2); - - SLIC_INFO("" - <<"Original quadratic: "<< b - << "\nCurves after splitting at t = "<< t - << "\n\t c1: " << c1 - << "\n\t c2: " << c2); - - // Check values - for(int p=0 ; p <= order ; ++p) + for(int j = 0; j < bPolygon.numEdges(); ++j) { - for ( int i=0 ; i Date: Tue, 23 Jul 2019 14:42:48 -0700 Subject: [PATCH 03/38] Added more CurvedPolygon tests --- src/axom/primal/geometry/CurvedPolygon.hpp | 6 +- .../primal/tests/primal_curved_polygon.cpp | 120 ++++++++++++++---- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 7809c2898e..324c4350c8 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -172,14 +172,16 @@ class CurvedPolygon for(int i = 0; i < (ngon - 1); ++i) { if(!axom::utilities::isNearlyEqual(m_edges[i][m_edges[i].getOrder()][p], - m_edges[i + 1][0][p])) + m_edges[i + 1][0][p], + 1e-15)) { return false; } } if(!axom::utilities::isNearlyEqual( m_edges[ngon - 1][m_edges[ngon - 1].getOrder()][p], - m_edges[0][0][p])) + m_edges[0][0][p], + 1e-15)) { return false; } diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index aee50f466b..d4882f5968 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -120,7 +120,7 @@ TEST(primal_curvedpolygon, is_Valid) } //---------------------------------------------------------------------------------- -TEST(primal_beziercurve, area) +TEST(primal_beziercurve, area_triangle_linear) { const int DIM = 2; using CoordType = double; @@ -128,19 +128,19 @@ TEST(primal_beziercurve, area) using PointType = primal::Point; using BezierCurveType = primal::BezierCurve; - SLIC_INFO("Test checking CurvedPolygon area computation."); + SLIC_INFO("Test checking CurvedPolygon linear area triangle computation."); CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.0, 1.6)}; - PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; + PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.3, 2.0)}; - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; + PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.6, 1.2)}; BezierCurveType bCurve(controlPoints, 1); bPolygon.addEdge(bCurve); @@ -152,9 +152,91 @@ TEST(primal_beziercurve, area) bPolygon.addEdge(bCurve3); CoordType A = bPolygon.area(); - CoordType trueA = -.18; + CoordType trueA = .18; - EXPECT_TRUE(axom::utilities::isNearlyEqual(trueA, A)); + EXPECT_DOUBLE_EQ(trueA, A); +} + +//---------------------------------------------------------------------------------- +TEST(primal_beziercurve, area_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 checking CurvedPolygon linear area triangle computation."); + + 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); + + CoordType A = bPolygon.area(); + + CoordType trueA = -.09733333333333333333; + EXPECT_DOUBLE_EQ(trueA, A); +} + +//---------------------------------------------------------------------------------- +TEST(primal_beziercurve, area_triangle_mixed_order) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO("Test checking CurvedPolygon linear area triangle computation."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[3] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.4, 1.3), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[3] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.27, 1.5), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, 2); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, 2); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); + + CoordType A = bPolygon.area(); + + CoordType trueA = -.0906666666666666666666; + EXPECT_DOUBLE_EQ(trueA, A); } //---------------------------------------------------------------------------------- @@ -189,13 +271,13 @@ TEST(primal_beziercurve, split_edge) BezierCurveType bCurve3(controlPoints3, 1); bPolygon.addEdge(bCurve3); - /* bPolygon.splitEdge(0,.5); - bCurve.split(.5,bCurve2,bCurve3); -*/ + bPolygon.splitEdge(0, .5); + bCurve.split(.5, bCurve2, bCurve3); + CurvedPolygonType bPolygon2 = bPolygon; std::vector bPolygon3; - EXPECT_EQ(bPolygon.numEdges(), 3); + EXPECT_EQ(bPolygon.numEdges(), 4); for(int i = 0; i < bPolygon[0].getOrder(); ++i) { for(int dimi = 0; dimi < DIM; ++dimi) @@ -204,16 +286,6 @@ TEST(primal_beziercurve, split_edge) EXPECT_EQ(bPolygon[1][i][dimi], bCurve3[i][dimi]); } } - for(int j = 0; j < bPolygon.numEdges(); ++j) - { - for(int i = 0; i <= bPolygon[j].getOrder(); ++i) - { - for(int dimi = 0; dimi < DIM; ++dimi) - { - bPolygon2[j][i][dimi] += .11; - } - } - } } int main(int argc, char* argv[]) From 4bf7636176a00d24ed377d401eaafa7c9ace873d Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 23 Jul 2019 17:52:09 -0700 Subject: [PATCH 04/38] More curved polygon tests --- .../primal/tests/primal_curved_polygon.cpp | 156 +++++++++++------- 1 file changed, 100 insertions(+), 56 deletions(-) diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index d4882f5968..20771c85c2 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -34,7 +34,7 @@ TEST(primal_curvedpolygon, constructor) } { - SLIC_INFO("Testing CurvedPolygon order constructor "); + SLIC_INFO("Testing CurvedPolygon numEdges constructor "); CurvedPolygonType bPolygon(1); int expNumEdges = 1; @@ -81,7 +81,7 @@ TEST(primal_curvedpolygon, add_edges) } //---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, is_Valid) +TEST(primal_curvedpolygon, isClosed) { const int DIM = 2; using CoordType = double; @@ -93,6 +93,7 @@ TEST(primal_curvedpolygon, is_Valid) CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); + EXPECT_EQ(false, bPolygon.isClosed()); PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), PointType::make_point(0.0, 1.6)}; @@ -105,6 +106,7 @@ TEST(primal_curvedpolygon, is_Valid) BezierCurveType bCurve(controlPoints, 1); bPolygon.addEdge(bCurve); + EXPECT_EQ(false, bPolygon.isClosed()); BezierCurveType bCurve2(controlPoints2, 1); bPolygon.addEdge(bCurve2); @@ -117,10 +119,99 @@ TEST(primal_curvedpolygon, is_Valid) EXPECT_EQ(3, bPolygon.numEdges()); EXPECT_EQ(true, bPolygon.isClosed()); + + bPolygon[2][1][0] -= 2e-15; + EXPECT_EQ(false, bPolygon.isClosed()); +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, split_edge) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO("Test checking CurvedPolygon edge split."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, 1); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, 1); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); + + bPolygon.splitEdge(0, .5); + bCurve.split(.5, bCurve2, bCurve3); + + EXPECT_EQ(bPolygon.numEdges(), 4); + for(int i = 0; i < bPolygon[0].getOrder(); ++i) + { + for(int dimi = 0; dimi < DIM; ++dimi) + { + EXPECT_EQ(bPolygon[0][i][dimi], bCurve2[i][dimi]); + EXPECT_EQ(bPolygon[1][i][dimi], bCurve3[i][dimi]); + } + } +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, area_triangle_degenerate) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test checking CurvedPolygon degenerate triangle area computation."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + EXPECT_EQ(0.0, bPolygon.area()); + + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, 1); + bPolygon.addEdge(bCurve); + EXPECT_EQ(0.0, bPolygon.area()); + + BezierCurveType bCurve2(controlPoints2, 1); + bPolygon.addEdge(bCurve2); + EXPECT_EQ(0.0, bPolygon.area()); + + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); + + bPolygon[2][1][0] -= 2e-15; + EXPECT_EQ(0.0, bPolygon.area()); } //---------------------------------------------------------------------------------- -TEST(primal_beziercurve, area_triangle_linear) +TEST(primal_curvedpolygon, area_triangle_linear) { const int DIM = 2; using CoordType = double; @@ -128,7 +219,7 @@ TEST(primal_beziercurve, area_triangle_linear) using PointType = primal::Point; using BezierCurveType = primal::BezierCurve; - SLIC_INFO("Test checking CurvedPolygon linear area triangle computation."); + SLIC_INFO("Test checking CurvedPolygon linear triangle area computation."); CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); @@ -158,7 +249,7 @@ TEST(primal_beziercurve, area_triangle_linear) } //---------------------------------------------------------------------------------- -TEST(primal_beziercurve, area_triangle_quadratic) +TEST(primal_curvedpolygon, area_triangle_quadratic) { const int DIM = 2; const int order = 2; @@ -167,7 +258,7 @@ TEST(primal_beziercurve, area_triangle_quadratic) using PointType = primal::Point; using BezierCurveType = primal::BezierCurve; - SLIC_INFO("Test checking CurvedPolygon linear area triangle computation."); + SLIC_INFO("Test checking CurvedPolygon quadratic triangle area computation."); CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); @@ -200,7 +291,7 @@ TEST(primal_beziercurve, area_triangle_quadratic) } //---------------------------------------------------------------------------------- -TEST(primal_beziercurve, area_triangle_mixed_order) +TEST(primal_curvedpolygon, area_triangle_mixed_order) { const int DIM = 2; using CoordType = double; @@ -208,7 +299,8 @@ TEST(primal_beziercurve, area_triangle_mixed_order) using PointType = primal::Point; using BezierCurveType = primal::BezierCurve; - SLIC_INFO("Test checking CurvedPolygon linear area triangle computation."); + SLIC_INFO( + "Test checking CurvedPolygon mixed order triangle area computation."); CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); @@ -240,54 +332,6 @@ TEST(primal_beziercurve, area_triangle_mixed_order) } //---------------------------------------------------------------------------------- -TEST(primal_beziercurve, split_edge) -{ - const int DIM = 2; - using CoordType = double; - using CurvedPolygonType = primal::CurvedPolygon; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - SLIC_INFO("Test checking CurvedPolygon edge split."); - - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, 1); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, 1); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, 1); - bPolygon.addEdge(bCurve3); - - bPolygon.splitEdge(0, .5); - bCurve.split(.5, bCurve2, bCurve3); - - CurvedPolygonType bPolygon2 = bPolygon; - std::vector bPolygon3; - - EXPECT_EQ(bPolygon.numEdges(), 4); - for(int i = 0; i < bPolygon[0].getOrder(); ++i) - { - for(int dimi = 0; dimi < DIM; ++dimi) - { - EXPECT_EQ(bPolygon[0][i][dimi], bCurve2[i][dimi]); - EXPECT_EQ(bPolygon[1][i][dimi], bCurve3[i][dimi]); - } - } -} - int main(int argc, char* argv[]) { int result = 0; From 23fcc1c21597f6f07d6c2252ba66afe8a36e73f1 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Thu, 25 Jul 2019 13:14:06 -0700 Subject: [PATCH 05/38] Added tolerances to CurvedPolygon functions, unit tests for area, dt function (tangent vector of BezierCurve), and stable orientation function for directional walking method --- src/axom/primal/geometry/BezierCurve.hpp | 40 +++++++++++++- src/axom/primal/geometry/CurvedPolygon.hpp | 13 ++--- src/axom/primal/tests/primal_bezier_curve.cpp | 54 ++++++++++++++++--- .../primal/tests/primal_curved_polygon.cpp | 36 ++++++------- 4 files changed, 112 insertions(+), 31 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 88d3a7c08e..ffcb3e5fb0 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -12,7 +12,7 @@ #ifndef AXOM_PRIMAL_BEZIERCURVE_HPP_ #define AXOM_PRIMAL_BEZIERCURVE_HPP_ -#include "axom/core/utilities/Utilities.hpp" +#include "axom/core.hpp" #include "axom/slic.hpp" #include "axom/primal/geometry/NumericArray.hpp" @@ -242,6 +242,44 @@ class BezierCurve return ptval; } + /*! + * \brief Computes the tangent of a Bezier curve at a particular parameter value \a t + * + * \param [in] t parameter value at which to compute tangent + * \return p the tangent vector of the Bezier curve at t + * + * \note We typically find the tangent of the curve at \a t between 0 and 1 + */ + + PointType dt(T t) const + { + PointType ptval; + + const int ord = getOrder(); + std::vector dCarray(ord + 1); + + // Run de Casteljau algorithm on each dimension + for(int i = 0; i < NDIMS; ++i) + { + for(int p = 0; p <= ord; ++p) + { + dCarray[p] = m_controlPoints[p][i]; + } + + for(int p = 1; p <= ord - 1; ++p) + { + const int end = ord - p; + for(int k = 0; k <= end; ++k) + { + dCarray[k] = (1 - t) * dCarray[k] + t * dCarray[k + 1]; + } + } + ptval[i] = ord * (dCarray[1] - dCarray[0]); + } + + return ptval; + } + /*! * \brief Splits a Bezier curve into two Bezier curves at particular parameter * value between 0 and 1 diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 324c4350c8..0e544c0a0f 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -158,10 +158,10 @@ class CurvedPolygon * Check is that the endpoint of each edge coincides with startpoint of next edge * \return True, if the polygon is closed, False otherwise */ - bool isClosed() const + bool isClosed(double tol = 1e-15) const { const int ngon = numEdges(); - if(ngon <= 1) + if(ngon <= 2) { return false; } @@ -173,7 +173,7 @@ class CurvedPolygon { if(!axom::utilities::isNearlyEqual(m_edges[i][m_edges[i].getOrder()][p], m_edges[i + 1][0][p], - 1e-15)) + tol)) { return false; } @@ -181,7 +181,7 @@ class CurvedPolygon if(!axom::utilities::isNearlyEqual( m_edges[ngon - 1][m_edges[ngon - 1].getOrder()][p], m_edges[0][0][p], - 1e-15)) + tol)) { return false; } @@ -197,13 +197,14 @@ class CurvedPolygon * \return True, if the polygon is closed, False otherwise */ - T area() const + T area(double tol = 1e-15) const { const int ngon = numEdges(); T A = 0.0; - if(!isClosed()) + if(!isClosed(tol)) { return A; + SLIC_INFO("Warning! The area is 0 because the element is not closed."); } else { diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 391ed0b515..fd1ba575f8 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -3,12 +3,15 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -/* /file primal_bezier_curve.cpp +/* + * /file primal_bezier_curve.cpp * /brief This file tests primal's Bezier curve functionality */ #include "gtest/gtest.h" +#include "axom/slic.hpp" + #include "axom/primal/geometry/BezierCurve.hpp" #include "axom/primal/operators/squared_distance.hpp" @@ -164,6 +167,42 @@ TEST(primal_beziercurve, evaluate) } } +//------------------------------------------------------------------------------ +TEST(primal_beziercurve_, tangent) +{ + SLIC_INFO("Testing Bezier tangent calculation"); + + const int DIM = 3; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + const int order = 3; + PointType data[order + 1] = {PointType::make_point(0.6, 1.2, 1.0), + PointType::make_point(1.3, 1.6, 1.8), + PointType::make_point(2.9, 2.4, 2.3), + PointType::make_point(3.2, 3.5, 3.0)}; + + BezierCurveType b2Curve(data, order); + + PointType midtval = PointType::make_point(3.15, 2.325, 1.875); + PointType starttval = PointType::make_point(2.1, 1.2, 2.4); + PointType endtval = PointType::make_point(.9, 3.3, 2.1); + + // Evaluate the curve at several parameter values + // Curve should be tangent to control net at endpoints + PointType eval0 = b2Curve.dt(0.0); + PointType eval1 = b2Curve.dt(1.0); + PointType evalMid = b2Curve.dt(0.5); + + for(int i = 0; i < DIM; ++i) + { + EXPECT_NEAR(starttval[i], eval0[i], 1e-15); + EXPECT_NEAR(endtval[i], eval1[i], 1e-15); + EXPECT_NEAR(midtval[i], evalMid[i], 1e-15); + } +} + //------------------------------------------------------------------------------ TEST(primal_beziercurve, sector_area_cubic) { @@ -194,7 +233,7 @@ TEST(primal_beziercurve, sector_area_point) using BezierCurveType = primal::BezierCurve; { - SLIC_INFO("Testing Bezier sector area calculation for a cubic"); + SLIC_INFO("Testing Bezier sector area calculation for a point"); const int order = 0; PointType data[order + 1] = {PointType::make_point(0.6, 1.2)}; @@ -358,9 +397,11 @@ TEST(primal_beziercurve, split_quadratic) BezierCurveType c1, c2; b.split(t, c1, c2); - SLIC_INFO("" - << "Original quadratic: " << b << "\nCurves after splitting at t = " - << t << "\n\t c1: " << c1 << "\n\t c2: " << c2); + SLIC_INFO("" // + << "Original quadratic: " << b // + << "\nCurves after splitting at t = " << t // + << "\n\t c1: " << c1 // + << "\n\t c2: " << c2); // Check values for(int p = 0; p <= order; ++p) @@ -449,7 +490,8 @@ int main(int argc, char* argv[]) int result = 0; ::testing::InitGoogleTest(&argc, argv); - axom::slic::SimpleLogger logger; // create & initialize test logger, + + axom::slic::SimpleLogger logger; result = RUN_ALL_TESTS(); diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 20771c85c2..9955c5f229 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -96,12 +96,12 @@ TEST(primal_curvedpolygon, isClosed) EXPECT_EQ(false, bPolygon.isClosed()); PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.0, 1.6)}; + PointType::make_point(0.3, 2.0)}; - PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.3, 2.0)}; + PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), PointType::make_point(0.6, 1.2)}; BezierCurveType bCurve(controlPoints, 1); @@ -121,7 +121,7 @@ TEST(primal_curvedpolygon, isClosed) EXPECT_EQ(true, bPolygon.isClosed()); bPolygon[2][1][0] -= 2e-15; - EXPECT_EQ(false, bPolygon.isClosed()); + EXPECT_EQ(false, bPolygon.isClosed(1e-15)); } //---------------------------------------------------------------------------------- @@ -139,12 +139,12 @@ TEST(primal_curvedpolygon, split_edge) EXPECT_EQ(0, bPolygon.numEdges()); PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.0, 1.6)}; + PointType::make_point(0.3, 2.0)}; - PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.3, 2.0)}; + PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), PointType::make_point(0.6, 1.2)}; BezierCurveType bCurve(controlPoints, 1); @@ -187,12 +187,12 @@ TEST(primal_curvedpolygon, area_triangle_degenerate) EXPECT_EQ(0.0, bPolygon.area()); PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.0, 1.6)}; + PointType::make_point(0.3, 2.0)}; - PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.3, 2.0)}; + PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), PointType::make_point(0.6, 1.2)}; BezierCurveType bCurve(controlPoints, 1); @@ -225,12 +225,12 @@ TEST(primal_curvedpolygon, area_triangle_linear) EXPECT_EQ(0, bPolygon.numEdges()); PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.0, 1.6)}; + PointType::make_point(0.3, 2.0)}; - PointType controlPoints2[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.3, 2.0)}; + PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.3, 2.0), + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), PointType::make_point(0.6, 1.2)}; BezierCurveType bCurve(controlPoints, 1); @@ -243,7 +243,7 @@ TEST(primal_curvedpolygon, area_triangle_linear) bPolygon.addEdge(bCurve3); CoordType A = bPolygon.area(); - CoordType trueA = .18; + CoordType trueA = -.18; EXPECT_DOUBLE_EQ(trueA, A); } From 151835c7e4cc2c7890e5bd6796e45f2d0f602a00 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jul 2019 18:27:03 -0700 Subject: [PATCH 06/38] Improves documentation, error checking and testing of binomialCoefficient function --- src/axom/core/tests/utils_utilities.hpp | 84 ++++++++++++++++++++++++ src/axom/core/utilities/Utilities.cpp | 42 +++++++----- src/axom/core/utilities/Utilities.hpp | 10 ++- src/axom/primal/geometry/BezierCurve.hpp | 6 +- 4 files changed, 121 insertions(+), 21 deletions(-) diff --git a/src/axom/core/tests/utils_utilities.hpp b/src/axom/core/tests/utils_utilities.hpp index 7bf48e025e..2fec4d8efc 100644 --- a/src/axom/core/tests/utils_utilities.hpp +++ b/src/axom/core/tests/utils_utilities.hpp @@ -158,3 +158,87 @@ TEST(utils_utilities, floor_ceil) EXPECT_EQ(-5.0, axom::utilities::ceil(val)); } } + +//------------------------------------------------------------------------------ +TEST(utils_utilities, binomial_coefficient) +{ + std::cout << "Testing binomial coefficient function." << std::endl; + + // test n less than zero + { + const int n = -1; + const int exp = 0; + for(int k = -1; k < 10; ++k) + { + auto binom_k_n = axom::utilities::binomialCoefficient(n, k); + EXPECT_EQ(exp, binom_k_n); + } + } + + // test n := 0 + { + const int n = 0; + + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 0)); + + EXPECT_EQ(0, axom::utilities::binomialCoefficient(n, -1)); + EXPECT_EQ(0, axom::utilities::binomialCoefficient(n, 1)); + } + + // test n := 1 + { + const int n = 1; + + EXPECT_EQ(0, axom::utilities::binomialCoefficient(n, -1)); + + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 0)); + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 1)); + + EXPECT_EQ(0, axom::utilities::binomialCoefficient(n, 2)); + } + + // test n := 2 + { + const int n = 2; + + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 0)); + EXPECT_EQ(2, axom::utilities::binomialCoefficient(n, 1)); + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 2)); + } + + // test n := 3 + { + const int n = 3; + + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 0)); + EXPECT_EQ(3, axom::utilities::binomialCoefficient(n, 1)); + EXPECT_EQ(3, axom::utilities::binomialCoefficient(n, 2)); + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 3)); + } + + // test n := 4 + { + const int n = 4; + + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 0)); + EXPECT_EQ(4, axom::utilities::binomialCoefficient(n, 1)); + EXPECT_EQ(6, axom::utilities::binomialCoefficient(n, 2)); + EXPECT_EQ(4, axom::utilities::binomialCoefficient(n, 3)); + EXPECT_EQ(1, axom::utilities::binomialCoefficient(n, 4)); + } + + // test recurrence relation nCk = (n-1)C(k-1) + (n-1)C(k) + { + for(int n = 1; n < 10; ++n) + { + for(int k = 1; k <= n; ++k) + { + auto binom_n_k = axom::utilities::binomialCoefficient(n, k); + auto binom_n1_k1 = axom::utilities::binomialCoefficient(n - 1, k - 1); + auto binom_n1_k = axom::utilities::binomialCoefficient(n - 1, k); + + EXPECT_EQ(binom_n_k, binom_n1_k1 + binom_n1_k); + } + } + } +} diff --git a/src/axom/core/utilities/Utilities.cpp b/src/axom/core/utilities/Utilities.cpp index 85d0bc9050..88234d4072 100644 --- a/src/axom/core/utilities/Utilities.cpp +++ b/src/axom/core/utilities/Utilities.cpp @@ -4,8 +4,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) /*! - * - * \file + * \file Utilities.cpp * * \brief Implementation file for utility functions. * @@ -24,21 +23,6 @@ namespace axom { namespace utilities { -int binomial_coefficient(int n, int k) -{ - if(k > n - k) - { - k = n - k; - } - int val = 1; - for(int i = 1; i <= k; ++i) - { - val *= (n - k + i); - val /= i; - } - return val; -} - void processAbort() { #ifndef AXOM_USE_MPI @@ -54,5 +38,29 @@ void processAbort() #endif } +int binomialCoefficient(int n, int k) +{ + if(k > n || k < 0) // check if out-of-bounds + { + return 0; + } + if(k == n || k == 0) // early return + { + return 1; + } + if(k > n - k) // exploit symmetry to reduce work + { + k = n - k; + } + + int val = 1; + for(int i = 1; i <= k; ++i) + { + val *= (n - k + i); + val /= i; + } + return val; +} + } // end namespace utilities } // end namespace axom diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index 542be49515..f96ed3618f 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -32,7 +32,7 @@ namespace utilities * \brief Gracefully aborts the application */ void processAbort(); -int binomial_coefficient(int n, int k); + /*! * \brief Returns the absolute value of x. * \accelerated @@ -163,6 +163,14 @@ inline AXOM_HOST_DEVICE T clampLower(T val, T lower) return val < lower ? lower : val; } +/*! + * \brief Computes the binomial coefficient `n choose k` + * + * \return \f$ {n\choose k} = n! / (k! * (n-k)!)\f$ + * when \f$ n \ge k \ge 0 \f$, 0 otherwise. + */ +int binomialCoefficient(int n, int k); + /*! * \brief Returns a random real number within the specified interval * diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index ffcb3e5fb0..4c663f14b8 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -333,7 +333,7 @@ class BezierCurve if(UedaAreaMats.find(ord) == UedaAreaMats.end()) { std::vector newUedaAreaMat((ord + 1) * (ord + 1)); - int twonchoosen = axom::utilities::binomial_coefficient(2 * ord, ord); + int twonchoosen = axom::utilities::binomialCoefficient(2 * ord, ord); for(int i = 0; i <= ord; ++i) { for(int j = 0; j <= ord; ++j) @@ -346,10 +346,10 @@ class BezierCurve { newUedaAreaMat[i * (ord + 1) + j] = ((1.0 * j - i) / 2) * (2.0 * (ord) / (1.0 * twonchoosen)) * - (1.0 * axom::utilities::binomial_coefficient(i + j, i) / + (1.0 * axom::utilities::binomialCoefficient(i + j, i) / (1.0 * i + j)) * (1.0 * - axom::utilities::binomial_coefficient(2 * (ord)-i - j, (ord)-j) / + axom::utilities::binomialCoefficient(2 * (ord)-i - j, (ord)-j) / (1.0 * (ord)-j + (ord)-i)); } } From d4c3337457ebc596b03ea8a3809945a90c6b69e7 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jul 2019 18:36:08 -0700 Subject: [PATCH 07/38] Makes Matrix's default constructor public Matrix can now be used as a value type for containers like std::map. --- src/axom/core/numerics/Matrix.hpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/axom/core/numerics/Matrix.hpp b/src/axom/core/numerics/Matrix.hpp index c2a071a7ad..901148c8d0 100644 --- a/src/axom/core/numerics/Matrix.hpp +++ b/src/axom/core/numerics/Matrix.hpp @@ -117,6 +117,11 @@ template class Matrix { public: + /*! + * \brief Default constructor + */ + Matrix() : m_rows(0), m_cols(0), m_data(nullptr), m_usingExternal(false) { } + /*! * \brief Constructor, creates a Matrix with the given rows and columns. * @@ -527,12 +532,6 @@ class Matrix /// @} private: - /*! - * \brief Default constructor. Does nothing. - * \note Made private to prevent host-code from calling this. - */ - Matrix() : m_rows(0), m_cols(0), m_data(nullptr) {}; - /// \name Private Helper Methods /// @{ From beab6f83e783abdd6ca1c111efaa67e1de52f2a1 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 25 Jul 2019 21:42:28 -0700 Subject: [PATCH 08/38] Refactors and tests the BezierCurve sectorArea() weights calculation --- src/axom/primal/geometry/BezierCurve.hpp | 109 +++++++++------ src/axom/primal/tests/primal_bezier_curve.cpp | 126 +++++++++++++++++- .../primal/tests/primal_curved_polygon.cpp | 1 - 3 files changed, 196 insertions(+), 40 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 4c663f14b8..1c54d7002d 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -25,6 +25,7 @@ #include "axom/primal/operators/squared_distance.hpp" #include +#include #include namespace axom @@ -39,8 +40,19 @@ class BezierCurve; template std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve); -// UedaAreaMats is where all of the area matrices from Ueda '99 are stored -extern std::map> UedaAreaMats; +/*! + * \brief Computes the weights for BezierCurve's sectorArea() function + * + * \param order The polynomial order of the curve + * \return An anti-symmetric matrix with (order+1)*{order+1) entries + * containing the integration weights for entry (i,j) + * + * The derivation is provided in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ +template +numerics::Matrix generateBezierCurveSectorWeights(int order); /*! * \class BezierCurve @@ -66,6 +78,13 @@ class BezierCurve using BoundingBoxType = BoundingBox; using OrientedBoundingBoxType = OrientedBoundingBox; +private: + // Caches of precomputed coefficients for sector area calculations + // on a given polynomial order + using SectorWeights = numerics::Matrix; + using WeightsMap = std::map; + static WeightsMap s_sectorWeightsMap; + public: /*! * \brief Constructor for a Bezier Curve that reserves space for @@ -320,50 +339,32 @@ class BezierCurve } /*! - * \brief Calculates the sector area (area between curve and origin) of a Bezier Curve + * \brief Calculates the sector area of a Bezier Curve * - * \param [in] tol a tolerance parameter controlling definition of - * near-linearity - * \param [out] boolean TRUE if c1 is near-linear + * The sector area is the area between the curve and the origin. + * The equation and derivation is described in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. */ T sectorArea() const { T A = 0; - int ord = getOrder(); - if(UedaAreaMats.find(ord) == UedaAreaMats.end()) + const int ord = getOrder(); + + // Compute and cache the weights if they are not already available + if(s_sectorWeightsMap.find(ord) == s_sectorWeightsMap.end()) { - std::vector newUedaAreaMat((ord + 1) * (ord + 1)); - int twonchoosen = axom::utilities::binomialCoefficient(2 * ord, ord); - for(int i = 0; i <= ord; ++i) - { - for(int j = 0; j <= ord; ++j) - { - if((i == 0 && j == 0) || (i == (ord) && j == (ord))) - { - newUedaAreaMat[i * (ord + 1) + j] = 0.0; - } - else - { - newUedaAreaMat[i * (ord + 1) + j] = ((1.0 * j - i) / 2) * - (2.0 * (ord) / (1.0 * twonchoosen)) * - (1.0 * axom::utilities::binomialCoefficient(i + j, i) / - (1.0 * i + j)) * - (1.0 * - axom::utilities::binomialCoefficient(2 * (ord)-i - j, (ord)-j) / - (1.0 * (ord)-j + (ord)-i)); - } - } - } - UedaAreaMats.insert( - std::pair>(ord, newUedaAreaMat)); + auto wts = generateBezierCurveSectorWeights(ord); + s_sectorWeightsMap.emplace(std::make_pair(ord, wts)); } - const std::vector& whicharea = (UedaAreaMats.find(ord)->second); - for(int i = 0; i <= ord; ++i) + + const auto& weights = s_sectorWeightsMap[ord]; + + for(int p = 0; p <= ord; ++p) { - for(int j = 0; j <= ord; ++j) + for(int q = 0; q <= ord; ++q) { - A += static_cast(whicharea[i * (ord + 1) + j]) * - m_controlPoints[i][1] * m_controlPoints[j][0]; + A += weights(p, q) * m_controlPoints[p][1] * m_controlPoints[q][0]; } } return A; @@ -421,8 +422,12 @@ class BezierCurve CoordsVec m_controlPoints; }; +// Declaration of sectorArea weights map +template +typename BezierCurve::WeightsMap BezierCurve::s_sectorWeightsMap; + //------------------------------------------------------------------------------ -/// Free functions implementing BezierCurve's operators +/// Free functions related to BezierCurve //------------------------------------------------------------------------------ template std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve) @@ -431,6 +436,34 @@ std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve) return os; } +template +numerics::Matrix generateBezierCurveSectorWeights(int ord) +{ + numerics::Matrix weights(ord + 1, ord + 1); + T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); + for(int i = 0; i <= ord; ++i) + { + weights(i, i) = 0.; // zero on the diagonal + for(int j = i + 1; j <= ord; ++j) + { + double val = 0.; + if(i != j) + { + T binom_ij_i = static_cast(utilities::binomialCoefficient(i + j, i)); + T binom_2nij_nj = static_cast( + utilities::binomialCoefficient(2 * ord - i - j, ord - j)); + + val = ((j - i) * ord) / binom_2n_n * + (binom_ij_i / static_cast(i + j)) * + (binom_2nij_nj / (2. * ord - j - i)); + } + weights(i, j) = val; // antisymmetric + weights(j, i) = -val; + } + } + return weights; +} + } // namespace primal } // namespace axom diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index fd1ba575f8..54ec8af204 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -17,7 +17,6 @@ namespace primal = axom::primal; -std::map> primal::UedaAreaMats; //TODO: put this in the BezierCurve.cpp file //------------------------------------------------------------------------------ TEST(primal_beziercurve, constructor) { @@ -483,6 +482,131 @@ TEST(primal_beziercurve, isLinear) } } +TEST(primal_beziercurve, sector_weights) +{ + SLIC_INFO("Testing weights for BezierCurve::sectorArea()"); + + // NOTE: Expected weights are provided in the reference paper [Ueda99] + // See doxygen comment for BezierCurve::sectorArea() + + using CoordType = double; + + // order 1 + { + const int ord = 1; + auto weights = primal::generateBezierCurveSectorWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(2, 1); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 1; + exp(1,0) = -1; exp(1,1) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 2 + { + const int ord = 2; + auto weights = primal::generateBezierCurveSectorWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(4, 2); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 2; exp(0,2) = 1; + exp(1,0) = -2; exp(1,1) = 0; exp(1,2) = 2; + exp(2,0) = -1; exp(2,1) = -2; exp(2,2) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 3 + { + const int ord = 3; + auto weights = primal::generateBezierCurveSectorWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(6, 3); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 6; exp(0,2) = 3; exp(0,3) = 1; + exp(1,0) = -6; exp(1,1) = 0; exp(1,2) = 3; exp(1,3) = 3; + exp(2,0) = -3; exp(2,1) = -3; exp(2,2) = 0; exp(2,3) = 6; + exp(3,0) = -1; exp(3,1) = -3; exp(3,2) = -6; exp(3,3) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 4 + { + const int ord = 4; + auto weights = primal::generateBezierCurveSectorWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(8, 4); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 20; exp(0,2) = 10; exp(0,3) = 4; exp(0,4) = 1; + exp(1,0) =-20; exp(1,1) = 0; exp(1,2) = 8; exp(1,3) = 8; exp(1,4) = 4; + exp(2,0) =-10; exp(2,1) = -8; exp(2,2) = 0; exp(2,3) = 8; exp(2,4) = 10; + exp(3,0) = -4; exp(3,1) = -8; exp(3,2) = -8; exp(3,3) = 0; exp(3,4) = 20; + exp(4,0) = -1; exp(4,1) = -4; exp(4,2) =-10; exp(4,3) =-20; exp(4,4) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 5 + { + const int ord = 5; + auto weights = primal::generateBezierCurveSectorWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(10, 5); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 70; exp(0,2) = 35; exp(0,3) = 15; exp(0,4) = 5; exp(0,5) = 1; + exp(1,0) =-70; exp(1,1) = 0; exp(1,2) = 25; exp(1,3) = 25; exp(1,4) = 15; exp(1,5) = 5; + exp(2,0) =-35; exp(2,1) =-25; exp(2,2) = 0; exp(2,3) = 20; exp(2,4) = 25; exp(2,5) = 15; + exp(3,0) =-15; exp(3,1) =-25; exp(3,2) =-20; exp(3,3) = 0; exp(3,4) = 25; exp(3,5) = 35; + exp(4,0) = -5; exp(4,1) =-15; exp(4,2) =-25; exp(4,3) =-25; exp(4,4) = 0; exp(4,5) = 70; + exp(5,0) = -1; exp(5,1) = -5; exp(5,2) =-15; exp(5,3) =-35; exp(5,4) =-70; exp(5,5) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } +} + //------------------------------------------------------------------------------ int main(int argc, char* argv[]) diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 9955c5f229..f5a490a821 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -14,7 +14,6 @@ namespace primal = axom::primal; -std::map> primal::UedaAreaMats; //TODO: put this in the BezierCurve.cpp file //------------------------------------------------------------------------------ TEST(primal_curvedpolygon, constructor) { From 13b1544258f9291ee62b7f0d101f1e398d5b05f0 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Thu, 10 Jun 2021 23:05:37 -0700 Subject: [PATCH 09/38] Reworks memoization of BezierCurve::sectorArea() matrices axom::deallocate did not play nicely with destruction of static data associated with the axom::Matrix class. --- src/axom/primal/geometry/BezierCurve.hpp | 125 +++++++++++------- src/axom/primal/tests/primal_bezier_curve.cpp | 12 +- 2 files changed, 84 insertions(+), 53 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 1c54d7002d..23dc448cc9 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -32,6 +32,79 @@ namespace axom { namespace primal { +namespace internal +{ +/// Utility class that caches precomputed coefficient matrices for sectorArea computation +template +class MemoizedSectorAreaWeights +{ +public: + using SectorWeights = numerics::Matrix; + + MemoizedSectorAreaWeights() = default; + + ~MemoizedSectorAreaWeights() + { + for(auto& p : m_sectorWeightsMap) + { + delete[] p.second->data(); // delete the matrix's data + delete p.second; // delete the matrix + p.second = nullptr; + } + m_sectorWeightsMap.clear(); + } + + /// Returns a memoized matrix of coeficients for sector area computation + const SectorWeights& getWeights(int order) const + { + // Compute and cache the weights if they are not already available + if(m_sectorWeightsMap.find(order) == m_sectorWeightsMap.end()) + { + SectorWeights* weights = generateBezierCurveSectorWeights(order); + m_sectorWeightsMap[order] = weights; + } + + return *(m_sectorWeightsMap[order]); + } + +private: + /// Generate a matrix of sector weights; our destructor will manage the associated memory + SectorWeights* generateBezierCurveSectorWeights(int ord) const + { + const bool memoryIsExternal = true; + const int sz = ord + 1; + SectorWeights* weights = + new SectorWeights(sz, sz, new T[sz * sz], memoryIsExternal); + + T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); + for(int i = 0; i <= ord; ++i) + { + (*weights)(i, i) = 0.; // zero on the diagonal + for(int j = i + 1; j <= ord; ++j) + { + double val = 0.; + if(i != j) + { + T binom_ij_i = static_cast(utilities::binomialCoefficient(i + j, i)); + T binom_2nij_nj = static_cast( + utilities::binomialCoefficient(2 * ord - i - j, ord - j)); + + val = ((j - i) * ord) / binom_2n_n * + (binom_ij_i / static_cast(i + j)) * + (binom_2nij_nj / (2. * ord - j - i)); + } + (*weights)(i, j) = val; // antisymmetric + (*weights)(j, i) = -val; + } + } + return weights; + } + +private: + mutable std::map m_sectorWeightsMap; +}; +} // namespace internal + // Forward declare the templated classes and operator functions template class BezierCurve; @@ -78,13 +151,6 @@ class BezierCurve using BoundingBoxType = BoundingBox; using OrientedBoundingBoxType = OrientedBoundingBox; -private: - // Caches of precomputed coefficients for sector area calculations - // on a given polynomial order - using SectorWeights = numerics::Matrix; - using WeightsMap = std::map; - static WeightsMap s_sectorWeightsMap; - public: /*! * \brief Constructor for a Bezier Curve that reserves space for @@ -348,17 +414,12 @@ class BezierCurve */ T sectorArea() const { + // Weights for each polynomial order are precomputed and memoized + static internal::MemoizedSectorAreaWeights s_weights; + T A = 0; const int ord = getOrder(); - - // Compute and cache the weights if they are not already available - if(s_sectorWeightsMap.find(ord) == s_sectorWeightsMap.end()) - { - auto wts = generateBezierCurveSectorWeights(ord); - s_sectorWeightsMap.emplace(std::make_pair(ord, wts)); - } - - const auto& weights = s_sectorWeightsMap[ord]; + const auto& weights = s_weights.getWeights(ord); for(int p = 0; p <= ord; ++p) { @@ -422,10 +483,6 @@ class BezierCurve CoordsVec m_controlPoints; }; -// Declaration of sectorArea weights map -template -typename BezierCurve::WeightsMap BezierCurve::s_sectorWeightsMap; - //------------------------------------------------------------------------------ /// Free functions related to BezierCurve //------------------------------------------------------------------------------ @@ -436,34 +493,6 @@ std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve) return os; } -template -numerics::Matrix generateBezierCurveSectorWeights(int ord) -{ - numerics::Matrix weights(ord + 1, ord + 1); - T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); - for(int i = 0; i <= ord; ++i) - { - weights(i, i) = 0.; // zero on the diagonal - for(int j = i + 1; j <= ord; ++j) - { - double val = 0.; - if(i != j) - { - T binom_ij_i = static_cast(utilities::binomialCoefficient(i + j, i)); - T binom_2nij_nj = static_cast( - utilities::binomialCoefficient(2 * ord - i - j, ord - j)); - - val = ((j - i) * ord) / binom_2n_n * - (binom_ij_i / static_cast(i + j)) * - (binom_2nij_nj / (2. * ord - j - i)); - } - weights(i, j) = val; // antisymmetric - weights(j, i) = -val; - } - } - return weights; -} - } // namespace primal } // namespace axom diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 54ec8af204..2a2ff3264e 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -491,10 +491,12 @@ TEST(primal_beziercurve, sector_weights) using CoordType = double; + primal::internal::MemoizedSectorAreaWeights memoizedSectorWeights; + // order 1 { const int ord = 1; - auto weights = primal::generateBezierCurveSectorWeights(ord); + auto weights = memoizedSectorWeights.getWeights(ord); double binomInv = 1. / axom::utilities::binomialCoefficient(2, 1); axom::numerics::Matrix exp(ord + 1, ord + 1); @@ -515,7 +517,7 @@ TEST(primal_beziercurve, sector_weights) // order 2 { const int ord = 2; - auto weights = primal::generateBezierCurveSectorWeights(ord); + auto weights = memoizedSectorWeights.getWeights(ord); double binomInv = 1. / axom::utilities::binomialCoefficient(4, 2); axom::numerics::Matrix exp(ord + 1, ord + 1); @@ -537,7 +539,7 @@ TEST(primal_beziercurve, sector_weights) // order 3 { const int ord = 3; - auto weights = primal::generateBezierCurveSectorWeights(ord); + auto weights = memoizedSectorWeights.getWeights(ord); double binomInv = 1. / axom::utilities::binomialCoefficient(6, 3); axom::numerics::Matrix exp(ord + 1, ord + 1); @@ -560,7 +562,7 @@ TEST(primal_beziercurve, sector_weights) // order 4 { const int ord = 4; - auto weights = primal::generateBezierCurveSectorWeights(ord); + auto weights = memoizedSectorWeights.getWeights(ord); double binomInv = 1. / axom::utilities::binomialCoefficient(8, 4); axom::numerics::Matrix exp(ord + 1, ord + 1); @@ -584,7 +586,7 @@ TEST(primal_beziercurve, sector_weights) // order 5 { const int ord = 5; - auto weights = primal::generateBezierCurveSectorWeights(ord); + auto weights = memoizedSectorWeights.getWeights(ord); double binomInv = 1. / axom::utilities::binomialCoefficient(10, 5); axom::numerics::Matrix exp(ord + 1, ord + 1); From e628052d195258408adc21d42f287e1b6019c3e0 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Fri, 26 Jul 2019 13:07:58 -0700 Subject: [PATCH 10/38] Added sectorMoment() to BezierCurve, moment() to CurvedPolygon, tests for each Adjusted sectorMoment for refactoried memoization by K. Weiss. --- src/axom/primal/geometry/BezierCurve.hpp | 156 ++++++++++++++++-- src/axom/primal/geometry/CurvedPolygon.hpp | 32 +++- src/axom/primal/tests/primal_bezier_curve.cpp | 43 +++++ .../primal/tests/primal_curved_polygon.cpp | 82 +++++++++ 4 files changed, 292 insertions(+), 21 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 23dc448cc9..1db1ec3861 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -68,13 +68,23 @@ class MemoizedSectorAreaWeights } private: - /// Generate a matrix of sector weights; our destructor will manage the associated memory + /*! + * \brief Computes the weights for BezierCurve's sectorArea() function + * + * \param order The polynomial order of the curve + * \return An anti-symmetric matrix with (order+1)*{order+1) entries + * containing the integration weights for entry (i,j) + * + * The derivation is provided in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ SectorWeights* generateBezierCurveSectorWeights(int ord) const { const bool memoryIsExternal = true; - const int sz = ord + 1; + const int SZ = ord + 1; SectorWeights* weights = - new SectorWeights(sz, sz, new T[sz * sz], memoryIsExternal); + new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); for(int i = 0; i <= ord; ++i) @@ -103,6 +113,96 @@ class MemoizedSectorAreaWeights private: mutable std::map m_sectorWeightsMap; }; + +/// Utility class that caches precomputed coefficient matrices for sectorMoment computation +template +class MemoizedSectorMomentWeights +{ +public: + using SectorWeights = numerics::Matrix; + + MemoizedSectorMomentWeights() = default; + + ~MemoizedSectorMomentWeights() + { + for(auto& p : m_sectorWeightsMap) // for each matrix of weights + { + delete[] p.second->data(); // delete the matrix's data + delete p.second; // delete the matrix + p.second = nullptr; + } + m_sectorWeightsMap.clear(); + } + + /// Returns a memoized matrix of sector moment coeficients for component \a dim or order \a order + const SectorWeights& getWeights(int order, int dim) const + { + // Compute and cache the weights if they are not already available + if(m_sectorWeightsMap.find(std::make_pair(order, dim)) == + m_sectorWeightsMap.end()) + { + auto vec = generateBezierCurveSectorMomentsWeights(order); + for(int d = 0; d <= order; ++d) + { + m_sectorWeightsMap[std::make_pair(order, d)] = vec[d]; + } + } + + return *(m_sectorWeightsMap[std::make_pair(order, dim)]); + } + + /*! + * \brief Computes the weights for BezierCurve's sectorMoment() function + * + * \param order The polynomial order of the curve + * \return An anti-symmetric matrix with (order+1)*{order+1) entries + * containing the integration weights for entry (i,j) + * + * The derivation is provided in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ + std::vector generateBezierCurveSectorMomentsWeights(int ord) const + { + const bool memoryIsExternal = true; + const int SZ = ord + 1; + + std::vector weights; + weights.resize(SZ); + for(int k = 0; k <= ord; ++k) + { + SectorWeights* weights_k = + new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); + for(int i = 0; i <= ord; ++i) + { + (*weights_k)(i, i) = 0.; // zero on the diagonal + for(int j = i + 1; j <= ord; ++j) + { + double val = 0.; + if(i != j) + { + T binom_n_i = static_cast(utilities::binomialCoefficient(ord, i)); + T binom_n_j = static_cast(utilities::binomialCoefficient(ord, j)); + T binom_n_k = static_cast(utilities::binomialCoefficient(ord, k)); + T binom_3n2_ijk1 = static_cast( + utilities::binomialCoefficient(3 * ord - 2, i + j + k - 1)); + + val = (1. * (j - i)) / (3. * (3 * ord - 1)) * + (1. * binom_n_i * binom_n_j * binom_n_k / (1. * binom_3n2_ijk1)); + } + (*weights_k)(i, j) = val; // antisymmetric + (*weights_k)(j, i) = -val; + } + } + weights[k] = weights_k; + } + return weights; + } + +private: + mutable std::map, SectorWeights*> m_sectorWeightsMap; +}; + } // namespace internal // Forward declare the templated classes and operator functions @@ -113,20 +213,6 @@ class BezierCurve; template std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve); -/*! - * \brief Computes the weights for BezierCurve's sectorArea() function - * - * \param order The polynomial order of the curve - * \return An anti-symmetric matrix with (order+1)*{order+1) entries - * containing the integration weights for entry (i,j) - * - * The derivation is provided in: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ -template -numerics::Matrix generateBezierCurveSectorWeights(int order); - /*! * \class BezierCurve * @@ -404,7 +490,41 @@ class BezierCurve return; } - /*! + /*! + * \brief Calculates the sector moment of a Bezier Curve + * + * The sector moment is the moment between the curve and the origin. + * The equation and derivation are generalizations of: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ + PointType sectorMoment() const + { + // Weights for each polynomial order's moments are precomputed and memoized + static internal::MemoizedSectorMomentWeights s_weights; + + T Mx = 0; + T My = 0; + const int ord = getOrder(); + for(int r = 0; r <= ord; ++r) + { + const auto& weights_r = s_weights.getWeights(ord, r); + for(int p = 0; p <= ord; ++p) + { + for(int q = 0; q <= ord; ++q) + { + Mx += weights_r(p, q) * m_controlPoints[p][1] * + m_controlPoints[q][0] * m_controlPoints[r][0]; + My += weights_r(p, q) * m_controlPoints[p][1] * + m_controlPoints[q][0] * m_controlPoints[r][1]; + } + } + } + PointType M = PointType::make_point(Mx, My); + return M; + } + + /*! * \brief Calculates the sector area of a Bezier Curve * * The sector area is the area between the curve and the origin. diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 0e544c0a0f..e59ffff15f 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -21,7 +21,7 @@ #include "axom/primal/operators/intersect.hpp" #include -#include // for std::ostream +#include namespace axom { @@ -196,15 +196,14 @@ class CurvedPolygon * Check is that the endpoint of each edge coincides with startpoint of next edge * \return True, if the polygon is closed, False otherwise */ - T area(double tol = 1e-15) const { const int ngon = numEdges(); T A = 0.0; if(!isClosed(tol)) { + SLIC_DEBUG("Warning! The area is 0 because the element is not closed."); return A; - SLIC_INFO("Warning! The area is 0 because the element is not closed."); } else { @@ -216,6 +215,33 @@ class CurvedPolygon } } + PointType moment(double tol = 1e-15) const + { + const int ngon = numEdges(); + PointType M = PointType::make_point(0.0, 0.0); + if(!isClosed(tol)) + { + SLIC_DEBUG( + "Warning! The moments are 0 because the element is not closed."); + return M; + } + else + { + const T A = area(); + if(A != 0.) + { + for(int ed = 0; ed < ngon; ++ed) + { + PointType Mc = m_edges[ed].sectorMoment(); + M[0] += (Mc[0]); + M[1] += (Mc[1]); + } + M.array() /= A; + } + return M; + } + } + private: std::vector> m_edges; }; diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 2a2ff3264e..26acf0fdcd 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -223,6 +223,29 @@ TEST(primal_beziercurve, sector_area_cubic) } } +//------------------------------------------------------------------------------ +TEST(primal_beziercurve, sector_moment_cubic) +{ + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing Bezier sector moment calculation for a cubic"); + const int order = 3; + PointType data[order + 1] = {PointType::make_point(0.6, 1.2), + PointType::make_point(1.3, 1.6), + PointType::make_point(2.9, 2.4), + PointType::make_point(3.2, 3.5)}; + + BezierCurveType bCurve(data, order); + PointType M = bCurve.sectorMoment(); + EXPECT_NEAR(M[0], -.429321428571429, 2e-15); + EXPECT_NEAR(M[1], -.354010714285715, 2e-15); + } +} + //------------------------------------------------------------------------------ TEST(primal_beziercurve, sector_area_point) { @@ -241,6 +264,26 @@ TEST(primal_beziercurve, sector_area_point) } } +//------------------------------------------------------------------------------ +TEST(primal_beziercurve, sector_moment_point) +{ + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing Bezier sector moment calculation for a point"); + const int order = 0; + PointType data[order + 1] = {PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(data, order); + PointType M = bCurve.sectorMoment(); + EXPECT_DOUBLE_EQ(M[0], 0.0); + EXPECT_DOUBLE_EQ(M[1], 0.0); + } +} + //------------------------------------------------------------------------------ TEST(primal_beziercurve, split_cubic) { diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index f5a490a821..f7247d3fd0 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -330,6 +330,88 @@ TEST(primal_curvedpolygon, area_triangle_mixed_order) EXPECT_DOUBLE_EQ(trueA, A); } +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, moment_triangle_linear) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO("Test checking CurvedPolygon linear triangle moment computation."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.0, 1.6)}; + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, 1); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, 1); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); + + PointType M = bPolygon.moment(); + CoordType trueM1 = 0.3; + CoordType trueM2 = 1.6; + + EXPECT_DOUBLE_EQ(trueM1, M[0]); + EXPECT_DOUBLE_EQ(trueM2, M[1]); +} + +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, moment_triangle_mixed_order) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + SLIC_INFO( + "Test checking CurvedPolygon mixed order triangle area computation."); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + + PointType controlPoints[3] = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.4, 1.3), + PointType::make_point(0.3, 2.0)}; + + PointType controlPoints2[3] = {PointType::make_point(0.3, 2.0), + PointType::make_point(0.27, 1.5), + PointType::make_point(0.0, 1.6)}; + + PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), + PointType::make_point(0.6, 1.2)}; + + BezierCurveType bCurve(controlPoints, 2); + bPolygon.addEdge(bCurve); + + BezierCurveType bCurve2(controlPoints2, 2); + bPolygon.addEdge(bCurve2); + + BezierCurveType bCurve3(controlPoints3, 1); + bPolygon.addEdge(bCurve3); + + PointType M = bPolygon.moment(); + CoordType trueM2 = 1.55764705882353; + CoordType trueM1 = .2970147058823527; + + EXPECT_DOUBLE_EQ(trueM1, M[0]); + EXPECT_DOUBLE_EQ(trueM2, M[1]); +} + //---------------------------------------------------------------------------------- int main(int argc, char* argv[]) { From f5daa389166e421953a0375a4d3e04d3099e2d46 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 29 Jul 2019 08:18:43 -0700 Subject: [PATCH 11/38] Build system: Adds missing openmp and/or mpi dependency to quest tests and examples --- src/axom/primal/tests/primal_bezier_curve.cpp | 4 ++-- src/axom/quest/examples/CMakeLists.txt | 2 ++ src/axom/quest/tests/CMakeLists.txt | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 26acf0fdcd..529a3b81a2 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -59,8 +59,8 @@ TEST(primal_beziercurve, set_order) EXPECT_EQ(-1, bCurve.getOrder()); const int order = 1; - PointType controlPoints[] = {PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(0.0, 1.6, 1.8)}; + PointType controlPoints[2] = {PointType::make_point(0.6, 1.2, 1.0), + PointType::make_point(0.0, 1.6, 1.8)}; bCurve.setOrder(order); EXPECT_EQ(order, bCurve.getOrder()); diff --git a/src/axom/quest/examples/CMakeLists.txt b/src/axom/quest/examples/CMakeLists.txt index 65cb8423e4..591a307d7c 100644 --- a/src/axom/quest/examples/CMakeLists.txt +++ b/src/axom/quest/examples/CMakeLists.txt @@ -8,6 +8,8 @@ set(quest_example_depends axom fmt cli11) +blt_list_append(TO quest_example_depends ELEMENTS mpi IF ENABLE_MPI) +blt_list_append(TO quest_example_depends ELEMENTS openmp IF ENABLE_OPENMP) blt_list_append(TO quest_example_depends ELEMENTS cuda IF ENABLE_CUDA) blt_list_append(TO quest_example_depends ELEMENTS blt::hip IF ENABLE_HIP) blt_list_append(TO quest_example_depends ELEMENTS RAJA IF RAJA_FOUND) diff --git a/src/axom/quest/tests/CMakeLists.txt b/src/axom/quest/tests/CMakeLists.txt index 46e0bd292e..4666415b43 100644 --- a/src/axom/quest/tests/CMakeLists.txt +++ b/src/axom/quest/tests/CMakeLists.txt @@ -31,6 +31,7 @@ set(quest_tests_depends gtest ) +blt_list_append( TO quest_tests_depends ELEMENTS mpi IF ENABLE_MPI ) blt_list_append( TO quest_tests_depends ELEMENTS cuda IF ENABLE_CUDA ) blt_list_append( TO quest_tests_depends ELEMENTS blt::hip IF ENABLE_HIP ) From 0b203fee8333e2f301137ae714bf66cfd88f4944 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Fri, 16 Aug 2019 08:49:10 -0700 Subject: [PATCH 12/38] Added reverseorientation functions to BezierCurve and CurvedPolygon classes --- src/axom/primal/geometry/BezierCurve.hpp | 11 +++++++++++ src/axom/primal/geometry/CurvedPolygon.hpp | 14 ++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 1db1ec3861..732329cd47 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -361,6 +361,17 @@ class BezierCurve /*! Returns a copy of the Bezier curve's control points */ CoordsVec getControlPoints() const { return m_controlPoints; } + /*! Reverses the order of the Bezier curve's control points */ + void reverseOrientation() + { + const int ord = getOrder(); + CoordsVec old_controlPoints = m_controlPoints; + for(int i = 0; i <= ord; ++i) + { + m_controlPoints[i] = old_controlPoints[ord - i]; + } + } + /*! Returns an axis-aligned bounding box containing the Bezier curve */ BoundingBoxType boundingBox() const { diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index e59ffff15f..f7a058325c 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -242,6 +242,20 @@ class CurvedPolygon } } + /*! + * \brief Reverses orientation of a CurvedPolygon + */ + void reverseOrientation() + { + const int ngon = numEdges(); + std::vector> old_edges = m_edges; + for(int i = 0; i < ngon; ++i) + { + old_edges[ngon - 1 - i].reverseOrientation(); + m_edges[i] = old_edges[ngon - 1 - i]; + } + } + private: std::vector> m_edges; }; From 55d86496a4a6de190ccda8dd508666b73fb2a812 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 27 Aug 2019 09:31:59 -0700 Subject: [PATCH 13/38] Renamed sectorMoment to sectorCentroid in BezierCurve class and tweaked some tolerances --- src/axom/primal/CMakeLists.txt | 2 +- src/axom/primal/geometry/BezierCurve.hpp | 24 +++++++++---------- src/axom/primal/geometry/CurvedPolygon.hpp | 12 +++++----- src/axom/primal/tests/primal_bezier_curve.cpp | 4 ++-- .../primal/tests/primal_curved_polygon.cpp | 10 ++++---- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index d0dfe7e98f..539a9ec8a4 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -48,9 +48,9 @@ set( primal_headers operators/detail/clip_impl.hpp operators/detail/intersect_bezier_impl.hpp - operators/detail/intersect_ray_impl.hpp operators/detail/intersect_bounding_box_impl.hpp operators/detail/intersect_impl.hpp + operators/detail/intersect_ray_impl.hpp ## utils utils/ZipIndexable.hpp diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 732329cd47..24cc1a40cd 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -114,16 +114,16 @@ class MemoizedSectorAreaWeights mutable std::map m_sectorWeightsMap; }; -/// Utility class that caches precomputed coefficient matrices for sectorMoment computation +/// Utility class that caches precomputed coefficient matrices for sectorCentroid() computation template -class MemoizedSectorMomentWeights +class MemoizedSectorCentroidWeights { public: using SectorWeights = numerics::Matrix; - MemoizedSectorMomentWeights() = default; + MemoizedSectorCentroidWeights() = default; - ~MemoizedSectorMomentWeights() + ~MemoizedSectorCentroidWeights() { for(auto& p : m_sectorWeightsMap) // for each matrix of weights { @@ -141,7 +141,7 @@ class MemoizedSectorMomentWeights if(m_sectorWeightsMap.find(std::make_pair(order, dim)) == m_sectorWeightsMap.end()) { - auto vec = generateBezierCurveSectorMomentsWeights(order); + auto vec = generateBezierCurveSectorCentroidWeights(order); for(int d = 0; d <= order; ++d) { m_sectorWeightsMap[std::make_pair(order, d)] = vec[d]; @@ -152,7 +152,7 @@ class MemoizedSectorMomentWeights } /*! - * \brief Computes the weights for BezierCurve's sectorMoment() function + * \brief Computes the weights for BezierCurve's sectorCentroid() function * * \param order The polynomial order of the curve * \return An anti-symmetric matrix with (order+1)*{order+1) entries @@ -162,7 +162,7 @@ class MemoizedSectorMomentWeights * Ueda, K. "Signed area of sectors between spline curves and the origin" * IEEE International Conference on Information Visualization, 1999. */ - std::vector generateBezierCurveSectorMomentsWeights(int ord) const + std::vector generateBezierCurveSectorCentroidWeights(int ord) const { const bool memoryIsExternal = true; const int SZ = ord + 1; @@ -502,17 +502,17 @@ class BezierCurve } /*! - * \brief Calculates the sector moment of a Bezier Curve + * \brief Calculates the sector centroid of a Bezier Curve * - * The sector moment is the moment between the curve and the origin. + * This is the centroid of the region between the curve and the origin. * The equation and derivation are generalizations of: * Ueda, K. "Signed area of sectors between spline curves and the origin" * IEEE International Conference on Information Visualization, 1999. */ - PointType sectorMoment() const + PointType sectorCentroid() const { - // Weights for each polynomial order's moments are precomputed and memoized - static internal::MemoizedSectorMomentWeights s_weights; + // Weights for each polynomial order's centroid are precomputed and memoized + static internal::MemoizedSectorCentroidWeights s_weights; T Mx = 0; T My = 0; diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index f7a058325c..2c57fa85d4 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -18,7 +18,6 @@ #include "axom/primal/geometry/Vector.hpp" #include "axom/primal/geometry/NumericArray.hpp" #include "axom/primal/geometry/BezierCurve.hpp" -#include "axom/primal/operators/intersect.hpp" #include #include @@ -65,7 +64,7 @@ class CurvedPolygon * \pre numExpectedEdges is at least 1 * */ - CurvedPolygon(int nEdges) + explicit CurvedPolygon(int nEdges) { SLIC_ASSERT(nEdges >= 1); m_edges.reserve(nEdges); @@ -113,6 +112,7 @@ class CurvedPolygon /*! Splits an edge "in place" */ void splitEdge(int idx, T t) { + SLIC_ASSERT(idx < m_edges.size()); m_edges.insert(m_edges.begin() + idx + 1, 1, m_edges[idx]); BezierCurve csplit = m_edges[idx]; csplit.split(t, m_edges[idx], m_edges[idx + 1]); @@ -158,7 +158,7 @@ class CurvedPolygon * Check is that the endpoint of each edge coincides with startpoint of next edge * \return True, if the polygon is closed, False otherwise */ - bool isClosed(double tol = 1e-15) const + bool isClosed(double tol = 1e-8) const { const int ngon = numEdges(); if(ngon <= 2) @@ -196,7 +196,7 @@ class CurvedPolygon * Check is that the endpoint of each edge coincides with startpoint of next edge * \return True, if the polygon is closed, False otherwise */ - T area(double tol = 1e-15) const + T area(double tol = 1e-8) const { const int ngon = numEdges(); T A = 0.0; @@ -215,7 +215,7 @@ class CurvedPolygon } } - PointType moment(double tol = 1e-15) const + PointType moment(double tol = 1e-8) const { const int ngon = numEdges(); PointType M = PointType::make_point(0.0, 0.0); @@ -232,7 +232,7 @@ class CurvedPolygon { for(int ed = 0; ed < ngon; ++ed) { - PointType Mc = m_edges[ed].sectorMoment(); + PointType Mc = m_edges[ed].sectorCentroid(); M[0] += (Mc[0]); M[1] += (Mc[1]); } diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 529a3b81a2..ac34574a8f 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -240,7 +240,7 @@ TEST(primal_beziercurve, sector_moment_cubic) PointType::make_point(3.2, 3.5)}; BezierCurveType bCurve(data, order); - PointType M = bCurve.sectorMoment(); + PointType M = bCurve.sectorCentroid(); EXPECT_NEAR(M[0], -.429321428571429, 2e-15); EXPECT_NEAR(M[1], -.354010714285715, 2e-15); } @@ -278,7 +278,7 @@ TEST(primal_beziercurve, sector_moment_point) PointType data[order + 1] = {PointType::make_point(0.6, 1.2)}; BezierCurveType bCurve(data, order); - PointType M = bCurve.sectorMoment(); + PointType M = bCurve.sectorCentroid(); EXPECT_DOUBLE_EQ(M[0], 0.0); EXPECT_DOUBLE_EQ(M[1], 0.0); } diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index f7247d3fd0..c57a440716 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -1,10 +1,10 @@ // 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) -/* /file bezier_test.cpp - * /brief This file tests the BezierCurve.hpp and eval_bezier.hpp files +/* /file primal_curved_polygon.cpp + * /brief This file tests the CurvedPolygon class */ #include "gtest/gtest.h" @@ -205,8 +205,8 @@ TEST(primal_curvedpolygon, area_triangle_degenerate) BezierCurveType bCurve3(controlPoints3, 1); bPolygon.addEdge(bCurve3); - bPolygon[2][1][0] -= 2e-15; - EXPECT_EQ(0.0, bPolygon.area()); + bPolygon[2][1][0] -= 1e-10; + EXPECT_EQ(0.0, bPolygon.area(1e-11)); } //---------------------------------------------------------------------------------- From 0a456e0a2ed2f6c9d34f69ef93e638c62c15f32c Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Wed, 18 Sep 2019 09:44:56 -0700 Subject: [PATCH 14/38] More tolerance tweaking --- src/axom/primal/geometry/CurvedPolygon.hpp | 6 +++--- src/axom/primal/tests/primal_curved_polygon.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 2c57fa85d4..45dab72d06 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -158,7 +158,7 @@ class CurvedPolygon * Check is that the endpoint of each edge coincides with startpoint of next edge * \return True, if the polygon is closed, False otherwise */ - bool isClosed(double tol = 1e-8) const + bool isClosed(double tol = 1e-5) const { const int ngon = numEdges(); if(ngon <= 2) @@ -200,7 +200,7 @@ class CurvedPolygon { const int ngon = numEdges(); T A = 0.0; - if(!isClosed(tol)) + if(!isClosed(1e3 * tol)) { SLIC_DEBUG("Warning! The area is 0 because the element is not closed."); return A; @@ -219,7 +219,7 @@ class CurvedPolygon { const int ngon = numEdges(); PointType M = PointType::make_point(0.0, 0.0); - if(!isClosed(tol)) + if(!isClosed(1e3 * tol)) { SLIC_DEBUG( "Warning! The moments are 0 because the element is not closed."); diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index c57a440716..b07e2a8252 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -206,7 +206,7 @@ TEST(primal_curvedpolygon, area_triangle_degenerate) bPolygon.addEdge(bCurve3); bPolygon[2][1][0] -= 1e-10; - EXPECT_EQ(0.0, bPolygon.area(1e-11)); + EXPECT_EQ(0.0, bPolygon.area(1e-14)); } //---------------------------------------------------------------------------------- From 35ff2a0a3051f50f6e16fcf8c844934ab624c2de Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Mon, 30 Sep 2019 15:15:06 -0700 Subject: [PATCH 15/38] Reformatted tests for CurvedPolygon --- src/axom/primal/geometry/BezierCurve.hpp | 17 +++ src/axom/primal/geometry/CurvedPolygon.hpp | 2 +- .../primal/tests/primal_curved_polygon.cpp | 143 ++++++++++++------ 3 files changed, 111 insertions(+), 51 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 24cc1a40cd..1d6e8403cc 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -323,6 +323,23 @@ class BezierCurve } } + /*! + * \brief Constructor for a Bezier Curve from an vector of coordinates + * + * \param [in] pts a vector with ord+1 control points + * \param [in] ord The Curve's polynomial order + * \pre order is greater than or equal to zero + * + */ + BezierCurve(std::vector pts, int ord) + { + SLIC_ASSERT(ord >= 0); + + const int sz = utilities::max(0, ord + 1); + m_controlPoints.resize(sz); + m_controlPoints = pts; + } + /*! Sets the order of the Bezier Curve*/ void setOrder(int ord) { m_controlPoints.resize(ord + 1); } diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 45dab72d06..4c21aa23a4 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -215,7 +215,7 @@ class CurvedPolygon } } - PointType moment(double tol = 1e-8) const + PointType centroid(double tol = 1e-8) const { const int ngon = numEdges(); PointType M = PointType::make_point(0.0, 0.0); diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index b07e2a8252..940c036c07 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -14,6 +14,63 @@ 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) +{ + using Array = std::vector; + + EXPECT_DOUBLE_EQ(expArea, bPolygon.area(eps)); + for(int i = 0; i < DIM; ++i) + { + EXPECT_DOUBLE_EQ(expMoment[i], bPolygon.centroid(eps)[i]); + } +} + +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, constructor) { @@ -86,7 +143,6 @@ TEST(primal_curvedpolygon, isClosed) using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; SLIC_INFO("Test checking if CurvedPolygon is closed."); @@ -94,27 +150,19 @@ TEST(primal_curvedpolygon, isClosed) EXPECT_EQ(0, bPolygon.numEdges()); EXPECT_EQ(false, bPolygon.isClosed()); - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, 1); - bPolygon.addEdge(bCurve); - EXPECT_EQ(false, bPolygon.isClosed()); - - BezierCurveType bCurve2(controlPoints2, 1); - bPolygon.addEdge(bCurve2); + 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}; - EXPECT_EQ(2, bPolygon.numEdges()); - EXPECT_EQ(false, bPolygon.isClosed()); + std::vector subCP = {PointType::make_point(0.6, 1.2), + PointType::make_point(0.3, 2.0)}; + std::vector suborders = {1}; + CurvedPolygonType subPolygon = createPolygon(subCP, suborders); + EXPECT_EQ(false, subPolygon.isClosed()); - BezierCurveType bCurve3(controlPoints3, 1); - bPolygon.addEdge(bCurve3); + bPolygon = createPolygon(CP, orders); EXPECT_EQ(3, bPolygon.numEdges()); EXPECT_EQ(true, bPolygon.isClosed()); @@ -134,43 +182,37 @@ TEST(primal_curvedpolygon, split_edge) SLIC_INFO("Test checking CurvedPolygon edge split."); - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); + 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)}; - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; + std::vector orders32 = {1, 1, 1}; + CurvedPolygonType bPolygon32 = createPolygon(CP, orders32); + std::cout << "Got here!! " << std::endl; + std::vector subCP; - PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; + subCP.assign(CP.begin(), CP.begin() + 2); + BezierCurveType bCurve(subCP, 1); + bPolygon32.splitEdge(0, .5); - BezierCurveType bCurve(controlPoints, 1); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, 1); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, 1); - bPolygon.addEdge(bCurve3); - - bPolygon.splitEdge(0, .5); + BezierCurveType bCurve2; + BezierCurveType bCurve3; bCurve.split(.5, bCurve2, bCurve3); - EXPECT_EQ(bPolygon.numEdges(), 4); - for(int i = 0; i < bPolygon[0].getOrder(); ++i) + EXPECT_EQ(bPolygon32.numEdges(), 4); + for(int i = 0; i < bPolygon32[0].getOrder(); ++i) { for(int dimi = 0; dimi < DIM; ++dimi) { - EXPECT_EQ(bPolygon[0][i][dimi], bCurve2[i][dimi]); - EXPECT_EQ(bPolygon[1][i][dimi], bCurve3[i][dimi]); + EXPECT_EQ(bPolygon32[0][i][dimi], bCurve2[i][dimi]); + EXPECT_EQ(bPolygon32[1][i][dimi], bCurve3[i][dimi]); } } } //---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, area_triangle_degenerate) +TEST(primal_curvedpolygon, moments_triangle_degenerate) { const int DIM = 2; using CoordType = double; @@ -184,6 +226,7 @@ TEST(primal_curvedpolygon, area_triangle_degenerate) CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); EXPECT_EQ(0.0, bPolygon.area()); + PointType origin = PointType::make_point(0.0, 0.0); PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), PointType::make_point(0.3, 2.0)}; @@ -196,17 +239,17 @@ TEST(primal_curvedpolygon, area_triangle_degenerate) BezierCurveType bCurve(controlPoints, 1); bPolygon.addEdge(bCurve); - EXPECT_EQ(0.0, bPolygon.area()); + checkMoments(bPolygon, 0.0, origin, 1e-14, 1e-15); BezierCurveType bCurve2(controlPoints2, 1); bPolygon.addEdge(bCurve2); - EXPECT_EQ(0.0, bPolygon.area()); + checkMoments(bPolygon, 0.0, origin, 1e-14, 1e-15); BezierCurveType bCurve3(controlPoints3, 1); bPolygon.addEdge(bCurve3); - bPolygon[2][1][0] -= 1e-10; - EXPECT_EQ(0.0, bPolygon.area(1e-14)); + bPolygon[2][1][0] -= 1e-11; + checkMoments(bPolygon, 0.0, origin, 1e-14, 1e-15); } //---------------------------------------------------------------------------------- @@ -361,7 +404,7 @@ TEST(primal_curvedpolygon, moment_triangle_linear) BezierCurveType bCurve3(controlPoints3, 1); bPolygon.addEdge(bCurve3); - PointType M = bPolygon.moment(); + PointType M = bPolygon.centroid(); CoordType trueM1 = 0.3; CoordType trueM2 = 1.6; @@ -404,7 +447,7 @@ TEST(primal_curvedpolygon, moment_triangle_mixed_order) BezierCurveType bCurve3(controlPoints3, 1); bPolygon.addEdge(bCurve3); - PointType M = bPolygon.moment(); + PointType M = bPolygon.centroid(); CoordType trueM2 = 1.55764705882353; CoordType trueM1 = .2970147058823527; From feed77e937c0d1e18935e9b36439bd800378c079 Mon Sep 17 00:00:00 2001 From: David Gunderman Date: Tue, 1 Oct 2019 11:45:39 -0700 Subject: [PATCH 16/38] Added/refactored some CurvedPolygon tests --- .../primal/tests/primal_curved_polygon.cpp | 258 +++++++----------- 1 file changed, 93 insertions(+), 165 deletions(-) diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 940c036c07..0d9d0a0550 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -24,19 +24,19 @@ void checkMoments(const primal::CurvedPolygon& bPolygon, double eps, double test_eps) { - using Array = std::vector; - - EXPECT_DOUBLE_EQ(expArea, bPolygon.area(eps)); + EXPECT_NEAR(expArea, bPolygon.area(eps), test_eps); for(int i = 0; i < DIM; ++i) { - EXPECT_DOUBLE_EQ(expMoment[i], bPolygon.centroid(eps)[i]); + EXPECT_NEAR(expMoment[i], bPolygon.centroid(eps)[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; @@ -45,17 +45,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) @@ -67,7 +61,6 @@ primal::CurvedPolygon createPolygon( bPolygon.addEdge(addCurve); iter += (orders[j]); } - std::cout << bPolygon << std::endl; return bPolygon; } @@ -253,206 +246,141 @@ TEST(primal_curvedpolygon, moments_triangle_degenerate) } //---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, area_triangle_linear) +TEST(primal_curvedpolygon, moments_triangle_linear) { const int DIM = 2; using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - SLIC_INFO("Test checking CurvedPolygon linear triangle area computation."); - - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, 1); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, 1); - bPolygon.addEdge(bCurve2); + SLIC_INFO("Test checking CurvedPolygon linear triangle moment computation."); + 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)}; - BezierCurveType bCurve3(controlPoints3, 1); - bPolygon.addEdge(bCurve3); + std::vector orders = {1, 1, 1}; + CurvedPolygonType bPolygon = createPolygon(CP, orders); - CoordType A = bPolygon.area(); CoordType trueA = -.18; + PointType trueC = PointType::make_point(0.3, 1.6); - EXPECT_DOUBLE_EQ(trueA, A); + checkMoments(bPolygon, trueA, trueC, 1e-14, 1e-15); } //---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, area_triangle_quadratic) +TEST(primal_curvedpolygon, moments_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 checking CurvedPolygon quadratic triangle area computation."); - - 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); + SLIC_INFO( + "Test checking CurvedPolygon quadratic triangle moment computation."); - BezierCurveType bCurve2(controlPoints2, order); - bPolygon.addEdge(bCurve2); + 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)}; - BezierCurveType bCurve3(controlPoints3, order); - bPolygon.addEdge(bCurve3); + std::vector orders = {2, 2, 2}; + CurvedPolygonType bPolygon = createPolygon(CP, orders); - CoordType A = bPolygon.area(); + CoordType trueA = -0.097333333333333; + PointType trueC = PointType::make_point(.294479452054794, 1.548219178082190); - CoordType trueA = -.09733333333333333333; - EXPECT_DOUBLE_EQ(trueA, A); + checkMoments(bPolygon, trueA, trueC, 1e-15, 1e-14); } //---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, area_triangle_mixed_order) +TEST(primal_curvedpolygon, moments_triangle_mixed_order) { const int DIM = 2; using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; SLIC_INFO( - "Test checking CurvedPolygon mixed order triangle area computation."); - - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[3] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.4, 1.3), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[3] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.27, 1.5), - PointType::make_point(0.0, 1.6)}; + "Test checking CurvedPolygon mixed order triangle moment computation."); - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, 2); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, 2); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, 1); - bPolygon.addEdge(bCurve3); + 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.6, 1.2)}; - CoordType A = bPolygon.area(); + std::vector orders = {2, 2, 1}; + CurvedPolygonType bPolygon = createPolygon(CP, orders); CoordType trueA = -.0906666666666666666666; - EXPECT_DOUBLE_EQ(trueA, A); -} - -//---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, moment_triangle_linear) -{ - const int DIM = 2; - using CoordType = double; - using CurvedPolygonType = primal::CurvedPolygon; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - SLIC_INFO("Test checking CurvedPolygon linear triangle moment computation."); + PointType trueC = PointType::make_point(.2970147058823527, 1.55764705882353); - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, 1); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, 1); - bPolygon.addEdge(bCurve2); - - BezierCurveType bCurve3(controlPoints3, 1); - bPolygon.addEdge(bCurve3); - - PointType M = bPolygon.centroid(); - CoordType trueM1 = 0.3; - CoordType trueM2 = 1.6; - - EXPECT_DOUBLE_EQ(trueM1, M[0]); - EXPECT_DOUBLE_EQ(trueM2, M[1]); + checkMoments(bPolygon, trueA, trueC, 1e-14, 1e-15); } //---------------------------------------------------------------------------------- -TEST(primal_curvedpolygon, moment_triangle_mixed_order) +TEST(primal_curvedpolygon, moments_quad_all_orders) { const int DIM = 2; using CoordType = double; using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - SLIC_INFO( - "Test checking CurvedPolygon mixed order triangle area computation."); - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - - PointType controlPoints[3] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.4, 1.3), - PointType::make_point(0.3, 2.0)}; - - PointType controlPoints2[3] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.27, 1.5), - PointType::make_point(0.0, 1.6)}; - - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(controlPoints, 2); - bPolygon.addEdge(bCurve); - - BezierCurveType bCurve2(controlPoints2, 2); - bPolygon.addEdge(bCurve2); + SLIC_INFO("Test checking CurvedPolygon linear triangle moment computation."); + std::vector CPorig = {PointType::make_point(0.0, 0.0), + PointType::make_point(0.0, 1.0), + PointType::make_point(1.0, 1.0), + PointType::make_point(1.0, 0.0), + PointType::make_point(0.0, 0.0)}; - BezierCurveType bCurve3(controlPoints3, 1); - bPolygon.addEdge(bCurve3); + std::vector orders = {1, 1, 1, 1}; + CurvedPolygonType bPolygon = createPolygon(CPorig, orders); - PointType M = bPolygon.centroid(); - CoordType trueM2 = 1.55764705882353; - CoordType trueM1 = .2970147058823527; + CoordType trueA = 1.0; + PointType trueC = PointType::make_point(0.5, 0.5); - EXPECT_DOUBLE_EQ(trueM1, M[0]); - EXPECT_DOUBLE_EQ(trueM2, M[1]); + checkMoments(bPolygon, trueA, trueC, 1e-14, 1e-15); + for(int p = 2; p < 11; ++p) + { + std::vector CP = CPorig; + for(int side = 0; side < 4; ++side) + { + for(int i = 1; i < p; ++i) + { + switch(side) + { + case 0: + CP.insert(CP.begin() + i + (side * p), + (PointType::make_point(0.0, 1.0 * i / p))); + break; + case 1: + CP.insert(CP.begin() + i + (side * p), + (PointType::make_point(1.0 * i / p, 1.0))); + break; + case 2: + CP.insert(CP.begin() + i + (side * p), + (PointType::make_point(1.0, 1.0 - (1.0 * i / p)))); + break; + case 3: + CP.insert(CP.begin() + i + (side * p), + (PointType::make_point(1.0 - (1.0 * i / p), 0.0))); + break; + } + } + orders[side] += 1; + } + /*for (int i=0; i Date: Tue, 8 Oct 2019 10:32:33 -0700 Subject: [PATCH 17/38] Bugfixes for curved polygon tests in MSVC --- src/axom/primal/geometry/BezierCurve.hpp | 2 +- src/axom/primal/tests/primal_curved_polygon.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 1d6e8403cc..e19f725d35 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -331,7 +331,7 @@ class BezierCurve * \pre order is greater than or equal to zero * */ - BezierCurve(std::vector pts, int ord) + BezierCurve(const std::vector& pts, int ord) { SLIC_ASSERT(ord >= 0); diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 0d9d0a0550..a7948830a7 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -12,6 +12,8 @@ #include "axom/slic.hpp" #include "axom/primal/geometry/CurvedPolygon.hpp" +#include + namespace primal = axom::primal; /** @@ -46,8 +48,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 6e1c30232df50b3992615ec88f3664e85ad08cad Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Tue, 8 Oct 2019 11:39:03 -0700 Subject: [PATCH 18/38] primal's BezierCurve::dt should return a Vector rather than a Point --- src/axom/primal/geometry/BezierCurve.hpp | 9 +++++---- src/axom/primal/tests/primal_bezier_curve.cpp | 13 +++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index e19f725d35..996b738f5d 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -450,9 +450,9 @@ class BezierCurve * \note We typically find the tangent of the curve at \a t between 0 and 1 */ - PointType dt(T t) const + VectorType dt(T t) const { - PointType ptval; + VectorType val; const int ord = getOrder(); std::vector dCarray(ord + 1); @@ -465,6 +465,7 @@ class BezierCurve dCarray[p] = m_controlPoints[p][i]; } + // stop one step early and take difference of last two values for(int p = 1; p <= ord - 1; ++p) { const int end = ord - p; @@ -473,10 +474,10 @@ class BezierCurve dCarray[k] = (1 - t) * dCarray[k] + t * dCarray[k + 1]; } } - ptval[i] = ord * (dCarray[1] - dCarray[0]); + val[i] = ord * (dCarray[1] - dCarray[0]); } - return ptval; + return val; } /*! diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index ac34574a8f..e67ddbafb7 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -174,6 +174,7 @@ TEST(primal_beziercurve_, tangent) const int DIM = 3; using CoordType = double; using PointType = primal::Point; + using VectorType = primal::Vector; using BezierCurveType = primal::BezierCurve; const int order = 3; @@ -184,15 +185,15 @@ TEST(primal_beziercurve_, tangent) BezierCurveType b2Curve(data, order); - PointType midtval = PointType::make_point(3.15, 2.325, 1.875); - PointType starttval = PointType::make_point(2.1, 1.2, 2.4); - PointType endtval = PointType::make_point(.9, 3.3, 2.1); + VectorType midtval = VectorType::make_vector(3.15, 2.325, 1.875); + VectorType starttval = VectorType::make_vector(2.1, 1.2, 2.4); + VectorType endtval = VectorType::make_vector(.9, 3.3, 2.1); // Evaluate the curve at several parameter values // Curve should be tangent to control net at endpoints - PointType eval0 = b2Curve.dt(0.0); - PointType eval1 = b2Curve.dt(1.0); - PointType evalMid = b2Curve.dt(0.5); + VectorType eval0 = b2Curve.dt(0.0); + VectorType eval1 = b2Curve.dt(1.0); + VectorType evalMid = b2Curve.dt(0.5); for(int i = 0; i < DIM; ++i) { From a73c6cd31357b1b41a21e400939f376d6ff1fed0 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Tue, 8 Oct 2019 16:26:41 -0700 Subject: [PATCH 19/38] Minor updates to CurvedPolygon --- src/axom/primal/geometry/CurvedPolygon.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 4c21aa23a4..a0d2e59ec9 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -1,12 +1,12 @@ // 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) /*! * \file CurvedPolygon.hpp * - * \brief A CurvedPolygon primitive for primal based on Bezier Curves + * \brief A CurvedPolygon primitive whose edges are Bezier Curves */ #ifndef AXOM_PRIMAL_CURVEDPOLYGON_HPP_ @@ -37,11 +37,11 @@ std::ostream& operator<<(std::ostream& os, const CurvedPolygon& poly); /*! * \class CurvedPolygon * - * \brief Represents a curved polygon defined by a vector of BezierCurves + * \brief Represents a polygon with curved edges defined by BezierCurves * \tparam T the coordinate type, e.g., double, float, etc. * \tparam NDIMS the number of dimensions * \note The component curves should be ordered in a counter clockwise - * orientation with respect to the polygon's desired normal vector + * orientation with respect to the polygon's normal vector */ template class CurvedPolygon @@ -114,7 +114,7 @@ class CurvedPolygon { SLIC_ASSERT(idx < m_edges.size()); m_edges.insert(m_edges.begin() + idx + 1, 1, m_edges[idx]); - BezierCurve csplit = m_edges[idx]; + auto& csplit = m_edges[idx]; csplit.split(t, m_edges[idx], m_edges[idx + 1]); } From 638f85f965ca54a41803126902ae17d7040a2351 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 11 Jun 2021 17:09:16 -0700 Subject: [PATCH 20/38] Fixes warnings from clang compiler --- src/axom/primal/geometry/CurvedPolygon.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index a0d2e59ec9..b92d9356c2 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -112,7 +112,7 @@ class CurvedPolygon /*! Splits an edge "in place" */ void splitEdge(int idx, T t) { - SLIC_ASSERT(idx < m_edges.size()); + SLIC_ASSERT(idx < static_cast(m_edges.size())); m_edges.insert(m_edges.begin() + idx + 1, 1, m_edges[idx]); auto& csplit = m_edges[idx]; csplit.split(t, m_edges[idx], m_edges[idx + 1]); From 6e84b7f665f7d746932961921844bf203dfdc45a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 11 Jun 2021 17:56:58 -0700 Subject: [PATCH 21/38] Minor cleanup for BezierCurve and CurvedPolygon classes and tests --- src/axom/primal/geometry/BezierCurve.hpp | 56 +++++++---------- src/axom/primal/geometry/CurvedPolygon.hpp | 62 ++++++++++--------- src/axom/primal/tests/primal_bezier_curve.cpp | 6 +- .../primal/tests/primal_bezier_intersect.cpp | 5 +- .../primal/tests/primal_curved_polygon.cpp | 38 +++++++----- 5 files changed, 80 insertions(+), 87 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 996b738f5d..dc1315d489 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -224,7 +224,6 @@ std::ostream& operator<<(std::ostream& os, const BezierCurve& bCurve); * The curve is approximated by the control points, * parametrized from t=0 to t=1. */ - template class BezierCurve { @@ -237,6 +236,12 @@ class BezierCurve using BoundingBoxType = BoundingBox; using OrientedBoundingBoxType = OrientedBoundingBox; + AXOM_STATIC_ASSERT_MSG((NDIMS == 2) || (NDIMS == 3), + "A Bezier Curve object may be defined in 2-D or 3-D"); + AXOM_STATIC_ASSERT_MSG( + std::is_arithmetic::value, + "A Bezier Curve must be defined using an arithmetic type"); + public: /*! * \brief Constructor for a Bezier Curve that reserves space for @@ -247,12 +252,6 @@ class BezierCurve */ explicit BezierCurve(int ord = -1) { - AXOM_STATIC_ASSERT_MSG( - (NDIMS == 2) || (NDIMS == 3), - "A Bezier Curve object may be defined in 2-D or 3-D"); - AXOM_STATIC_ASSERT_MSG( - std::is_arithmetic::value, - "A Bezier Curve must be defined using an arithmetic type"); SLIC_ASSERT(ord >= -1); const int sz = utilities::max(-1, ord + 1); m_controlPoints.resize(sz); @@ -272,12 +271,6 @@ class BezierCurve */ BezierCurve(T* pts, int ord) { - AXOM_STATIC_ASSERT_MSG( - (NDIMS == 2) || (NDIMS == 3), - "A Bezier Curve object may be defined in 2-D or 3-D"); - AXOM_STATIC_ASSERT_MSG( - std::is_arithmetic::value, - "A Bezier Curve must be defined using an arithmetic type"); SLIC_ASSERT(pts != nullptr); SLIC_ASSERT(ord >= 0); @@ -302,15 +295,8 @@ class BezierCurve * \pre order is greater than or equal to zero * */ - BezierCurve(PointType* pts, int ord) { - AXOM_STATIC_ASSERT_MSG( - (NDIMS == 2) || (NDIMS == 3), - "A Bezier Curve object may be defined in 2-D or 3-D"); - AXOM_STATIC_ASSERT_MSG( - std::is_arithmetic::value, - "A Bezier Curve must be defined using an arithmetic type"); SLIC_ASSERT(pts != nullptr); SLIC_ASSERT(ord >= 0); @@ -340,13 +326,13 @@ class BezierCurve m_controlPoints = pts; } - /*! Sets the order of the Bezier Curve*/ + /// Sets the order of the Bezier Curve void setOrder(int ord) { m_controlPoints.resize(ord + 1); } - /*! Returns the order of the Bezier Curve*/ + /// Returns the order of the Bezier Curve int getOrder() const { return static_cast(m_controlPoints.size()) - 1; } - /*! Clears the list of control points*/ + /// Clears the list of control points void clear() { const int ord = getOrder(); @@ -356,13 +342,13 @@ class BezierCurve } } - /*! Retrieves the control point at index \a idx */ + /// Retrieves the control point at index \a idx PointType& operator[](int idx) { return m_controlPoints[idx]; } - /*! Retrieves the control point at index \a idx */ + /// Retrieves the control point at index \a idx const PointType& operator[](int idx) const { return m_controlPoints[idx]; } - /* Checks equality of two Bezier Curve */ + /// Checks equality of two Bezier Curve friend inline bool operator==(const BezierCurve& lhs, const BezierCurve& rhs) { @@ -375,10 +361,10 @@ class BezierCurve return !(lhs == rhs); } - /*! Returns a copy of the Bezier curve's control points */ + /// Returns a copy of the Bezier curve's control points CoordsVec getControlPoints() const { return m_controlPoints; } - /*! Reverses the order of the Bezier curve's control points */ + /// Reverses the order of the Bezier curve's control points void reverseOrientation() { const int ord = getOrder(); @@ -389,14 +375,14 @@ class BezierCurve } } - /*! Returns an axis-aligned bounding box containing the Bezier curve */ + /// Returns an axis-aligned bounding box containing the Bezier curve BoundingBoxType boundingBox() const { return BoundingBoxType(m_controlPoints.data(), static_cast(m_controlPoints.size())); } - /*! Returns an oriented bounding box containing the Bezier curve */ + /// Returns an oriented bounding box containing the Bezier curve OrientedBoundingBoxType orientedBoundingBox() const { return OrientedBoundingBoxType(m_controlPoints.data(), @@ -411,7 +397,6 @@ class BezierCurve * * \note We typically evaluate the curve at \a t between 0 and 1 */ - PointType evaluate(T t) const { PointType ptval; @@ -449,7 +434,6 @@ class BezierCurve * * \note We typically find the tangent of the curve at \a t between 0 and 1 */ - VectorType dt(T t) const { VectorType val; @@ -481,11 +465,13 @@ class BezierCurve } /*! - * \brief Splits a Bezier curve into two Bezier curves at particular parameter - * value between 0 and 1 + * \brief Splits a Bezier curve into two Bezier curves at a given parameter value * * \param [in] t parameter value between 0 and 1 at which to evaluate - * \param [out] c1, c2 Bezier curves that split the original + * \param [out] c1 First output Bezier curve + * \param [out] c2 Second output Bezier curve + * + * \pre Parameter \a t must be between 0 and 1 */ void split(T t, BezierCurve& c1, BezierCurve& c2) const { diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index b92d9356c2..7d7b96a825 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -6,7 +6,7 @@ /*! * \file CurvedPolygon.hpp * - * \brief A CurvedPolygon primitive whose edges are Bezier Curves + * \brief A polygon primitive whose edges are Bezier curves */ #ifndef AXOM_PRIMAL_CURVEDPOLYGON_HPP_ @@ -53,7 +53,7 @@ class CurvedPolygon using BezierCurveType = BezierCurve; public: - /*! Default constructor for an empty polygon */ + /// Default constructor for an empty polygon CurvedPolygon() = default; /*! @@ -71,6 +71,7 @@ class CurvedPolygon m_edges.resize(nEdges); } + /// Constructor from an array of \a nEdges curves CurvedPolygon(BezierCurveType* curves, int nEdges) { SLIC_ASSERT(curves != nullptr); @@ -84,7 +85,13 @@ class CurvedPolygon } } - /*! Return the number of edges in the polygon */ + /// Clears the list of edges + void clear() { m_edges.clear(); } + + /// \name Operations on edges + /// @{ + + /// Return the number of edges in the polygon int numEdges() const { return m_edges.size(); } void setNumEdges(int ngon) @@ -93,41 +100,42 @@ class CurvedPolygon m_edges.resize(ngon); } - /* Checks equality of two Bezier Curve */ - friend inline bool operator==(const CurvedPolygon& lhs, - const CurvedPolygon& rhs) - { - return lhs.m_edges == rhs.m_edges; - } - - friend inline bool operator!=(const CurvedPolygon& lhs, - const CurvedPolygon& rhs) - { - return !(lhs == rhs); - } - - /*! Appends a BezierCurve to the list of edges */ + /// Appends a BezierCurve to the list of edges void addEdge(const BezierCurveType& c1) { m_edges.push_back(c1); } - /*! Splits an edge "in place" */ + /// Splits an edge "in place" void splitEdge(int idx, T t) { SLIC_ASSERT(idx < static_cast(m_edges.size())); + m_edges.insert(m_edges.begin() + idx + 1, 1, m_edges[idx]); auto& csplit = m_edges[idx]; csplit.split(t, m_edges[idx], m_edges[idx + 1]); } - /*! Clears the list of edges */ - void clear() { m_edges.clear(); } - std::vector> getEdges() const { return m_edges; } + /// @} + /*! Retrieves the Bezier Curve at index idx */ BezierCurveType& operator[](int idx) { return m_edges[idx]; } /*! Retrieves the vertex at index idx */ const BezierCurveType& operator[](int idx) const { return m_edges[idx]; } + /// Tests equality of two CurvedPolygons + friend inline bool operator==(const CurvedPolygon& lhs, + const CurvedPolygon& rhs) + { + return lhs.m_edges == rhs.m_edges; + } + + /// Tests inequality of two CurvedPolygons + friend inline bool operator!=(const CurvedPolygon& lhs, + const CurvedPolygon& rhs) + { + return !(lhs == rhs); + } + /*! * \brief Simple formatted print of a CurvedPolygon instance * @@ -190,12 +198,7 @@ class CurvedPolygon return true; } - /*! - * \brief Check closedness of a CurvedPolygon - * - * Check is that the endpoint of each edge coincides with startpoint of next edge - * \return True, if the polygon is closed, False otherwise - */ + /// \brief Returns the area enclosed by the CurvedPolygon T area(double tol = 1e-8) const { const int ngon = numEdges(); @@ -215,6 +218,7 @@ class CurvedPolygon } } + /// \brief Returns the centroid of the CurvedPolygon PointType centroid(double tol = 1e-8) const { const int ngon = numEdges(); @@ -242,9 +246,7 @@ class CurvedPolygon } } - /*! - * \brief Reverses orientation of a CurvedPolygon - */ + /// \brief Reverses orientation of a CurvedPolygon void reverseOrientation() { const int ngon = numEdges(); diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index e67ddbafb7..625c52fbdf 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -3,9 +3,9 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -/* - * /file primal_bezier_curve.cpp - * /brief This file tests primal's Bezier curve functionality +/*! + * \file primal_bezier_curve.cpp + * \brief This file tests primal's Bezier curve functionality */ #include "gtest/gtest.h" diff --git a/src/axom/primal/tests/primal_bezier_intersect.cpp b/src/axom/primal/tests/primal_bezier_intersect.cpp index 87cbea79dc..4993e24a37 100644 --- a/src/axom/primal/tests/primal_bezier_intersect.cpp +++ b/src/axom/primal/tests/primal_bezier_intersect.cpp @@ -3,8 +3,9 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -/* /file primal_bezier_intersect.cpp - * /brief This file tests the Bezier curve intersection routines +/*! + * \file primal_bezier_intersect.cpp + * \brief This file tests the Bezier curve intersection routines */ #include "gtest/gtest.h" diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index a7948830a7..488bf7b0ba 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -3,8 +3,9 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -/* /file primal_curved_polygon.cpp - * /brief This file tests the CurvedPolygon class +/*! + * \file primal_curved_polygon.cpp + * \brief This file tests the CurvedPolygon class */ #include "gtest/gtest.h" @@ -16,8 +17,9 @@ 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 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, @@ -33,7 +35,11 @@ void checkMoments(const primal::CurvedPolygon& bPolygon, } } -/* 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( @@ -217,8 +223,7 @@ TEST(primal_curvedpolygon, moments_triangle_degenerate) using PointType = primal::Point; using BezierCurveType = primal::BezierCurve; - SLIC_INFO( - "Test checking CurvedPolygon degenerate triangle area computation."); + SLIC_INFO("Testing area computation of degenerate triangles"); CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); @@ -230,7 +235,6 @@ TEST(primal_curvedpolygon, moments_triangle_degenerate) PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), PointType::make_point(0.6, 1.2)}; @@ -257,7 +261,7 @@ TEST(primal_curvedpolygon, moments_triangle_linear) using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - SLIC_INFO("Test checking CurvedPolygon linear triangle moment computation."); + SLIC_INFO("Test moment computation of a linear triangle"); std::vector CP = {PointType::make_point(0.6, 1.2), PointType::make_point(0.3, 2.0), PointType::make_point(0.0, 1.6), @@ -280,8 +284,7 @@ TEST(primal_curvedpolygon, moments_triangle_quadratic) using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - SLIC_INFO( - "Test checking CurvedPolygon quadratic triangle moment computation."); + SLIC_INFO("Test moment computation of quadratic triangle"); std::vector CP = {PointType::make_point(0.6, 1.2), PointType::make_point(0.4, 1.3), @@ -309,7 +312,7 @@ TEST(primal_curvedpolygon, moments_triangle_mixed_order) using PointType = primal::Point; SLIC_INFO( - "Test checking CurvedPolygon mixed order triangle moment computation."); + "Test moment computation for curved triangle with mixed order edges"); std::vector CP = {PointType::make_point(0.6, 1.2), PointType::make_point(0.4, 1.3), @@ -335,7 +338,7 @@ TEST(primal_curvedpolygon, moments_quad_all_orders) using CurvedPolygonType = primal::CurvedPolygon; using PointType = primal::Point; - SLIC_INFO("Test checking CurvedPolygon linear triangle moment computation."); + SLIC_INFO("Test moment computation for quads of different orders"); std::vector CPorig = {PointType::make_point(0.0, 0.0), PointType::make_point(0.0, 1.0), PointType::make_point(1.0, 1.0), @@ -378,10 +381,11 @@ TEST(primal_curvedpolygon, moments_quad_all_orders) } orders[side] += 1; } - /*for (int i=0; i Date: Fri, 11 Jun 2021 18:29:17 -0700 Subject: [PATCH 22/38] Use initializer lists to construct points and vectors for Bezier curve tests --- src/axom/primal/geometry/BezierCurve.hpp | 3 +- src/axom/primal/tests/primal_bezier_curve.cpp | 77 ++++++++------- .../primal/tests/primal_bezier_intersect.cpp | 82 ++++++++-------- .../primal/tests/primal_curved_polygon.cpp | 93 +++++++++---------- 4 files changed, 119 insertions(+), 136 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index dc1315d489..55f1baaf3e 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -535,8 +535,7 @@ class BezierCurve } } } - PointType M = PointType::make_point(Mx, My); - return M; + return PointType {Mx, My}; } /*! diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index 625c52fbdf..e6b65c8a7e 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -59,8 +59,8 @@ TEST(primal_beziercurve, set_order) EXPECT_EQ(-1, bCurve.getOrder()); const int order = 1; - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(0.0, 1.6, 1.8)}; + PointType controlPoints[2] = {PointType {0.6, 1.2, 1.0}, + PointType {0.0, 1.6, 1.8}}; bCurve.setOrder(order); EXPECT_EQ(order, bCurve.getOrder()); @@ -88,8 +88,8 @@ TEST(primal_beziercurve, point_array_constructor) using PointType = primal::Point; using BezierCurveType = primal::BezierCurve; - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(0.0, 1.6, 1.8)}; + PointType controlPoints[2] = {PointType {0.6, 1.2, 1.0}, + PointType {0.0, 1.6, 1.8}}; BezierCurveType bCurve(controlPoints, 1); @@ -143,14 +143,14 @@ TEST(primal_beziercurve, evaluate) using BezierCurveType = primal::BezierCurve; const int order = 3; - PointType data[order + 1] = {PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(1.3, 1.6, 1.8), - PointType::make_point(2.9, 2.4, 2.3), - PointType::make_point(3.2, 3.5, 3.0)}; + PointType data[order + 1] = {PointType {0.6, 1.2, 1.0}, + PointType {1.3, 1.6, 1.8}, + PointType {2.9, 2.4, 2.3}, + PointType {3.2, 3.5, 3.0}}; BezierCurveType b2Curve(data, order); - PointType midtval = PointType::make_point(2.05, 2.0875, 2.0375); + PointType midtval {2.05, 2.0875, 2.0375}; // Evaluate the curve at several parameter values // Curve should interpolate endpoints @@ -178,16 +178,16 @@ TEST(primal_beziercurve_, tangent) using BezierCurveType = primal::BezierCurve; const int order = 3; - PointType data[order + 1] = {PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(1.3, 1.6, 1.8), - PointType::make_point(2.9, 2.4, 2.3), - PointType::make_point(3.2, 3.5, 3.0)}; + PointType data[order + 1] = {PointType {0.6, 1.2, 1.0}, + PointType {1.3, 1.6, 1.8}, + PointType {2.9, 2.4, 2.3}, + PointType {3.2, 3.5, 3.0}}; BezierCurveType b2Curve(data, order); - VectorType midtval = VectorType::make_vector(3.15, 2.325, 1.875); - VectorType starttval = VectorType::make_vector(2.1, 1.2, 2.4); - VectorType endtval = VectorType::make_vector(.9, 3.3, 2.1); + VectorType midtval = VectorType {3.15, 2.325, 1.875}; + VectorType starttval = VectorType {2.1, 1.2, 2.4}; + VectorType endtval = VectorType {.9, 3.3, 2.1}; // Evaluate the curve at several parameter values // Curve should be tangent to control net at endpoints @@ -214,10 +214,10 @@ TEST(primal_beziercurve, sector_area_cubic) { SLIC_INFO("Testing Bezier sector area calculation for a cubic"); const int order = 3; - PointType data[order + 1] = {PointType::make_point(0.6, 1.2), - PointType::make_point(1.3, 1.6), - PointType::make_point(2.9, 2.4), - PointType::make_point(3.2, 3.5)}; + PointType data[order + 1] = {PointType {0.6, 1.2}, + PointType {1.3, 1.6}, + PointType {2.9, 2.4}, + PointType {3.2, 3.5}}; BezierCurveType bCurve(data, order); EXPECT_TRUE(axom::utilities::isNearlyEqual(bCurve.sectorArea(), .1455)); @@ -235,10 +235,10 @@ TEST(primal_beziercurve, sector_moment_cubic) { SLIC_INFO("Testing Bezier sector moment calculation for a cubic"); const int order = 3; - PointType data[order + 1] = {PointType::make_point(0.6, 1.2), - PointType::make_point(1.3, 1.6), - PointType::make_point(2.9, 2.4), - PointType::make_point(3.2, 3.5)}; + PointType data[order + 1] = {PointType {0.6, 1.2}, + PointType {1.3, 1.6}, + PointType {2.9, 2.4}, + PointType {3.2, 3.5}}; BezierCurveType bCurve(data, order); PointType M = bCurve.sectorCentroid(); @@ -296,10 +296,10 @@ TEST(primal_beziercurve, split_cubic) using BezierCurveType = primal::BezierCurve; const int order = 3; - PointType data[order + 1] = {PointType::make_point(0.6, 1.2, 1.0), - PointType::make_point(1.3, 1.6, 1.8), - PointType::make_point(2.9, 2.4, 2.3), - PointType::make_point(3.2, 3.5, 3.0)}; + PointType data[order + 1] = {PointType {0.6, 1.2, 1.0}, + PointType {1.3, 1.6, 1.8}, + PointType {2.9, 2.4, 2.3}, + PointType {3.2, 3.5, 3.0}}; BezierCurveType b2Curve(data, order); BezierCurveType b3Curve(order); // Checks split with order constructor @@ -364,8 +364,7 @@ TEST(primal_beziercurve, split_linear) using BezierCurveType = primal::BezierCurve; const int order = 1; - PointType data[order + 1] = {PointType::make_point(-1, -5), - PointType::make_point(1, 5)}; + PointType data[order + 1] = {PointType {-1, -5}, PointType {1, 5}}; BezierCurveType b(data, order); { @@ -414,9 +413,9 @@ TEST(primal_beziercurve, split_quadratic) const int order = 2; // Control points for the three levels of the quadratic de Casteljau algorithm - PointType lev0[3] = {PointType::make_point(1.1, 1.1), - PointType::make_point(5.5, 5.5), - PointType::make_point(9.9, 2.2)}; + PointType lev0[3] = {PointType {1.1, 1.1}, + PointType {5.5, 5.5}, + PointType {9.9, 2.2}}; PointType lev1[2] = {PointType::lerp(lev0[0], lev0[1], t), PointType::lerp(lev0[1], lev0[2], t)}; @@ -484,8 +483,8 @@ TEST(primal_beziercurve, isLinear) auto curve = BezierCurveType(order); EXPECT_TRUE(curve.isLinear()); - curve[0] = PointType::make_point(1., 1.8); - curve[1] = PointType::make_point(-12., 3.5); + curve[0] = PointType {1., 1.8}; + curve[1] = PointType {-12., 3.5}; EXPECT_TRUE(curve.isLinear()); } @@ -496,14 +495,14 @@ TEST(primal_beziercurve, isLinear) EXPECT_TRUE(curve.isLinear()); // straight line - curve[0] = PointType::make_point(1, 1); - curve[1] = PointType::make_point(2, 2); - curve[2] = PointType::make_point(3, 3); + curve[0] = PointType {1, 1}; + curve[1] = PointType {2, 2}; + curve[2] = PointType {3, 3}; EXPECT_TRUE(curve.isLinear()); // move middle point and check linearity with different tolerances VectorType v(curve[2], curve[0]); - auto normal = VectorType::make_vector(-v[1], v[0]); + auto normal = VectorType {-v[1], v[0]}; curve[1].array() += 0.005 * normal.array(); SLIC_INFO("Updated curve: " << curve); diff --git a/src/axom/primal/tests/primal_bezier_intersect.cpp b/src/axom/primal/tests/primal_bezier_intersect.cpp index 4993e24a37..1ca0003a1a 100644 --- a/src/axom/primal/tests/primal_bezier_intersect.cpp +++ b/src/axom/primal/tests/primal_bezier_intersect.cpp @@ -128,13 +128,11 @@ TEST(primal_bezier_inter, linear_bezier) { SCOPED_TRACE("linear bezier simple"); - PointType data1[order + 1] = {PointType::make_point(0.0, 0.0), - PointType::make_point(1.0, 1.0)}; + PointType data1[order + 1] = {PointType {0.0, 0.0}, PointType {1.0, 1.0}}; BezierCurveType curve1(data1, order); - PointType data2[order + 1] = {PointType::make_point(0.0, 1.0), - PointType::make_point(1.0, 0.0)}; + PointType data2[order + 1] = {PointType {0.0, 1.0}, PointType {1.0, 0.0}}; BezierCurveType curve2(data2, order); std::vector exp_intersections1 = {0.5}; @@ -153,13 +151,11 @@ TEST(primal_bezier_inter, linear_bezier) { SCOPED_TRACE("linear bezier endpoints"); - PointType data1[order + 1] = {PointType::make_point(1.0, 1.0), - PointType::make_point(0.0, 0.0)}; + PointType data1[order + 1] = {PointType {1.0, 1.0}, PointType {0.0, 0.0}}; BezierCurveType curve1(data1, order); - PointType data2[order + 1] = {PointType::make_point(1.0, 1.0), - PointType::make_point(2.0, 0.0)}; + PointType data2[order + 1] = {PointType {1.0, 1.0}, PointType {2.0, 0.0}}; BezierCurveType curve2(data2, order); std::vector exp_intersections1 = {0.0}; @@ -178,13 +174,11 @@ TEST(primal_bezier_inter, linear_bezier) { SCOPED_TRACE("linear bezier non-midpoint"); - PointType data1[order + 1] = {PointType::make_point(0.0, 0.0), - PointType::make_point(4.0, 2.0)}; + PointType data1[order + 1] = {PointType {0.0, 0.0}, PointType {4.0, 2.0}}; BezierCurveType curve1(data1, order); - PointType data2[order + 1] = {PointType::make_point(-2.0, 2.0), - PointType::make_point(2.0, 0.0)}; + PointType data2[order + 1] = {PointType {-2.0, 2.0}, PointType {2.0, 0.0}}; BezierCurveType curve2(data2, order); std::vector exp_intersections1 = {.25}; @@ -226,12 +220,10 @@ TEST(primal_bezier_inter, linear_bezier_interp_params) sstr << "linear bezier perpendicular (s,t) = (" << s << "," << t << ")"; SCOPED_TRACE(sstr.str()); - PointType data1[order + 1] = {PointType::make_point(0.0, s), - PointType::make_point(1.0, s)}; + PointType data1[order + 1] = {PointType {0.0, s}, PointType {1.0, s}}; BezierCurveType curve1(data1, order); - PointType data2[order + 1] = {PointType::make_point(t, 0.0), - PointType::make_point(t, 1.0)}; + PointType data2[order + 1] = {PointType {t, 0.0}, PointType {t, 1.0}}; BezierCurveType curve2(data2, order); std::vector exp_intersections1 = {t}; @@ -270,18 +262,18 @@ TEST(primal_bezier_inter, no_intersections_bezier) const int order = 3; // cubic line - PointType data1[order + 1] = {PointType::make_point(0.0, 0.0), - PointType::make_point(1.0, 0.0), - PointType::make_point(2.0, 0.0), - PointType::make_point(3.0, 0.0)}; + PointType data1[order + 1] = {PointType {0.0, 0.0}, + PointType {1.0, 0.0}, + PointType {2.0, 0.0}, + PointType {3.0, 0.0}}; BezierCurveType curve1(data1, order); // Cubic curve - PointType data2[order + 1] = {PointType::make_point(0.0, 0.5), - PointType::make_point(1.0, 1.0), - PointType::make_point(2.0, 3.0), - PointType::make_point(3.0, 1.5)}; + PointType data2[order + 1] = {PointType {0.0, 0.5}, + PointType {1.0, 1.0}, + PointType {2.0, 3.0}, + PointType {3.0, 1.5}}; BezierCurveType curve2(data2, order); std::vector exp_intersections; @@ -317,10 +309,10 @@ TEST(primal_bezier_inter, cubic_quadratic_bezier) curve1[0] = data1; // Cubic curve - PointType data2[order2 + 1] = {PointType::make_point(0.0, 0.5), - PointType::make_point(1.0, -1.0), - PointType::make_point(2.0, 1.0), - PointType::make_point(3.0, -0.5)}; + PointType data2[order2 + 1] = {PointType {0.0, 0.5}, + PointType {1.0, -1.0}, + PointType {2.0, 1.0}, + PointType {3.0, -0.5}}; BezierCurveType curve2(data2, order2); // Note: same intersection params for curve and line @@ -338,7 +330,7 @@ TEST(primal_bezier_inter, cubic_quadratic_bezier) { curve1[i][0] = curve1[i][0] * (otherorder - 1) / (1.0 * otherorder); } - curve1[otherorder] = PointType::make_point(3.0, 0); + curve1[otherorder] = PointType {3.0, 0}; SLIC_INFO("Testing w/ order 3 and " << otherorder); std::stringstream sstr; @@ -367,18 +359,18 @@ TEST(primal_bezier_inter, cubic_bezier_varying_eps) const int order = 3; // cubic line - PointType data1[order + 1] = {PointType::make_point(0.0, 0.0), - PointType::make_point(1.0, 0.0), - PointType::make_point(2.0, 0.0), - PointType::make_point(3.0, 0.0)}; + PointType data1[order + 1] = {PointType {0.0, 0.0}, + PointType {1.0, 0.0}, + PointType {2.0, 0.0}, + PointType {3.0, 0.0}}; BezierCurveType curve1(data1, order); // Cubic curve - PointType data2[order + 1] = {PointType::make_point(0.0, 0.5), - PointType::make_point(1.0, -1.0), - PointType::make_point(2.0, 1.0), - PointType::make_point(3.0, -0.5)}; + PointType data2[order + 1] = {PointType {0.0, 0.5}, + PointType {1.0, -1.0}, + PointType {2.0, 1.0}, + PointType {3.0, -0.5}}; BezierCurveType curve2(data2, order); // Note: same intersection params for curve and line @@ -416,17 +408,17 @@ TEST(primal_bezier_inter, cubic_bezier_nine_intersections) // A configuration of two cubic bezier curves that intersect at nine points const int order = 3; - PointType data1[order + 1] = {PointType::make_point(100, 90), - PointType::make_point(125, 260), - PointType::make_point(125, 0), - PointType::make_point(140, 145)}; + PointType data1[order + 1] = {PointType {100, 90}, + PointType {125, 260}, + PointType {125, 0}, + PointType {140, 145}}; BezierCurveType curve1(data1, order); - PointType data2[order + 1] = {PointType::make_point(75, 110), - PointType::make_point(265, 120), - PointType::make_point(0, 130), - PointType::make_point(145, 135)}; + PointType data2[order + 1] = {PointType {75, 110}, + PointType {265, 120}, + PointType {0, 130}, + PointType {145, 135}}; BezierCurveType curve2(data2, order); const double eps = 1E-16; diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 488bf7b0ba..a9c08f965e 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -116,8 +116,7 @@ TEST(primal_curvedpolygon, add_edges) CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.0, 1.6)}; + PointType controlPoints[2] = {PointType {0.6, 1.2}, PointType {0.0, 1.6}}; BezierCurveType bCurve(controlPoints, 1); @@ -153,14 +152,13 @@ TEST(primal_curvedpolygon, isClosed) EXPECT_EQ(0, bPolygon.numEdges()); EXPECT_EQ(false, bPolygon.isClosed()); - 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}; - std::vector subCP = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; + std::vector subCP = {PointType {0.6, 1.2}, PointType {0.3, 2.0}}; std::vector suborders = {1}; CurvedPolygonType subPolygon = createPolygon(subCP, suborders); EXPECT_EQ(false, subPolygon.isClosed()); @@ -185,10 +183,10 @@ TEST(primal_curvedpolygon, split_edge) SLIC_INFO("Test checking CurvedPolygon edge split."); - 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 orders32 = {1, 1, 1}; CurvedPolygonType bPolygon32 = createPolygon(CP, orders32); @@ -230,13 +228,10 @@ TEST(primal_curvedpolygon, moments_triangle_degenerate) EXPECT_EQ(0.0, bPolygon.area()); PointType origin = PointType::make_point(0.0, 0.0); - PointType controlPoints[2] = {PointType::make_point(0.6, 1.2), - PointType::make_point(0.3, 2.0)}; + PointType controlPoints[2] = {PointType {0.6, 1.2}, PointType {0.3, 2.0}}; - PointType controlPoints2[2] = {PointType::make_point(0.3, 2.0), - PointType::make_point(0.0, 1.6)}; - PointType controlPoints3[2] = {PointType::make_point(0.0, 1.6), - PointType::make_point(0.6, 1.2)}; + PointType controlPoints2[2] = {PointType {0.3, 2.0}, PointType {0.0, 1.6}}; + PointType controlPoints3[2] = {PointType {0.0, 1.6}, PointType {0.6, 1.2}}; BezierCurveType bCurve(controlPoints, 1); bPolygon.addEdge(bCurve); @@ -262,10 +257,10 @@ TEST(primal_curvedpolygon, moments_triangle_linear) using PointType = primal::Point; SLIC_INFO("Test moment computation of a linear triangle"); - 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 bPolygon = createPolygon(CP, orders); @@ -286,19 +281,19 @@ TEST(primal_curvedpolygon, moments_triangle_quadratic) SLIC_INFO("Test moment computation of quadratic triangle"); - 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 bPolygon = createPolygon(CP, orders); CoordType trueA = -0.097333333333333; - PointType trueC = PointType::make_point(.294479452054794, 1.548219178082190); + PointType trueC {.294479452054794, 1.548219178082190}; checkMoments(bPolygon, trueA, trueC, 1e-15, 1e-14); } @@ -314,18 +309,18 @@ TEST(primal_curvedpolygon, moments_triangle_mixed_order) SLIC_INFO( "Test moment computation for curved triangle with mixed order edges"); - 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.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.6, 1.2}}; std::vector orders = {2, 2, 1}; CurvedPolygonType bPolygon = createPolygon(CP, orders); CoordType trueA = -.0906666666666666666666; - PointType trueC = PointType::make_point(.2970147058823527, 1.55764705882353); + PointType trueC {.2970147058823527, 1.55764705882353}; checkMoments(bPolygon, trueA, trueC, 1e-14, 1e-15); } @@ -339,11 +334,11 @@ TEST(primal_curvedpolygon, moments_quad_all_orders) using PointType = primal::Point; SLIC_INFO("Test moment computation for quads of different orders"); - std::vector CPorig = {PointType::make_point(0.0, 0.0), - PointType::make_point(0.0, 1.0), - PointType::make_point(1.0, 1.0), - PointType::make_point(1.0, 0.0), - PointType::make_point(0.0, 0.0)}; + std::vector CPorig = {PointType {0.0, 0.0}, + PointType {0.0, 1.0}, + PointType {1.0, 1.0}, + PointType {1.0, 0.0}, + PointType {0.0, 0.0}}; std::vector orders = {1, 1, 1, 1}; CurvedPolygonType bPolygon = createPolygon(CPorig, orders); @@ -359,23 +354,21 @@ TEST(primal_curvedpolygon, moments_quad_all_orders) { for(int i = 1; i < p; ++i) { + const int offset = i + (side * p); + const double t = static_cast(i) / p; switch(side) { case 0: - CP.insert(CP.begin() + i + (side * p), - (PointType::make_point(0.0, 1.0 * i / p))); + CP.insert(CP.begin() + offset, PointType {0., t}); break; case 1: - CP.insert(CP.begin() + i + (side * p), - (PointType::make_point(1.0 * i / p, 1.0))); + CP.insert(CP.begin() + offset, PointType {t, 1.}); break; case 2: - CP.insert(CP.begin() + i + (side * p), - (PointType::make_point(1.0, 1.0 - (1.0 * i / p)))); + CP.insert(CP.begin() + offset, PointType {1., 1. - t}); break; case 3: - CP.insert(CP.begin() + i + (side * p), - (PointType::make_point(1.0 - (1.0 * i / p), 0.0))); + CP.insert(CP.begin() + offset, PointType {1. - t, 0.}); break; } } From eeec51eb5e6fd22a7905b0322a14f26d90be9966 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 14 Jun 2021 19:58:52 -0700 Subject: [PATCH 23/38] Bugfix: Curved polygon with only two edges can be closed Fixes CurvedPolygon::isClosed() and adds unit test. --- src/axom/primal/geometry/CurvedPolygon.hpp | 45 +++++++++---------- .../primal/tests/primal_curved_polygon.cpp | 30 +++++++++++++ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 7d7b96a825..5e50849b2d 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -163,39 +163,38 @@ class CurvedPolygon /*! * \brief Check closedness of a CurvedPolygon * - * Check is that the endpoint of each edge coincides with startpoint of next edge - * \return True, if the polygon is closed, False otherwise + * A CurvedPolygon is closed when the endpoint of each edge coincides with startpoint of next edge + * \return \a true, if the polygon is closed, \a false otherwise */ bool isClosed(double tol = 1e-5) const { - const int ngon = numEdges(); - if(ngon <= 2) + using axom::utilities::isNearlyEqual; + + const double sq_tol = tol * tol; + const int nEdges = numEdges(); + + // initial basic check: no edges, or one edge or linear or quadratic order cannot be closed + if(nEdges < 1 || (nEdges == 1 && m_edges[0].getOrder() <= 2)) { return false; } - else + + // foreach edge: check last vertex of current edge against first vertex of next edge + for(int i = 1; i < nEdges; ++i) { - for(int p = 0; p < NDIMS; ++p) + const auto ord = m_edges[i - 1].getOrder(); + const auto& lastPrev = m_edges[i - 1][ord]; + const auto& firstCur = m_edges[i][0]; + if(!isNearlyEqual(squared_distance(lastPrev, firstCur), 0., sq_tol)) { - for(int i = 0; i < (ngon - 1); ++i) - { - if(!axom::utilities::isNearlyEqual(m_edges[i][m_edges[i].getOrder()][p], - m_edges[i + 1][0][p], - tol)) - { - return false; - } - } - if(!axom::utilities::isNearlyEqual( - m_edges[ngon - 1][m_edges[ngon - 1].getOrder()][p], - m_edges[0][0][p], - tol)) - { - return false; - } + return false; } } - return true; + // check last edge against first + const auto ord = m_edges[nEdges - 1].getOrder(); + const auto& lastPrev = m_edges[nEdges - 1][ord]; + const auto& firstCur = m_edges[0][0]; + return isNearlyEqual(squared_distance(lastPrev, firstCur), 0., sq_tol); } /// \brief Returns the area enclosed by the CurvedPolygon diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index a9c08f965e..de64329dba 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -172,6 +172,36 @@ TEST(primal_curvedpolygon, isClosed) EXPECT_EQ(false, bPolygon.isClosed(1e-15)); } +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, isClosed_BiGon) +{ + const int DIM = 2; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + + SLIC_INFO("Test checking if CurvedPolygon is closed for a Bi-Gon"); + + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + EXPECT_EQ(false, bPolygon.isClosed()); + + // Bi-gon defined by a quadratic edge and a straight line + std::vector CP = {PointType {0.8, .25}, + PointType {2.0, .50}, + PointType {0.8, .75}, + PointType {0.8, .25}}; + std::vector orders = {2, 1}; + + CurvedPolygonType poly = createPolygon(CP, orders); + EXPECT_TRUE(poly.isClosed()); + + // modify a vertex of the quadratic and check again + CurvedPolygonType poly2 = poly; + poly2[0][2] = PointType {0.8, 1.0}; + EXPECT_FALSE(poly2.isClosed()); +} + //---------------------------------------------------------------------------------- TEST(primal_curvedpolygon, split_edge) { From e680f861fefa8a4076043ac65cfec1ae95d04da5 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 15:24:16 -0700 Subject: [PATCH 24/38] Adds empty() and boundingBox() to primal::CurvedPolygon --- src/axom/primal/geometry/CurvedPolygon.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 5e50849b2d..a68f542465 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -18,6 +18,7 @@ #include "axom/primal/geometry/Vector.hpp" #include "axom/primal/geometry/NumericArray.hpp" #include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/BoundingBox.hpp" #include #include @@ -51,6 +52,7 @@ class CurvedPolygon using VectorType = Vector; using NumArrayType = NumericArray; using BezierCurveType = BezierCurve; + using BoundingBoxType = typename BezierCurveType::BoundingBoxType; public: /// Default constructor for an empty polygon @@ -88,6 +90,9 @@ class CurvedPolygon /// Clears the list of edges void clear() { m_edges.clear(); } + /// Returns true if the polygon has no edges + bool empty() const { return m_edges.empty(); } + /// \name Operations on edges /// @{ @@ -257,6 +262,17 @@ class CurvedPolygon } } + /// Returns an axis-aligned bounding box containing the CurvedPolygon + BoundingBoxType boundingBox() const + { + BoundingBoxType bbox; + for(const auto& cp : m_edges) + { + bbox.addBox(cp.boundingBox()); + } + return bbox; + } + private: std::vector> m_edges; }; From 62fc789c342a8313603af4a96b9a73e418fdc5af Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Sun, 20 Jun 2021 15:24:47 -0700 Subject: [PATCH 25/38] Includes axom/config.hpp in primal's intersect header --- src/axom/primal/operators/intersect.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index e6e7baac13..7a488e0939 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -12,6 +12,7 @@ #ifndef AXOM_PRIMAL_INTERSECT_HPP_ #define AXOM_PRIMAL_INTERSECT_HPP_ +#include "axom/config.hpp" #include "axom/core/Macros.hpp" #include "axom/core/utilities/Utilities.hpp" From 1446ba95bb80a8847485ae192e8dce1144185a5b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 22 Nov 2021 20:23:50 -0800 Subject: [PATCH 26/38] Renamed `primal::Polygon::centroid()` to `primal::Polygon::vertexMean()` The new name is more accurate: The function is computing the mean of the vertex positions, not the centroid. --- src/axom/primal/geometry/Polygon.hpp | 22 +++---- src/axom/primal/tests/primal_clip.cpp | 88 ++++++++++++++------------- src/axom/quest/InOutOctree.hpp | 2 +- 3 files changed, 54 insertions(+), 58 deletions(-) diff --git a/src/axom/primal/geometry/Polygon.hpp b/src/axom/primal/geometry/Polygon.hpp index 0b47173729..194e719880 100644 --- a/src/axom/primal/geometry/Polygon.hpp +++ b/src/axom/primal/geometry/Polygon.hpp @@ -13,11 +13,9 @@ #define AXOM_PRIMAL_POLYGON_HPP_ #include "axom/primal/geometry/Point.hpp" -#include "axom/primal/geometry/Vector.hpp" -#include "axom/primal/geometry/NumericArray.hpp" #include -#include // for std::ostream +#include namespace axom { @@ -45,8 +43,6 @@ class Polygon { public: using PointType = Point; - using VectorType = Vector; - using NumArrayType = NumericArray; private: using Coords = std::vector; @@ -84,26 +80,24 @@ class Polygon const PointType& operator[](int idx) const { return m_vertices[idx]; } /*! - * \brief Computes the centroid as the average of the polygon's vertex - * positions - * - * \return The centroid of the polygon's vertices + * \brief Computes the average of the polygon's vertex positions * + * \return A point at the mean of the polygon's vertices * \pre polygon.isValid() is true */ - PointType centroid() const + PointType vertexMean() const { SLIC_ASSERT(isValid()); - NumArrayType sum; + PointType sum; for(int i = 0; i < numVertices(); ++i) { - sum += m_vertices[i].array(); + sum.array() += m_vertices[i].array(); } - sum /= numVertices(); + sum.array() /= numVertices(); - return PointType(sum); + return sum; } /*! diff --git a/src/axom/primal/tests/primal_clip.cpp b/src/axom/primal/tests/primal_clip.cpp index 578f206cb9..7b87081dbd 100644 --- a/src/axom/primal/tests/primal_clip.cpp +++ b/src/axom/primal/tests/primal_clip.cpp @@ -43,16 +43,18 @@ TEST(primal_clip, simple_clip) bbox.addPoint(PointType::ones()); PointType points[] = { - PointType::make_point(2, 2, 2), - PointType::make_point(2, 2, 4), - PointType::make_point(2, 4, 2), - PointType::make_point(-100, -100, 0.5), - PointType::make_point(-100, 100, 0.5), - PointType::make_point(100, 0, 0.5), - PointType::make_point(0.25, 0.25, 0.5), - PointType::make_point(0.75, 0.25, 0.5), - PointType::make_point(0.66, 0.5, 0.5), - PointType::make_point(1.5, 0.5, 0.5), + PointType {2, 2, 2}, + PointType {2, 2, 4}, + PointType {2, 4, 2}, + + PointType {-100, -100, 0.5}, + PointType {-100, 100, 0.5}, + PointType {100, 0, 0.5}, + + PointType {0.25, 0.25, 0.5}, + PointType {0.75, 0.25, 0.5}, + PointType {0.66, 0.5, 0.5}, + PointType {1.5, 0.5, 0.5}, }; { @@ -78,7 +80,7 @@ TEST(primal_clip, simple_clip) PolygonType poly = axom::primal::clip(tri, bbox); EXPECT_EQ(4, poly.numVertices()); - EXPECT_EQ(PointType(.5), poly.centroid()); + EXPECT_EQ(PointType(.5), poly.vertexMean()); SLIC_INFO("Intersection of triangle " << tri << " and bounding box " << bbox << " is polygon" << poly); @@ -101,12 +103,12 @@ TEST(primal_clip, unit_simplex) double delta = 1e-5; // Test the "unit simplex", and a jittered version - PointType points[] = {PointType::make_point(1, 0, 0), - PointType::make_point(0, 1, 0), - PointType::make_point(0, 0, 1), - PointType::make_point(1 + delta, delta, delta), - PointType::make_point(delta, 1 + delta, delta), - PointType::make_point(delta, delta, 1 + delta)}; + PointType points[] = {PointType {1, 0, 0}, + PointType {0, 1, 0}, + PointType {0, 0, 1}, + PointType {1 + delta, delta, delta}, + PointType {delta, 1 + delta, delta}, + PointType {delta, delta, 1 + delta}}; BoundingBoxType bbox; bbox.addPoint(PointType::zero()); @@ -150,25 +152,25 @@ TEST(primal_clip, boundingBoxOptimization) PointType midpoint = PointType::zero(); PointType points[] = { - PointType::make_point(VAL1, VAL2, 0), - PointType::make_point(-VAL1, VAL2, 0), - PointType::make_point(VAL1, -VAL2, 0), - PointType::make_point(-VAL1, -VAL2, 0), - - PointType::make_point(VAL1, 0, VAL2), - PointType::make_point(-VAL1, 0, VAL2), - PointType::make_point(VAL1, 0, -VAL2), - PointType::make_point(-VAL1, 0, -VAL2), - - PointType::make_point(0, VAL2, VAL1), - PointType::make_point(0, VAL2, -VAL1), - PointType::make_point(0, -VAL2, VAL1), - PointType::make_point(0, -VAL2, -VAL1), - - PointType::make_point(0, VAL1, VAL2), - PointType::make_point(0, -VAL1, VAL2), - PointType::make_point(0, VAL1, -VAL2), - PointType::make_point(0, -VAL1, -VAL2), + PointType {VAL1, VAL2, 0}, + PointType {-VAL1, VAL2, 0}, + PointType {VAL1, -VAL2, 0}, + PointType {-VAL1, -VAL2, 0}, + + PointType {VAL1, 0, VAL2}, + PointType {-VAL1, 0, VAL2}, + PointType {VAL1, 0, -VAL2}, + PointType {-VAL1, 0, -VAL2}, + + PointType {0, VAL2, VAL1}, + PointType {0, VAL2, -VAL1}, + PointType {0, -VAL2, VAL1}, + PointType {0, -VAL2, -VAL1}, + + PointType {0, VAL1, VAL2}, + PointType {0, -VAL1, VAL2}, + PointType {0, VAL1, -VAL2}, + PointType {0, -VAL1, -VAL2}, }; for(int i = 0; i < 16; i += 2) @@ -187,20 +189,20 @@ TEST(primal_clip, experimentalData) const double EPS = 1e-8; // Triangle 248 from sphere mesh - TriangleType tri(PointType::make_point(0.405431, 3.91921, 3.07821), - PointType::make_point(1.06511, 3.96325, 2.85626), - PointType::make_point(0.656002, 4.32465, 2.42221)); + TriangleType tri(PointType {0.405431, 3.91921, 3.07821}, + PointType {1.06511, 3.96325, 2.85626}, + PointType {0.656002, 4.32465, 2.42221}); // Block index {grid pt: (19,29,24); level: 5} from InOutOctree - BoundingBoxType box12(PointType::make_point(0.937594, 4.06291, 2.50025), - PointType::make_point(1.25012, 4.37544, 2.81278)); + BoundingBoxType box12(PointType {0.937594, 4.06291, 2.50025}, + PointType {1.25012, 4.37544, 2.81278}); PolygonType poly = axom::primal::clip(tri, box12); EXPECT_EQ(3, poly.numVertices()); SLIC_INFO("Intersection of triangle " << tri << " \n\t and bounding box " << box12 << " \n\t is polygon" - << poly << " with centroid " << poly.centroid()); + << poly << " with centroid " << poly.vertexMean()); // Check that the polygon vertices are on the triangle for(int i = 0; i < poly.numVertices(); ++i) @@ -226,7 +228,7 @@ TEST(primal_clip, experimentalData) // Check that the polygon centroid is on the triangle { - PointType centroid = poly.centroid(); + PointType centroid = poly.vertexMean(); PointType bary = tri.physToBarycentric(centroid); PointType reconstructed = tri.baryToPhysical(bary); diff --git a/src/axom/quest/InOutOctree.hpp b/src/axom/quest/InOutOctree.hpp index 0111786f8e..9bf9fd3fc8 100644 --- a/src/axom/quest/InOutOctree.hpp +++ b/src/axom/quest/InOutOctree.hpp @@ -1123,7 +1123,7 @@ typename std::enable_if::type InOutOctree::withinGrayBlock } } - triPt = poly.centroid(); + triPt = poly.vertexMean(); /// Use a ray from the query point to the triangle point to find an /// intersection. Note: We have to check all triangles to ensure that From ad93e4524a4827aa007e8dc70c57250146269f8a Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Mon, 22 Nov 2021 21:18:58 -0800 Subject: [PATCH 27/38] Use axom::Array in primal::Polygon instead of std::vector --- src/axom/primal/geometry/Polygon.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/axom/primal/geometry/Polygon.hpp b/src/axom/primal/geometry/Polygon.hpp index 194e719880..eb3db11078 100644 --- a/src/axom/primal/geometry/Polygon.hpp +++ b/src/axom/primal/geometry/Polygon.hpp @@ -12,9 +12,9 @@ #ifndef AXOM_PRIMAL_POLYGON_HPP_ #define AXOM_PRIMAL_POLYGON_HPP_ +#include "axom/core/Array.hpp" #include "axom/primal/geometry/Point.hpp" -#include #include namespace axom @@ -42,10 +42,7 @@ template class Polygon { public: - using PointType = Point; - -private: - using Coords = std::vector; + using PointType = primal::Point; public: /*! Default constructor for an empty polygon */ @@ -91,11 +88,12 @@ class Polygon PointType sum; - for(int i = 0; i < numVertices(); ++i) + const int sz = numVertices(); + for(int i = 0; i < sz; ++i) { sum.array() += m_vertices[i].array(); } - sum.array() /= numVertices(); + sum.array() /= sz; return sum; } @@ -133,7 +131,7 @@ class Polygon bool isValid() const { return m_vertices.size() >= 3; } private: - Coords m_vertices; + axom::Array m_vertices; }; //------------------------------------------------------------------------------ From 2c18c8ec1175d998ca773ab8108a6680dad950dd Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 23 Nov 2021 10:45:55 -0800 Subject: [PATCH 28/38] Outsources moment computation (area and centroid) in BezierCurve and CurvedPolygon These are now provided as primal operators: * BezierCurve: `sector_area()` / `sector_centroid()` * CurvedPolygon: `area()` / `centroid()` There operations only apply to 2D (polynomial) BezierCurves, so don't belong in the dimension-templated classes. --- src/axom/primal/CMakeLists.txt | 1 + src/axom/primal/geometry/BezierCurve.hpp | 234 ------------- src/axom/primal/geometry/CurvedPolygon.hpp | 48 --- src/axom/primal/operators/compute_moments.hpp | 326 ++++++++++++++++++ src/axom/primal/tests/CMakeLists.txt | 1 + src/axom/primal/tests/primal_bezier_curve.cpp | 209 ----------- .../primal/tests/primal_compute_moments.cpp | 250 ++++++++++++++ .../primal/tests/primal_curved_polygon.cpp | 20 +- 8 files changed, 590 insertions(+), 499 deletions(-) create mode 100644 src/axom/primal/operators/compute_moments.hpp create mode 100644 src/axom/primal/tests/primal_compute_moments.cpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 539a9ec8a4..2189965b30 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -43,6 +43,7 @@ set( primal_headers operators/orientation.hpp operators/squared_distance.hpp operators/compute_bounding_box.hpp + operators/compute_moments.hpp operators/in_sphere.hpp operators/split.hpp diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 55f1baaf3e..1ef0cb0826 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -25,186 +25,12 @@ #include "axom/primal/operators/squared_distance.hpp" #include -#include #include namespace axom { namespace primal { -namespace internal -{ -/// Utility class that caches precomputed coefficient matrices for sectorArea computation -template -class MemoizedSectorAreaWeights -{ -public: - using SectorWeights = numerics::Matrix; - - MemoizedSectorAreaWeights() = default; - - ~MemoizedSectorAreaWeights() - { - for(auto& p : m_sectorWeightsMap) - { - delete[] p.second->data(); // delete the matrix's data - delete p.second; // delete the matrix - p.second = nullptr; - } - m_sectorWeightsMap.clear(); - } - - /// Returns a memoized matrix of coeficients for sector area computation - const SectorWeights& getWeights(int order) const - { - // Compute and cache the weights if they are not already available - if(m_sectorWeightsMap.find(order) == m_sectorWeightsMap.end()) - { - SectorWeights* weights = generateBezierCurveSectorWeights(order); - m_sectorWeightsMap[order] = weights; - } - - return *(m_sectorWeightsMap[order]); - } - -private: - /*! - * \brief Computes the weights for BezierCurve's sectorArea() function - * - * \param order The polynomial order of the curve - * \return An anti-symmetric matrix with (order+1)*{order+1) entries - * containing the integration weights for entry (i,j) - * - * The derivation is provided in: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ - SectorWeights* generateBezierCurveSectorWeights(int ord) const - { - const bool memoryIsExternal = true; - const int SZ = ord + 1; - SectorWeights* weights = - new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); - - T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); - for(int i = 0; i <= ord; ++i) - { - (*weights)(i, i) = 0.; // zero on the diagonal - for(int j = i + 1; j <= ord; ++j) - { - double val = 0.; - if(i != j) - { - T binom_ij_i = static_cast(utilities::binomialCoefficient(i + j, i)); - T binom_2nij_nj = static_cast( - utilities::binomialCoefficient(2 * ord - i - j, ord - j)); - - val = ((j - i) * ord) / binom_2n_n * - (binom_ij_i / static_cast(i + j)) * - (binom_2nij_nj / (2. * ord - j - i)); - } - (*weights)(i, j) = val; // antisymmetric - (*weights)(j, i) = -val; - } - } - return weights; - } - -private: - mutable std::map m_sectorWeightsMap; -}; - -/// Utility class that caches precomputed coefficient matrices for sectorCentroid() computation -template -class MemoizedSectorCentroidWeights -{ -public: - using SectorWeights = numerics::Matrix; - - MemoizedSectorCentroidWeights() = default; - - ~MemoizedSectorCentroidWeights() - { - for(auto& p : m_sectorWeightsMap) // for each matrix of weights - { - delete[] p.second->data(); // delete the matrix's data - delete p.second; // delete the matrix - p.second = nullptr; - } - m_sectorWeightsMap.clear(); - } - - /// Returns a memoized matrix of sector moment coeficients for component \a dim or order \a order - const SectorWeights& getWeights(int order, int dim) const - { - // Compute and cache the weights if they are not already available - if(m_sectorWeightsMap.find(std::make_pair(order, dim)) == - m_sectorWeightsMap.end()) - { - auto vec = generateBezierCurveSectorCentroidWeights(order); - for(int d = 0; d <= order; ++d) - { - m_sectorWeightsMap[std::make_pair(order, d)] = vec[d]; - } - } - - return *(m_sectorWeightsMap[std::make_pair(order, dim)]); - } - - /*! - * \brief Computes the weights for BezierCurve's sectorCentroid() function - * - * \param order The polynomial order of the curve - * \return An anti-symmetric matrix with (order+1)*{order+1) entries - * containing the integration weights for entry (i,j) - * - * The derivation is provided in: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ - std::vector generateBezierCurveSectorCentroidWeights(int ord) const - { - const bool memoryIsExternal = true; - const int SZ = ord + 1; - - std::vector weights; - weights.resize(SZ); - for(int k = 0; k <= ord; ++k) - { - SectorWeights* weights_k = - new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); - for(int i = 0; i <= ord; ++i) - { - (*weights_k)(i, i) = 0.; // zero on the diagonal - for(int j = i + 1; j <= ord; ++j) - { - double val = 0.; - if(i != j) - { - T binom_n_i = static_cast(utilities::binomialCoefficient(ord, i)); - T binom_n_j = static_cast(utilities::binomialCoefficient(ord, j)); - T binom_n_k = static_cast(utilities::binomialCoefficient(ord, k)); - T binom_3n2_ijk1 = static_cast( - utilities::binomialCoefficient(3 * ord - 2, i + j + k - 1)); - - val = (1. * (j - i)) / (3. * (3 * ord - 1)) * - (1. * binom_n_i * binom_n_j * binom_n_k / (1. * binom_3n2_ijk1)); - } - (*weights_k)(i, j) = val; // antisymmetric - (*weights_k)(j, i) = -val; - } - } - weights[k] = weights_k; - } - return weights; - } - -private: - mutable std::map, SectorWeights*> m_sectorWeightsMap; -}; - -} // namespace internal - // Forward declare the templated classes and operator functions template class BezierCurve; @@ -505,66 +331,6 @@ class BezierCurve return; } - /*! - * \brief Calculates the sector centroid of a Bezier Curve - * - * This is the centroid of the region between the curve and the origin. - * The equation and derivation are generalizations of: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ - PointType sectorCentroid() const - { - // Weights for each polynomial order's centroid are precomputed and memoized - static internal::MemoizedSectorCentroidWeights s_weights; - - T Mx = 0; - T My = 0; - const int ord = getOrder(); - for(int r = 0; r <= ord; ++r) - { - const auto& weights_r = s_weights.getWeights(ord, r); - for(int p = 0; p <= ord; ++p) - { - for(int q = 0; q <= ord; ++q) - { - Mx += weights_r(p, q) * m_controlPoints[p][1] * - m_controlPoints[q][0] * m_controlPoints[r][0]; - My += weights_r(p, q) * m_controlPoints[p][1] * - m_controlPoints[q][0] * m_controlPoints[r][1]; - } - } - } - return PointType {Mx, My}; - } - - /*! - * \brief Calculates the sector area of a Bezier Curve - * - * The sector area is the area between the curve and the origin. - * The equation and derivation is described in: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ - T sectorArea() const - { - // Weights for each polynomial order are precomputed and memoized - static internal::MemoizedSectorAreaWeights s_weights; - - T A = 0; - const int ord = getOrder(); - const auto& weights = s_weights.getWeights(ord); - - for(int p = 0; p <= ord; ++p) - { - for(int q = 0; q <= ord; ++q) - { - A += weights(p, q) * m_controlPoints[p][1] * m_controlPoints[q][0]; - } - } - return A; - } - /*! * \brief Predicate to check if the Bezier curve is approximately linear * diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index a68f542465..e6a924f505 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -202,54 +202,6 @@ class CurvedPolygon return isNearlyEqual(squared_distance(lastPrev, firstCur), 0., sq_tol); } - /// \brief Returns the area enclosed by the CurvedPolygon - T area(double tol = 1e-8) const - { - const int ngon = numEdges(); - T A = 0.0; - if(!isClosed(1e3 * tol)) - { - SLIC_DEBUG("Warning! The area is 0 because the element is not closed."); - return A; - } - else - { - for(int ed = 0; ed < ngon; ++ed) - { - A += m_edges[ed].sectorArea(); - } - return A; - } - } - - /// \brief Returns the centroid of the CurvedPolygon - PointType centroid(double tol = 1e-8) const - { - const int ngon = numEdges(); - PointType M = PointType::make_point(0.0, 0.0); - if(!isClosed(1e3 * tol)) - { - SLIC_DEBUG( - "Warning! The moments are 0 because the element is not closed."); - return M; - } - else - { - const T A = area(); - if(A != 0.) - { - for(int ed = 0; ed < ngon; ++ed) - { - PointType Mc = m_edges[ed].sectorCentroid(); - M[0] += (Mc[0]); - M[1] += (Mc[1]); - } - M.array() /= A; - } - return M; - } - } - /// \brief Reverses orientation of a CurvedPolygon void reverseOrientation() { diff --git a/src/axom/primal/operators/compute_moments.hpp b/src/axom/primal/operators/compute_moments.hpp new file mode 100644 index 0000000000..06829225ec --- /dev/null +++ b/src/axom/primal/operators/compute_moments.hpp @@ -0,0 +1,326 @@ +// 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) + +#ifndef AXOM_PRIMAL_COMPUTE_MOMENTS_HPP_ +#define AXOM_PRIMAL_COMPUTE_MOMENTS_HPP_ + +/*! + * \file compute_moments.hpp + * + * \brief Consists of a set of methods to compute areas/volumes and centroids + * for Polygons and CurvedPolygons composed of BezierCurves + */ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/CurvedPolygon.hpp" + +#include +#include + +namespace axom +{ +namespace primal +{ +namespace internal +{ +template +class MemoizedSectorAreaWeights; +template +class MemoizedSectorCentroidWeights; +} // namespace internal + +/*! + * \brief Calculates the sector area of a planar Bezier Curve + * + * The sector area is the area between the curve and the origin. + * The equation and derivation is described in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ +template +T sector_area(const primal::BezierCurve& curve) +{ + // Weights for each polynomial order are precomputed and memoized + static internal::MemoizedSectorAreaWeights s_weights; + + T A = 0; + const int ord = curve.getOrder(); + const auto& weights = s_weights.getWeights(ord); + + for(int p = 0; p <= ord; ++p) + { + for(int q = 0; q <= ord; ++q) + { + A += weights(p, q) * curve[p][1] * curve[q][0]; + } + } + return A; +} + +/*! + * \brief Calculates the sector centroid of a planar Bezier Curve + * + * This is the centroid of the region between the curve and the origin. + * The equation and derivation are generalizations of: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ +template +primal::Point sector_centroid(const primal::BezierCurve& curve) +{ + // Weights for each polynomial order's centroid are precomputed and memoized + static internal::MemoizedSectorCentroidWeights s_weights; + + T Mx = 0; + T My = 0; + const int ord = curve.getOrder(); + for(int r = 0; r <= ord; ++r) + { + const auto& weights_r = s_weights.getWeights(ord, r); + for(int p = 0; p <= ord; ++p) + { + for(int q = 0; q <= ord; ++q) + { + Mx += weights_r(p, q) * curve[p][1] * curve[q][0] * curve[r][0]; + My += weights_r(p, q) * curve[p][1] * curve[q][0] * curve[r][1]; + } + } + } + return primal::Point {Mx, My}; +} + +/// \brief Returns the area enclosed by the CurvedPolygon +template +T area(const primal::CurvedPolygon& poly, double tol = 1e-8) +{ + const int ngon = poly.numEdges(); + T A = 0.0; + if(!poly.isClosed(1e3 * tol)) + { + SLIC_DEBUG( + "Warning! The area is 0 because the curved polygon is not closed."); + return A; + } + else + { + for(int ed = 0; ed < ngon; ++ed) + { + A += primal::sector_area(poly[ed]); + } + return A; + } +} + +/// \brief Returns the centroid of the CurvedPolygon +template +primal::Point centroid(const primal::CurvedPolygon& poly, + double tol = 1e-8) +{ + using PointType = primal::Point; + + const int ngon = poly.numEdges(); + PointType M; + + if(!poly.isClosed(1e3 * tol)) + { + SLIC_DEBUG( + "Warning! The moments are 0 because the curved polygon is not closed."); + return M; + } + else + { + const T A = area(poly, tol); + if(A != 0.) + { + for(int ed = 0; ed < ngon; ++ed) + { + M.array() += primal::sector_centroid(poly[ed]).array(); + } + M.array() /= A; + } + return M; + } +} + +namespace internal +{ +/// Utility class that caches precomputed coefficient matrices for sectorArea computation +template +class MemoizedSectorAreaWeights +{ +public: + using SectorWeights = numerics::Matrix; + + MemoizedSectorAreaWeights() = default; + + ~MemoizedSectorAreaWeights() + { + for(auto& p : m_sectorWeightsMap) + { + delete[] p.second->data(); // delete the matrix's data + delete p.second; // delete the matrix + p.second = nullptr; + } + m_sectorWeightsMap.clear(); + } + + /// Returns a memoized matrix of coeficients for sector area computation + const SectorWeights& getWeights(int order) const + { + // Compute and cache the weights if they are not already available + if(m_sectorWeightsMap.find(order) == m_sectorWeightsMap.end()) + { + SectorWeights* weights = generateBezierCurveSectorWeights(order); + m_sectorWeightsMap[order] = weights; + } + + return *(m_sectorWeightsMap[order]); + } + +private: + /*! + * \brief Computes the weights for BezierCurve's sectorArea() function + * + * \param order The polynomial order of the curve + * \return An anti-symmetric matrix with (order+1)*{order+1) entries + * containing the integration weights for entry (i,j) + * + * The derivation is provided in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ + SectorWeights* generateBezierCurveSectorWeights(int ord) const + { + const bool memoryIsExternal = true; + const int SZ = ord + 1; + SectorWeights* weights = + new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); + + T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); + for(int i = 0; i <= ord; ++i) + { + (*weights)(i, i) = 0.; // zero on the diagonal + for(int j = i + 1; j <= ord; ++j) + { + double val = 0.; + if(i != j) + { + T binom_ij_i = static_cast(utilities::binomialCoefficient(i + j, i)); + T binom_2nij_nj = static_cast( + utilities::binomialCoefficient(2 * ord - i - j, ord - j)); + + val = ((j - i) * ord) / binom_2n_n * + (binom_ij_i / static_cast(i + j)) * + (binom_2nij_nj / (2. * ord - j - i)); + } + (*weights)(i, j) = val; // antisymmetric + (*weights)(j, i) = -val; + } + } + return weights; + } + +private: + mutable std::map m_sectorWeightsMap; +}; + +/// Utility class that caches precomputed coefficient matrices for sector_centroid() computation +template +class MemoizedSectorCentroidWeights +{ +public: + using SectorWeights = numerics::Matrix; + + MemoizedSectorCentroidWeights() = default; + + ~MemoizedSectorCentroidWeights() + { + for(auto& p : m_sectorWeightsMap) // for each matrix of weights + { + delete[] p.second->data(); // delete the matrix's data + delete p.second; // delete the matrix + p.second = nullptr; + } + m_sectorWeightsMap.clear(); + } + + /// Returns a memoized matrix of sector moment coeficients for component \a dim or order \a order + const SectorWeights& getWeights(int order, int dim) const + { + // Compute and cache the weights if they are not already available + if(m_sectorWeightsMap.find(std::make_pair(order, dim)) == + m_sectorWeightsMap.end()) + { + auto vec = generateBezierCurveSectorCentroidWeights(order); + for(int d = 0; d <= order; ++d) + { + m_sectorWeightsMap[std::make_pair(order, d)] = vec[d]; + } + } + + return *(m_sectorWeightsMap[std::make_pair(order, dim)]); + } + + /*! + * \brief Computes the weights for BezierCurve's sectorCentroid() function + * + * \param order The polynomial order of the curve + * \return An anti-symmetric matrix with (order+1)*{order+1) entries + * containing the integration weights for entry (i,j) + * + * The derivation is provided in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ + std::vector generateBezierCurveSectorCentroidWeights(int ord) const + { + const bool memoryIsExternal = true; + const int SZ = ord + 1; + + std::vector weights; + weights.resize(SZ); + for(int k = 0; k <= ord; ++k) + { + SectorWeights* weights_k = + new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); + for(int i = 0; i <= ord; ++i) + { + (*weights_k)(i, i) = 0.; // zero on the diagonal + for(int j = i + 1; j <= ord; ++j) + { + double val = 0.; + if(i != j) + { + T binom_n_i = static_cast(utilities::binomialCoefficient(ord, i)); + T binom_n_j = static_cast(utilities::binomialCoefficient(ord, j)); + T binom_n_k = static_cast(utilities::binomialCoefficient(ord, k)); + T binom_3n2_ijk1 = static_cast( + utilities::binomialCoefficient(3 * ord - 2, i + j + k - 1)); + + val = (1. * (j - i)) / (3. * (3 * ord - 1)) * + (1. * binom_n_i * binom_n_j * binom_n_k / (1. * binom_3n2_ijk1)); + } + (*weights_k)(i, j) = val; // antisymmetric + (*weights_k)(j, i) = -val; + } + } + weights[k] = weights_k; + } + return weights; + } + +private: + mutable std::map, SectorWeights*> m_sectorWeightsMap; +}; + +} // namespace internal + +} // namespace primal +} // namespace axom + +#endif // AXOM_PRIMAL_COMPUTE_MOMENTS_HPP_ diff --git a/src/axom/primal/tests/CMakeLists.txt b/src/axom/primal/tests/CMakeLists.txt index 776f3daab4..6d246266bb 100644 --- a/src/axom/primal/tests/CMakeLists.txt +++ b/src/axom/primal/tests/CMakeLists.txt @@ -13,6 +13,7 @@ set( primal_tests primal_clip.cpp primal_closest_point.cpp primal_compute_bounding_box.cpp + primal_compute_moments.cpp primal_curved_polygon.cpp primal_in_sphere.cpp primal_intersect.cpp diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index e6b65c8a7e..d660d4672e 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -203,88 +203,6 @@ TEST(primal_beziercurve_, tangent) } } -//------------------------------------------------------------------------------ -TEST(primal_beziercurve, sector_area_cubic) -{ - const int DIM = 2; - using CoordType = double; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - { - SLIC_INFO("Testing Bezier sector area calculation for a cubic"); - const int order = 3; - PointType data[order + 1] = {PointType {0.6, 1.2}, - PointType {1.3, 1.6}, - PointType {2.9, 2.4}, - PointType {3.2, 3.5}}; - - BezierCurveType bCurve(data, order); - EXPECT_TRUE(axom::utilities::isNearlyEqual(bCurve.sectorArea(), .1455)); - } -} - -//------------------------------------------------------------------------------ -TEST(primal_beziercurve, sector_moment_cubic) -{ - const int DIM = 2; - using CoordType = double; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - { - SLIC_INFO("Testing Bezier sector moment calculation for a cubic"); - const int order = 3; - PointType data[order + 1] = {PointType {0.6, 1.2}, - PointType {1.3, 1.6}, - PointType {2.9, 2.4}, - PointType {3.2, 3.5}}; - - BezierCurveType bCurve(data, order); - PointType M = bCurve.sectorCentroid(); - EXPECT_NEAR(M[0], -.429321428571429, 2e-15); - EXPECT_NEAR(M[1], -.354010714285715, 2e-15); - } -} - -//------------------------------------------------------------------------------ -TEST(primal_beziercurve, sector_area_point) -{ - const int DIM = 2; - using CoordType = double; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - { - SLIC_INFO("Testing Bezier sector area calculation for a point"); - const int order = 0; - PointType data[order + 1] = {PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(data, order); - EXPECT_DOUBLE_EQ(bCurve.sectorArea(), 0.0); - } -} - -//------------------------------------------------------------------------------ -TEST(primal_beziercurve, sector_moment_point) -{ - const int DIM = 2; - using CoordType = double; - using PointType = primal::Point; - using BezierCurveType = primal::BezierCurve; - - { - SLIC_INFO("Testing Bezier sector moment calculation for a point"); - const int order = 0; - PointType data[order + 1] = {PointType::make_point(0.6, 1.2)}; - - BezierCurveType bCurve(data, order); - PointType M = bCurve.sectorCentroid(); - EXPECT_DOUBLE_EQ(M[0], 0.0); - EXPECT_DOUBLE_EQ(M[1], 0.0); - } -} - //------------------------------------------------------------------------------ TEST(primal_beziercurve, split_cubic) { @@ -525,133 +443,6 @@ TEST(primal_beziercurve, isLinear) } } -TEST(primal_beziercurve, sector_weights) -{ - SLIC_INFO("Testing weights for BezierCurve::sectorArea()"); - - // NOTE: Expected weights are provided in the reference paper [Ueda99] - // See doxygen comment for BezierCurve::sectorArea() - - using CoordType = double; - - primal::internal::MemoizedSectorAreaWeights memoizedSectorWeights; - - // order 1 - { - const int ord = 1; - auto weights = memoizedSectorWeights.getWeights(ord); - - double binomInv = 1. / axom::utilities::binomialCoefficient(2, 1); - axom::numerics::Matrix exp(ord + 1, ord + 1); - // clang-format off - exp(0,0) = 0; exp(0,1) = 1; - exp(1,0) = -1; exp(1,1) = 0; - // clang-format on - - for(int i = 0; i <= ord; ++i) - { - for(int j = 0; j <= ord; ++j) - { - EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); - } - } - } - - // order 2 - { - const int ord = 2; - auto weights = memoizedSectorWeights.getWeights(ord); - - double binomInv = 1. / axom::utilities::binomialCoefficient(4, 2); - axom::numerics::Matrix exp(ord + 1, ord + 1); - // clang-format off - exp(0,0) = 0; exp(0,1) = 2; exp(0,2) = 1; - exp(1,0) = -2; exp(1,1) = 0; exp(1,2) = 2; - exp(2,0) = -1; exp(2,1) = -2; exp(2,2) = 0; - // clang-format on - - for(int i = 0; i <= ord; ++i) - { - for(int j = 0; j <= ord; ++j) - { - EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); - } - } - } - - // order 3 - { - const int ord = 3; - auto weights = memoizedSectorWeights.getWeights(ord); - - double binomInv = 1. / axom::utilities::binomialCoefficient(6, 3); - axom::numerics::Matrix exp(ord + 1, ord + 1); - // clang-format off - exp(0,0) = 0; exp(0,1) = 6; exp(0,2) = 3; exp(0,3) = 1; - exp(1,0) = -6; exp(1,1) = 0; exp(1,2) = 3; exp(1,3) = 3; - exp(2,0) = -3; exp(2,1) = -3; exp(2,2) = 0; exp(2,3) = 6; - exp(3,0) = -1; exp(3,1) = -3; exp(3,2) = -6; exp(3,3) = 0; - // clang-format on - - for(int i = 0; i <= ord; ++i) - { - for(int j = 0; j <= ord; ++j) - { - EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); - } - } - } - - // order 4 - { - const int ord = 4; - auto weights = memoizedSectorWeights.getWeights(ord); - - double binomInv = 1. / axom::utilities::binomialCoefficient(8, 4); - axom::numerics::Matrix exp(ord + 1, ord + 1); - // clang-format off - exp(0,0) = 0; exp(0,1) = 20; exp(0,2) = 10; exp(0,3) = 4; exp(0,4) = 1; - exp(1,0) =-20; exp(1,1) = 0; exp(1,2) = 8; exp(1,3) = 8; exp(1,4) = 4; - exp(2,0) =-10; exp(2,1) = -8; exp(2,2) = 0; exp(2,3) = 8; exp(2,4) = 10; - exp(3,0) = -4; exp(3,1) = -8; exp(3,2) = -8; exp(3,3) = 0; exp(3,4) = 20; - exp(4,0) = -1; exp(4,1) = -4; exp(4,2) =-10; exp(4,3) =-20; exp(4,4) = 0; - // clang-format on - - for(int i = 0; i <= ord; ++i) - { - for(int j = 0; j <= ord; ++j) - { - EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); - } - } - } - - // order 5 - { - const int ord = 5; - auto weights = memoizedSectorWeights.getWeights(ord); - - double binomInv = 1. / axom::utilities::binomialCoefficient(10, 5); - axom::numerics::Matrix exp(ord + 1, ord + 1); - // clang-format off - exp(0,0) = 0; exp(0,1) = 70; exp(0,2) = 35; exp(0,3) = 15; exp(0,4) = 5; exp(0,5) = 1; - exp(1,0) =-70; exp(1,1) = 0; exp(1,2) = 25; exp(1,3) = 25; exp(1,4) = 15; exp(1,5) = 5; - exp(2,0) =-35; exp(2,1) =-25; exp(2,2) = 0; exp(2,3) = 20; exp(2,4) = 25; exp(2,5) = 15; - exp(3,0) =-15; exp(3,1) =-25; exp(3,2) =-20; exp(3,3) = 0; exp(3,4) = 25; exp(3,5) = 35; - exp(4,0) = -5; exp(4,1) =-15; exp(4,2) =-25; exp(4,3) =-25; exp(4,4) = 0; exp(4,5) = 70; - exp(5,0) = -1; exp(5,1) = -5; exp(5,2) =-15; exp(5,3) =-35; exp(5,4) =-70; exp(5,5) = 0; - // clang-format on - - for(int i = 0; i <= ord; ++i) - { - for(int j = 0; j <= ord; ++j) - { - EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); - } - } - } -} - //------------------------------------------------------------------------------ int main(int argc, char* argv[]) diff --git a/src/axom/primal/tests/primal_compute_moments.cpp b/src/axom/primal/tests/primal_compute_moments.cpp new file mode 100644 index 0000000000..c45a24aeb7 --- /dev/null +++ b/src/axom/primal/tests/primal_compute_moments.cpp @@ -0,0 +1,250 @@ +// 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_compute_moments.cpp + * \brief This file tests primal's functionality related to computing moments + */ + +#include "gtest/gtest.h" + +#include "axom/core.hpp" +#include "axom/slic.hpp" + +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/CurvedPolygon.hpp" +#include "axom/primal/operators/compute_moments.hpp" + +namespace primal = axom::primal; + +const double EPS = 2e-15; + +//------------------------------------------------------------------------------ +TEST(primal_compute_moments, sector_area_cubic) +{ + const int DIM = 2; + using T = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + using axom::utilities::isNearlyEqual; + + { + SLIC_INFO("Testing Bezier sector area calculation for a cubic"); + const int order = 3; + PointType data[order + 1] = {PointType {0.6, 1.2}, + PointType {1.3, 1.6}, + PointType {2.9, 2.4}, + PointType {3.2, 3.5}}; + + BezierCurveType bCurve(data, order); + const T area = primal::sector_area(bCurve); + + EXPECT_NEAR(.1455, area, EPS); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_compute_moments, sector_moment_cubic) +{ + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing Bezier sector moment calculation for a cubic"); + const int order = 3; + PointType data[order + 1] = {PointType {0.6, 1.2}, + PointType {1.3, 1.6}, + PointType {2.9, 2.4}, + PointType {3.2, 3.5}}; + + BezierCurveType bCurve(data, order); + PointType M = primal::sector_centroid(bCurve); + EXPECT_NEAR(-.429321428571429, M[0], EPS); + EXPECT_NEAR(-.354010714285715, M[1], EPS); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_compute_moments, sector_area_point) +{ + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing Bezier sector area calculation for a point"); + const int order = 0; + PointType data[order + 1] = {PointType {0.6, 1.2}}; + + BezierCurveType bCurve(data, order); + EXPECT_DOUBLE_EQ(0., primal::sector_area(bCurve)); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_compute_moments, sector_moment_point) +{ + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + { + SLIC_INFO("Testing Bezier sector moment calculation for a point"); + const int order = 0; + PointType data[order + 1] = {PointType {0.6, 1.2}}; + + BezierCurveType bCurve(data, order); + PointType M = primal::sector_centroid(bCurve); + EXPECT_DOUBLE_EQ(M[0], 0.0); + EXPECT_DOUBLE_EQ(M[1], 0.0); + } +} + +//------------------------------------------------------------------------------ +TEST(primal_compute_moments, sector_weights) +{ + SLIC_INFO("Testing weights for BezierCurve::sectorArea()"); + + // NOTE: Expected weights are provided in the reference paper [Ueda99] + // See doxygen comment for primal::sector_area(BezierCurve) + + using CoordType = double; + primal::internal::MemoizedSectorAreaWeights memoizedSectorWeights; + + // order 1 + { + const int ord = 1; + auto weights = memoizedSectorWeights.getWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(2, 1); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 1; + exp(1,0) = -1; exp(1,1) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 2 + { + const int ord = 2; + auto weights = memoizedSectorWeights.getWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(4, 2); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 2; exp(0,2) = 1; + exp(1,0) = -2; exp(1,1) = 0; exp(1,2) = 2; + exp(2,0) = -1; exp(2,1) = -2; exp(2,2) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 3 + { + const int ord = 3; + auto weights = memoizedSectorWeights.getWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(6, 3); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 6; exp(0,2) = 3; exp(0,3) = 1; + exp(1,0) = -6; exp(1,1) = 0; exp(1,2) = 3; exp(1,3) = 3; + exp(2,0) = -3; exp(2,1) = -3; exp(2,2) = 0; exp(2,3) = 6; + exp(3,0) = -1; exp(3,1) = -3; exp(3,2) = -6; exp(3,3) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 4 + { + const int ord = 4; + auto weights = memoizedSectorWeights.getWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(8, 4); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 20; exp(0,2) = 10; exp(0,3) = 4; exp(0,4) = 1; + exp(1,0) =-20; exp(1,1) = 0; exp(1,2) = 8; exp(1,3) = 8; exp(1,4) = 4; + exp(2,0) =-10; exp(2,1) = -8; exp(2,2) = 0; exp(2,3) = 8; exp(2,4) = 10; + exp(3,0) = -4; exp(3,1) = -8; exp(3,2) = -8; exp(3,3) = 0; exp(3,4) = 20; + exp(4,0) = -1; exp(4,1) = -4; exp(4,2) =-10; exp(4,3) =-20; exp(4,4) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } + + // order 5 + { + const int ord = 5; + auto weights = memoizedSectorWeights.getWeights(ord); + + double binomInv = 1. / axom::utilities::binomialCoefficient(10, 5); + axom::numerics::Matrix exp(ord + 1, ord + 1); + // clang-format off + exp(0,0) = 0; exp(0,1) = 70; exp(0,2) = 35; exp(0,3) = 15; exp(0,4) = 5; exp(0,5) = 1; + exp(1,0) =-70; exp(1,1) = 0; exp(1,2) = 25; exp(1,3) = 25; exp(1,4) = 15; exp(1,5) = 5; + exp(2,0) =-35; exp(2,1) =-25; exp(2,2) = 0; exp(2,3) = 20; exp(2,4) = 25; exp(2,5) = 15; + exp(3,0) =-15; exp(3,1) =-25; exp(3,2) =-20; exp(3,3) = 0; exp(3,4) = 25; exp(3,5) = 35; + exp(4,0) = -5; exp(4,1) =-15; exp(4,2) =-25; exp(4,3) =-25; exp(4,4) = 0; exp(4,5) = 70; + exp(5,0) = -1; exp(5,1) = -5; exp(5,2) =-15; exp(5,3) =-35; exp(5,4) =-70; exp(5,5) = 0; + // clang-format on + + for(int i = 0; i <= ord; ++i) + { + for(int j = 0; j <= ord; ++j) + { + EXPECT_DOUBLE_EQ(exp(i, j) * binomInv, weights(i, j)); + } + } + } +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + + axom::slic::SimpleLogger logger; + + result = RUN_ALL_TESTS(); + + return result; +} diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index de64329dba..b768651cea 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -11,7 +11,9 @@ #include "gtest/gtest.h" #include "axom/slic.hpp" +#include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/CurvedPolygon.hpp" +#include "axom/primal/operators/compute_moments.hpp" #include @@ -21,17 +23,19 @@ 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, +template +void checkMoments(const primal::CurvedPolygon& bPolygon, const CoordType expArea, - const primal::Point& expMoment, + const primal::Point& expMoment, double eps, double test_eps) { - EXPECT_NEAR(expArea, bPolygon.area(eps), test_eps); - for(int i = 0; i < DIM; ++i) + EXPECT_NEAR(expArea, primal::area(bPolygon, eps), test_eps); + + const auto centroid = primal::centroid(bPolygon, eps); + for(int i = 0; i < 2; ++i) { - EXPECT_NEAR(expMoment[i], bPolygon.centroid(eps)[i], test_eps); + EXPECT_NEAR(expMoment[i], centroid[i], test_eps); } } @@ -255,8 +259,8 @@ TEST(primal_curvedpolygon, moments_triangle_degenerate) CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); - EXPECT_EQ(0.0, bPolygon.area()); - PointType origin = PointType::make_point(0.0, 0.0); + EXPECT_EQ(0.0, primal::area(bPolygon)); + PointType origin {0.0, 0.0}; PointType controlPoints[2] = {PointType {0.6, 1.2}, PointType {0.3, 2.0}}; From c4ea106de17036ebdd5c2429121657f0361cac9c Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 23 Nov 2021 11:36:27 -0800 Subject: [PATCH 29/38] Adds linear interpolation `lerp` function to axom utilities * Moved from C2CReader * Adds unit tests * Use in `BezierCurve` class --- src/axom/core/tests/utils_utilities.hpp | 50 ++++++++++++++++++++++++ src/axom/core/utilities/Utilities.hpp | 9 +++++ src/axom/primal/geometry/BezierCurve.hpp | 14 +++---- src/axom/quest/readers/C2CReader.cpp | 5 +-- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/axom/core/tests/utils_utilities.hpp b/src/axom/core/tests/utils_utilities.hpp index 2fec4d8efc..bf1887f263 100644 --- a/src/axom/core/tests/utils_utilities.hpp +++ b/src/axom/core/tests/utils_utilities.hpp @@ -242,3 +242,53 @@ TEST(utils_utilities, binomial_coefficient) } } } + +TEST(utils_utilities, lerp) +{ + std::cout << "Testing lerp function." << std::endl; + + { + double A = 0.; + double B = 1.; + + EXPECT_DOUBLE_EQ(0., axom::utilities::lerp(A, B, 0.)); + EXPECT_DOUBLE_EQ(1., axom::utilities::lerp(A, B, 1.)); + EXPECT_DOUBLE_EQ(.5, axom::utilities::lerp(A, B, .5)); + + for(double t = -2.0; t < 2.0; t += 0.05) + { + EXPECT_DOUBLE_EQ(t, axom::utilities::lerp(A, B, t)); + } + } + + // Test endpoint interpolation + { + const double lower = -.23; // Arbitrary end points + const double upper = 5.73; + for(int i = 0; i < 100; ++i) + { + double A = axom::utilities::random_real(lower, upper); + double B = axom::utilities::random_real(lower, upper); + + EXPECT_DOUBLE_EQ(A, axom::utilities::lerp(A, B, 0.)); + EXPECT_DOUBLE_EQ(B, axom::utilities::lerp(B, A, 0.)); + EXPECT_DOUBLE_EQ(B, axom::utilities::lerp(A, B, 1.)); + EXPECT_DOUBLE_EQ(A, axom::utilities::lerp(B, A, 1.)); + } + } + + // Compute using different form + { + const double lower = -.23; // Arbitrary end points + const double upper = 5.73; + for(int i = 0; i < 100; ++i) + { + double A = axom::utilities::random_real(lower, upper); + double B = axom::utilities::random_real(lower, upper); + double t = axom::utilities::random_real(-1.5, 1.5); + + double exp = A + (B - A) * t; + EXPECT_NEAR(exp, axom::utilities::lerp(A, B, t), 1e-12); + } + } +} diff --git a/src/axom/core/utilities/Utilities.hpp b/src/axom/core/utilities/Utilities.hpp index f96ed3618f..b74234b921 100644 --- a/src/axom/core/utilities/Utilities.hpp +++ b/src/axom/core/utilities/Utilities.hpp @@ -109,6 +109,15 @@ inline AXOM_HOST_DEVICE void swap(T& a, T& b) b = tmp; } +/*! + * \brief returns the linear interpolation of \a A and \a B at \a t. i.e. (1-t)A+tB + */ +template +inline AXOM_HOST_DEVICE T lerp(T A, T B, T t) +{ + return (1 - t) * A + t * B; +} + /*! * \brief Returns the base 2 logarithm of the input. * \param [in] val The input value diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 1ef0cb0826..6d2ce74229 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -225,6 +225,8 @@ class BezierCurve */ PointType evaluate(T t) const { + using axom::utilities::lerp; + PointType ptval; const int ord = getOrder(); @@ -243,7 +245,7 @@ class BezierCurve const int end = ord - p; for(int k = 0; k <= end; ++k) { - dCarray[k] = (1 - t) * dCarray[k] + t * dCarray[k + 1]; + dCarray[k] = lerp(dCarray[k], dCarray[k + 1], t); } } ptval[i] = dCarray[0]; @@ -262,6 +264,7 @@ class BezierCurve */ VectorType dt(T t) const { + using axom::utilities::lerp; VectorType val; const int ord = getOrder(); @@ -281,7 +284,7 @@ class BezierCurve const int end = ord - p; for(int k = 0; k <= end; ++k) { - dCarray[k] = (1 - t) * dCarray[k] + t * dCarray[k + 1]; + dCarray[k] = lerp(dCarray[k], dCarray[k + 1], t); } } val[i] = ord * (dCarray[1] - dCarray[0]); @@ -318,12 +321,7 @@ class BezierCurve const int end = ord - p; for(int k = 0; k <= end; ++k) { - PointType& pt1 = c2[k]; - const PointType& pt2 = c2[k + 1]; - for(int i = 0; i < NDIMS; ++i) - { - pt1[i] = (1 - t) * pt1[i] + t * pt2[i]; - } + c2[k] = PointType::lerp(c2[k], c2[k + 1], t); } c1[p] = c2[0]; } diff --git a/src/axom/quest/readers/C2CReader.cpp b/src/axom/quest/readers/C2CReader.cpp index 3bfc04c2c0..91c98981df 100644 --- a/src/axom/quest/readers/C2CReader.cpp +++ b/src/axom/quest/readers/C2CReader.cpp @@ -17,9 +17,6 @@ namespace axom { namespace quest { -/// returns the linear interpolation of \a A and \a B at \a t. i.e. (1-t)A+tB -inline double lerp(double A, double B, double t) { return (1 - t) * A + t * B; } - /*! * \brief Helper class for interpolating points on a NURBS curve * @@ -282,6 +279,8 @@ void C2CReader::log() void C2CReader::getLinearMesh(mint::UnstructuredMesh* mesh, int segmentsPerKnotSpan) { + using axom::utilities::lerp; + // Sanity checks SLIC_ERROR_IF(mesh == nullptr, "supplied mesh is null!"); SLIC_ERROR_IF(mesh->getDimension() != 2, "C2C reader expects a 2D mesh!"); From b65b41bd6a624effb6f85d0008673646bd9cd113 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Tue, 23 Nov 2021 12:51:29 -0800 Subject: [PATCH 30/38] Outsources implementation details for compute_sector and compute_area on BezierCurve --- src/axom/primal/CMakeLists.txt | 1 + src/axom/primal/operators/compute_moments.hpp | 186 +--------------- .../operators/detail/compute_moments_impl.hpp | 204 ++++++++++++++++++ .../primal/tests/primal_compute_moments.cpp | 3 +- 4 files changed, 210 insertions(+), 184 deletions(-) create mode 100644 src/axom/primal/operators/detail/compute_moments_impl.hpp diff --git a/src/axom/primal/CMakeLists.txt b/src/axom/primal/CMakeLists.txt index 2189965b30..920fd4a3b8 100644 --- a/src/axom/primal/CMakeLists.txt +++ b/src/axom/primal/CMakeLists.txt @@ -48,6 +48,7 @@ set( primal_headers 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_impl.hpp diff --git a/src/axom/primal/operators/compute_moments.hpp b/src/axom/primal/operators/compute_moments.hpp index 06829225ec..5545e1bc3f 100644 --- a/src/axom/primal/operators/compute_moments.hpp +++ b/src/axom/primal/operators/compute_moments.hpp @@ -18,6 +18,7 @@ #include "axom/primal/geometry/Point.hpp" #include "axom/primal/geometry/BezierCurve.hpp" #include "axom/primal/geometry/CurvedPolygon.hpp" +#include "axom/primal/operators/detail/compute_moments_impl.hpp" #include #include @@ -26,14 +27,6 @@ namespace axom { namespace primal { -namespace internal -{ -template -class MemoizedSectorAreaWeights; -template -class MemoizedSectorCentroidWeights; -} // namespace internal - /*! * \brief Calculates the sector area of a planar Bezier Curve * @@ -46,7 +39,7 @@ template T sector_area(const primal::BezierCurve& curve) { // Weights for each polynomial order are precomputed and memoized - static internal::MemoizedSectorAreaWeights s_weights; + static detail::MemoizedSectorAreaWeights s_weights; T A = 0; const int ord = curve.getOrder(); @@ -74,7 +67,7 @@ template primal::Point sector_centroid(const primal::BezierCurve& curve) { // Weights for each polynomial order's centroid are precomputed and memoized - static internal::MemoizedSectorCentroidWeights s_weights; + static detail::MemoizedSectorCentroidWeights s_weights; T Mx = 0; T My = 0; @@ -147,179 +140,6 @@ primal::Point centroid(const primal::CurvedPolygon& poly, } } -namespace internal -{ -/// Utility class that caches precomputed coefficient matrices for sectorArea computation -template -class MemoizedSectorAreaWeights -{ -public: - using SectorWeights = numerics::Matrix; - - MemoizedSectorAreaWeights() = default; - - ~MemoizedSectorAreaWeights() - { - for(auto& p : m_sectorWeightsMap) - { - delete[] p.second->data(); // delete the matrix's data - delete p.second; // delete the matrix - p.second = nullptr; - } - m_sectorWeightsMap.clear(); - } - - /// Returns a memoized matrix of coeficients for sector area computation - const SectorWeights& getWeights(int order) const - { - // Compute and cache the weights if they are not already available - if(m_sectorWeightsMap.find(order) == m_sectorWeightsMap.end()) - { - SectorWeights* weights = generateBezierCurveSectorWeights(order); - m_sectorWeightsMap[order] = weights; - } - - return *(m_sectorWeightsMap[order]); - } - -private: - /*! - * \brief Computes the weights for BezierCurve's sectorArea() function - * - * \param order The polynomial order of the curve - * \return An anti-symmetric matrix with (order+1)*{order+1) entries - * containing the integration weights for entry (i,j) - * - * The derivation is provided in: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ - SectorWeights* generateBezierCurveSectorWeights(int ord) const - { - const bool memoryIsExternal = true; - const int SZ = ord + 1; - SectorWeights* weights = - new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); - - T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); - for(int i = 0; i <= ord; ++i) - { - (*weights)(i, i) = 0.; // zero on the diagonal - for(int j = i + 1; j <= ord; ++j) - { - double val = 0.; - if(i != j) - { - T binom_ij_i = static_cast(utilities::binomialCoefficient(i + j, i)); - T binom_2nij_nj = static_cast( - utilities::binomialCoefficient(2 * ord - i - j, ord - j)); - - val = ((j - i) * ord) / binom_2n_n * - (binom_ij_i / static_cast(i + j)) * - (binom_2nij_nj / (2. * ord - j - i)); - } - (*weights)(i, j) = val; // antisymmetric - (*weights)(j, i) = -val; - } - } - return weights; - } - -private: - mutable std::map m_sectorWeightsMap; -}; - -/// Utility class that caches precomputed coefficient matrices for sector_centroid() computation -template -class MemoizedSectorCentroidWeights -{ -public: - using SectorWeights = numerics::Matrix; - - MemoizedSectorCentroidWeights() = default; - - ~MemoizedSectorCentroidWeights() - { - for(auto& p : m_sectorWeightsMap) // for each matrix of weights - { - delete[] p.second->data(); // delete the matrix's data - delete p.second; // delete the matrix - p.second = nullptr; - } - m_sectorWeightsMap.clear(); - } - - /// Returns a memoized matrix of sector moment coeficients for component \a dim or order \a order - const SectorWeights& getWeights(int order, int dim) const - { - // Compute and cache the weights if they are not already available - if(m_sectorWeightsMap.find(std::make_pair(order, dim)) == - m_sectorWeightsMap.end()) - { - auto vec = generateBezierCurveSectorCentroidWeights(order); - for(int d = 0; d <= order; ++d) - { - m_sectorWeightsMap[std::make_pair(order, d)] = vec[d]; - } - } - - return *(m_sectorWeightsMap[std::make_pair(order, dim)]); - } - - /*! - * \brief Computes the weights for BezierCurve's sectorCentroid() function - * - * \param order The polynomial order of the curve - * \return An anti-symmetric matrix with (order+1)*{order+1) entries - * containing the integration weights for entry (i,j) - * - * The derivation is provided in: - * Ueda, K. "Signed area of sectors between spline curves and the origin" - * IEEE International Conference on Information Visualization, 1999. - */ - std::vector generateBezierCurveSectorCentroidWeights(int ord) const - { - const bool memoryIsExternal = true; - const int SZ = ord + 1; - - std::vector weights; - weights.resize(SZ); - for(int k = 0; k <= ord; ++k) - { - SectorWeights* weights_k = - new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); - for(int i = 0; i <= ord; ++i) - { - (*weights_k)(i, i) = 0.; // zero on the diagonal - for(int j = i + 1; j <= ord; ++j) - { - double val = 0.; - if(i != j) - { - T binom_n_i = static_cast(utilities::binomialCoefficient(ord, i)); - T binom_n_j = static_cast(utilities::binomialCoefficient(ord, j)); - T binom_n_k = static_cast(utilities::binomialCoefficient(ord, k)); - T binom_3n2_ijk1 = static_cast( - utilities::binomialCoefficient(3 * ord - 2, i + j + k - 1)); - - val = (1. * (j - i)) / (3. * (3 * ord - 1)) * - (1. * binom_n_i * binom_n_j * binom_n_k / (1. * binom_3n2_ijk1)); - } - (*weights_k)(i, j) = val; // antisymmetric - (*weights_k)(j, i) = -val; - } - } - weights[k] = weights_k; - } - return weights; - } - -private: - mutable std::map, SectorWeights*> m_sectorWeightsMap; -}; - -} // namespace internal - } // namespace primal } // namespace axom diff --git a/src/axom/primal/operators/detail/compute_moments_impl.hpp b/src/axom/primal/operators/detail/compute_moments_impl.hpp new file mode 100644 index 0000000000..9fa3612d84 --- /dev/null +++ b/src/axom/primal/operators/detail/compute_moments_impl.hpp @@ -0,0 +1,204 @@ +// 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) + +#ifndef AXOM_PRIMAL_COMPUTE_MOMENTS_IMPL_HPP_ +#define AXOM_PRIMAL_COMPUTE_MOMENTS_IMPL_HPP_ + +/*! + * \file compute_moments_impl.hpp + * + * \brief Consists of implementation helpers for computing areas/volumes and centroids + * for Polygons and CurvedPolygons composed of BezierCurves + */ + +#include "axom/config.hpp" +#include "axom/core.hpp" +#include "axom/primal/geometry/Point.hpp" +#include "axom/primal/geometry/BezierCurve.hpp" +#include "axom/primal/geometry/CurvedPolygon.hpp" + +#include +#include + +namespace axom +{ +namespace primal +{ +namespace detail +{ +/// Utility class that caches precomputed coefficient matrices for sector_area computation +template +class MemoizedSectorAreaWeights +{ +public: + using SectorWeights = numerics::Matrix; + + MemoizedSectorAreaWeights() = default; + + ~MemoizedSectorAreaWeights() + { + for(auto& p : m_sectorWeightsMap) + { + delete[] p.second->data(); // delete the matrix's data + delete p.second; // delete the matrix + p.second = nullptr; + } + m_sectorWeightsMap.clear(); + } + + /// Returns a memoized matrix of coeficients for sector area computation + const SectorWeights& getWeights(int order) const + { + // Compute and cache the weights if they are not already available + if(m_sectorWeightsMap.find(order) == m_sectorWeightsMap.end()) + { + SectorWeights* weights = generateBezierCurveSectorWeights(order); + m_sectorWeightsMap[order] = weights; + } + + return *(m_sectorWeightsMap[order]); + } + +private: + /*! + * \brief Computes the weights for BezierCurve's sector_area() function + * + * \param order The polynomial order of the curve + * \return An anti-symmetric matrix with (order+1)*{order+1) entries + * containing the integration weights for entry (i,j) + * + * The derivation is provided in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ + SectorWeights* generateBezierCurveSectorWeights(int ord) const + { + const bool memoryIsExternal = true; + const int SZ = ord + 1; + SectorWeights* weights = + new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); + + T binom_2n_n = static_cast(utilities::binomialCoefficient(2 * ord, ord)); + for(int i = 0; i <= ord; ++i) + { + (*weights)(i, i) = 0.; // zero on the diagonal + for(int j = i + 1; j <= ord; ++j) + { + double val = 0.; + if(i != j) + { + T binom_ij_i = static_cast(utilities::binomialCoefficient(i + j, i)); + T binom_2nij_nj = static_cast( + utilities::binomialCoefficient(2 * ord - i - j, ord - j)); + + val = ((j - i) * ord) / binom_2n_n * + (binom_ij_i / static_cast(i + j)) * + (binom_2nij_nj / (2. * ord - j - i)); + } + (*weights)(i, j) = val; // antisymmetric + (*weights)(j, i) = -val; + } + } + return weights; + } + +private: + mutable std::map m_sectorWeightsMap; +}; + +/// Utility class that caches precomputed coefficient matrices for sector_centroid() computation +template +class MemoizedSectorCentroidWeights +{ +public: + using SectorWeights = numerics::Matrix; + + MemoizedSectorCentroidWeights() = default; + + ~MemoizedSectorCentroidWeights() + { + for(auto& p : m_sectorWeightsMap) // for each matrix of weights + { + delete[] p.second->data(); // delete the matrix's data + delete p.second; // delete the matrix + p.second = nullptr; + } + m_sectorWeightsMap.clear(); + } + + /// Returns a memoized matrix of sector moment coeficients for component \a dim or order \a order + const SectorWeights& getWeights(int order, int dim) const + { + // Compute and cache the weights if they are not already available + if(m_sectorWeightsMap.find(std::make_pair(order, dim)) == + m_sectorWeightsMap.end()) + { + auto vec = generateBezierCurveSectorCentroidWeights(order); + for(int d = 0; d <= order; ++d) + { + m_sectorWeightsMap[std::make_pair(order, d)] = vec[d]; + } + } + + return *(m_sectorWeightsMap[std::make_pair(order, dim)]); + } + + /*! + * \brief Computes the weights for BezierCurve's sectorCentroid() function + * + * \param order The polynomial order of the curve + * \return An anti-symmetric matrix with (order+1)*{order+1) entries + * containing the integration weights for entry (i,j) + * + * The derivation is provided in: + * Ueda, K. "Signed area of sectors between spline curves and the origin" + * IEEE International Conference on Information Visualization, 1999. + */ + std::vector generateBezierCurveSectorCentroidWeights(int ord) const + { + const bool memoryIsExternal = true; + const int SZ = ord + 1; + + std::vector weights; + weights.resize(SZ); + for(int k = 0; k <= ord; ++k) + { + SectorWeights* weights_k = + new SectorWeights(SZ, SZ, new T[SZ * SZ], memoryIsExternal); + for(int i = 0; i <= ord; ++i) + { + (*weights_k)(i, i) = 0.; // zero on the diagonal + for(int j = i + 1; j <= ord; ++j) + { + double val = 0.; + if(i != j) + { + T binom_n_i = static_cast(utilities::binomialCoefficient(ord, i)); + T binom_n_j = static_cast(utilities::binomialCoefficient(ord, j)); + T binom_n_k = static_cast(utilities::binomialCoefficient(ord, k)); + T binom_3n2_ijk1 = static_cast( + utilities::binomialCoefficient(3 * ord - 2, i + j + k - 1)); + + val = (1. * (j - i)) / (3. * (3 * ord - 1)) * + (1. * binom_n_i * binom_n_j * binom_n_k / (1. * binom_3n2_ijk1)); + } + (*weights_k)(i, j) = val; // antisymmetric + (*weights_k)(j, i) = -val; + } + } + weights[k] = weights_k; + } + return weights; + } + +private: + mutable std::map, SectorWeights*> m_sectorWeightsMap; +}; + +} // namespace detail +} // namespace primal +} // namespace axom + +#endif // AXOM_PRIMAL_COMPUTE_MOMENTS_IMPL_HPP_ \ No newline at end of file diff --git a/src/axom/primal/tests/primal_compute_moments.cpp b/src/axom/primal/tests/primal_compute_moments.cpp index c45a24aeb7..3f0edb2ab3 100644 --- a/src/axom/primal/tests/primal_compute_moments.cpp +++ b/src/axom/primal/tests/primal_compute_moments.cpp @@ -17,6 +17,7 @@ #include "axom/primal/geometry/BezierCurve.hpp" #include "axom/primal/geometry/CurvedPolygon.hpp" #include "axom/primal/operators/compute_moments.hpp" +#include "axom/primal/operators/detail/compute_moments_impl.hpp" namespace primal = axom::primal; @@ -116,7 +117,7 @@ TEST(primal_compute_moments, sector_weights) // See doxygen comment for primal::sector_area(BezierCurve) using CoordType = double; - primal::internal::MemoizedSectorAreaWeights memoizedSectorWeights; + primal::detail::MemoizedSectorAreaWeights memoizedSectorWeights; // order 1 { From 28cb69ca3bf87cb3c55a843b0439fc633882749f Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Thu, 25 Nov 2021 11:45:39 -0800 Subject: [PATCH 31/38] Opt: perform in-place BezierCurve::reverseOrientation() --- src/axom/primal/geometry/BezierCurve.hpp | 6 +-- src/axom/primal/tests/primal_bezier_curve.cpp | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/axom/primal/geometry/BezierCurve.hpp b/src/axom/primal/geometry/BezierCurve.hpp index 6d2ce74229..76d1a39781 100644 --- a/src/axom/primal/geometry/BezierCurve.hpp +++ b/src/axom/primal/geometry/BezierCurve.hpp @@ -194,10 +194,10 @@ class BezierCurve void reverseOrientation() { const int ord = getOrder(); - CoordsVec old_controlPoints = m_controlPoints; - for(int i = 0; i <= ord; ++i) + const int mid = (ord + 1) / 2; + for(int i = 0; i < mid; ++i) { - m_controlPoints[i] = old_controlPoints[ord - i]; + axom::utilities::swap(m_controlPoints[i], m_controlPoints[ord - i]); } } diff --git a/src/axom/primal/tests/primal_bezier_curve.cpp b/src/axom/primal/tests/primal_bezier_curve.cpp index d660d4672e..e355424043 100644 --- a/src/axom/primal/tests/primal_bezier_curve.cpp +++ b/src/axom/primal/tests/primal_bezier_curve.cpp @@ -443,6 +443,55 @@ TEST(primal_beziercurve, isLinear) } } +TEST(primal_beziercurve, reverseOrientation) +{ + SLIC_INFO("Testing reverseOrientation() on Bezier curves"); + + { + const int DIM = 2; + using CoordType = double; + using PointType = primal::Point; + using BezierCurveType = primal::BezierCurve; + + // test different orders + for(int order = 0; order <= 10; ++order) + { + // control points for curve monotonically increase + axom::Array pts(order + 1); + for(int i = 0; i <= order; ++i) + { + pts[i] = PointType(i); + } + BezierCurveType curve(pts.data(), order); + + for(int i = 1; i <= order; ++i) + { + EXPECT_GT(curve[i][0], curve[i - 1][0]); + } + + // create a reversed curve and check that it monotonically decreases + BezierCurveType reversed = curve; + reversed.reverseOrientation(); + + for(int i = 1; i <= order; ++i) + { + EXPECT_LT(reversed[i][0], reversed[i - 1][0]); + } + + // Check that the control points are actually reversed + for(int i = 0; i <= order; ++i) + { + EXPECT_EQ(curve[i], reversed[order - i]); + } + + // check that reversing again reverts to the original + BezierCurveType reversedAgain = reversed; + reversedAgain.reverseOrientation(); + EXPECT_EQ(curve, reversedAgain); + } + } +} + //------------------------------------------------------------------------------ int main(int argc, char* argv[]) From 8ea7ee5dbb7bf20065e07f1e82b0c5d70349217e Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Thu, 25 Nov 2021 11:47:11 -0800 Subject: [PATCH 32/38] Opt: perform in-place CurvedPolygon::reverseOrientation() --- src/axom/primal/geometry/CurvedPolygon.hpp | 20 ++++- .../primal/tests/primal_curved_polygon.cpp | 79 +++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index e6a924f505..291392b85d 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -206,11 +206,23 @@ class CurvedPolygon void reverseOrientation() { const int ngon = numEdges(); - std::vector> old_edges = m_edges; - for(int i = 0; i < ngon; ++i) + const int mid = ngon >> 1; + const bool isOdd = (ngon & 1) != 0; + + // Swap matching left/right cases, unmatched center is dealt with below + for(int i = 0; i < mid; ++i) + { + const int left = i; + const int right = ngon - i - 1; + m_edges[left].reverseOrientation(); + m_edges[right].reverseOrientation(); + axom::utilities::swap(m_edges[left], m_edges[right]); + } + + // Handle unmatched center curve, if necessary + if(isOdd) { - old_edges[ngon - 1 - i].reverseOrientation(); - m_edges[i] = old_edges[ngon - 1 - i]; + m_edges[mid].reverseOrientation(); } } diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index b768651cea..6561507708 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -10,12 +10,18 @@ #include "gtest/gtest.h" +#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 +#include namespace primal = axom::primal; @@ -418,6 +424,79 @@ TEST(primal_curvedpolygon, moments_quad_all_orders) } } +//---------------------------------------------------------------------------------- +TEST(primal_curvedpolygon, reverseOrientation) +{ + const int DIM = 2; + const int order = 1; + using CoordType = double; + using CurvedPolygonType = primal::CurvedPolygon; + using PointType = primal::Point; + using SegmentType = primal::Segment; + using BezierCurveType = primal::BezierCurve; + + // Create a set of line segments on the unit circle + const int MAX_SEG = 10; + const PointType origin; + for(int nseg = 3; nseg < MAX_SEG; ++nseg) + { + CurvedPolygonType poly(nseg); + axom::Array pts(nseg + 1); + for(int i = 0; i < nseg; ++i) + { + const double theta = i / static_cast(nseg) * (2. * M_PI); + pts[i] = PointType {cos(theta), sin(theta)}; + } + pts[nseg] = pts[0]; + + for(int i = 0; i < nseg; ++i) + { + poly[i] = BezierCurveType(&pts[i], order); + } + + // Perform some checks + for(int i = 0; i < nseg; ++i) + { + // check that the end point of each segment is equal to the start of the next + auto& currentEnd = poly[i][order]; + auto& nextStart = poly[(i + 1) % nseg][0]; + EXPECT_EQ(currentEnd, nextStart); + + // check that the orientation of segment midpoints goes in the same direction + SegmentType seg(poly[i].evaluate(0.5), poly[(i + 1) % nseg].evaluate(0.5)); + EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(origin, seg)); + } + + // Create a polygon of reversed segments; + CurvedPolygonType reversed = poly; + reversed.reverseOrientation(); + + EXPECT_EQ(poly.numEdges(), reversed.numEdges()); + + // Perform some checks on the reversed polygon + for(int i = 0; i < nseg; ++i) + { + // check that order of each segment stayed the same + EXPECT_EQ(poly[i].getOrder(), reversed[i].getOrder()); + + // check that the end point of each segment is equal to the start of the next + auto& currentEnd = reversed[i][order]; + auto& nextStart = reversed[(i + 1) % nseg][0]; + EXPECT_EQ(currentEnd, nextStart); + + // check that segment midpoints are oriented in same direction (opposite of origin) + SegmentType seg(reversed[i].evaluate(0.5), + reversed[(i + 1) % nseg].evaluate(0.5)); + EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(origin, seg)); + } + + // Check that reversing twice yields the original; + CurvedPolygonType reversedAgain = reversed; + reversedAgain.reverseOrientation(); + EXPECT_EQ(poly, reversedAgain); + } +} + //---------------------------------------------------------------------------------- int main(int argc, char* argv[]) { From f9edc6e4a393f79b831e5e5a1999fc1065450105 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Thu, 25 Nov 2021 12:16:09 -0800 Subject: [PATCH 33/38] Slight clean up of `primal::CurvedPolygon::isClosed()` --- src/axom/primal/geometry/CurvedPolygon.hpp | 17 +++---- .../primal/tests/primal_curved_polygon.cpp | 48 ++++++++++++------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 291392b85d..5e53278784 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.hpp @@ -184,22 +184,19 @@ class CurvedPolygon return false; } - // foreach edge: check last vertex of current edge against first vertex of next edge - for(int i = 1; i < nEdges; ++i) + // foreach edge: check last vertex of previous edge against first vertex of current edge + for(int cur = 0, prev = nEdges - 1; cur < nEdges; prev = cur++) { - const auto ord = m_edges[i - 1].getOrder(); - const auto& lastPrev = m_edges[i - 1][ord]; - const auto& firstCur = m_edges[i][0]; + const auto ord = m_edges[prev].getOrder(); + const auto& lastPrev = m_edges[prev][ord]; + const auto& firstCur = m_edges[cur][0]; if(!isNearlyEqual(squared_distance(lastPrev, firstCur), 0., sq_tol)) { return false; } } - // check last edge against first - const auto ord = m_edges[nEdges - 1].getOrder(); - const auto& lastPrev = m_edges[nEdges - 1][ord]; - const auto& firstCur = m_edges[0][0]; - return isNearlyEqual(squared_distance(lastPrev, firstCur), 0., sq_tol); + + return true; } /// \brief Reverses orientation of a CurvedPolygon diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 6561507708..8efaea0081 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -158,9 +158,11 @@ TEST(primal_curvedpolygon, isClosed) SLIC_INFO("Test checking if CurvedPolygon is closed."); - CurvedPolygonType bPolygon; - EXPECT_EQ(0, bPolygon.numEdges()); - EXPECT_EQ(false, bPolygon.isClosed()); + { + CurvedPolygonType bPolygon; + EXPECT_EQ(0, bPolygon.numEdges()); + EXPECT_FALSE(bPolygon.isClosed()); + } std::vector CP = {PointType {0.6, 1.2}, PointType {0.3, 2.0}, @@ -168,18 +170,28 @@ TEST(primal_curvedpolygon, isClosed) PointType {0.6, 1.2}}; std::vector orders = {1, 1, 1}; - std::vector subCP = {PointType {0.6, 1.2}, PointType {0.3, 2.0}}; - std::vector suborders = {1}; - CurvedPolygonType subPolygon = createPolygon(subCP, suborders); - EXPECT_EQ(false, subPolygon.isClosed()); + { + std::vector subCP = {PointType {0.6, 1.2}, PointType {0.3, 2.0}}; + std::vector suborders = {1}; + CurvedPolygonType subPolygon = createPolygon(subCP, suborders); + EXPECT_FALSE(subPolygon.isClosed()); + } + + { + CurvedPolygonType bPolygon = createPolygon(CP, orders); + EXPECT_EQ(3, bPolygon.numEdges()); + EXPECT_TRUE(bPolygon.isClosed()); - bPolygon = createPolygon(CP, orders); + bPolygon[2][1][0] -= 2e-15; + EXPECT_FALSE(bPolygon.isClosed(1e-15)); + } - EXPECT_EQ(3, bPolygon.numEdges()); - EXPECT_EQ(true, bPolygon.isClosed()); + { + CurvedPolygonType bPolygon = createPolygon(CP, orders); - bPolygon[2][1][0] -= 2e-15; - EXPECT_EQ(false, bPolygon.isClosed(1e-15)); + bPolygon[1][0][0] = 5; + EXPECT_FALSE(bPolygon.isClosed(1e-15)); + } } //---------------------------------------------------------------------------------- @@ -194,7 +206,7 @@ TEST(primal_curvedpolygon, isClosed_BiGon) CurvedPolygonType bPolygon; EXPECT_EQ(0, bPolygon.numEdges()); - EXPECT_EQ(false, bPolygon.isClosed()); + EXPECT_FALSE(bPolygon.isClosed()); // Bi-gon defined by a quadratic edge and a straight line std::vector CP = {PointType {0.8, .25}, @@ -435,11 +447,12 @@ TEST(primal_curvedpolygon, reverseOrientation) using SegmentType = primal::Segment; using BezierCurveType = primal::BezierCurve; - // Create a set of line segments on the unit circle + // Test several n-gons discretizing the unit circle const int MAX_SEG = 10; const PointType origin; for(int nseg = 3; nseg < MAX_SEG; ++nseg) { + // Create an n-gon with line segments going CCW along the unit circle CurvedPolygonType poly(nseg); axom::Array pts(nseg + 1); for(int i = 0; i < nseg; ++i) @@ -453,8 +466,9 @@ TEST(primal_curvedpolygon, reverseOrientation) { poly[i] = BezierCurveType(&pts[i], order); } + EXPECT_TRUE(poly.isClosed()); - // Perform some checks + // Perform some checks on the polygon for(int i = 0; i < nseg; ++i) { // check that the end point of each segment is equal to the start of the next @@ -467,7 +481,7 @@ TEST(primal_curvedpolygon, reverseOrientation) EXPECT_EQ(primal::ON_NEGATIVE_SIDE, primal::orientation(origin, seg)); } - // Create a polygon of reversed segments; + // Create a polygon with reversed orientation CurvedPolygonType reversed = poly; reversed.reverseOrientation(); @@ -490,7 +504,7 @@ TEST(primal_curvedpolygon, reverseOrientation) EXPECT_EQ(primal::ON_POSITIVE_SIDE, primal::orientation(origin, seg)); } - // Check that reversing twice yields the original; + // Check that reversing twice yields the original CurvedPolygonType reversedAgain = reversed; reversedAgain.reverseOrientation(); EXPECT_EQ(poly, reversedAgain); From a8850acd7ba44a4157ae6a676f64ea378f47b896 Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Mon, 29 Nov 2021 09:04:04 -0800 Subject: [PATCH 34/38] BezierCurve intersection only works in 2D, so remove NDIMS template paramter --- .../detail/intersect_bezier_impl.hpp | 46 +++++++++---------- src/axom/primal/operators/intersect.hpp | 6 +-- .../primal/tests/primal_bezier_intersect.cpp | 9 ++-- .../primal/tests/primal_curved_polygon.cpp | 8 ++-- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp index a89bbb96f4..66925002a8 100644 --- a/src/axom/primal/operators/detail/intersect_bezier_impl.hpp +++ b/src/axom/primal/operators/detail/intersect_bezier_impl.hpp @@ -57,9 +57,9 @@ namespace detail * \return True if the two curves intersect, False otherwise * \sa intersect_bezier */ -template -bool intersect_bezier_curves(const BezierCurve &c1, - const BezierCurve &c2, +template +bool intersect_bezier_curves(const BezierCurve &c1, + const BezierCurve &c2, std::vector &sp, std::vector &tp, double sq_tol, @@ -97,19 +97,19 @@ bool intersect_bezier_curves(const BezierCurve &c1, * \note This function does not properly handle collinear lines */ -template -bool intersect_2d_linear(const Point &a, - const Point &b, - const Point &c, - const Point &d, +template +bool intersect_2d_linear(const Point &a, + const Point &b, + const Point &c, + const Point &d, T &s, T &t); //------------------------------ IMPLEMENTATIONS ------------------------------ -template -bool intersect_bezier_curves(const BezierCurve &c1, - const BezierCurve &c2, +template +bool intersect_bezier_curves(const BezierCurve &c1, + const BezierCurve &c2, std::vector &sp, std::vector &tp, double sq_tol, @@ -120,8 +120,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, double t_offset, double t_scale) { - using BCurve = BezierCurve; - SLIC_ASSERT(NDIMS == 2); + using BCurve = BezierCurve; // Check bounding boxes to short-circuit the intersection if(!intersect(c1.boundingBox(), c2.boundingBox())) @@ -152,6 +151,7 @@ bool intersect_bezier_curves(const BezierCurve &c1, s_scale *= scaleFac; + // Note: we want to find all intersections, so don't short-circuit if(intersect_bezier_curves(c2, c3, tp, @@ -185,11 +185,11 @@ bool intersect_bezier_curves(const BezierCurve &c1, return foundIntersection; } -template -bool intersect_2d_linear(const Point &a, - const Point &b, - const Point &c, - const Point &d, +template +bool intersect_2d_linear(const Point &a, + const Point &b, + const Point &c, + const Point &d, T &s, T &t) { @@ -199,18 +199,16 @@ bool intersect_2d_linear(const Point &a, // Note: Uses exact floating point comparisons since the subdivision algorithm // provides both sides of the line segments for interior curve points. - AXOM_STATIC_ASSERT(NDIMS == 2); - // compute signed areas of endpoints of segment (c,d) w.r.t. segment (a,b) - auto area1 = twoDcross(a, b, c); - auto area2 = twoDcross(a, b, d); + const auto area1 = twoDcross(a, b, c); + const auto area2 = twoDcross(a, b, d); // early return if both have same orientation, or if d is collinear w/ (a,b) if(area2 == 0. || (area1 * area2) > 0.) return false; // compute signed areas of endpoints of segment (a,b) w.r.t. segment (c,d) - auto area3 = twoDcross(c, d, a); - auto area4 = area3 + area1 - area2; // equivalent to twoDcross(c,d,b) + const auto area3 = twoDcross(c, d, a); + const auto area4 = area3 + area1 - area2; // equivalent to twoDcross(c,d,b) // early return if both have same orientation, or if b is collinear w/ (c,d) if(area4 == 0. || (area3 * area4) > 0.) return false; diff --git a/src/axom/primal/operators/intersect.hpp b/src/axom/primal/operators/intersect.hpp index 7a488e0939..f20eca17d0 100644 --- a/src/axom/primal/operators/intersect.hpp +++ b/src/axom/primal/operators/intersect.hpp @@ -506,9 +506,9 @@ bool intersect(const OrientedBoundingBox& b1, * contain their first endpoint, but not their last endpoint. Thus, the * curves do not intersect at \f$ s==1 \f$ or at \f$ t==1 \f$. */ -template -bool intersect(const BezierCurve& c1, - const BezierCurve& c2, +template +bool intersect(const BezierCurve& c1, + const BezierCurve& c2, std::vector& sp, std::vector& tp, double tol = 1E-8) diff --git a/src/axom/primal/tests/primal_bezier_intersect.cpp b/src/axom/primal/tests/primal_bezier_intersect.cpp index 1ca0003a1a..2988f01b7c 100644 --- a/src/axom/primal/tests/primal_bezier_intersect.cpp +++ b/src/axom/primal/tests/primal_bezier_intersect.cpp @@ -32,15 +32,16 @@ namespace primal = axom::primal; * Param \a shouldPrintIntersections is used for debugging and for generating * the initial array of expected intersections. */ -template -void checkIntersections(const primal::BezierCurve& curve1, - const primal::BezierCurve& curve2, +template +void checkIntersections(const primal::BezierCurve& curve1, + const primal::BezierCurve& curve2, const std::vector& exp_s, const std::vector& exp_t, double eps, double test_eps, bool shouldPrintIntersections = false) { + constexpr int DIM = 2; using Array = std::vector; // Check validity of input data exp_s and exp_t. @@ -196,7 +197,7 @@ TEST(primal_bezier_inter, linear_bezier) TEST(primal_bezier_inter, linear_bezier_interp_params) { - static const int DIM = 2; + constexpr int DIM = 2; using CoordType = double; using PointType = primal::Point; diff --git a/src/axom/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index 8efaea0081..dc224872ab 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.cpp @@ -26,7 +26,7 @@ namespace primal = axom::primal; /*! - * Helper function to compute the area and centroid of a curved polygon and to check that they match expectations, + * 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 @@ -46,9 +46,9 @@ void checkMoments(const primal::CurvedPolygon& bPolygon, } /*! - * 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). + * 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 From 312326bd4ecca00b5face305a455dfc3b80d8b5c Mon Sep 17 00:00:00 2001 From: Axom Shared User Date: Thu, 13 Jan 2022 15:45:36 -0800 Subject: [PATCH 35/38] Updates copyright year --- src/axom/primal/geometry/CurvedPolygon.hpp | 2 +- src/axom/primal/operators/compute_moments.hpp | 2 +- src/axom/primal/operators/detail/compute_moments_impl.hpp | 2 +- src/axom/primal/tests/primal_compute_moments.cpp | 2 +- src/axom/primal/tests/primal_curved_polygon.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/axom/primal/geometry/CurvedPolygon.hpp b/src/axom/primal/geometry/CurvedPolygon.hpp index 5e53278784..5805572c40 100644 --- a/src/axom/primal/geometry/CurvedPolygon.hpp +++ b/src/axom/primal/geometry/CurvedPolygon.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/operators/compute_moments.hpp b/src/axom/primal/operators/compute_moments.hpp index 5545e1bc3f..88f258737e 100644 --- a/src/axom/primal/operators/compute_moments.hpp +++ b/src/axom/primal/operators/compute_moments.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/operators/detail/compute_moments_impl.hpp b/src/axom/primal/operators/detail/compute_moments_impl.hpp index 9fa3612d84..b3df6060cb 100644 --- a/src/axom/primal/operators/detail/compute_moments_impl.hpp +++ b/src/axom/primal/operators/detail/compute_moments_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_compute_moments.cpp b/src/axom/primal/tests/primal_compute_moments.cpp index 3f0edb2ab3..0fb0d018f0 100644 --- a/src/axom/primal/tests/primal_compute_moments.cpp +++ b/src/axom/primal/tests/primal_compute_moments.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/primal/tests/primal_curved_polygon.cpp b/src/axom/primal/tests/primal_curved_polygon.cpp index dc224872ab..ef0ef95373 100644 --- a/src/axom/primal/tests/primal_curved_polygon.cpp +++ b/src/axom/primal/tests/primal_curved_polygon.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) From c699374764666be86b271675ad9b36e5ea0c7b2b Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Fri, 17 Jun 2022 16:02:17 -0700 Subject: [PATCH 36/38] Updates RELEASE-NOTES --- .mailmap | 2 ++ RELEASE-NOTES.md | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/.mailmap b/.mailmap index fdd425c3a1..5f5e8e3031 100644 --- a/.mailmap +++ b/.mailmap @@ -15,10 +15,12 @@ Benjamin Curtice Corbett Benjamin Corbett Ben Corbett <32752943+corbett5@users.noreply.github.com> Brian Manh Hien Han Brian Han Brian Manh Hien Han Brian Manh Hien Han +Brian T.N. Gunney Brian Gunney <45609916+gunney1@users.noreply.github.com> Chris White Christopher A. White Cyrus D. Harrison Cyrus Harrison Cyrus D. Harrison Cyrus Harrison Cyrus D. Harrison Cyrus +Daniel Taller Danny Taller <66029857+dtaller@users.noreply.github.com> Esteban Pauli Esteban Pauli <40901502+estebanpauli@users.noreply.github.com> Evan Taylor Desantola Evan Taylor DeSantola George Zagaris George Zagaris diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index b01e8460c2..e3912e3eb4 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -88,6 +88,9 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Adds an overload to quest's `SignedDistance` query to return the closest point on the surface to the query point and the surface normal at that point. Also exposes this functionality in quest's signed_distance C API. +- Adds utility function for linear interpolation (`lerp`) of two numbers and to compute binomial coefficients +- Adds a `CurvedPolygon` class to primal representing a polygon with `BezierCurves` as edges +- Adds functions to compute the moments (area, centroid) of a `CurvedPolygon` ### Changed - Axom now requires C++14 and will default to that if not specified via `BLT_CXX_STD`. @@ -140,6 +143,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ and added a new derived class `sidre::IndexedCollection` - Spin: `BVH::findPoints/Rays/BoundingBoxes()` candidate search methods now accept an `axom::ArrayView` for the `offsets` and `counts` output arrays, and return `candidates` as an `axom::Array`. +- Renamed `primal::Polygon::centroid()` to `primal::Polygon::vertexMean()` because it was not actualy computing the centroid. ### Fixed - Fixed a bug relating to swap and assignment operations for multidimensional `axom::Array`s From f59b94c4a05eb7903b190b8b56437ed7957bb203 Mon Sep 17 00:00:00 2001 From: Kenneth Weiss Date: Wed, 22 Jun 2022 11:17:11 -0700 Subject: [PATCH 37/38] Minor changes per PR suggestions --- src/axom/core/tests/utils_utilities.hpp | 2 ++ src/axom/primal/geometry/Polygon.hpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/axom/core/tests/utils_utilities.hpp b/src/axom/core/tests/utils_utilities.hpp index bf1887f263..287cabacf4 100644 --- a/src/axom/core/tests/utils_utilities.hpp +++ b/src/axom/core/tests/utils_utilities.hpp @@ -285,6 +285,8 @@ TEST(utils_utilities, lerp) { double A = axom::utilities::random_real(lower, upper); double B = axom::utilities::random_real(lower, upper); + + // Test interpolation and also extrapolation beyond endpoints. double t = axom::utilities::random_real(-1.5, 1.5); double exp = A + (B - A) * t; diff --git a/src/axom/primal/geometry/Polygon.hpp b/src/axom/primal/geometry/Polygon.hpp index eb3db11078..b90c1822a1 100644 --- a/src/axom/primal/geometry/Polygon.hpp +++ b/src/axom/primal/geometry/Polygon.hpp @@ -42,7 +42,7 @@ template class Polygon { public: - using PointType = primal::Point; + using PointType = Point; public: /*! Default constructor for an empty polygon */ From 7dca106e6a8408303012365781a4a90fcaddf89f Mon Sep 17 00:00:00 2001 From: Kenny Weiss Date: Wed, 22 Jun 2022 11:20:16 -0700 Subject: [PATCH 38/38] Minor clarifications to README --- RELEASE-NOTES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index e3912e3eb4..d175c020f1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -88,7 +88,8 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ - Adds an overload to quest's `SignedDistance` query to return the closest point on the surface to the query point and the surface normal at that point. Also exposes this functionality in quest's signed_distance C API. -- Adds utility function for linear interpolation (`lerp`) of two numbers and to compute binomial coefficients +- Adds utility function for linear interpolation (`lerp`) of two numbers +- Adds utility function to compute binomial coefficients - Adds a `CurvedPolygon` class to primal representing a polygon with `BezierCurves` as edges - Adds functions to compute the moments (area, centroid) of a `CurvedPolygon` @@ -143,7 +144,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/ and added a new derived class `sidre::IndexedCollection` - Spin: `BVH::findPoints/Rays/BoundingBoxes()` candidate search methods now accept an `axom::ArrayView` for the `offsets` and `counts` output arrays, and return `candidates` as an `axom::Array`. -- Renamed `primal::Polygon::centroid()` to `primal::Polygon::vertexMean()` because it was not actualy computing the centroid. +- Renamed `primal::Polygon::centroid()` to `primal::Polygon::vertexMean()` because it was not actually computing the centroid. ### Fixed - Fixed a bug relating to swap and assignment operations for multidimensional `axom::Array`s