Skip to content

Commit 056187d

Browse files
authored
Merge pull request #1477 from LLNL/feature/spainhour/ray_surface_intersection
Adds ray Bezier/NURBS surface intersection routines
2 parents 2f1a7b6 + 0ba7615 commit 056187d

21 files changed

+3538
-85
lines changed

RELEASE-NOTES.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ to use Open Cascade's file I/O capabilities in support of Quest applications.
4040
- Adds a Quest example that reads in a STEP file using Open Cascade and processes its geometry
4141
- Adds a piecewise method to load external data using `sidre::IOManager`. This adds new overloaded methods
4242
of `loadExternalData` in `sidre::IOManager` and `sidre::Group`.
43+
- Adds intersection routines between `primal::Ray` objects and `primal::NURBSCurve`/`primal::NURBSPatch` objects.
4344

4445
### Changed
4546
- `primal::NumericArray` has been moved to `core`. The header is `core/NumericArray.hpp`.
@@ -63,8 +64,9 @@ to use Open Cascade's file I/O capabilities in support of Quest applications.
6364

6465
### Fixed
6566
- Fixes compilation issue with [email protected] on 32-bit Windows configurations.
66-
This required a [RAJA fix to avoid 64-bit intrinsics](https://github.com/LLNL/RAJA/pull/1746),
67-
as well as support for 32-bit `Word`s in Slam's `BitSet` class.
67+
This required a [RAJA fix to avoid 64-bit intrinsics](https://github.com/LLNL/RAJA/pull/1746),
68+
as well as support for 32-bit `Word`s in Slam's `BitSet` class.
69+
- Minor bugfix to `primal::intersect(segment, ray)` to better handle cases when segment and ray overlap.
6870
- Fixes a memory leak in `axom::Array` copy constructor.
6971
- Fixes robustness issue with the `axom::primal::clip` overload for clipping a 2D polygon against another 2D polygon.
7072

src/axom/primal/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set( primal_headers
2626
geometry/CurvedPolygon.hpp
2727
geometry/Hexahedron.hpp
2828
geometry/KnotVector.hpp
29+
geometry/Line.hpp
2930
geometry/OrientedBoundingBox.hpp
3031
geometry/OrientationResult.hpp
3132
geometry/NURBSCurve.hpp
@@ -65,6 +66,7 @@ set( primal_headers
6566
operators/detail/fuzzy_comparators.hpp
6667
operators/detail/intersect_bezier_impl.hpp
6768
operators/detail/intersect_bounding_box_impl.hpp
69+
operators/detail/intersect_patch_impl.hpp
6870
operators/detail/intersect_impl.hpp
6971
operators/detail/intersect_ray_impl.hpp
7072
operators/detail/winding_number_impl.hpp

src/axom/primal/geometry/BezierCurve.hpp

+33-7
Original file line numberDiff line numberDiff line change
@@ -826,23 +826,49 @@ class BezierCurve
826826
* are approximately on the line defined by its two endpoints
827827
*
828828
* \param [in] tol Threshold for sum of squared distances
829-
* \return True if c1 is near-linear
829+
* \param [in] useStrictLinear If true, checks that the control points are
830+
* evenly spaced along the line
831+
* \return True if curve is near-linear
830832
*/
831-
bool isLinear(double tol = 1E-8) const
833+
bool isLinear(double tol = 1e-8, bool useStrictLinear = false) const
832834
{
833835
const int ord = getOrder();
834836
if(ord <= 1)
835837
{
836838
return true;
837839
}
838840

839-
SegmentType seg(m_controlPoints[0], m_controlPoints[ord]);
840-
double sqDist = 0.0;
841-
for(int p = 1; p < ord && sqDist <= tol; ++p) // check interior control points
841+
if(useStrictLinear)
842842
{
843-
sqDist += squared_distance(m_controlPoints[p], seg);
843+
// Check that the control points are not too far away from the line,
844+
// AND that they are evenly spaced along the line
845+
for(int p = 1; p < ord; ++p)
846+
{
847+
double t = p / static_cast<T>(ord);
848+
PointType the_pt =
849+
PointType::lerp(m_controlPoints[0], m_controlPoints[ord], t);
850+
851+
if(squared_distance(m_controlPoints[p], the_pt) > tol)
852+
{
853+
return false;
854+
}
855+
}
844856
}
845-
return (sqDist <= tol);
857+
else
858+
{
859+
// Check that the control points are not too far away from the line between control points
860+
SegmentType seg(m_controlPoints[0], m_controlPoints[ord]);
861+
862+
for(int p = 1; p < ord; ++p) // check interior control points
863+
{
864+
if(squared_distance(m_controlPoints[p], seg) > tol)
865+
{
866+
return false;
867+
}
868+
}
869+
}
870+
871+
return true;
846872
}
847873

848874
/*!

src/axom/primal/geometry/BezierPatch.hpp

+121-22
Original file line numberDiff line numberDiff line change
@@ -1861,15 +1861,16 @@ class BezierPatch
18611861
}
18621862

18631863
/*!
1864-
* \brief Predicate to check if the Bezier patch is approximately planar
1864+
* \brief Predicate to check if the Bezier patch is approximately planar (i.e. flat)
18651865
*
18661866
* This function checks if all control points of the BezierPatch
18671867
* are approximately on the plane defined by its four corners
18681868
*
1869-
* \param [in] tol Threshold for sum of squared distances
1870-
* \return True if c1 is near-planar
1869+
* \param [in] sq_tol Threshold for sum of squared distances
1870+
* \param [in] EPS Threshold for nearness to zero
1871+
* \return True if the object is planar up to tolerance \a sq_tol
18711872
*/
1872-
bool isPlanar(double tol = 1E-8) const
1873+
bool isPlanar(double sq_tol = 1e-8, double EPS = 1e-8) const
18731874
{
18741875
const int ord_u = getOrder_u();
18751876
const int ord_v = getOrder_v();
@@ -1894,49 +1895,51 @@ class BezierPatch
18941895
if(!axom::utilities::isNearlyEqual(
18951896
VectorType::scalar_triple_product(v1, v2, v3),
18961897
0.0,
1897-
tol))
1898+
EPS))
18981899
{
18991900
return false;
19001901
}
19011902

19021903
// Find three points that produce a nonzero normal
19031904
Vector3D plane_normal = VectorType::cross_product(v1, v2);
1904-
if(axom::utilities::isNearlyEqual(plane_normal.norm(), 0.0, tol))
1905+
if(axom::utilities::isNearlyEqual(plane_normal.norm(), 0.0, EPS))
19051906
{
19061907
plane_normal = VectorType::cross_product(v1, v3);
19071908
}
1908-
if(axom::utilities::isNearlyEqual(plane_normal.norm(), 0.0, tol))
1909+
if(axom::utilities::isNearlyEqual(plane_normal.norm(), 0.0, EPS))
19091910
{
19101911
plane_normal = VectorType::cross_product(v2, v3);
19111912
}
19121913
plane_normal = plane_normal.unitVector();
19131914

1914-
double sqDist = 0.0;
1915-
19161915
// Check all control points for simplicity
1917-
for(int p = 0; p <= ord_u && sqDist <= tol; ++p)
1916+
for(int p = 0; p <= ord_u; ++p)
19181917
{
1919-
for(int q = ((p == 0) ? 1 : 0); q <= ord_v && sqDist <= tol; ++q)
1918+
for(int q = ((p == 0) ? 1 : 0); q <= ord_v; ++q)
19201919
{
19211920
const double signedDist =
19221921
plane_normal.dot(m_controlPoints(p, q) - m_controlPoints(0, 0));
1923-
sqDist += signedDist * signedDist;
1922+
1923+
if(signedDist * signedDist > sq_tol)
1924+
{
1925+
return false;
1926+
}
19241927
}
19251928
}
1926-
1927-
return (sqDist <= tol);
1929+
return true;
19281930
}
19291931

19301932
/*!
19311933
* \brief Predicate to check if the patch can be approximated by a polygon
19321934
*
19331935
* This function checks if a BezierPatch lies in a plane
1934-
* and that the edged are linear up to tolerance `tol`
1936+
* and that the edges are linear up to tolerance `sq_tol`
19351937
*
19361938
* \param [in] tol Threshold for sum of squared distances
1937-
* \return True if c1 is near-planar-polygonal
1939+
* \param [in] EPS Threshold for nearness to zero
1940+
* \return True if c1 is planar-polygonal up to tolerance \a sq_tol
19381941
*/
1939-
bool isPolygonal(double tol = 1E-8) const
1942+
bool isPolygonal(double sq_tol = 1e-8, double EPS = 1e-8) const
19401943
{
19411944
const int ord_u = getOrder_u();
19421945
const int ord_v = getOrder_v();
@@ -1955,32 +1958,128 @@ class BezierPatch
19551958
}
19561959

19571960
// Check if the patch is planar
1958-
if(!isPlanar(tol))
1961+
if(!isPlanar(sq_tol, EPS))
19591962
{
19601963
return false;
19611964
}
19621965

19631966
// Check if each bounding curve is linear
1964-
if(!isocurve_u(0).isLinear(tol))
1967+
if(!isocurve_u(0).isLinear(sq_tol))
19651968
{
19661969
return false;
19671970
}
1968-
if(!isocurve_v(0).isLinear(tol))
1971+
if(!isocurve_v(0).isLinear(sq_tol))
19691972
{
19701973
return false;
19711974
}
1972-
if(!isocurve_u(1).isLinear(tol))
1975+
if(!isocurve_u(1).isLinear(sq_tol))
19731976
{
19741977
return false;
19751978
}
1976-
if(!isocurve_v(1).isLinear(tol))
1979+
if(!isocurve_v(1).isLinear(sq_tol))
19771980
{
19781981
return false;
19791982
}
19801983

19811984
return true;
19821985
}
19831986

1987+
/*!
1988+
* \brief Predicate to check if the Bezier patch is approximately bilinear
1989+
*
1990+
* This function checks if the patch is (nearly) bilinear.
1991+
* A necessary condition for a geometrically bilinear patch is that each line of
1992+
* control points in the net is approximately linear.
1993+
* A necessary condition for a parametrically bilinear patch is that the control
1994+
* points are coincident with the surface of the bilinear patch defined by
1995+
* its corners evaluated at uniform parameter values,
1996+
* i.e. the control points are also equally spaced on the net.
1997+
*
1998+
* \param [in] sq_tol Threshold for absolute squared distances
1999+
* \param [in] useStrictBilinear If true, require the patch be parametrically bilinear
2000+
* \return True if patch is bilinear up to tolerance \a sq_tol
2001+
*/
2002+
bool isBilinear(double sq_tol = 1e-8, bool useStrictBilinear = false) const
2003+
{
2004+
const int ord_u = getOrder_u();
2005+
const int ord_v = getOrder_v();
2006+
2007+
if(ord_u <= 1 && ord_v <= 1)
2008+
{
2009+
return true;
2010+
}
2011+
2012+
if(useStrictBilinear)
2013+
{
2014+
// Anonymous function to evaluate the bilinear patch defined by the corners
2015+
auto bilinear_patch = [&](T u, T v) -> PointType {
2016+
PointType val;
2017+
for(int N = 0; N < NDIMS; ++N)
2018+
{
2019+
val[N] = axom::utilities::lerp(
2020+
axom::utilities::lerp(m_controlPoints(0, 0)[N],
2021+
m_controlPoints(0, ord_v)[N],
2022+
v),
2023+
axom::utilities::lerp(m_controlPoints(ord_u, 0)[N],
2024+
m_controlPoints(ord_u, ord_v)[N],
2025+
v),
2026+
u);
2027+
}
2028+
return val;
2029+
};
2030+
2031+
for(int u = 0; u <= ord_u; ++u)
2032+
{
2033+
for(int v = 0; v <= ord_v; ++v)
2034+
{
2035+
// Don't need to check the corners
2036+
if((u == 0 && v == 0) || (u == 0 && v == ord_v) ||
2037+
(u == ord_u && v == 0) || (u == ord_u && v == ord_v))
2038+
{
2039+
continue;
2040+
}
2041+
2042+
// Evaluate where the control point would be if the patch *was* bilinear
2043+
PointType bilinear_point =
2044+
bilinear_patch(u / static_cast<T>(ord_u), v / static_cast<T>(ord_v));
2045+
2046+
if(squared_distance(m_controlPoints(u, v), bilinear_point) > sq_tol)
2047+
{
2048+
return false;
2049+
}
2050+
}
2051+
}
2052+
}
2053+
else
2054+
{
2055+
for(int p = 0; p <= ord_u; ++p)
2056+
{
2057+
Segment<T, 3> seg(m_controlPoints(p, 0), m_controlPoints(p, ord_v));
2058+
for(int q = 1; q < ord_v; ++q)
2059+
{
2060+
if(squared_distance(m_controlPoints(p, q), seg))
2061+
{
2062+
return false;
2063+
}
2064+
}
2065+
}
2066+
2067+
for(int q = 0; q <= ord_v; ++q)
2068+
{
2069+
Segment<T, 3> seg(m_controlPoints(0, q), m_controlPoints(ord_u, q));
2070+
for(int p = 1; p < ord_u; ++p)
2071+
{
2072+
if(squared_distance(m_controlPoints(p, q), seg))
2073+
{
2074+
return false;
2075+
}
2076+
}
2077+
}
2078+
}
2079+
2080+
return true;
2081+
}
2082+
19842083
/*!
19852084
* \brief Simple formatted print of a Bezier Patch instance
19862085
*

src/axom/primal/geometry/KnotVector.hpp

+15
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,21 @@ class KnotVector
191191
/// \brief Return the number of knots in the knot vector
192192
axom::IndexType getNumKnots() const { return m_knots.size(); }
193193

194+
/// \brief Return an array of the unique knot values
195+
axom::Array<T> getUniqueKnots() const
196+
{
197+
axom::Array<T> unique_knots;
198+
for(int i = 0; i < m_knots.size(); ++i)
199+
{
200+
if(i == 0 || m_knots[i] != m_knots[i - 1])
201+
{
202+
unique_knots.push_back(m_knots[i]);
203+
}
204+
}
205+
206+
return unique_knots;
207+
}
208+
194209
/// \brief Return the number of valid knot spans
195210
axom::IndexType getNumKnotSpans() const
196211
{

0 commit comments

Comments
 (0)