diff --git a/dumux/geometry/geometryintersection.hh b/dumux/geometry/geometryintersection.hh index 5522ff36e45f2f01de23618d68df9c42f1274de2..f8ce13bec151b7cc52a9e45b9ebdf77689886f46 100644 --- a/dumux/geometry/geometryintersection.hh +++ b/dumux/geometry/geometryintersection.hh @@ -830,6 +830,190 @@ public: } }; +/*! + * \ingroup Geometry + * \brief A class for polygon--polygon intersections in 3d space + */ +template <class Geometry1, class Geometry2, class Policy> +class GeometryIntersection<Geometry1, Geometry2, Policy, 3, 2, 2> +{ + enum { dimworld = 3 }; + enum { dim1 = 2 }; + enum { dim2 = 2 }; + +public: + using ctype = typename Policy::ctype; + using Point = typename Policy::Point; + using Intersection = typename Policy::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 vertices of first polygon that are inside the second polygon + * Add intersections of polygon edges + * Remove duplicate points from the list + * Compute the convex hull polygon + * \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"); + + // if the geometries are not parallel, there cannot be a polygon intersection + const auto a1 = geo1.corner(1) - geo1.corner(0); + const auto b1 = geo1.corner(2) - geo1.corner(0); + const auto n1 = crossProduct(a1, b1); + + const auto a2 = geo2.corner(1) - geo2.corner(0); + const auto b2 = geo2.corner(2) - geo2.corner(0); + const auto n2 = crossProduct(a2, b2); + + using std::max; + using std::sqrt; + const auto a1Norm2 = a1.two_norm2(); + const auto b1Norm2 = b1.two_norm2(); + const auto maxNorm2 = max(a1Norm2, b1Norm2); + const auto eps2 = maxNorm2*eps_; + const auto eps = sqrt(maxNorm2)*eps_; + const auto eps3 = eps*eps2; + + // early exit if polygons are not parallel + using std::abs; + if (crossProduct(n1, n2).two_norm2() > eps2*maxNorm2) + return false; + + // they are parallel, verify that they are on the same plane + auto d1 = geo2.corner(0) - geo1.corner(0); + if (d1.two_norm2() < eps2) + d1 = geo2.corner(1) - geo1.corner(0); + + if (abs(d1*n2) > eps3) + return false; + + // the candidate intersection points + std::vector<Point> points; points.reserve(8); + + // 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(); + const bool resultIsGeo1 = numPoints1 == geo1.corners(); + if (!resultIsGeo1) + { + // 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)); + + const bool resultIsGeo2 = (points.size() - numPoints1) == geo2.corners(); + if (!resultIsGeo2) + { + 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 + 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] + : (abs(a[1]-b[1]) > eps ? a[1] < b[1] + : (a[2] < b[2]))); + }); + + const auto squaredEps = eps*eps; + points.erase(std::unique( + points.begin(), points.end(), + [squaredEps] (const auto& a, const auto&b) { return (b-a).two_norm2() < squaredEps; }), + 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--polygon intersection in 3d space @@ -951,18 +1135,20 @@ public: // 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 + const auto notEqual = [eps] (auto a, auto b) { using std::abs; return abs(b-a) > eps; }; + std::sort(points.begin(), points.end(), [notEqual](const auto& a, const auto& b) -> bool { - using std::abs; - return (abs(a[0]-b[0]) > eps ? a[0] < b[0] : (abs(a[1]-b[1]) > eps ? a[1] < b[1] : (a[2] < b[2]))); + return (notEqual(a[0], b[0]) ? a[0] < b[0] + : (notEqual(a[1], b[1]) ? a[1] < b[1] + : (a[2] < b[2]))); }); - 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()); + const auto squaredEps = eps*eps; + points.erase(std::unique( + points.begin(), points.end(), + [squaredEps] (const auto& a, const auto&b) { return (b-a).two_norm2() < squaredEps; }), + points.end() + ); // return false if we don't have more than three unique points if (points.size() < 3) return false; @@ -1316,15 +1502,69 @@ public: /*! * \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 point-like intersections are seeked - * \todo implement this query + * \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"); - DUNE_THROW(Dune::NotImplemented, "segment-segment intersection detection for point-like intersections"); + + 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); + + const auto v1Norm2 = v1.two_norm2(); + const auto eps2 = eps_*v1Norm2; + + const auto n = crossProduct(v1, v2); + + // first check if segments are parallel + using std::abs; + if ( n.two_norm2() < eps2*v1Norm2 ) + { + // check if they lie on the same line + if (crossProduct(v1, ac).two_norm2() > eps2) + return false; + + // they lie on the same line, + // if so, point intersection can only be one of the corners + const auto sp = v1*v2; + if ( sp < 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; + } + + // in-plane normal vector on v1 + const auto v1Normal = crossProduct(v1, n); + + // intersection point on v2 in local coords + const auto t2 = - 1.0*(ac*v1Normal) / (v2*v1Normal); + + // check if the local coords are valid + if (t2 < -1.0*eps_ || t2 > 1.0 + eps_) + return false; + + if (auto ip = geo2.global(t2); intersectsPointGeometry(ip, geo1)) + { intersection = std::move(ip); return true; } + + return false; } /*! diff --git a/test/geometry/test_1d1d_intersection.cc b/test/geometry/test_1d1d_intersection.cc index 0c16920668ecec0ba5e870b39d525ae399c7903e..ef5590f642f22dec0eeee9eeea24d1a749b5aaed 100644 --- a/test/geometry/test_1d1d_intersection.cc +++ b/test/geometry/test_1d1d_intersection.cc @@ -196,8 +196,8 @@ int main (int argc, char *argv[]) // test for dimWorld = 2 testPointIntersections<2>(returns); - // TODO: implement and test for dimWorld = 3 - // testPointIntersections<3>(returns); + // test for dimWorld = 3 + testPointIntersections<3>(returns); // test segment intersection for dimWorld = 2 testSegmentIntersections<2>(returns); diff --git a/test/geometry/test_2d2d_intersection.cc b/test/geometry/test_2d2d_intersection.cc index a9951bc455045a6a62f2f072d35c9f6d3d23530d..426b591fe73a8d074ce535cab1b93e64572193c0 100644 --- a/test/geometry/test_2d2d_intersection.cc +++ b/test/geometry/test_2d2d_intersection.cc @@ -128,14 +128,68 @@ void testPolygonIntersections(std::vector<bool>& returns) } } +void testParallelPolygons(std::vector<bool>& returns) +{ + using Point = Dune::FieldVector<double, 3>; + + for (auto scaling : {1.0, 1e3, 1e12, 1e-12}) + { + const double unit = 1.0*scaling; + const double offUnit = (1.0 + 1e-6)*scaling; + + std::cout << "Test with scaling " << scaling << std::endl; + const auto tria1 = makeTriangle( Point{{0.0, 0.0, unit}}, + Point{{unit, 0.0, unit}}, + Point{{unit, unit, unit}} ); + const auto tria2 = makeTriangle( Point{{0.0, 0.0, offUnit}}, + Point{{0.0, unit, offUnit}}, + Point{{unit, 0.0, offUnit}} ); + returns.push_back(testPolygonIntersection<3>(tria1, tria2, false)); + } + + std::cout << std::endl; +} + +void testNonParallelPolygons(std::vector<bool>& returns) +{ + using Point = Dune::FieldVector<double, 3>; + + for (auto scaling : {1.0, 1e3, 1e12, 1e-12}) + { + const double unit = 1.0*scaling; + const double offUnit = (1.0 + 1e-6)*scaling; + + std::cout << "Test with scaling " << scaling << std::endl; + const auto tria1 = makeTriangle( Point{{0.0, 0.0, unit}}, + Point{{unit, 0.0, unit}}, + Point{{unit, unit, unit}} ); + const auto tria2 = makeTriangle( Point{{0.0, 0.0, unit}}, + Point{{0.0, unit, unit}}, + Point{{unit, 0.0, offUnit}} ); + returns.push_back(testPolygonIntersection<3>(tria1, tria2, false)); + } + + std::cout << std::endl; +} + #endif int main(int argc, char* argv[]) { std::vector<bool> returns; + + std::cout << "Testing intersections in 2d space" << std::endl; testPolygonIntersections<2>(returns); - // TODO: implement and test intersections in 3d + std::cout << "Testing intersecions in 3d space" << std::endl; + testPolygonIntersections<3>(returns); + + std::cout << "Testing parallel polygons in 3d space" << std::endl; + testParallelPolygons(returns); + + std::cout << "Testing non-parallel polygons in 3d space" << std::endl; + testNonParallelPolygons(returns); + // TODO: implement and test point and segment intersections // determine the exit code diff --git a/test/geometry/test_2d3d_intersection.cc b/test/geometry/test_2d3d_intersection.cc index 7bd05ed525a24eb25b838711fd915ab388b4016c..bf06825c0d4b01dafcdafc10d1cbd12158902c31 100644 --- a/test/geometry/test_2d3d_intersection.cc +++ b/test/geometry/test_2d3d_intersection.cc @@ -48,157 +48,201 @@ void testSegTriangle(const Dune::FieldVector<double, dimworld>& a, } } -int main(int argc, char* argv[]) +template<typename ctype> +auto makeUnitCube(ctype scale = 1.0) { - constexpr int dimworld = 3; - using Point = Dune::FieldVector<double, dimworld>; - - Point a{0.0, 0.0, 0.1}; - Point b{1.0, 0.0, 0.0}; - Point c{0.0, 1.0, 0.0}; - - Point p0{0.5, 0.5, -1.0}; - Point q0{0.5, 0.5, 1.0}; - - testSegTriangle(a, b, c, p0, q0); - testSegTriangle(a, b, c, q0, p0); - - Point p1 = a; - Point q1 = b; + using CubeGeometry = Dune::MultiLinearGeometry<ctype, 3, 3>; + using GlobalPosition = typename CubeGeometry::GlobalCoordinate; + + const auto unit = scale*1.0; + return CubeGeometry( + Dune::GeometryTypes::cube(3), + std::vector<GlobalPosition>({ + {0.0, 0.0, 0.0}, {unit, 0.0, 0.0}, {0.0, unit, 0.0}, {unit, unit, 0.0}, + {0.0, 0.0, unit}, {unit, 0.0, unit}, {0.0, unit, unit}, {unit, unit, unit} + }) + ); +} - testSegTriangle(a, b, c, p1, q1, false); - testSegTriangle(a, b, c, q1, p1, false); +template<typename ctype> +auto makeHorizontalQuad(ctype scale = 1.0, ctype zPositionUnscaled = 1.0) +{ + using QuadGeometry = Dune::MultiLinearGeometry<ctype, 2, 3>; + using GlobalPosition = typename QuadGeometry::GlobalCoordinate; + + zPositionUnscaled *= scale; + const auto unit = scale*1.0; + return QuadGeometry( + Dune::GeometryTypes::cube(2), + std::vector<GlobalPosition>({ + {0.0, 0.0, zPositionUnscaled}, {unit, 0.0, zPositionUnscaled}, + {0.0, unit, zPositionUnscaled}, {unit, unit, zPositionUnscaled} + }) + ); +} - Point p2{0.0, 0.0, 0.0}; - Point q2{0.0, 0.0, 0.2}; +template<typename ctype> +auto makeInclinedTriangle(ctype scale = 1.0) +{ + using TriaGeometry = Dune::MultiLinearGeometry<ctype, 2, 3>; + using GlobalPosition = typename TriaGeometry::GlobalCoordinate; + return TriaGeometry( + Dune::GeometryTypes::simplex(2), + std::vector<GlobalPosition>({ + {-0.1*scale, -0.1*scale, 0.3*scale}, + {1.1*scale, -0.1*scale, 0.3*scale}, + {0.5*scale, 2.0*scale, 0.8*scale} + }) + ); +} - testSegTriangle(a, b, c, p2, q2); +int main(int argc, char* argv[]) +{ + constexpr int dimworld = 3; + using Point = Dune::FieldVector<double, dimworld>; - using Geometry3D = Dune::MultiLinearGeometry<double, 3, dimworld>; - using Geometry2D = Dune::MultiLinearGeometry<double, 2, dimworld>; - using Test = Dumux::GeometryIntersection<Geometry3D, Geometry2D>; - typename Test::Intersection intersectionPolygon; + for (auto scale : {1.0, 1e3, 1e12, 1e-12}) + { + std::cout << "Testing scale " << scale << std::endl; - std::vector<Dune::FieldVector<double, dimworld>> cubeCorners({ - {0.0, 0.0, 0.0}, {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 1.0, 0.0}, - {0.0, 0.0, 1.0}, {1.0, 0.0, 1.0}, {0.0, 1.0, 1.0}, {1.0, 1.0, 1.0} - }); + const Point a{0.0*scale, 0.0*scale, 0.1*scale}; + const Point b{1.0*scale, 0.0*scale, 0.0*scale}; + const Point c{0.0*scale, 1.0*scale, 0.0*scale}; - std::vector<Dune::FieldVector<double, dimworld>> quadCorners({ - {0.0, 0.0, 0.5}, {1.0, 0.0, 0.5}, {0.0, 1.0, 0.5}, {1.0, 1.0, 0.5} - }); + const Point p0{0.5*scale, 0.5*scale, -1.0*scale}; + const Point q0{0.5*scale, 0.5*scale, 1.0*scale}; - std::vector<Dune::FieldVector<double, dimworld>> triCorners({ - {-0.1, -0.1, 0.3}, {1.1, -0.1, 0.3}, {0.5, 2.0, 0.8} - }); + testSegTriangle(a, b, c, p0, q0); + testSegTriangle(a, b, c, q0, p0); - Geometry3D cube(Dune::GeometryTypes::cube(dimworld), cubeCorners); - Geometry2D quad(Dune::GeometryTypes::cube(dimworld-1), quadCorners); - Geometry2D tri(Dune::GeometryTypes::simplex(dimworld-1), triCorners); + const Point p1 = a; + const Point q1 = b; - if (Test::intersection(cube, quad, intersectionPolygon)) - { - const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); - Dumux::writeVTKPolyDataTriangle(triangulation, "quad_intersections"); - if (triangulation.size() != 4) - DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 4 intersections!"); - } - else - DUNE_THROW(Dune::InvalidStateException, "No intersections found!"); + testSegTriangle(a, b, c, p1, q1, false); + testSegTriangle(a, b, c, q1, p1, false); - if (Test::intersection(cube, tri, intersectionPolygon)) - { - const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); - Dumux::writeVTKPolyDataTriangle(triangulation, "tri_intersections"); - if (triangulation.size() != 6) - DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 6 intersections!"); - } - else - DUNE_THROW(Dune::InvalidStateException, "No intersections found!"); - - // Test a tricky situation intersecting a hexagon with two quadrilaterals. - // One face of the hexagon is in-plane with the two quadrilaterals, which - // together enclose the entire hexagon face. Thus, the sum of the areas of - // the two intersections must be equal to that of the respective hexagon face. - // The separating edge between the two quadrilaterals passes very close by one - // of the hex face corners. This situation was taken from an actual mortar - // application which exposed a bug in the 1d-2d algorithm in 3d space. - std::vector<Dune::FieldVector<double, dimworld>> hexCorners({ - {0.8619712261141845, 1.206565697102448, 0.4152254463380627}, - {0.8715547951379876, 1.175481516085758, 0.2599429674402531}, - {0.7046736652858085, 1.209207158782034, 0.4447521639960824}, - {0.7307137896935333, 1.171606500732276, 0.2574814772189353}, - {0.8926414294658755, 1.0, 0.4012781039132332}, - {0.8316315415883838, 1.0, 0.2581023823497354}, - {0.7349272235105835, 1.0, 0.4613391568883559}, - {0.7479805294570721, 1.0, 0.3155931875126768} - }); - - std::vector<Dune::FieldVector<double, dimworld>> quad1Corners({ - {1.000000000000000000000000000000, 1.0, 1.000000000000000000000000000000}, - {1.000000000000000000000000000000, 1.0, 0.327819111366782434124900191819}, - {0.303996171086607036571081152942, 1.0, 1.000000000000000000000000000000}, - {0.297170743844278550938042826601, 1.0, 0.293625720570556747457402479995} - }); - - std::vector<Dune::FieldVector<double, dimworld>> quad2Corners({ - {1.000000000000000000000000000000, 1.0, 0.327819111366782434124900191819}, - {1.000000000000000000000000000000, 1.0, 0.000000000000000000000000000000}, - {0.297170743844278550938042826601, 1.0, 0.293625720570556747457402479995}, - {0.325413399309280815252520824288, 1.0, 0.000000000000000000000000000000} - }); - - Geometry3D hex(Dune::GeometryTypes::cube(dimworld), hexCorners); - Geometry2D quad1(Dune::GeometryTypes::cube(dimworld-1), quad1Corners); - Geometry2D quad2(Dune::GeometryTypes::cube(dimworld-1), quad2Corners); - - // lambda to compute the area of a triangulated intersection - auto computeArea = [] (const auto& triangulation) - { - double a = 0.0; - for (const auto& t : triangulation) - a += Geometry2D(Dune::GeometryTypes::simplex(dimworld-1), - std::vector<Point>(t.begin(), t.end())).volume(); - return a; - }; - - double area = 0.0; - if (Test::intersection(hex, quad1, intersectionPolygon)) - { - const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); - Dumux::writeVTKPolyDataTriangle(triangulation, "quad1_intersections"); - if (triangulation.size() != 5) - DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 5 intersections!"); + const Point p2{0.0*scale, 0.0*scale, 0.0*scale}; + const Point q2{0.0*scale, 0.0*scale, 0.2*scale}; - area += computeArea(triangulation); - } + testSegTriangle(a, b, c, p2, q2); - if (Test::intersection(hex, quad2, intersectionPolygon)) - { - const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); - Dumux::writeVTKPolyDataTriangle(triangulation, "quad2_intersections"); - if (triangulation.size() != 1) - DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 1 intersections!"); + const auto cube = makeUnitCube(scale); + const auto quad = makeHorizontalQuad(scale, 0.5); + using Geometry3D = decltype(cube); + using Geometry2D = decltype(quad); + using Test = Dumux::GeometryIntersection<Geometry3D, Geometry2D>; + typename Test::Intersection intersectionPolygon; - area += computeArea(triangulation); + if (Test::intersection(cube, quad, intersectionPolygon)) + { + const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); + Dumux::writeVTKPolyDataTriangle(triangulation, "quad_intersections"); + if (triangulation.size() != 4) + DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 4 intersections!"); + } + else + DUNE_THROW(Dune::InvalidStateException, "No intersections found!"); + + // quad that is slightly above the cube + const auto close_quad = makeHorizontalQuad(scale, 1.0+1e-6); + if (Test::intersection(cube, close_quad, intersectionPolygon)) + DUNE_THROW(Dune::InvalidStateException, "Found unexpected intersection with close quad"); + + const auto tri = makeInclinedTriangle(scale); + if (Test::intersection(cube, tri, intersectionPolygon)) + { + const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); + Dumux::writeVTKPolyDataTriangle(triangulation, "tri_intersections"); + if (triangulation.size() != 6) + DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 6 intersections!"); + } + else + DUNE_THROW(Dune::InvalidStateException, "No intersections found!"); + + // Test a tricky situation intersecting a hexagon with two quadrilaterals. + // One face of the hexagon is in-plane with the two quadrilaterals, which + // together enclose the entire hexagon face. Thus, the sum of the areas of + // the two intersections must be equal to that of the respective hexagon face. + // The separating edge between the two quadrilaterals passes very close by one + // of the hex face corners. This situation was taken from an actual mortar + // application which exposed a bug in the 1d-2d algorithm in 3d space. + std::vector<Dune::FieldVector<double, dimworld>> hexCorners({ + {0.8619712261141845*scale, 1.206565697102448*scale, 0.4152254463380627*scale}, + {0.8715547951379876*scale, 1.175481516085758*scale, 0.2599429674402531*scale}, + {0.7046736652858085*scale, 1.209207158782034*scale, 0.4447521639960824*scale}, + {0.7307137896935333*scale, 1.171606500732276*scale, 0.2574814772189353*scale}, + {0.8926414294658755*scale, 1.0*scale, 0.4012781039132332*scale}, + {0.8316315415883838*scale, 1.0*scale, 0.2581023823497354*scale}, + {0.7349272235105835*scale, 1.0*scale, 0.4613391568883559*scale}, + {0.7479805294570721*scale, 1.0*scale, 0.3155931875126768*scale} + }); + + std::vector<Dune::FieldVector<double, dimworld>> quad1Corners({ + {1.000000000000000000000000000000*scale, 1.0*scale, 1.000000000000000000000000000000*scale}, + {1.000000000000000000000000000000*scale, 1.0*scale, 0.327819111366782434124900191819*scale}, + {0.303996171086607036571081152942*scale, 1.0*scale, 1.000000000000000000000000000000*scale}, + {0.297170743844278550938042826601*scale, 1.0*scale, 0.293625720570556747457402479995*scale} + }); + + std::vector<Dune::FieldVector<double, dimworld>> quad2Corners({ + {1.000000000000000000000000000000*scale, 1.0*scale, 0.327819111366782434124900191819*scale}, + {1.000000000000000000000000000000*scale, 1.0*scale, 0.000000000000000000000000000000*scale}, + {0.297170743844278550938042826601*scale, 1.0*scale, 0.293625720570556747457402479995*scale}, + {0.325413399309280815252520824288*scale, 1.0*scale, 0.000000000000000000000000000000*scale} + }); + + Geometry3D hex(Dune::GeometryTypes::cube(dimworld), hexCorners); + Geometry2D quad1(Dune::GeometryTypes::cube(dimworld-1), quad1Corners); + Geometry2D quad2(Dune::GeometryTypes::cube(dimworld-1), quad2Corners); + + // lambda to compute the area of a triangulated intersection + auto computeArea = [] (const auto& triangulation) + { + double a = 0.0; + for (const auto& t : triangulation) + a += Geometry2D(Dune::GeometryTypes::simplex(dimworld-1), + std::vector<Point>(t.begin(), t.end())).volume(); + return a; + }; + + double area = 0.0; + if (Test::intersection(hex, quad1, intersectionPolygon)) + { + const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); + Dumux::writeVTKPolyDataTriangle(triangulation, "quad1_intersections"); + if (triangulation.size() != 5) + DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 5 intersections!"); + + area += computeArea(triangulation); + } + + if (Test::intersection(hex, quad2, intersectionPolygon)) + { + const auto triangulation = Dumux::triangulate<2, dimworld>(intersectionPolygon); + Dumux::writeVTKPolyDataTriangle(triangulation, "quad2_intersections"); + if (triangulation.size() != 1) + DUNE_THROW(Dune::InvalidStateException, "Found " << triangulation.size() << " instead of 1 intersections!"); + + area += computeArea(triangulation); + } + + // compute area of the intersecting hexagon face + std::vector<Dune::FieldVector<double, dimworld>> hexFaceCorners({ + {0.8926414294658755*scale, 1.0*scale, 0.4012781039132332*scale}, + {0.8316315415883838*scale, 1.0*scale, 0.2581023823497354*scale}, + {0.7349272235105835*scale, 1.0*scale, 0.4613391568883559*scale}, + {0.7479805294570721*scale, 1.0*scale, 0.3155931875126768*scale} + }); + Geometry2D hexFace(Dune::GeometryTypes::cube(dimworld-1), hexFaceCorners); + const auto hexFaceArea = hexFace.volume(); + + using std::abs; + const auto areaMismatch = abs(area-hexFaceArea); + std::cout << "Area mismatch is: " << areaMismatch << std::endl; + if (areaMismatch > hexFaceArea*1e-7) + DUNE_THROW(Dune::InvalidStateException, "Intersection area mismatch too large!"); } - // compute area of the intersecting hexagon face - std::vector<Dune::FieldVector<double, dimworld>> hexFaceCorners({ - {0.8926414294658755, 1.0, 0.4012781039132332}, - {0.8316315415883838, 1.0, 0.2581023823497354}, - {0.7349272235105835, 1.0, 0.4613391568883559}, - {0.7479805294570721, 1.0, 0.3155931875126768} - }); - Geometry2D hexFace(Dune::GeometryTypes::cube(dimworld-1), hexFaceCorners); - const auto hexFaceArea = hexFace.volume(); - - using std::abs; - const auto areaMismatch = abs(area-hexFaceArea); - std::cout << "Area mismatch is: " << areaMismatch << std::endl; - if (areaMismatch > 1e-14) - DUNE_THROW(Dune::InvalidStateException, "Intersection area mismatch too large!"); - std::cout << "All tests passed!" << std::endl; return 0;