Skip to content

Commit 56c8787

Browse files
authored
Merge pull request #1470 from LLNL/feature/spainhour/nurbs_patches
Adds untrimmed NURBS patches to primal
2 parents 6b5ff3b + c717605 commit 56c8787

11 files changed

+4574
-134
lines changed

RELEASE-NOTES.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The Axom project release numbers follow [Semantic Versioning](http://semver.org/
2424
- Sina C++ library is now a component of Axom
2525
- Adds optional dependency on [Open CASCADE](https://dev.opencascade.org). The initial intention is
2626
to use Open CASCADE's file I/O capabilities in support of Quest applications.
27+
- Adds `primal::NURBSCurve` and `primal::NURBSPatch` classes, supported by `primal::KnotVector`.
2728

2829
### Changed
2930
- Importing Conduit array data into `sidre::View` now allocates destination

src/axom/core/numerics/matvecops.hpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -533,10 +533,11 @@ inline bool linspace(const T& x0, const T& x1, T* v, int N)
533533

534534
const T h = (x1 - x0) / static_cast<T>(N - 1);
535535

536-
for(int i = 0; i < N; ++i)
536+
for(int i = 0; i < N - 1; ++i)
537537
{
538538
v[i] = x0 + i * h;
539539
}
540+
v[N - 1] = x1;
540541

541542
return true;
542543
}

src/axom/primal/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ set( primal_headers
3030
geometry/OrientationResult.hpp
3131
geometry/NumericArray.hpp
3232
geometry/NURBSCurve.hpp
33+
geometry/NURBSPatch.hpp
3334
geometry/Plane.hpp
3435
geometry/Point.hpp
3536
geometry/Polygon.hpp

src/axom/primal/geometry/KnotVector.hpp

+69-28
Original file line numberDiff line numberDiff line change
@@ -227,19 +227,28 @@ class KnotVector
227227
* For knot vector {u_0, ..., u_n}, returns i such that u_i <= t < u_i+1
228228
* if t == u_n, returns i such that u_i < t <= u_i+1 (i.e. i = n - degree - 1)
229229
*
230-
* \pre Assumes that the input t is within the knot vector
231-
*
232230
* Implementation adapted from Algorithm A2.1 on page 68 of "The NURBS Book"
233231
*
232+
* \pre Assumes that the input t is in the span [u_0, u_n] (up to some tolerance)
233+
*
234+
* \note If t is outside the knot span up to this tolerance, it is clamped to the span
235+
*
234236
* \return The index of the knot span containing t
235237
*/
236238
axom::IndexType findSpan(T t) const
237239
{
238-
SLIC_ASSERT(t >= m_knots[0] && t <= m_knots[m_knots.size() - 1]);
240+
SLIC_ASSERT(isValidParameter(t));
239241

240242
const axom::IndexType nkts = m_knots.size();
241243

242-
if(t == m_knots[nkts - 1])
244+
// Handle cases where t is outside the knot span within a tolerance
245+
// by implicitly clamping it to the nearest span
246+
if(t <= m_knots[0])
247+
{
248+
return m_deg;
249+
}
250+
251+
if(t >= m_knots[nkts - 1])
243252
{
244253
return nkts - m_deg - 2;
245254
}
@@ -266,19 +275,29 @@ class KnotVector
266275
* For knot vector {u_0, ..., u_n}, returns i such that u_i <= t < u_i+1
267276
* if t == u_n, returns i such that u_i < t <= u_i+1 (i.e. i = n - degree - 1)
268277
*
269-
* \pre Assumes that the input t is within the knot vector
278+
* \pre Assumes that the input t is within the knot vector (up to some tolerance)
279+
*
280+
* \note If t is outside the knot span up to this tolerance, the returned multiplicity
281+
* will be equal to the degree + 1 (required for clamped curves)
270282
*
271283
* \return The index of the knot span containing t
272284
*/
273285
axom::IndexType findSpan(T t, int& multiplicity) const
274286
{
275-
SLIC_ASSERT(t >= m_knots[0] && t <= m_knots[m_knots.size() - 1]);
287+
SLIC_ASSERT(isValidParameter(t));
276288

277289
const auto nkts = m_knots.size();
278290
const auto span = findSpan(t);
279291

292+
// Early exit for known multiplicities
293+
if(t <= m_knots[0] || t >= m_knots[nkts - 1])
294+
{
295+
multiplicity = m_deg + 1;
296+
return span;
297+
}
298+
280299
multiplicity = 0;
281-
for(auto i = (t == m_knots[nkts - 1]) ? nkts - 1 : span; i >= 0; --i)
300+
for(auto i = span; i >= 0; --i)
282301
{
283302
if(m_knots[i] == t)
284303
{
@@ -356,20 +375,22 @@ class KnotVector
356375
* \param [in] t The value of the knot to insert
357376
* \param [in] target_mutliplicity The number of times the knot will be present
358377
*
359-
* \pre Assumes that the input t is within the knot vector
378+
* \pre Assumes that the input t is within the knot vector (up to some tolerance)
360379
*
361380
* \note If the knot is already present, it will be inserted
362381
* up to the given multiplicity, or the maximum permitted by the degree
363382
*/
364383
void insertKnot(T t, int target_multiplicity)
365384
{
366-
SLIC_ASSERT(t >= m_knots[0] && t <= m_knots[m_knots.size() - 1]);
385+
SLIC_ASSERT(isValidParameter(t));
367386

368387
int multiplicity;
369388
auto span = findSpan(t, multiplicity);
370389

371-
int r =
372-
axom::utilities::clampVal(target_multiplicity - multiplicity, 0, m_deg);
390+
// Compute how many knots should be inserted
391+
int r = axom::utilities::clampVal(target_multiplicity - multiplicity,
392+
0,
393+
m_deg - multiplicity);
373394

374395
insertKnotBySpan(span, t, r);
375396
}
@@ -424,8 +445,8 @@ class KnotVector
424445
k2.normalize();
425446
}
426447

427-
// SLIC_ASSERT(k1.isValid());
428-
// SLIC_ASSERT(k2.isValid());
448+
SLIC_ASSERT(k1.isValid());
449+
SLIC_ASSERT(k2.isValid());
429450
}
430451

431452
/*!
@@ -436,11 +457,11 @@ class KnotVector
436457
* \param [out] k2 The second knot vector
437458
* \param [in] normalize Whether to normalize the output knot vectors
438459
*
439-
* \pre Assumes that the input t is within the knot vector
460+
* \pre Assumes that the input t is *interior* to the knot vector
440461
*/
441462
void split(T t, KnotVector& k1, KnotVector& k2, bool normalize = false) const
442463
{
443-
SLIC_ASSERT(t > m_knots[0] && t < m_knots[m_knots.size() - 1]);
464+
SLIC_ASSERT(isValidInteriorParameter(t));
444465

445466
int multiplicity;
446467
axom::IndexType span = findSpan(t, multiplicity);
@@ -457,7 +478,7 @@ class KnotVector
457478
* \param [in] span The span in which to evaluate the basis functions
458479
* \param [in] t The parameter value
459480
*
460-
* \pre Assumes that the input t is within the knot vector and that the span is valid
481+
* \pre Assumes that the input t is within the correct span
461482
* Implementation adapted from Algorithm A2.2 on page 70 of "The NURBS Book".
462483
*
463484
* \return An array of the `m_deg + 1` non-zero basis functions evaluated at t
@@ -496,13 +517,13 @@ class KnotVector
496517
*
497518
* \param [in] t The parameter value
498519
*
499-
* \pre Assumes that the input t is within the knot vector
520+
* \pre Assumes that the input t is within the knot vector (up to a tolerance)
500521
*
501522
* \return An array of the `m_deg + 1` non-zero basis functions evaluated at t
502523
*/
503524
axom::Array<T> calculateBasisFunctions(T t) const
504525
{
505-
SLIC_ASSERT(t >= m_knots[0] && t <= m_knots[m_knots.size() - 1]);
526+
SLIC_ASSERT(isValidParameter(t));
506527
return calculateBasisFunctionsBySpan(findSpan(t), t);
507528
}
508529

@@ -512,19 +533,23 @@ class KnotVector
512533
* \param [in] span The span in which to evaluate the basis functions
513534
* \param [in] t The parameter value
514535
* \param [in] n The number of derivatives to compute
515-
* \param [out] ders An array of the `n + 1` derivatives evaluated at t
516536
*
517537
* Implementation adapted from Algorithm A2.2 on page 70 of "The NURBS Book".
538+
*
539+
* \pre Assumes that the input t is within the provided knot span
540+
*
541+
* \return An array of the `n + 1` derivatives evaluated at t
518542
*/
519-
void derivativeBasisFunctionsBySpan(axom::IndexType span,
520-
T t,
521-
int n,
522-
axom::Array<axom::Array<T>>& ders) const
543+
axom::Array<axom::Array<T>> derivativeBasisFunctionsBySpan(axom::IndexType span,
544+
T t,
545+
int n) const
523546
{
524547
SLIC_ASSERT(isValidSpan(span, t));
525548

526549
const int m_deg = getDegree();
527550

551+
axom::Array<axom::Array<T>> ders(n + 1);
552+
528553
axom::Array<axom::Array<T>> ndu(m_deg + 1), a(2);
529554
axom::Array<T> left(m_deg + 1), right(m_deg + 1);
530555
for(int j = 0; j <= m_deg; j++)
@@ -596,6 +621,7 @@ class KnotVector
596621
std::swap(s1, s2);
597622
}
598623
}
624+
599625
// Multiply through by the correct factors (Eq. [2.9])
600626
T r = static_cast<T>(m_deg);
601627
for(int k = 1; k <= n; k++)
@@ -606,21 +632,24 @@ class KnotVector
606632
}
607633
r *= static_cast<T>(m_deg - k);
608634
}
635+
636+
return ders;
609637
}
610638

611639
/*!
612640
* \brief Evaluates the NURBS basis functions and derivatives for parameter value t
613641
*
614642
* \param [in] t The parameter value
615643
* \param [in] n The number of derivatives to compute
616-
* \param [out] ders An array of the `n + 1` derivatives evaluated at t
617644
*
618-
* \pre Assumes that the input t is within the knot vector
645+
* \pre Assumes that the input t is within the knot vector (up to a tolerance)
646+
*
647+
* \return An array of the `n + 1` derivatives evaluated at t
619648
*/
620-
void derivativeBasisFunctions(T t, int n, axom::Array<axom::Array<T>>& ders) const
649+
axom::Array<axom::Array<T>> derivativeBasisFunctions(T t, int n) const
621650
{
622-
SLIC_ASSERT(t >= m_knots[0] && t <= m_knots[m_knots.size() - 1]);
623-
derivativeBasisFunctionsBySpan(findSpan(t), t, n, ders);
651+
SLIC_ASSERT(isValidParameter(t));
652+
return derivativeBasisFunctionsBySpan(findSpan(t), t, n);
624653
}
625654

626655
/// \brief Reverse the knot vector
@@ -755,6 +784,18 @@ class KnotVector
755784
return !(lhs == rhs);
756785
}
757786

787+
/// \brief Checks if given parameter is in knot span (to a tolerance)
788+
bool isValidParameter(T t, T EPS = 1e-5) const
789+
{
790+
return t >= m_knots[0] - EPS && t <= m_knots[m_knots.size() - 1] + EPS;
791+
}
792+
793+
/// \brief Checks if given parameter is *interior* to knot span (to a tolerance)
794+
bool isValidInteriorParameter(T t) const
795+
{
796+
return t > m_knots[0] && t < m_knots[m_knots.size() - 1];
797+
}
798+
758799
/*!
759800
* \brief Simple formatted print of a knot vector instance
760801
*

0 commit comments

Comments
 (0)