Files
opennurbs/opennurbs_knot.cpp
2024-11-20 02:35:59 -08:00

1458 lines
38 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
/////////////////////////////////////////////////////////////////
//
// Computes tolerance associated with a generic evaluation domain
//
double ON_DomainTolerance( double a, double b )
{
if ( a == b )
return 0.0;
double tol = (fabs(a)+fabs(b)+fabs(a-b))* ON_SQRT_EPSILON;
if ( tol < ON_EPSILON )
tol = ON_EPSILON;
return tol;
}
/////////////////////////////////////////////////////////////////
//
// Computes tolerance associated with knot[i]
//
double ON_KnotTolerance( int order, int cv_count, const double* knot,
int knot_index )
{
const int knot_count = ON_KnotCount( order, cv_count );
int i0, i1, j;
double a, b, tol;
i0 = knot_index-order+1;
if ( i0 < 0 )
i0 = 0;
i1 = knot_index+order-1;
if ( i1 >= knot_count )
i1 = knot_count-1;
for ( j = knot_index; j > i0; j-- ) {
if ( knot[j] != knot[knot_index] )
break;
}
a = fabs(knot[knot_index] - knot[j]);
for ( j = knot_index; j < i1; j++ ) {
if ( knot[j] != knot[knot_index] )
break;
}
b = fabs(knot[knot_index] - knot[j]);
tol = (a==0.0 && b==0.0) ? 0.0 : (a + b + fabs(knot[knot_index]))* ON_SQRT_EPSILON;
return tol;
}
/////////////////////////////////////////////////////////////////
//
// Computes tolerance associated with span of a knot vector
//
double ON_SpanTolerance( int order, int cv_count, const double* knot, int span_index )
{
const int i0 = span_index+order-2;
return ON_DomainTolerance( knot[i0], knot[i0+1] );
}
/////////////////////////////////////////////////////////////////
//
// Computes number of knots in knot vector
//
int ON_KnotCount( int order, int cv_count )
{
return (order+cv_count-2);
}
const ON_2dex ON_BsplineControlPointSpans(
int order,
int control_point_count,
int control_point_index
)
{
for (;;)
{
if (order < 2 || control_point_count < order)
break;
if (control_point_index < 0 || control_point_index >= control_point_count)
break;
const int i0 = control_point_index - order + 1;
const int i1 = control_point_index + 1;
const int span_count = control_point_count - order + 1;
return ON_2dex(i0 >= 0 ? i0 : 0, i1 <= span_count ? i1 : span_count);
}
return ON_2dex(0, 0);
}
ON_DECL const ON_Interval ON_BsplineControlPointSupport(int order, int control_point_count, const double* knots, int control_point_index)
{
for (;;)
{
if (nullptr == knots)
break;
const ON_2dex active_spans = ON_BsplineControlPointSpans(order, control_point_count, control_point_index);
if (active_spans.i < active_spans.j)
return ON_Interval(knots[active_spans.i + order - 2], knots[active_spans.j + order - 2]);
break;
}
return ON_Interval::Nan;
}
/////////////////////////////////////////////////////////////////
//
// Computes number of knots in knot vector
//
int ON_KnotMultiplicity(
int order, // order (>=2)
int cv_count, // cv_count (>=order)
const double* knot, // knot[]
int knot_index // knot_index
)
{
int knot_count = order+cv_count-2;
int km = 0;
if ( knot && knot_index >= 0 && knot_index < knot_count ) {
while(knot_index > 0 && knot[knot_index] == knot[knot_index-1])
knot_index--;
knot += knot_index;
knot_count -= knot_index;
km = 1;
while ( km < knot_count && knot[0] == knot[km] )
km++;
}
return km;
}
/////////////////////////////////////////////////////////////////
//
// Computes number of non-empty spans
//
int ON_KnotVectorSpanCount(
int order, // order (>=2)
int cv_count, // cv count
const double* knot // knot[] array
)
{
if ( 0 == knot )
{
if ( 0 != order || 0 != cv_count )
{
ON_ERROR("nullptr knot[] passed to ON_KnotVectorSpanCount.");
}
return 0;
}
int i, span_count = 0;
for ( i = order-1; i < cv_count; i++ ) {
if ( knot[i] > knot[i-1] )
span_count++;
}
return span_count;
}
/////////////////////////////////////////////////////////////////
//
// Gets span vector from knot vector
//
bool ON_GetKnotVectorSpanVector(
int order, // order (>=2)
int cv_count, // cv count
const double* knot, // knot[] array
double* s // s[] array
)
{
if ( 0 == knot || 0 == s )
{
if ( 0 != order || 0 != cv_count )
{
ON_ERROR("nullptr knot[] or s[] passed to ON_KnotVectorSpanCount.");
return false;
}
return true;
}
int i, span_count = 0;
s[span_count++] = knot[order-2];
for ( i = order-1; i < cv_count; i++ ) {
if ( knot[i] > knot[i-1] )
s[span_count++] = knot[i];
}
return (span_count>1) ? true : false;
}
/////////////////////////////////////////////////////////////////
//
// Computes span index for evaluation of parameter
//
int ON_NurbsSpanIndex(
int order, // (>=2)
int cv_count,
const double* knot, // knot[] array or length ON_KnotCount(order,cv_count)
double t, // evaluation parameter
int side, // side 0 = default, -1 = from below, +1 = from above
int hint // hint (or 0 if no hint available)
)
{
int j, len;
// shift knot so that domain is knot[0] to knot[len]
knot += (order-2);
len = cv_count-order+2;
// see if hint helps
if (hint > 0 && hint < len-1) {
while(hint > 0 && knot[hint-1] == knot[hint]) hint--;
if (hint > 0) {
// have knot[hint-1] < knot[hint]
if (t < knot[hint]) {
len = hint+1;
hint = 0;
}
else {
if (side < 0 && t == knot[hint])
hint--;
knot += hint;
len -= hint;
}
}
}
else
hint = 0;
j = ON_SearchMonotoneArray(knot,len,t);
if (j < 0)
j = 0;
else if (j >= len-1)
j = len-2;
else if (side < 0) {
// if user wants limit from below and t = an internal knot,
// back up to previous span
while(j > 0 && t == knot[j])
j--;
}
return (j + hint);
}
int ON_NextNurbsSpanIndex( int order, int cv_count, const double* knot, int span_index )
/*
Get index of next non-degenerate NURBS span
INPUT:
order, cv_count, knot
knot vector
span_index
current span index ( >= 0 and <= cv_count-order )
OUTPUT:
i = ON_NextNurbsSpanIndex()
i>=0: successful - the index of the next span or
cv_count-order if the input value
was cv_count-or
knot[i+order-2] < knot[i+order-1]
<0: failure
COMMENTS:
The first span in a NURBS has span_index = 0. The last span in a NURBS
has span_index = cv_count-order.
A span of a degree d NURBS is defined by d+1 CVs and 2*d knots. For a
given span_index, the associated knots and CVs are
{knot[span_index], ..., knot[span_index+2*d-1]}
and
{CV[span_index], ..., CV[span_index + d]}
The domain of the span is
[ knot[span_index+order-2], knot[span_index+order-1] ].
EXAMPLE:
// print the values of all distinct knots in a NURBS's domain
int span_index = 0;
int next_span_index = 0;
for (;;) {
printf( "knot[%2d] = %g\n", span_index+order-2, knot[span_index+order-2] );
next_span_index =ON_NextNurbsSpanIndex( order, cv_count, knot, span_index );
if ( next_span_index < 0 )
break; // illegal input
if ( next_span_index == span_index ) {
// end of the domain
printf( "knot[%2d] = %g\n", cv_count-1, knot[cv_count-1] );
break;
}
next_span_index = span_index;
}
*/
{
if (span_index < 0 || span_index > cv_count-order || !knot)
return -1;
if ( span_index < cv_count-order ) {
do {
span_index++;
}
while ( span_index < cv_count-order &&
knot[span_index+order-2] == knot[span_index+order-1] );
}
return span_index;
}
int ON_GetSpanIndices(int order,
int cv_count,
const double* knot,
int* span_indices)
/* span_indices should have size greater than the number of
spans (cv_count is big enough).
returns span count.
fills in span_indices with index of last in each bunch of multiple knots at
start of span, and first in buch at end of nurb.
*/
{
int span_index, next_span_index, j;
span_index = -1;
next_span_index = 0;
j = 0;
while (span_index != next_span_index) {
span_index = next_span_index;
span_indices[j] = span_index + order - 2;
next_span_index = ON_NextNurbsSpanIndex(order, cv_count, knot, span_index);
if (next_span_index < 0)
return next_span_index;
j++;
}
span_indices[j] = span_index + order - 1;
return j;
}
/////////////////////////////////////////////////////////////////
//
// Computes value for superfluous knot used in systems like OpenGL and 3dsMax
//
double ON_SuperfluousKnot(
int order, int cv_count, const double* knot,
int end )
{
double k;
const int knot_count = order+cv_count-2;
// gets superfluous knot for translation to other formats
k = knot[(end) ? knot_count-1 : 0];
if (order > 2 && cv_count >= 2*order - 2 && cv_count >= 6 ) {
// check for non-clamped knots
if (end) {
if ( knot[cv_count-1] < knot[knot_count-1] )
k += (knot[order+1] - knot[order]);
}
else {
if ( knot[0] < knot[order-2] )
k -= (knot[cv_count-order+1] - knot[cv_count-order]);
}
}
return k;
}
/////////////////////////////////////////////////////////////////
//
// Used to determine when a knot vector is periodic
//
bool ON_IsKnotVectorPeriodic(
int order,
int cv_count,
const double* knot
)
{
double tol;
const double* k1;
int i;
if ( order < 2 || cv_count < order || !knot ) {
ON_ERROR("ON_IsKnotVectorPeriodic(): illegal input");
return false;
}
if ( order == 2 )
return false; // convention is that degree 1 curves cannot be periodic.
if (order <= 4) {
if (cv_count < order+2)
return false;
}
else if ( cv_count < 2*order-2 ) {
return false;
}
tol = fabs(knot[order-1] - knot[order-3])* ON_SQRT_EPSILON;
if (tol < fabs(knot[cv_count-1] - knot[order-2])* ON_SQRT_EPSILON)
tol = fabs(knot[cv_count-1] - knot[order-2])* ON_SQRT_EPSILON;
k1 = knot+cv_count-order+1;
i = 2*(order-2);
while(i--) {
if (fabs(knot[1] - knot[0] + k1[0] - k1[1]) > tol)
return false;
knot++; k1++;
}
return true;
}
/////////////////////////////////////////////////////////////////
//
// Used to determine when a knot vector is clamped
//
bool ON_IsKnotVectorClamped(
int order,
int cv_count,
const double* knot,
int end // (default = 2) 0 = left end, 1 = right end, 2 = both
)
{
if ( order <= 1 || cv_count < order || !knot || end < 0 || end > 2 )
return false;
bool rc = true;
if ( (end == 0 || end == 2) && knot[0] != knot[order-2] )
rc = false;
if ( (end == 1 || end == 2) && knot[cv_count-1] != knot[order+cv_count-3] )
rc = false;
return rc;
}
bool ON_IsKnotVectorUniform(
int order,
int cv_count,
const double* knot
)
{
bool rc = (order >= 2 && cv_count >= order && 0 != knot);
if (rc)
{
const double delta = knot[order-1] - knot[order-2];
rc = (0.0 != delta && delta > ON_UNSET_VALUE && delta < ON_UNSET_POSITIVE_VALUE);
if (rc)
{
const int i0 = ON_IsKnotVectorClamped(order, cv_count, knot, 0) ? order : 1;
const int i1 = ON_IsKnotVectorClamped(order, cv_count, knot, 1) ? cv_count : ON_KnotCount(order, cv_count);
double k0 = knot[i0 - 1];
const double delta_tol = fabs(ON_SQRT_EPSILON*delta);
for (int i = i0; i < i1 && rc; ++i)
{
const double d = knot[i] - k0;
rc = fabs(d - delta) <= delta_tol;
k0 = knot[i];
}
}
}
return rc;
}
/////////////////////////////////////////////////////////////////
//
// Used to determine properties of knot vector
//
bool ON_KnotVectorHasBezierSpans(
int order, // order (>=2)
int cv_count, // cv count
const double* knot // knot[] array
)
{
int knot_count = ON_KnotCount( order, cv_count );
if ( knot_count < 2 )
return false;
int span_count = ON_KnotVectorSpanCount( order, cv_count, knot );
if ( span_count < 1 )
return false;
if ( order >= 2 &&
cv_count >= order &&
knot_count == (span_count+1)*(order-1) &&
knot[0] == knot[order-2] && knot[cv_count-1] == knot[knot_count-1])
return true;
return false;
}
/////////////////////////////////////////////////////////////////
//
// Used to determine properties of knot vector
//
ON::knot_style ON_KnotVectorStyle(
int order,
int cv_count,
const double* knot
)
{
ON::knot_style s = ON::unknown_knot_style;
if ( order >= 2 && cv_count >= order && knot && knot[order-2] < knot[cv_count-1] ) {
const int knot_count = order+cv_count-2;
const double delta = 0.5*((knot[order-1] - knot[order-2]) + (knot[cv_count-1] - knot[cv_count-2]));
const double ktol = delta*1.0e-6;
int i;
if ( ON_IsKnotVectorClamped( order, cv_count, knot ) ) {
if ( order == cv_count ) {
s = ON::piecewise_bezier_knots;
}
else {
s = ON::clamped_end_knots;
for ( i = order-1; i <= cv_count-1; i++ ) {
if ( fabs(knot[i] - knot[i-1] - delta) > ktol ) {
break;
}
}
if ( i >= cv_count ) {
s = ON::quasi_uniform_knots;
}
else {
const int degree = order-1;
for ( i = order-1; i < cv_count-1; i += degree ) {
if ( knot[i] != knot[i+degree-1] )
break;
}
if ( i >= cv_count-1 )
s = ON::piecewise_bezier_knots;
}
}
}
else {
// check for uniform knots
s = ON::non_uniform_knots;
for ( i = 1; i < knot_count; i++ ) {
if ( fabs(knot[i] - knot[i-1] - delta) > ktol ) {
break;
}
}
if ( i >= knot_count )
s = ON::uniform_knots;
}
}
return s;
}
/////////////////////////////////////////////////////////////////
//
// Used to set the domain of a knot vector
//
bool ON_SetKnotVectorDomain( int order, int cv_count, double* knot, double t0, double t1 )
{
bool rc = false;
if ( order < 2 || cv_count < order || 0 == knot || t0 >= t1 || !ON_IsValid(t0) || !ON_IsValid(t1) )
{
ON_ERROR("ON_SetKnotVectorDomain - invalid input");
}
else if ( knot[order-2] >= knot[cv_count-1]
|| !ON_IsValid(knot[order-2])
|| !ON_IsValid(knot[cv_count-2]) )
{
ON_ERROR("ON_SetKnotVectorDomain - invalid input knot vector");
}
else
{
const ON_Interval oldd(knot[order-2],knot[cv_count-1]);
const ON_Interval newd(t0,t1);
if ( oldd != newd )
{
int i, knot_count = ON_KnotCount(order,cv_count);
for ( i = 0; i < knot_count; i++ )
{
knot[i] = newd.ParameterAt(oldd.NormalizedParameterAt(knot[i]));
}
}
rc = true;
}
return rc;
}
/////////////////////////////////////////////////////////////////
//
// Used to get the domain of a knot vector
//
bool ON_GetKnotVectorDomain(
int order,
int cv_count,
const double* knot,
double* k0, double* k1
)
{
if ( order < 2 || cv_count < order || knot == nullptr )
return false;
if ( k0 )
*k0 = knot[order-2];
if ( k1 )
*k1 = knot[cv_count-1];
return true;
}
/////////////////////////////////////////////////////////////////
//
// Used to reverse knot vectors
//
bool ON_ReverseKnotVector(
int order,
int cv_count,
double* knot
)
{
if ( order < 2 || cv_count < order || knot == nullptr )
return false;
const int knot_count = (order+cv_count-2);
double t;
int i, j;
for ( i = 0, j = knot_count-1; i <= j; i++, j-- ) {
t = knot[i];
knot[i] = -knot[j];
knot[j] = -t;
}
return true;
}
/////////////////////////////////////////////////////////////////
//
// Used to compare knot vectors
//
int ON_CompareKnotVector( // returns
// -1: first < second
// 0: first == second
// +1: first > second
int orderA,
int cv_countA,
const double* knotA,
int orderB,
int cv_countB,
const double* knotB
)
{
const int knot_count = ON_KnotCount(orderA,cv_countA);
int i;
double a, b, atol, btol, ktol, tol;
if ( orderA < orderB )
return -1;
if ( orderA > orderB )
return 1;
if ( cv_countA < cv_countB )
return -1;
if ( cv_countA > cv_countB )
return 1;
if ( !ON_GetKnotVectorDomain( orderA, cv_countA, knotA, &a, &b ) )
return -1;
atol = ON_DomainTolerance( a, b );
if ( !ON_GetKnotVectorDomain( orderA, cv_countA, knotA, &a, &b ) )
return 1;
btol = ON_DomainTolerance( a, b );
tol = (atol <= btol) ? atol : btol;
for ( i = 0; i < knot_count; i++ ) {
a = knotA[i];
b = knotB[i];
if ( a == b )
continue;
if ( a < b-tol )
return -1;
if ( b < a-tol )
return 1;
atol = ON_KnotTolerance( orderA, cv_countA, knotA, i );
btol = ON_KnotTolerance( orderB, cv_countB, knotB, i );
ktol = (atol <= btol) ? atol : btol;
if ( a < b-ktol )
return -1;
if ( b < a-ktol )
return 1;
}
return 0;
}
/////////////////////////////////////////////////////////////////
//
// Used to validate knot vectors
//
static bool ON_KnotVectorIsNotValid(bool bSilentError)
{
return bSilentError ? false : ON_IsNotValid(); // <-- good place for a breakpoint
}
bool ON_IsValidKnotVector( int order, int cv_count, const double* knot, ON_TextLog* text_logx )
{
// If low bit of text_log pointer is 1, then ON_Error is not called when the
// knot vector is invalid.
const ON__INT_PTR lowbit = 1;
const ON__INT_PTR hightbits = ~lowbit;
bool bSilentError = ( 0 != (lowbit & ((ON__INT_PTR)text_logx)) );
ON_TextLog* text_log = (ON_TextLog*)(((ON__INT_PTR)text_logx) & hightbits);
const double *k0, *k1;
int i;
if ( order < 2 )
{
if ( text_log )
{
text_log->Print("Knot vector order = %d (should be >= 2 )\n",order);
}
return ON_KnotVectorIsNotValid(bSilentError);
}
if ( cv_count < order )
{
if ( text_log )
{
text_log->Print("Knot vector cv_count = %d (should be >= order=%d )\n",cv_count,order);
}
return ON_KnotVectorIsNotValid(bSilentError);
}
if ( knot == nullptr )
{
if ( text_log )
{
text_log->Print("Knot vector knot array = nullptr.\n");
}
return ON_KnotVectorIsNotValid(bSilentError);
}
for ( i = 0; i < cv_count+order-2; i++ )
{
if ( !ON_IsValid(knot[i]) )
{
if ( text_log )
{
text_log->Print("Knot vector knot[%d]=%g is not valid.\n",i,knot[i]);
}
return ON_KnotVectorIsNotValid(bSilentError);
}
}
if ( !(knot[order-2] < knot[order-1]) )
{
if ( text_log )
{
text_log->Print("Knot vector order=%d and knot[%d]=%g >= knot[%d]=%g (should have knot[order-2] < knot[order-1]).\n",
order,order-2,knot[order-2],order-1,knot[order-1]);
}
return ON_KnotVectorIsNotValid(bSilentError);
}
if ( !(knot[cv_count-2] < knot[cv_count-1]) )
{
if ( text_log )
{
text_log->Print("Knot vector cv_count=%d and knot[%d]=%g >= knot[%d]=%g (should have knot[cv_count-2] < knot[cv_count-1]).\n",
cv_count,cv_count-2,knot[cv_count-2],cv_count-1,knot[cv_count-1]);
}
return ON_KnotVectorIsNotValid(bSilentError);
}
// entire array must be monotone increasing
k0 = knot;
k1 = knot+1;
i = order + cv_count - 3;
while (i--) {
if ( !(*k1 >= *k0) )
{
if ( text_log )
{
text_log->Print("Knot vector must be increasing but knot[%d]=%g > knot[%d]=%g\n",
order+cv_count-4-i, *k0, order+cv_count-3-i, *k1 );
}
return ON_KnotVectorIsNotValid(bSilentError);
}
k0++;
k1++;
}
// must have knot[i+order-1] > knot[i]
k0 = knot;
k1 = knot + order - 1;
i = cv_count-1;
while(i--) {
if ( !(*k1 > *k0) )
{
if ( text_log )
{
text_log->Print("Knot vector order = %d but knot[%d]=%g >= knot[%d]=%g\n",
order, cv_count-2-i, *k0, cv_count-1-i, *k1 );
}
return ON_KnotVectorIsNotValid(bSilentError);
}
k0++;
k1++;
}
return true;
}
bool ON_ClampKnotVector(
int order, // order (>=2)
int cv_count, // cv count
double* knot, // knot[] array
int end // 0 = clamp start, 1 = clamp end, 2 = clamp both ends
)
{
// sets initial/final order-2 knot values to match knot[order-2]/knot[cv_count-1]
bool rc = false;
int i, i0;
if ( knot && order >= 2 && cv_count >= order ) {
if ( end == 0 || end == 2 ) {
i0 = order-2;
for ( i = 0; i < i0; i++ ) {
knot[i] = knot[i0];
}
rc = true;
}
if ( end == 1 || end == 2 ) {
const int knot_count = ON_KnotCount(order,cv_count);
i0 = cv_count-1;
for ( i = i0+1; i < knot_count; i++ ) {
knot[i] = knot[i0];
}
rc = true;
}
}
return rc;
}
bool ON_MakeKnotVectorPeriodic(
int order, // order (>=2)
int cv_count, // cv count (>= (order>=4) ? 2*(order-1) : 5)
double* knot // knot[] array
)
{
double *k0, *k1;
int i;
if ( order < 2 || cv_count < order || !knot ) {
ON_ERROR("ON_MakePeriodicKnotVector(): illegal input");
return false;
}
switch(order) {
case 2:
if ( cv_count < 4 ) {
ON_ERROR("ON_MakePeriodicKnotVector(): illegal input degree=1, cv_count<4");
return false;
}
else {
return true;
}
break;
case 3:
if ( cv_count < 4 ) {
ON_ERROR("ON_MakePeriodicKnotVector(): illegal input degree=2, cv_count<5");
return false;
}
break;
default:
if ( cv_count < 2*order-2 ) {
ON_ERROR("ON_MakePeriodicKnotVector(): illegal input degree>=3, cv_count<2*degree");
return false;
}
break;
}
k0 = knot + order-2;
k1 = knot + cv_count-1;
i = order-2;
while (i--) {
k1[1] = k0[1]-k0[0]+k1[0];
k0++; k1++;
}
k0 = knot + order-2;
k1 = knot + cv_count-1;
i = order-2;
while (i--) {
k0[-1] = k1[-1] - k1[0] + k0[0];
k0--;
k1--;
}
return true;
}
ON_DECL
bool ON_MakeClampedUniformKnotVector(
int order,
int cv_count,
double* knot,
double delta
)
{
bool rc = false;
if ( order >= 2 && cv_count >= order && knot != nullptr && delta > 0.0 )
{
double k;
int i;
for ( i = order-2, k = 0.0; i < cv_count; i++, k += delta )
knot[i] = k;
ON_ClampKnotVector(order,cv_count,knot,2);
rc = true;
}
return rc;
}
// Description:
// Fill in knot values for a clamped uniform knot
// vector.
// Parameters:
// order - [in] (>=2) order (degree+1) of the NURBS
// cv_count - [in] (>=order) total number of control points
// in the NURBS.
// knot - [in/out] Input is an array with room for
// ON_KnotCount(order,cv_count) doubles. Output is
// a periodic uniform knot vector with domain
// (0, (1+cv_count-order)*delta).
// delta - [in] (>0, default=1.0) spacing between knots.
// Returns:
// true if successful
ON_DECL
bool ON_MakePeriodicUniformKnotVector(
int order,
int cv_count,
double* knot,
double delta
)
{
bool rc = false;
if ( order >= 2 && cv_count >= order && knot != nullptr && delta > 0.0 )
{
double k = 0.0;
int i, knot_count = ON_KnotCount(order,cv_count);
for ( i = order-2, k = 0.0; i < knot_count; i++, k += delta )
knot[i] = k;
for ( i = order-3, k = -delta; i >= 0; i--, k -= delta )
knot[i] = k;
rc = true;
}
return rc;
}
double ON_GrevilleAbcissa( // get Greville abcissa
int order, // order (>=2)
const double* knot // knot[order-1] array
)
{
double g=0.0;
if ( order <= 2 || knot[0] == knot[order-2])
{
g = knot[0]; // degree = 1 or fully multiple knot
}
else
{
// g = (knot[i]+...+knot[i+degree-1])/degree
order--;
const double k0 = knot[0];
const double k = knot[order/2];
const double k1 = knot[order-1];
const double tol = (k1-k0)*ON_SQRT_EPSILON;
const double const_ord = (double)order;
while ( order--)
{
g += *knot++;
}
//g /= ((double)order);
g /= const_ord;
if ( fabs(2*k - (k0+k1)) <= tol && fabs(g-k) <= (fabs(g)*ON_SQRT_EPSILON+tol) )
g = k; // sets g to exact value when knot vector is uniform
}
return g;
}
bool ON_GetGrevilleAbcissae( // get Greville abcissa from knots
int order, // order (>=2)
int cv_count, // cv count (>=order)
const double* knot, // knot[] array
bool bPeriodic,
double* g // has length cv_count in non-periodic case
// and length cv_count-order+1 in periodic case
)
{
// Grevielle abscissae for a given knot vector
if ( order < 2 || cv_count < order || !knot || !g )
return false;
const int g_count = (bPeriodic) ? cv_count-order+1 : cv_count;
if (order == 2)
{
// g[i] = knot[i] in degree 1 case
for (int i = 0; i < g_count; i++)
g[i] = knot[i];
}
else
{
// g = (knot[i]+...+knot[i+degree-1])/degree
const double t0 = knot[order-2];
if (bPeriodic)
{
for (int i = 0; i < order - 1; ++i)
{
g[i] = ON_GrevilleAbcissa(order, knot + i);
if (g[i] >= t0)
{
knot += ((i > 0 && (t0 - g[i - 1]) < (g[i] - t0)) ? (i - 1) : i);
break;
}
}
}
for ( int i = 0; i < g_count; ++i)
g[i] = ON_GrevilleAbcissa( order, knot+i );
if (bPeriodic && g[0] < t0)
g[0] = t0;
}
return true;
}
bool ON_GetGrevilleKnotVector( // get knots from Greville abcissa
int g_stride,
const double *g, // if not periodic, g[cv_count],
// if periodic, g[cv_count-order+2]
// usually, g[0] = 0, g[i] = |P[i]-P[i-1]|^q
bool bPeriodic,
int order,
int cv_count,
double* knot
)
{
bool rc = false;
double* p = nullptr;
int ki, knot_count, g_count, gi, j, degree;
double k, dd;
if ( g_stride < 1 || !g || !knot || order < 2 || cv_count < order )
return false;
if ( bPeriodic && order == 2 )
return false;
if ( bPeriodic && cv_count - order + 2 < 3 )
return false;
degree = order-1;
if ( degree == 1 ) {
for ( j = 0; j < cv_count; j++ ) {
knot[j] = g[j*g_stride];
}
return true;
}
dd = 1.0/degree;
knot_count = ON_KnotCount( order, cv_count );
g_count = (bPeriodic) ? cv_count-order+2 : cv_count;
if ( bPeriodic ) {
int half_degree = (degree%2) ? degree/2 : 0;
// step 1: set p[] = fully periodic list of abcissa
p = (double*)onmalloc((g_count + 2*degree)*sizeof(*p));
for ( j = 0, gi = g_count-order; j < degree; j++, gi++ ) {
p[j] = g[0] - g[g_count-1] + g[gi];
}
for ( gi = 0, j = degree; gi < g_count; gi++, j++ ) {
p[j] = g[gi];
}
for ( j = g_count+degree, gi = 1; j < g_count+2*degree; j++, gi++ ) {
p[j] = g[g_count-1] - g[0] + g[gi];
}
// step 2: set new p[i] = old (p[i] + ... + p[i+degree-1]) / degree
for ( j = 0; j < g_count+order; j++ ) {
k = p[j];
for ( ki = 1; ki < degree; ki++ )
k += p[j+ki];
k *= dd;
if ( half_degree ) {
// if g[]'s are uniform and degree is odd, then knots = g[]'s
if ( fabs(k-p[j+half_degree]) <= ON_SQRT_EPSILON*(p[j+degree-1]-p[j]) )
k = p[j+half_degree];
}
p[j] = k;
}
// step 3: determine where g[0] maximizes NURBS basis functions
{
double* B = (double*)alloca(order*order*sizeof(B[0]));
double maxB = 0.0;
int maxBj = 0;
for ( j = 0; j < 2*degree; j++ ) {
if ( g[0] > p[j+degree] )
continue;
if ( g[0] < p[j+degree-1] )
break;
ON_EvaluateNurbsBasis( order, p+j, g[0], B );
if ( B[0] > maxB ) {
maxB = B[0];
maxBj = j;
}
}
memcpy( knot, &p[maxBj], knot_count*sizeof(*knot) );
}
rc = ON_MakeKnotVectorPeriodic( order, cv_count, knot );
}
else {
// clamped knots
rc = true;
if ( g > knot && g < knot+knot_count ) {
p = (double*)onmalloc(cv_count*sizeof(*p));
for( j = 0; j < cv_count; j++ ) {
p[j] = g[j*g_stride];
}
g = p;
g_stride = 1;
}
for ( ki = 0; ki < degree; ki++ ) {
knot[ki] = g[0];
}
for ( ki = degree, gi = 1; ki < cv_count; ki++, gi++ ) {
k = 0.0;
for ( j = 0; j < degree; j++ ) {
k += g[(gi+j)*g_stride];
}
knot[ki] = k*dd;
if ( knot[ki] < knot[ki-1] || knot[ki] <= knot[ki-degree] ) {
rc = false;
}
}
for ( ki = cv_count-1; ki < knot_count; ki++ ) {
knot[ki] = g[(cv_count-1)*g_stride];
}
}
if (p)
onfree(p);
return rc;
}
bool ON_ClampKnotVector(
int cv_dim, // dimension of cv's = ( = dim+1 for rational cvs )
int order,
int cv_count,
int cv_stride,
double* cv, // nullptr or cv array with room for at least knot_multiplicity new cvs
double* knot, // knot array with room for at least knot_multiplicity new knots
int end // 0 = clamp start, 1 = clamp end, 2 = clamp both ends
)
{
// sets initial/final order-2 knot values to match knot[order-2]/knot[cv_count-1]
// Adjusts initial/final order many CVs so that the curve location is unchanged.
// Requires that knot[order-2]< knot[order-1] and/or knot[cv_count-2] < knot[cv_count-1]
// 17-June-2020 Improved error reporting.
bool rc = false;
int i, i0;
if (cv && knot && order >= 2 && cv_count >= order && end>=0 && end<=2 ) {
rc = true;
if ( end == 0 || end == 2 ) {
if (ON_EvaluateNurbsDeBoor(cv_dim, order, cv_stride, cv, knot, 1, 0.0, knot[order - 2]))
{
for (i = 0; i < order - 2; i++)
knot[i] = knot[order - 2];
}
else
rc = false;
}
if ( end == 1 || end == 2 ) {
i0 = cv_count-order;
knot += i0;
cv += i0*cv_stride;
if (ON_EvaluateNurbsDeBoor(cv_dim, order, cv_stride, cv, knot, -1, 0.0, knot[order - 1]))
{
i0 = order - 1;
for (i = 2 * order - 3; i > i0; i--)
knot[i] = knot[i0];
}
else
rc = false;
}
}
return rc;
}
static bool ON_InsertSingleKnot( int cv_dim, int order,
int cv_stride,
double *cv, // nullptr or array of length at least order*cv_stride+cv_dim
double *knot, // array of length at least 2*order-1 and existing knot values in
// knot[0], ..., knot[2*order-3]
double knot_value // knot[order-2] <= knot_value < knot[order-1]
// and knot[0] < knot_vale
)
{
double alpha0, alpha1;
double *k0, *k1, *prev_cv;
int i, d, cv_inc, degree;
if ( order < 2 || !knot || knot_value < knot[order-2] || knot[order-1] <= knot_value ) {
ON_ERROR( "ON_InsertSingleKnot() - illegal knot input" );
return false;
}
if ( cv ) {
if ( cv_dim < 1 || cv_stride < cv_dim ) {
ON_ERROR( "ON_InsertSingleKnot() - illegal cv input" );
return false;
}
}
degree = order-1;
// move last degree many knots over one spot
k1 = knot + 2*degree;
k0 = k1-1;
i = degree;
while (i--)
*k1-- = *k0--;
// insert new knot value
*k1 = knot_value;
if ( cv ) {
// move last cv over one spot
memcpy( cv+cv_dim*order, cv+cv_dim*degree, cv_dim*sizeof(*cv) );
// compute new cv values
k0 = knot + degree-1;
k1 = k0 + order;
cv += order*cv_stride;
prev_cv = cv - cv_stride;
cv_inc = cv_stride - cv_dim;
i = degree;
if (knot_value - *k0 <= *k1 - knot_value) {
while (i--) {
alpha1 = (knot_value - *k0)/(*k1 - *k0);
alpha0 = 1.0 - alpha1;
k0--; k1--;
cv -= cv_inc;
prev_cv -= cv_inc;
d = cv_dim;
while (d--) {
--cv;
--prev_cv;
*cv = *cv * alpha1 + *prev_cv * alpha0;
}
}
}
else {
while (i--) {
alpha0 = (*k1 - knot_value)/(*k1 - *k0);
alpha1 = 1.0 - alpha0;
k0--; k1--;
cv -= cv_inc;
prev_cv -= cv_inc;
d = cv_dim;
while (d--) {
--cv;
--prev_cv;
*cv = *cv * alpha1 + *prev_cv * alpha0;
}
}
}
}
return true;
}
int ON_InsertKnot(
double knot_value,
int knot_multiplicity,
int cv_dim, // dimension of cv's = ( = dim+1 for rational cvs )
int order,
int cv_count,
int cv_stride,
double* cv, // nullptr or cv array with room for at least knot_multiplicity new cvs
double* knot, // knot array with room for at least knot_multiplicity new knots
int* hint // optional hint about where to search for span to add knots to
// pass nullptr if no hint is available
)
{
int rc = 0; // return code = number of knots added
if ( order < 2 || cv_count < order || !knot )
{
ON_ERROR("ON_InsertKnot(): illegal input" );
return 0;
}
if ( cv )
{
if ( cv_dim < 1 || cv_stride < cv_dim )
{
ON_ERROR("ON_InsertKnot(): illegal input" );
return 0;
}
}
if ( knot_multiplicity >= order )
{
ON_ERROR("ON_InsertKnot(): requested knot_multiplicity > degree" );
return 0;
}
// shift knot vector and cv array so that knot_value lies in first span
int span_index = ON_NurbsSpanIndex( order, cv_count, knot, knot_value, 1, hint?*hint:0 );
knot += span_index;
if ( cv )
cv += (span_index*cv_stride);
cv_count -= span_index;
const double knot_tolerance = ON_SpanTolerance( order, cv_count, knot, 0 );
// check that knot_value is interior to NURBS domain
if ( span_index == 0 )
{
if ( knot_value < knot[order-1] )
{
if ( knot_value <= knot[order-2] + knot_tolerance )
{
ON_ERROR("ON_InsertKnot(): requested knot_value at start of NURBS domain" );
return 0;
}
}
}
if ( span_index == cv_count-order )
{
if ( knot_value > knot[order-2] && knot_value >= knot[order-1] - knot_tolerance )
{
ON_ERROR("ON_InsertKnot(): requested knot_value at end of NURBS domain" );
return 0;
}
}
// if knot_value is nearly equal to an existing knot, make it exactly equal
if ( knot_value <= 0.5*(knot[order-2]+knot[order-1]) && fabs( knot_value - knot[order-2] ) <= knot_tolerance ) {
knot_value = knot[order-2];
}
else if ( fabs( knot_value - knot[order-1] ) <= knot_tolerance ) {
knot_value = knot[order-1];
}
const int degree = order-1;
// set m = number of knots to add
int m = 0;
int j;
if ( knot_value == knot[order-2] ) {
for ( j = order-2; m < knot_multiplicity && knot[j-m] == knot_value; m++ )
; // empty for
}
else if ( knot_value == knot[order-1] ) {
for ( j = order-1; m < knot_multiplicity && knot[j+m] == knot_value; m++ )
; // empty for
}
m = knot_multiplicity - m;
if ( hint )
*hint = span_index+m;
if ( m <= 0 )
return 0; // no knots need to be added
double* new_knot = (double*)onmalloc( ((2*degree+m) + (order+m)*cv_dim)*sizeof(*new_knot) );
if ( !new_knot ) {
ON_ERROR("ON_InsertKnot(): out of memory");
return 0;
}
double* new_cv = 0;
memcpy( new_knot, knot, 2*degree*sizeof(*new_knot) );
if ( cv ) {
new_cv = new_knot + (2*degree+m);
for ( j = 0; j < order; j++ ) {
memcpy( new_cv + j*cv_dim, cv + j*cv_stride, cv_dim*sizeof(*new_cv) );
}
}
// add m more knots at knot_value
rc = 0;
while (m>0) {
if ( !ON_InsertSingleKnot(cv_dim,order,cv_dim,new_cv,new_knot,knot_value) )
break;
m--;
if ( new_cv )
new_cv += cv_stride;
new_knot++;
rc++;
}
new_knot -= rc;
new_cv -= rc*cv_stride;
if ( rc > 0 )
{
// make room for rc many new knots
int i0 = ON_KnotCount( order, cv_count ) - 1; // knot[i0] = last input knot
int i1 = i0 + rc;
int j_local = (cv_count-order);
while (j_local--)
knot[i1--] = knot[i0--];
// update knot vector
memcpy ( knot+degree, new_knot+degree, (degree+rc)*sizeof(*new_knot) );
if ( cv ) {
// make room for rc many new CVs
i0 = (cv_count-1)*cv_stride; // cv[i0] = last coord of last input cv */
i1 = i0 + rc*cv_stride;
j_local = cv_count-order;
while (j_local--) {
memcpy( cv+i1, cv+i0, cv_dim*sizeof(*cv) );
i1 -= cv_stride;
i0 -= cv_stride;
}
// update cv values
for ( j_local = 0; j_local < order+rc; j_local++ ) {
memcpy( cv, new_cv, cv_dim*sizeof(*new_cv) );
cv += cv_stride;
new_cv += cv_dim;
}
}
}
onfree(new_knot);
return rc;
}