diff --git a/dumux/common/geometry/geometryintersection.hh b/dumux/common/geometry/geometryintersection.hh index e3199636d76d4bd51c547223d0331a588aa5d298..69b5c894d3cd76dbf8e265d0708f5ce3e3c28595 100644 --- a/dumux/common/geometry/geometryintersection.hh +++ b/dumux/common/geometry/geometryintersection.hh @@ -32,6 +32,7 @@ #include <dumux/common/math.hh> #include <dumux/common/geometry/intersectspointgeometry.hh> #include <dumux/common/geometry/grahamconvexhull.hh> +#include <dumux/common/geometry/boundingboxtree.hh> namespace Dumux { namespace IntersectionPolicy { @@ -214,6 +215,126 @@ public: } }; +/*! + * \ingroup Geometry + * \brief A class for segment--segment intersection in 2d space + */ +template <class Geometry1, class Geometry2, class Policy> +class GeometryIntersection<Geometry1, Geometry2, Policy, 2, 1, 1> +{ + enum { dimworld = 2 }; + enum { dim1 = 1 }; + enum { dim2 = 1 }; + + // base epsilon for floating point comparisons + static constexpr typename Policy::ctype eps_ = 1.5e-7; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + + //! Deprecated alias, will be removed after 3.1 + using IntersectionType DUNE_DEPRECATED_MSG("Please use Intersection instead") = Intersection; + + /*! + * \brief Colliding two segments + * \param geo1/geo2 The geometries to intersect + * \param intersection The intersection point + * \note This overload is used when point-like intersections are seeked + */ + template<class P = Policy, std::enable_if_t<P::dimIntersection == 0, int> = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + const auto v1 = geo1.corner(1) - geo1.corner(0); + const auto v2 = geo2.corner(1) - geo2.corner(0); + const auto ac = geo2.corner(0) - geo1.corner(0); + + auto n2 = Point({-1.0*v2[1], v2[0]}); + n2 /= n2.two_norm(); + + // compute distance of first corner on geo2 to line1 + const auto dist12 = n2*ac; + + // first check parallel segments + using std::abs; + using std::sqrt; + + const auto v1Norm2 = v1.two_norm2(); + const auto eps = eps_*sqrt(v1Norm2); + const auto eps2 = eps_*v1Norm2; + + const auto sp = n2*v1; + if (abs(sp) < eps) + { + if (abs(dist12) > eps) + return false; + + // point intersection can only be one of the corners + if ( (v1*v2) < 0.0 ) + { + if ( ac.two_norm2() < eps2 ) + { intersection = geo2.corner(0); return true; } + + if ( (geo2.corner(1) - geo1.corner(1)).two_norm2() < eps2 ) + { intersection = geo2.corner(1); return true; } + } + else + { + if ( (geo2.corner(1) - geo1.corner(0)).two_norm2() < eps2 ) + { intersection = geo2.corner(1); return true; } + + if ( (geo2.corner(0) - geo1.corner(1)).two_norm2() < eps2 ) + { intersection = geo2.corner(0); return true; } + } + + // no intersection + return false; + } + + // intersection point on v1 in local coords + const auto t1 = dist12 / sp; + + // check if the local coords are valid + if (t1 < -1.0*eps_ || t1 > 1.0 + eps_) + return false; + + // compute global coordinates + auto isPoint = geo1.global(t1); + + // check if point is in bounding box of v2 + const auto c = geo2.corner(0); + const auto d = geo2.corner(1); + + using std::min; using std::max; + std::array<ctype, 4> bBox({ min(c[0], d[0]), min(c[1], d[1]), max(c[0], d[0]), max(c[1], d[1]) }); + + if ( intersectsPointBoundingBox(isPoint, bBox.data()) ) + { + intersection = std::move(isPoint); + return true; + } + + return false; + } + + /*! + * \brief Colliding two segments + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store corners of intersection segment + * \note this overload is used when segment-like intersections are seeked + * \todo implement this query + */ + template<class P = Policy, std::enable_if_t<P::dimIntersection == 1, int> = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "segment-segment intersection detection for segment-like intersections"); + } +}; + /*! * \ingroup Geometry * \brief A class for polygon--segment intersection in 2d space @@ -363,6 +484,155 @@ public: } }; +/*! + * \ingroup Geometry + * \brief A class for polygon--polygon intersection in 2d space + */ +template <class Geometry1, class Geometry2, class Policy> +class GeometryIntersection<Geometry1, Geometry2, Policy, 2, 2, 2> +{ + enum { dimworld = 2 }; + enum { dim1 = 2 }; + enum { dim2 = 2 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::Intersection; + + //! Deprecated alias, will be removed after 3.1 + using IntersectionType DUNE_DEPRECATED_MSG("Please use Intersection instead") = Intersection; + +private: + static constexpr ctype eps_ = 1.5e-7; // base epsilon for floating point comparisons + using ReferenceElementsGeo1 = typename Dune::ReferenceElements<ctype, dim1>; + using ReferenceElementsGeo2 = typename Dune::ReferenceElements<ctype, dim2>; + +public: + /*! + * \brief Colliding two polygons + * \note First we find the vertex candidates for the intersection region as follows: + * Add polygon vertices that are inside the other polygon + * Add intersections of polygon edges + * Remove duplicate points from the list + * Compute the convex hull polygon + * Return a triangulation of that polygon as intersection + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corner points of the polygon (as convex hull) + * \note This overload is used when polygon like intersections are seeked + */ + template<class P = Policy, std::enable_if_t<P::dimIntersection == 2, int> = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + + // the candidate intersection points + std::vector<Point> points; points.reserve(6); + + // add polygon1 corners that are inside polygon2 + for (int i = 0; i < geo1.corners(); ++i) + if (intersectsPointGeometry(geo1.corner(i), geo2)) + points.emplace_back(geo1.corner(i)); + + const auto numPoints1 = points.size(); + if (numPoints1 != geo1.corners()) + { + // add polygon2 corners that are inside polygon1 + for (int i = 0; i < geo2.corners(); ++i) + if (intersectsPointGeometry(geo2.corner(i), geo1)) + points.emplace_back(geo2.corner(i)); + + if (points.empty()) + return false; + + if (points.size() - numPoints1 != geo2.corners()) + { + const auto referenceElement1 = ReferenceElementsGeo1::general(geo1.type()); + const auto referenceElement2 = ReferenceElementsGeo2::general(geo2.type()); + + // add intersections of edges + using SegGeometry = Dune::MultiLinearGeometry<ctype, 1, dimworld>; + using PointPolicy = IntersectionPolicy::PointPolicy<ctype, dimworld>; + for (int i = 0; i < referenceElement1.size(dim1-1); ++i) + { + const auto localEdgeGeom1 = referenceElement1.template geometry<dim1-1>(i); + const auto edge1 = SegGeometry( Dune::GeometryTypes::line, + std::vector<Point>( {geo1.global(localEdgeGeom1.corner(0)), + geo1.global(localEdgeGeom1.corner(1))} )); + + for (int j = 0; j < referenceElement2.size(dim2-1); ++j) + { + const auto localEdgeGeom2 = referenceElement2.template geometry<dim2-1>(j); + const auto edge2 = SegGeometry( Dune::GeometryTypes::line, + std::vector<Point>( {geo2.global(localEdgeGeom2.corner(0)), + geo2.global(localEdgeGeom2.corner(1))} )); + + using EdgeTest = GeometryIntersection<SegGeometry, SegGeometry, PointPolicy>; + typename EdgeTest::Intersection edgeIntersection; + if (EdgeTest::intersection(edge1, edge2, edgeIntersection)) + points.emplace_back(edgeIntersection); + } + } + } + } + + if (points.empty()) + return false; + + // remove duplicates + const auto eps = (geo1.corner(0) - geo1.corner(1)).two_norm()*eps_; + std::sort(points.begin(), points.end(), [&eps](const auto& a, const auto& b) -> bool + { + using std::abs; + return (abs(a[0]-b[0]) > eps ? a[0] < b[0] : a[1] < b[1]); + }); + + auto removeIt = std::unique(points.begin(), points.end(), [&eps](const auto& a, const auto&b) + { + return (b-a).two_norm() < eps; + }); + + points.erase(removeIt, points.end()); + + // return false if we don't have at least three unique points + if (points.size() < 3) + return false; + + // intersection polygon is convex hull of above points + intersection = grahamConvexHull<2>(points); + assert(!intersection.empty()); + return true; + } + + /*! + * \brief Colliding two polygons + * \param geo1/geo2 The geometries to intersect + * \param intersection Container to store the corners of intersection segment + * \note this overload is used when segment-like intersections are seeked + * \todo implement this query + */ + template<class P = Policy, std::enable_if_t<P::dimIntersection == 1, int> = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polygon-polygon intersection detection for segment-like intersections"); + } + + /*! + * \brief Colliding two polygons + * \param geo1/geo2 The geometries to intersect + * \param intersection The intersection point + * \note this overload is used when point-like intersections are seeked + * \todo implement this query + */ + template<class P = Policy, std::enable_if_t<P::dimIntersection == 0, int> = 0> + static bool intersection(const Geometry1& geo1, const Geometry2& geo2, Intersection& intersection) + { + static_assert(int(dimworld) == int(Geometry2::coorddimension), "Can only collide geometries of same coordinate dimension"); + DUNE_THROW(Dune::NotImplemented, "Polygon-polygon intersection detection for touching points"); + } +}; + /*! * \ingroup Geometry * \brief A class for polyhedron--segment intersection in 3d space @@ -551,7 +821,7 @@ public: * Compute the convex hull polygon * Return a triangulation of that polygon as intersection * \param geo1/geo2 The geometries to intersect - * \param intersection A triangulation of the intersection polygon + * \param intersection Container to store the corner points of the polygon (as convex hull) * \note This overload is used when polygon like intersections are seeked */ template<class P = Policy, std::enable_if_t<P::dimIntersection == 2, int> = 0> @@ -658,7 +928,7 @@ public: if (points.size() < 3) return false; // intersection polygon is convex hull of above points - intersection = grahamConvexHull2d3d(points); + intersection = grahamConvexHull<2>(points); assert(!intersection.empty()); return true; @@ -675,7 +945,7 @@ public: * Compute the convex hull polygon * Return a triangulation of that polygon as intersection * \param geo1/geo2 The geometries to intersect - * \param intersection A triangulation of the intersection polygon + * \param intersection Container to store the intersection result * \todo implement overloads for segment or point-like intersections */ template<class P = Policy, std::enable_if_t<P::dimIntersection != 2, int> = 0> diff --git a/dumux/common/geometry/grahamconvexhull.hh b/dumux/common/geometry/grahamconvexhull.hh index 463b61f90714689ac3069ea1f9132aa165d69e47..9a26d4e44fbfca38abb34703a69bf81926acc923 100644 --- a/dumux/common/geometry/grahamconvexhull.hh +++ b/dumux/common/geometry/grahamconvexhull.hh @@ -26,6 +26,7 @@ #include <vector> #include <array> #include <algorithm> +#include <iterator> #include <dune/common/deprecated.hh> #include <dune/common/exceptions.hh> @@ -60,13 +61,13 @@ int getOrientation(const Dune::FieldVector<ctype, 3>& a, * \ingroup Geometry * \brief Compute the points making up the convex hull around the given set of unordered points * \note We assume that all points are coplanar and there are no indentical points in the list + * \note This is the overload if we are not allowed to write into the given points vector */ -template<class ctype> -std::vector<Dune::FieldVector<ctype, 3>> -grahamConvexHull2d3d(const std::vector<Dune::FieldVector<ctype, 3>>& points) +template<int dim, class Points> +Points grahamConvexHull(const Points& points) { auto copyPoints = points; - return grahamConvexHull2d3d(copyPoints); + return grahamConvexHull<dim>(copyPoints); } /*! @@ -76,9 +77,10 @@ grahamConvexHull2d3d(const std::vector<Dune::FieldVector<ctype, 3>>& points) * \note This algorithm changes the order of the given points a bit * as they are unordered anyway this shouldn't matter too much */ -template<class ctype> +template<int dim, class ctype, + std::enable_if_t<(dim==2), int> = 0> std::vector<Dune::FieldVector<ctype, 3>> -grahamConvexHull2d3d(std::vector<Dune::FieldVector<ctype, 3>>& points) +grahamConvexHull(std::vector<Dune::FieldVector<ctype, 3>>& points) { using Point = Dune::FieldVector<ctype, 3>; std::vector<Point> convexHull; @@ -175,6 +177,49 @@ grahamConvexHull2d3d(std::vector<Dune::FieldVector<ctype, 3>>& points) return convexHull; } +/*! + * \ingroup Geometry + * \brief Compute the points making up the convex hull around the given set of unordered points + * \note This is the specialization for 2d space. Here, we make use of the generic implementation + * for the case of coplanar points in 3d space (a more efficient implementation could be provided). + */ +template<int dim, class ctype, + std::enable_if_t<(dim==2), int> = 0> +std::vector<Dune::FieldVector<ctype, 2>> +grahamConvexHull(const std::vector<Dune::FieldVector<ctype, 2>>& points) +{ + std::vector<Dune::FieldVector<ctype, 3>> points3D; + points3D.reserve(points.size()); + std::transform(points.begin(), points.end(), std::back_inserter(points3D), + [](const auto& p) { return Dune::FieldVector<ctype, 3>({p[0], p[1], 0.0}); }); + + const auto result3D = grahamConvexHull<2>(points3D); + + std::vector<Dune::FieldVector<ctype, 2>> result2D; + result2D.reserve(result3D.size()); + std::transform(result3D.begin(), result3D.end(), std::back_inserter(result2D), + [](const auto& p) { return Dune::FieldVector<ctype, 2>({p[0], p[1]}); }); + + return result2D; +} + +// deprecated interfaces +#ifndef DOXYGEN +template<class ctype> +std::vector<Dune::FieldVector<ctype, 3>> +DUNE_DEPRECATED_MSG("Use grahamConvexHull<dim> with dim as template argument. Will be removed after 3.1") +grahamConvexHull2d3d(const std::vector<Dune::FieldVector<ctype, 3>>& points) +{ + auto copyPoints = points; + return grahamConvexHull<2>(copyPoints); +} + +template<class ctype> +std::vector<Dune::FieldVector<ctype, 3>> +DUNE_DEPRECATED_MSG("Use grahamConvexHull<dim> with dim as template argument. Will be removed after 3.1") +grahamConvexHull2d3d(std::vector<Dune::FieldVector<ctype, 3>>& points) +{ return grahamConvexHull<2>(points); } + /*! * \ingroup Geometry * \brief Triangulate area given points of the convex hull @@ -183,9 +228,10 @@ grahamConvexHull2d3d(std::vector<Dune::FieldVector<ctype, 3>>& points) */ template<class ctype> std::vector<std::array<Dune::FieldVector<ctype, 3>, 3> > -DUNE_DEPRECATED_MSG("Please use triangulate") +DUNE_DEPRECATED_MSG("Please use triangulate. Will be removed after 3.1") triangulateConvexHull(const std::vector<Dune::FieldVector<ctype, 3>>& convexHull) { return triangulate<2, 3>(convexHull); } +#endif } // end namespace Dumux diff --git a/dumux/common/geometry/triangulation.hh b/dumux/common/geometry/triangulation.hh index 8bc76f992dcdbbc7231c3a97ebea344cdf00dacd..2b04870cd6e7357f7d9ebc18a1fd2323caac982a 100644 --- a/dumux/common/geometry/triangulation.hh +++ b/dumux/common/geometry/triangulation.hh @@ -58,19 +58,19 @@ using Triangulation = std::vector< std::array<Dune::FieldVector<ctype, dimWorld> * \tparam dimWorld The dimension of the coordinate space * \tparam Policy Specifies the algorithm to be used for triangulation * - * \note this specialization is for 2d triangulations in 3d space using mid point policy + * \note this specialization is for 2d triangulations using mid point policy * \note Assumes all points of the convex hull are coplanar * \note This inserts a mid point and connects all corners with that point to triangles */ template< int dim, int dimWorld, class Policy = TriangulationPolicy::DefaultPolicy<dim, dimWorld>, class RandomAccessContainer, std::enable_if_t< std::is_same<Policy, TriangulationPolicy::MidPointPolicy>::value - && dim == 2 && dimWorld == 3, int> = 0 > + && dim == 2, int> = 0 > inline Triangulation<dim, dimWorld, typename RandomAccessContainer::value_type::value_type> triangulate(const RandomAccessContainer& convexHull) { using ctype = typename RandomAccessContainer::value_type::value_type; - using Point = Dune::FieldVector<ctype, 3>; + using Point = Dune::FieldVector<ctype, dimWorld>; using Triangle = std::array<Point, 3>; static_assert(std::is_same<typename RandomAccessContainer::value_type, Point>::value, diff --git a/test/common/geometry/CMakeLists.txt b/test/common/geometry/CMakeLists.txt index d6fb00cb610ac724531ef6311484431b8b0c0f29..28f60e2606f2dd9c02bb7d07fc883b172b730e77 100644 --- a/test/common/geometry/CMakeLists.txt +++ b/test/common/geometry/CMakeLists.txt @@ -1,6 +1,8 @@ dune_add_test(SOURCES test_0d1d_intersection.cc LABELS unit) +dune_add_test(SOURCES test_1d1d_intersection.cc LABELS unit) dune_add_test(SOURCES test_1d3d_intersection.cc LABELS unit) dune_add_test(SOURCES test_1d2d_intersection.cc LABELS unit) +dune_add_test(SOURCES test_2d2d_intersection.cc LABELS unit) dune_add_test(SOURCES test_2d3d_intersection.cc LABELS unit) dune_add_test(SOURCES test_graham_convex_hull.cc LABELS unit) dune_add_test(SOURCES test_makegeometry.cc LABELS unit) diff --git a/test/common/geometry/test_1d1d_intersection.cc b/test/common/geometry/test_1d1d_intersection.cc new file mode 100644 index 0000000000000000000000000000000000000000..787f4176d0b99297c6441f6522c9dd8185de0df3 --- /dev/null +++ b/test/common/geometry/test_1d1d_intersection.cc @@ -0,0 +1,131 @@ +#include <config.h> + +#include <iostream> +#include <algorithm> + +#include <dune/common/exceptions.hh> +#include <dune/common/parallel/mpihelper.hh> +#include <dune/common/fvector.hh> + +#include <dumux/common/geometry/geometryintersection.hh> + +#ifndef DOXYGEN +template<int dimWorld> +Dune::FieldVector<double, dimWorld> +makePosition( double x, double y ) +{ + if (dimWorld == 2) + return {x, y}; + else if (dimWorld == 3) + return {x, y, 1.0}; + else + DUNE_THROW(Dune::InvalidStateException, "Invalid dimworld"); +} + +template<int dimWorld> +Dune::MultiLinearGeometry<double, 1, dimWorld> +makeLine( Dune::FieldVector<double, dimWorld>&& source, + Dune::FieldVector<double, dimWorld>&& target) +{ + using CornerStorage = std::vector<Dune::FieldVector<double, dimWorld>>; + return { Dune::GeometryTypes::line, + CornerStorage({std::move(source), std::move(target)}) }; +} + +template<int dimworld> +bool testPointIntersection(const Dune::MultiLinearGeometry<double, 1, dimworld>& seg1, + const Dune::MultiLinearGeometry<double, 1, dimworld>& seg2, + bool foundExpected) +{ + using Segment = Dune::MultiLinearGeometry<double, 1, dimworld>; + using Policy = Dumux::IntersectionPolicy::PointPolicy<double, dimworld>; + using Algorithm = Dumux::GeometryIntersection<Segment, Segment, Policy>; + + typename Algorithm::Intersection is; + bool found = Algorithm::intersection(seg1, seg2, is); + + if (!found && foundExpected) + std::cerr << "Failed detecting intersection with "; + else if (found && foundExpected) + std::cout << "Found intersection at " << is << " with "; + else if (found && !foundExpected) + std::cerr << "Found false positive: intersection at " << is << " with "; + else if (!found && !foundExpected) + std::cout << "No intersection with "; + + std::cout << seg1.corner(0) << " - " << seg1.corner(1) << " / " + << seg2.corner(0) << " - " << seg2.corner(1) << std::endl; + + return (found == foundExpected); +} + +template<int dimWorld> +void testPointIntersections(std::vector<bool>& returns) +{ + // test intersection points of segments + for (auto scaling : {1.0, 1e3, 1e12, 1e-12}) + { + std::cout << "Test point intersections with scaling " << scaling << std::endl; + + const auto seg1 = makeLine( makePosition<dimWorld>(0.0, 0.0), makePosition<dimWorld>(0.0, scaling*1.0) ); + const auto seg2 = makeLine( makePosition<dimWorld>(0.0, 0.0), makePosition<dimWorld>(scaling*1.0, 0.0) ); + const auto seg3 = makeLine( makePosition<dimWorld>(scaling*0.5, 0.0), makePosition<dimWorld>(scaling*1.0, 0.0) ); + const auto seg4 = makeLine( makePosition<dimWorld>(-1.0*scaling*0.5, scaling*0.5), makePosition<dimWorld>(scaling*1.0, 0.0) ); + const auto seg5 = makeLine( makePosition<dimWorld>(0.0, scaling*1.0), makePosition<dimWorld>(scaling*1.0, 0.0) ); + const auto seg6 = makeLine( makePosition<dimWorld>(-1.0*scaling*0.5, scaling*0.5), makePosition<dimWorld>(0.0, scaling*0.5) ); + const auto seg7 = makeLine( makePosition<dimWorld>(-1.0*scaling*0.5, scaling*0.5), makePosition<dimWorld>(-1.0*scaling*0.1, scaling*0.5) ); + + // segments with same orientation touching in source or target or not at all + const auto seg8 = makeLine( makePosition<dimWorld>(0.0, 0.0), makePosition<dimWorld>(0.0, -1.0*scaling*1.0) ); + const auto seg9 = makeLine( makePosition<dimWorld>(0.0, scaling*1.0), makePosition<dimWorld>(0.0, scaling*2.0) ); + const auto seg10 = makeLine( makePosition<dimWorld>(0.0, -1.0*0.01*scaling), makePosition<dimWorld>(0.0, -1.0*scaling*1.0) ); + const auto seg11 = makeLine( makePosition<dimWorld>(0.0, scaling*1.01), makePosition<dimWorld>(0.0, scaling*2.0) ); + + returns.push_back(testPointIntersection(seg1, seg2, true)); + returns.push_back(testPointIntersection(seg1, seg3, false)); + returns.push_back(testPointIntersection(seg1, seg4, true)); + returns.push_back(testPointIntersection(seg1, seg5, true)); + returns.push_back(testPointIntersection(seg1, seg6, true)); + returns.push_back(testPointIntersection(seg1, seg7, false)); + returns.push_back(testPointIntersection(seg1, seg8, true)); + returns.push_back(testPointIntersection(seg1, seg9, true)); + returns.push_back(testPointIntersection(seg1, seg10, false)); + returns.push_back(testPointIntersection(seg1, seg11, false)); + + std::cout << std::endl; + } +} + +#endif + +int main (int argc, char *argv[]) try +{ + // maybe initialize mpi + Dune::MPIHelper::instance(argc, argv); + + // collect returns to determine exit code + std::vector<bool> returns; + + // test for dimWorld = 2 + testPointIntersections<2>(returns); + + // TODO: implement and test for dimWorld = 3 + // testPointIntersections<3>(returns); + + // TODO: implement and test segment intersections + + // determine the exit code + if (std::any_of(returns.begin(), returns.end(), [](bool i){ return !i; })) + return 1; + + std::cout << "All tests passed!" << std::endl; + + return 0; +} +// ////////////////////////////////// +// Error handler +// ///////////////////////////////// +catch (const Dune::Exception& e) { + std::cout << e << std::endl; + return 1; +} diff --git a/test/common/geometry/test_2d2d_intersection.cc b/test/common/geometry/test_2d2d_intersection.cc new file mode 100644 index 0000000000000000000000000000000000000000..1d54d848230bd4869bdaeb6e69229a2bb06532fd --- /dev/null +++ b/test/common/geometry/test_2d2d_intersection.cc @@ -0,0 +1,155 @@ +#include <config.h> + +#include <iostream> + +#include <dune/common/exceptions.hh> +#include <dune/common/fvector.hh> +#include <dune/geometry/multilineargeometry.hh> + +#include <dumux/common/geometry/geometryintersection.hh> + +#ifndef DOXYGEN +template<int dimWorld> +Dune::FieldVector<double, dimWorld> +makePosition( double x, double y ) +{ + if (dimWorld == 2) + return {x, y}; + else if (dimWorld == 3) + return {x, y, 1.0}; + else + DUNE_THROW(Dune::InvalidStateException, "Invalid dimworld"); +} + +template<int dimWorld> +Dune::MultiLinearGeometry<double, 2, dimWorld> +makeTriangle( Dune::FieldVector<double, dimWorld>&& a, + Dune::FieldVector<double, dimWorld>&& b, + Dune::FieldVector<double, dimWorld>&& c ) +{ + using CornerStorage = std::vector<Dune::FieldVector<double, dimWorld>>; + return { Dune::GeometryTypes::triangle, + CornerStorage({std::move(a), std::move(b), std::move(c)}) }; +} + +template<int dimWorld> +Dune::MultiLinearGeometry<double, 2, dimWorld> +makeQuadrilateral( Dune::FieldVector<double, dimWorld>&& a, + Dune::FieldVector<double, dimWorld>&& b, + Dune::FieldVector<double, dimWorld>&& c, + Dune::FieldVector<double, dimWorld>&& d ) +{ + using CornerStorage = std::vector<Dune::FieldVector<double, dimWorld>>; + return { Dune::GeometryTypes::quadrilateral, + CornerStorage({std::move(a), std::move(b), std::move(c), std::move(d)}) }; +} + +template<int dimworld, class Polygon1, class Polygon2> +bool testPolygonIntersection(const Polygon1& pol1, + const Polygon2& pol2, + bool expectIntersection) +{ + using Policy = Dumux::IntersectionPolicy::PolygonPolicy<double, dimworld>; + using Algorithm = Dumux::GeometryIntersection<Polygon1, Polygon2, Policy>; + + typename Algorithm::IntersectionType intersection; + const bool found = Algorithm::intersection(pol1, pol2, intersection); + if (found && !expectIntersection) + std::cout << "Found false positive!" << std::endl; + else if (!found && expectIntersection) + std::cout << "Could not find intersection!" << std::endl; + else if (found && expectIntersection) + std::cout << "Found intersection" << std::endl; + else + std::cout << "No intersection" << std::endl; + + return (found == expectIntersection); +} + +template<int dimWorld> +void testPolygonIntersections(std::vector<bool>& returns) +{ + for (auto scaling : {1.0, 1e3, 1e12, 1e-12}) + { + std::cout << "Test with scaling " << scaling << std::endl; + const auto tria1 = makeTriangle( makePosition<dimWorld>(0.0, 0.0), + makePosition<dimWorld>(0.0, 1.0*scaling), + makePosition<dimWorld>(1.0*scaling, 1.0*scaling) ); + const auto tria2 = makeTriangle( makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(0.0, 0.0), + makePosition<dimWorld>(0.0, 1.0*scaling) ); + const auto tria3 = makeTriangle( makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(0.0, 0.5*scaling), + makePosition<dimWorld>(1.0*scaling, 1.0*scaling) ); + const auto tria4 = makeTriangle( makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(1.0*scaling, 1.0*scaling), + makePosition<dimWorld>(2.0*scaling, 1.0*scaling) ); + const auto tria5 = makeTriangle( makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(0.5*scaling, 0.5*scaling), + makePosition<dimWorld>(2.0*scaling, 1.0*scaling) ); + const auto tria6 = makeTriangle( makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(0.501*scaling, 0.501*scaling), + makePosition<dimWorld>(2.0*scaling, 1.0*scaling) ); + const auto tria7 = makeTriangle( makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(0.499*scaling, 0.501*scaling), + makePosition<dimWorld>(2.0*scaling, 1.0*scaling) ); + + returns.push_back(testPolygonIntersection<dimWorld>(tria1, tria2, true)); + returns.push_back(testPolygonIntersection<dimWorld>(tria1, tria3, true)); + returns.push_back(testPolygonIntersection<dimWorld>(tria1, tria4, false)); // touches in point + returns.push_back(testPolygonIntersection<dimWorld>(tria1, tria5, false)); // touches in edge + returns.push_back(testPolygonIntersection<dimWorld>(tria1, tria6, false)); // little bit outside triangle + returns.push_back(testPolygonIntersection<dimWorld>(tria1, tria7, true)); // little bit inside triangle + + // test some quadrilaterals + const auto quad1 = makeQuadrilateral( makePosition<dimWorld>(0.0, 0.0), + makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(0.0, 1.0*scaling), + makePosition<dimWorld>(1.0*scaling, 1.0*scaling) ); + const auto quad2 = makeQuadrilateral( makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(2.0*scaling, 0.0), + makePosition<dimWorld>(1.0*scaling, 2.0*scaling), + makePosition<dimWorld>(2.0*scaling, 2.0*scaling) ); + const auto quad3 = makeQuadrilateral( makePosition<dimWorld>(-1.0*scaling, 0.0), + makePosition<dimWorld>(0.0, 0.0), + makePosition<dimWorld>(-1.0*scaling, 1.0*scaling), + makePosition<dimWorld>(0.0, 1.0*scaling) ); + const auto quad4 = makeQuadrilateral( makePosition<dimWorld>(0.5*scaling, 0.0), + makePosition<dimWorld>(0.5*scaling, 0.501*scaling), + makePosition<dimWorld>(1.0*scaling, 0.0), + makePosition<dimWorld>(1.0*scaling, 1.0*scaling) ); + + returns.push_back(testPolygonIntersection<dimWorld>(tria1, quad1, true)); + returns.push_back(testPolygonIntersection<dimWorld>(tria1, quad2, false)); // touches in point + returns.push_back(testPolygonIntersection<dimWorld>(tria1, quad3, false)); // touches in edge + returns.push_back(testPolygonIntersection<dimWorld>(tria1, quad4, true)); // small overlap region + + std::cout << std::endl; + } +} + +#endif + +int main(int argc, char* argv[]) try +{ + std::vector<bool> returns; + testPolygonIntersections<2>(returns); + + // TODO: implement and test intersections in 3d + // TODO: implement and test point and segment intersections + + // determine the exit code + if (std::any_of(returns.begin(), returns.end(), [](bool i){ return !i; })) + return 1; + + std::cout << "All tests passed!" << std::endl; + + return 0; +} +// ////////////////////////////////// +// Error handler +// ///////////////////////////////// +catch (const Dune::Exception& e) { + std::cout << e << std::endl; + return 1; +} diff --git a/test/common/geometry/test_graham_convex_hull.cc b/test/common/geometry/test_graham_convex_hull.cc index 5a663e4e41c51845c9fe60725af7d08f2d1186fe..a59029cc12325393b8006727d82baa7f400cedbb 100644 --- a/test/common/geometry/test_graham_convex_hull.cc +++ b/test/common/geometry/test_graham_convex_hull.cc @@ -97,6 +97,8 @@ void writeVTKPolyData(const std::vector<Dune::FieldVector<double, 3>>& p, int main(int argc, char* argv[]) try { + using namespace Dumux; + using Point = Dune::FieldVector<double, 3>; // create 100 random points std::vector<Point> points(100); @@ -109,30 +111,30 @@ int main(int argc, char* argv[]) try writeCSV(points, "points"); Dune::Timer timer; - auto convexHullPoints = Dumux::grahamConvexHull2d3d(points); + auto convexHullPoints = grahamConvexHull<2>(points); std::cout << "Computed convex hull of " << points.size() << " points in " << timer.elapsed() << " seconds." << std::endl; writeVTKPolyData(convexHullPoints, "convexhull"); Dune::Timer timer2; - auto triangles = Dumux::triangulate<2, 3>(convexHullPoints); + auto triangles = triangulate<2, 3>(convexHullPoints); std::cout << "Computed triangulation of convex hull with " << convexHullPoints.size() << " points in " << timer2.elapsed() << " seconds." << std::endl; - Dumux::writeVTKPolyDataTriangle(triangles, "triangulation"); + writeVTKPolyDataTriangle(triangles, "triangulation"); // some specific tests for corner cases with colinear points points = {{0.0,0.0,0.0}, {0.0,0.0,1.0}, {0.0,0.0,2.0}, {0.0,0.0,3.0}}; - auto hull = Dumux::grahamConvexHull2d3d(points); + auto hull = grahamConvexHull<2>(points); if (!(hull.empty())) DUNE_THROW(Dune::InvalidStateException, "False positive for finding a convex hull!"); points = {{0.0,0.0,0.0}, {0.0,0.0,1.0}, {0.0,0.0,2.0}, {0.0,0.0,3.0}, {0.0,0.0,4.0}, {2.0,3.0,3.0}}; - hull = Dumux::grahamConvexHull2d3d(points); + hull = grahamConvexHull<2>(points); if (hull.empty()) DUNE_THROW(Dune::InvalidStateException, "Didn't find convex hull!"); points = {{2.0,3.0,3.0}, {0.0,0.0,4.0}, {0.0,0.0,0.0}, {0.0,0.0,1.0}, {0.0,0.0,2.0}, {0.0,0.0,3.0}}; - hull = Dumux::grahamConvexHull2d3d(points); + hull = grahamConvexHull<2>(points); if (hull.empty()) DUNE_THROW(Dune::InvalidStateException, "Didn't find convex hull!"); return 0; diff --git a/test/common/geometry/writetriangulation.hh b/test/common/geometry/writetriangulation.hh index c106e285b76b3e2bb17429ef78b4141be86146dc..5b8bfa5d02bc72f0363c28327830c5c16299857e 100644 --- a/test/common/geometry/writetriangulation.hh +++ b/test/common/geometry/writetriangulation.hh @@ -42,8 +42,17 @@ void writeVTKPolyDataTriangle(const TriangleVector& triangles, << " <DataArray type=\"Float32\" Name=\"Coordinates\" NumberOfComponents=\"3\" format=\"ascii\">\n"; for (const auto& t : triangles) + { for (const auto& p : t) + { fout << p << " "; + if (p.size() == 1) + fout << "0.0 0.0 "; + else if (p.size() == 2) + fout << "0.0 "; + } + + } fout << '\n'; fout << " </DataArray>\n"