Skip to content

Commit b18130b

Browse files
authored
Merge pull request #1493 from LLNL/bugfix/han12/poly_robustness
2D polygon robustness
2 parents 850f7a9 + 41ac633 commit b18130b

File tree

5 files changed

+207
-13
lines changed

5 files changed

+207
-13
lines changed

RELEASE-NOTES.md

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ to use Open Cascade's file I/O capabilities in support of Quest applications.
5353
This required a [RAJA fix to avoid 64-bit intrinsics](https://github.com/LLNL/RAJA/pull/1746),
5454
as well as support for 32-bit `Word`s in Slam's `BitSet` class.
5555
- Fixes a memory leak in `axom::Array` copy constructor.
56+
- Fixes robustness issue with the `axom::primal::clip` overload for clipping a 2D polygon against another 2D polygon.
5657

5758
## [Version 0.10.1] - Release date 2024-10-22
5859

src/axom/primal/operators/detail/clip_impl.hpp

+23-9
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ AXOM_HOST_DEVICE Polygon<T, 2, ARRAY_TYPE, MAX_VERTS> clipPolygonPolygon(
732732
PointType intersecting_point;
733733
SegmentType subject_edge(prev_point, current_point);
734734

735-
if(intersect(plane, subject_edge, seg_param))
735+
if(intersect(plane, subject_edge, seg_param, eps))
736736
{
737737
intersecting_point = subject_edge.at(seg_param);
738738
}
@@ -742,13 +742,7 @@ AXOM_HOST_DEVICE Polygon<T, 2, ARRAY_TYPE, MAX_VERTS> clipPolygonPolygon(
742742

743743
if(cur_p_orientation == ON_POSITIVE_SIDE)
744744
{
745-
// Handles the edge case of 3 consecutive vertices with orientations
746-
// ON_POSITIVE_SIDE, ON_BOUNDARY, ON_POSITIVE. Default algorithm
747-
// check (prev_p_orientation != ON_POSITIVE_SIDE) results in the
748-
// vertex on the boundary being added twice.
749-
if(prev_p_orientation == ON_NEGATIVE_SIDE ||
750-
(prev_p_orientation == ON_BOUNDARY &&
751-
intersecting_point != outputList[outputList.numVertices() - 1]))
745+
if(prev_p_orientation != ON_POSITIVE_SIDE)
752746
{
753747
outputList.addVertex(intersecting_point);
754748
}
@@ -761,7 +755,27 @@ AXOM_HOST_DEVICE Polygon<T, 2, ARRAY_TYPE, MAX_VERTS> clipPolygonPolygon(
761755
}
762756
} // end of iteration through edges of clip polygon
763757

764-
return outputList;
758+
// Remove duplicate points.
759+
// Handles edge cases such as 3 consecutive vertices with orientations
760+
// ON_POSITIVE_SIDE, ON_BOUNDARY, ON_POSITIVE where point on the boundary
761+
// is added twice.
762+
PolygonType uniqueList;
763+
for(int i = 0; i < outputList.numVertices(); i++)
764+
{
765+
int prevIndex = ((i - 1) == -1) ? (outputList.numVertices() - 1) : (i - 1);
766+
767+
if(!axom::utilities::isNearlyEqual(outputList[i][0],
768+
outputList[prevIndex][0],
769+
eps) ||
770+
!axom::utilities::isNearlyEqual(outputList[i][1],
771+
outputList[prevIndex][1],
772+
eps))
773+
{
774+
uniqueList.addVertex(outputList[i]);
775+
}
776+
}
777+
778+
return uniqueList;
765779
}
766780

767781
} // namespace detail

src/axom/primal/operators/detail/intersect_impl.hpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -1102,12 +1102,14 @@ AXOM_HOST_DEVICE bool intersect_plane_bbox(const Plane<T, 3>& p,
11021102
* \param [in] seg A segment
11031103
* \param [out] t Intersection point of plane and seg, w.r.t.
11041104
* parametrization of seg
1105+
* \param [in] EPS tolerance parameter for determining if 0.0 <= t <= 1.0
11051106
* \return true iff plane intersects with segment, otherwise, false.
11061107
*/
11071108
template <typename T, int DIM>
11081109
AXOM_HOST_DEVICE bool intersect_plane_seg(const Plane<T, DIM>& plane,
11091110
const Segment<T, DIM>& seg,
1110-
T& t)
1111+
T& t,
1112+
double EPS = 1E-12)
11111113
{
11121114
using VectorType = Vector<T, DIM>;
11131115

@@ -1117,7 +1119,7 @@ AXOM_HOST_DEVICE bool intersect_plane_seg(const Plane<T, DIM>& plane,
11171119
t = (plane.getOffset() - normal.dot(VectorType(seg.source()))) /
11181120
(normal.dot(ab));
11191121

1120-
if(t >= 0.0 && t <= 1.0)
1122+
if(isGeq(t, 0.0, EPS) && isLeq(t, 1.0, EPS))
11211123
{
11221124
return true;
11231125
}

src/axom/primal/operators/intersect.hpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ AXOM_HOST_DEVICE bool intersect(const Plane<T, 3>& p,
575575
* \param [in] seg A line segment
576576
* \param [out] t Intersection point of plane and seg, w.r.t. seg's
577577
* parametrization
578+
* \param [in] EPS tolerance parameter for determining if 0.0 <= t <= 1.0
578579
* \note If there is an intersection, the intersection point pt is:
579580
* pt = seg.at(t)
580581
* \return true iff plane intersects with seg, otherwise, false.
@@ -586,9 +587,10 @@ AXOM_HOST_DEVICE bool intersect(const Plane<T, 3>& p,
586587
template <typename T, int DIM>
587588
AXOM_HOST_DEVICE bool intersect(const Plane<T, DIM>& plane,
588589
const Segment<T, DIM>& seg,
589-
T& t)
590+
T& t,
591+
const double& EPS = 1e-12)
590592
{
591-
return detail::intersect_plane_seg(plane, seg, t);
593+
return detail::intersect_plane_seg(plane, seg, t, EPS);
592594
}
593595

594596
/*!

src/axom/primal/tests/primal_clip.cpp

+175
Original file line numberDiff line numberDiff line change
@@ -1952,6 +1952,181 @@ TEST(primal_clip, polygon_intersects_polygon)
19521952
EXPECT_NEAR(poly[9][0], 100, EPS);
19531953
EXPECT_NEAR(poly[9][1], 250, EPS);
19541954
}
1955+
1956+
/*
1957+
* Edge case where polygons overlap along diagonal of one polygon,
1958+
* and the edge of the other:
1959+
* +-----------+
1960+
* /| \ |
1961+
* / | \ |
1962+
* + | + |
1963+
* \ | / |
1964+
* \| / |
1965+
* +-----------+
1966+
*/
1967+
{
1968+
Polygon2D subjectPolygon;
1969+
subjectPolygon.addVertex(Point2D {0., 0.});
1970+
subjectPolygon.addVertex(Point2D {1., 0.});
1971+
subjectPolygon.addVertex(Point2D {1., 1.});
1972+
subjectPolygon.addVertex(Point2D {0., 1.});
1973+
1974+
Polygon2D clipPolygon;
1975+
clipPolygon.addVertex(Point2D {0., 0.});
1976+
clipPolygon.addVertex(Point2D {0.3, 0.3});
1977+
clipPolygon.addVertex(Point2D {0., 1.});
1978+
clipPolygon.addVertex(Point2D {-0.3, 0.7});
1979+
1980+
Polygon2D poly = axom::primal::clip(subjectPolygon, clipPolygon, EPS);
1981+
EXPECT_NEAR(poly.signedArea(), 0.15, EPS);
1982+
EXPECT_EQ(poly.numVertices(), 3);
1983+
1984+
// Check vertices
1985+
EXPECT_NEAR(poly[0][0], 0, EPS);
1986+
EXPECT_NEAR(poly[0][1], 1, EPS);
1987+
1988+
EXPECT_NEAR(poly[1][0], 0, EPS);
1989+
EXPECT_NEAR(poly[1][1], 0, EPS);
1990+
1991+
EXPECT_NEAR(poly[2][0], 0.3, EPS);
1992+
EXPECT_NEAR(poly[2][1], 0.3, EPS);
1993+
}
1994+
}
1995+
1996+
/*
1997+
* 3x3 grid of quads that occupies the unit square,
1998+
* that clip against a lower or upper triangle:
1999+
*
2000+
* +---+---+---+
2001+
* | \ | | |
2002+
* | \| | |
2003+
* +---+---+---+
2004+
* | | \ | |
2005+
* | | \| |
2006+
* +---+---+---+
2007+
* | | | \ |
2008+
* | | | \|
2009+
* +---+---+---+
2010+
*/
2011+
TEST(primal_clip, polygon_intersect_robustness)
2012+
{
2013+
using Polygon2D = axom::primal::Polygon<double, 2>;
2014+
using Point2D = axom::primal::Point<double, 2>;
2015+
constexpr double EPS = 1e-8;
2016+
2017+
const double x[] = {0.,
2018+
1. / 3.,
2019+
2. / 3,
2020+
1.,
2021+
0.,
2022+
1. / 3.,
2023+
2. / 3,
2024+
1.,
2025+
0.,
2026+
1. / 3.,
2027+
2. / 3,
2028+
1.,
2029+
0.,
2030+
1. / 3.,
2031+
2. / 3,
2032+
1.};
2033+
const double y[] = {0.,
2034+
0.,
2035+
0.,
2036+
0.,
2037+
1. / 3.,
2038+
1. / 3,
2039+
1. / 3.,
2040+
1. / 3.,
2041+
2. / 3.,
2042+
2. / 3,
2043+
2. / 3.,
2044+
2. / 3.,
2045+
1.,
2046+
1.,
2047+
1.,
2048+
1.};
2049+
const int conn[][4] = {{0, 1, 5, 4},
2050+
{1, 2, 6, 5},
2051+
{2, 3, 7, 6},
2052+
{4, 5, 9, 8},
2053+
{5, 6, 10, 9},
2054+
{6, 7, 11, 10},
2055+
{8, 9, 13, 12},
2056+
{9, 10, 14, 13},
2057+
{10, 11, 15, 14}};
2058+
Polygon2D fine[9];
2059+
for(int p = 0; p < 9; p++)
2060+
{
2061+
fine[p].addVertex(Point2D {x[conn[p][0]], y[conn[p][0]]});
2062+
fine[p].addVertex(Point2D {x[conn[p][1]], y[conn[p][1]]});
2063+
fine[p].addVertex(Point2D {x[conn[p][2]], y[conn[p][2]]});
2064+
fine[p].addVertex(Point2D {x[conn[p][3]], y[conn[p][3]]});
2065+
}
2066+
2067+
// Overlap polygons
2068+
Polygon2D clipLower;
2069+
clipLower.addVertex(Point2D {0., 0.});
2070+
clipLower.addVertex(Point2D {1., 0.});
2071+
clipLower.addVertex(Point2D {0., 1.});
2072+
2073+
Polygon2D clipUpper;
2074+
clipUpper.addVertex(Point2D {1., 0.});
2075+
clipUpper.addVertex(Point2D {1., 1.});
2076+
clipUpper.addVertex(Point2D {0., 1.});
2077+
2078+
// Expected VFs (volume fractions, areas) for overlaps.
2079+
const double lower_vf[] = {1., 1., 0.5, 1., 0.5, 0., 0.5, 0., 0.};
2080+
const double upper_vf[] = {0., 0., 0.5, 0., 0.5, 1., 0.5, 1., 1.};
2081+
2082+
// Each quad has an area of 1/9 (3x3 quads in the unit square).
2083+
constexpr double fine_area = (1. / 3.) * (1. / 3.);
2084+
2085+
for(int p : std::vector<int> {0, 1, 2, 3, 4, 6})
2086+
{
2087+
// Clip clipLower with overlapping fine quad
2088+
const auto overlapPoly = axom::primal::clip(clipLower, fine[p], EPS);
2089+
EXPECT_TRUE(overlapPoly.isValid());
2090+
2091+
if(overlapPoly.isValid())
2092+
{
2093+
// Area of overlapped polygon is some fraction of 1/9.
2094+
// Divide by 1/9 to get a volume fraction.
2095+
const double vf = overlapPoly.area() / fine_area;
2096+
EXPECT_NEAR(vf, lower_vf[p], EPS);
2097+
}
2098+
}
2099+
2100+
for(int p : std::vector<int> {5, 7, 8})
2101+
{
2102+
// Clip clipLower with non-intersecting fine quad
2103+
const auto overlapPoly = axom::primal::clip(clipLower, fine[p], EPS);
2104+
EXPECT_FALSE(overlapPoly.isValid());
2105+
EXPECT_EQ(overlapPoly.numVertices(), 0);
2106+
}
2107+
2108+
for(int p : std::vector<int> {2, 4, 5, 6, 7, 8})
2109+
{
2110+
// Clip clipUpper with overlapping fine quad
2111+
const auto overlapPoly = axom::primal::clip(clipUpper, fine[p], EPS);
2112+
EXPECT_TRUE(overlapPoly.isValid());
2113+
2114+
if(overlapPoly.isValid())
2115+
{
2116+
// Area of overlapped polygon is some fraction of 1/9.
2117+
// Divide by 1/9 to get a volume fraction.
2118+
const double vf = overlapPoly.area() / fine_area;
2119+
EXPECT_NEAR(vf, upper_vf[p], EPS);
2120+
}
2121+
}
2122+
2123+
for(int p : std::vector<int> {0, 1, 3})
2124+
{
2125+
// Clip clipUpper with non-intersecting fine quad
2126+
const auto overlapPoly = axom::primal::clip(clipUpper, fine[p], EPS);
2127+
EXPECT_FALSE(overlapPoly.isValid());
2128+
EXPECT_EQ(overlapPoly.numVertices(), 0);
2129+
}
19552130
}
19562131

19572132
//------------------------------------------------------------------------------

0 commit comments

Comments
 (0)