// // 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 . // //////////////////////////////////////////////////////////////// #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 ON_OBJECT_IMPLEMENT(ON_NurbsCurve,ON_Curve,"4ED7D4DD-E947-11d3-BFE5-0010830122F0"); /* Description: Helper to make a deep copy (duplicate memory) from src to dest */ void ON_NurbsCurve::Internal_DeepCopyFrom( const ON_NurbsCurve& src ) { if (this == &src) { ON_ERROR("this and &src must be different."); return; } // erase tags m_knot_capacity_and_tags &= ON_NurbsCurve::masks::knot_capacity; const int knot_capacity = (nullptr != src.m_knot) ? src.KnotCount() : 0; if (knot_capacity > 0) { ReserveKnotCapacity(knot_capacity); if ( nullptr != m_knot ) memcpy( m_knot, src.m_knot, knot_capacity*sizeof(m_knot[0]) ); } else if (nullptr != m_knot && KnotCapacity() > 0) { onfree(m_knot); m_knot = nullptr; m_knot_capacity_and_tags = 0; } int cv_count = (src.m_cv_count>0) ? src.m_cv_count : 0; int cv_stride = (src.m_dim > 0) ? ((src.m_is_rat) ? (src.m_dim + 1) : src.m_dim) : 0; const int cv_capacity = (nullptr != src.m_cv) ? (cv_count*cv_stride) : 0; if (cv_capacity > 0) { ReserveCVCapacity(cv_capacity); if ( nullptr != m_cv ) { // copy cv array const int src_stride = src.m_cv_stride; const double *src_cv = src.m_cv; double *dst_cv = m_cv; if (src_stride == cv_stride) { memcpy(dst_cv, src_cv, cv_capacity * sizeof(dst_cv[0])); } else { const size_t sizeof_cv = (size_t)(cv_stride * sizeof(dst_cv[0])); for (int i = 0; i < cv_count; i++) { memcpy(dst_cv, src_cv, sizeof_cv); dst_cv += cv_stride; src_cv += src_stride; } } } } else { if (nullptr != m_cv && m_cv_capacity > 0) { onfree(m_cv); m_cv = nullptr; } m_cv_capacity = 0; cv_count = 0; cv_stride = 0; } m_dim = src.m_dim; m_is_rat = src.m_is_rat; m_order = src.m_order; m_cv_count = cv_count; m_cv_stride = cv_stride; // copy tags const unsigned int src_tags = (src.m_knot_capacity_and_tags & ON_NurbsCurve::masks::all_tags); m_knot_capacity_and_tags |= src_tags; } /* Description: Helper to move (shallow copy) from src to dest */ #if defined(ON_HAS_RVALUEREF) void ON_NurbsCurve::Internal_ShallowCopyFrom( const ON_NurbsCurve& src ) { if (this == &src) { ON_ERROR("this and &src must be different."); return; } m_dim = src.m_dim; m_is_rat = src.m_is_rat; m_order = src.m_order; m_cv_count = src.m_cv_count; m_knot_capacity_and_tags = src.m_knot_capacity_and_tags; m_knot = src.m_knot; m_cv_stride = src.m_cv_stride; m_cv_capacity = src.m_cv_capacity; m_cv = src.m_cv; } #endif /* Description: Helper to clear (set all member vars to zero) */ void ON_NurbsCurve::Internal_InitializeToZero() { m_dim = 0; m_is_rat = 0; m_order = 0; m_cv_count = 0; m_knot_capacity_and_tags = 0; m_knot = 0; m_cv_stride = 0; m_cv_capacity = 0; m_cv = 0; } /* Description: Helper to make a deep copy (duplicate memory) from src to dest */ void ON_NurbsCurve::Internal_Destroy() { double* cv = (nullptr != m_cv && CVCapacity() > 0 ) ? m_cv : nullptr; double* knot = (nullptr != m_knot && KnotCapacity() > 0 ) ? m_knot : nullptr; Internal_InitializeToZero(); if ( cv ) onfree(cv); if ( knot ) onfree(knot); } ON_NurbsCurve::ON_NurbsCurve() ON_NOEXCEPT { ON__SET__THIS__PTR(m_s_ON_NurbsCurve_ptr); Internal_InitializeToZero(); } ON_NurbsCurve::ON_NurbsCurve( const ON_NurbsCurve& src ) : ON_Curve(src) { ON__SET__THIS__PTR(m_s_ON_NurbsCurve_ptr); Internal_InitializeToZero(); Internal_DeepCopyFrom(src); } ON_NurbsCurve& ON_NurbsCurve::operator=( const ON_NurbsCurve& src ) { if ( this != &src ) { Internal_Destroy(); // consider removing this so "expert" knots and cv can be used. ON_Curve::operator=(src); Internal_DeepCopyFrom(src); } return *this; } #if defined(ON_HAS_RVALUEREF) ON_NurbsCurve::ON_NurbsCurve( ON_NurbsCurve&& src) ON_NOEXCEPT : ON_Curve(std::move(src)) { ON__SET__THIS__PTR(m_s_ON_NurbsCurve_ptr); Internal_ShallowCopyFrom(src); src.Internal_InitializeToZero(); } ON_NurbsCurve& ON_NurbsCurve::operator=( ON_NurbsCurve&& src) { if ( this != &src ) { Internal_Destroy(); ON_Curve::operator=(std::move(src)); Internal_ShallowCopyFrom(src); src.Internal_InitializeToZero(); } return *this; } #endif ON_NurbsCurve* ON_NurbsCurve::New() { return new ON_NurbsCurve(); } ON_NurbsCurve* ON_NurbsCurve::New( const ON_NurbsCurve& nurbs_curve ) { return new ON_NurbsCurve(nurbs_curve); } ON_NurbsCurve* ON_NurbsCurve::New( const ON_BezierCurve& bezier_curve ) { return new ON_NurbsCurve(bezier_curve); } ON_NurbsCurve* ON_NurbsCurve::New( int dimension, bool bIsRational, int order, int cv_count ) { return new ON_NurbsCurve(dimension,bIsRational,order,cv_count); } ON_NurbsCurve::ON_NurbsCurve( int dim, bool bIsRational, int order, int cv_count ) { ON__SET__THIS__PTR(m_s_ON_NurbsCurve_ptr); Initialize(); Create(dim,bIsRational,order,cv_count); } ON_NurbsCurve::ON_NurbsCurve(const ON_BezierCurve& src) { ON__SET__THIS__PTR(m_s_ON_NurbsCurve_ptr); Initialize(); ON_NurbsCurve::operator=(src); } ON_NurbsCurve::~ON_NurbsCurve() { Destroy(); } unsigned int ON_NurbsCurve::SizeOf() const { unsigned int sz = ON_Curve::SizeOf(); sz += (sizeof(*this) - sizeof(ON_Curve)); sz += KnotCapacity()*sizeof(*m_knot); sz += CVCapacity()*sizeof(*m_cv); return sz; } ON__UINT32 ON_NurbsCurve::DataCRC(ON__UINT32 current_remainder) const { current_remainder = ON_CRC32(current_remainder,sizeof(m_dim),&m_dim); current_remainder = ON_CRC32(current_remainder,sizeof(m_is_rat),&m_is_rat); current_remainder = ON_CRC32(current_remainder,sizeof(m_order),&m_order); current_remainder = ON_CRC32(current_remainder,sizeof(m_cv_count),&m_cv_count); if ( m_cv_count > 0 && m_cv_stride > 0 && m_cv ) { size_t sizeof_cv = CVSize()*sizeof(m_cv[0]); const double* cv = m_cv; int i; for ( i = 0; i < m_cv_count; i++ ) { current_remainder = ON_CRC32(current_remainder,sizeof_cv,cv); cv += m_cv_stride; } } current_remainder = ON_CRC32(current_remainder,KnotCount()*sizeof(m_knot[0]),m_knot); return current_remainder; } int ON_NurbsCurve::Dimension() const { return m_dim; } bool ON_NurbsCurve::IsRational() const { return m_is_rat?true:false; } int ON_NurbsCurve::CVSize() const { return (m_dim>0) ? (m_is_rat?(m_dim+1):m_dim) : 0; } int ON_NurbsCurve::Order( void ) const { return m_order; } int ON_NurbsCurve::CVCount( void ) const { return m_cv_count; } int ON_NurbsCurve::KnotCount( void ) const { return ON_KnotCount( m_order, m_cv_count ); } double* ON_NurbsCurve::CV( int i ) const { return (m_cv) ? (m_cv + i*m_cv_stride) : nullptr; } ON::point_style ON_NurbsCurve::CVStyle() const { return m_is_rat ? ON::homogeneous_rational : ON::not_rational; } double ON_NurbsCurve::Weight( int i ) const { return (m_cv && m_is_rat) ? m_cv[i*m_cv_stride+m_dim] : 1.0; } double ON_NurbsCurve::Knot( int i ) const { return (m_knot) ? m_knot[i] : 0.0; } int ON_NurbsCurve::KnotMultiplicity( int i ) const { return ON_KnotMultiplicity(m_order,m_cv_count,m_knot,i); } const double* ON_NurbsCurve::Knot() const { return m_knot; } double ON_NurbsCurve::SuperfluousKnot( int end ) const { return(m_knot) ? ON_SuperfluousKnot(m_order,m_cv_count,m_knot,end) : 0.0; } bool ON_NurbsCurve::MakePeriodicUniformKnotVector( double delta ) { DestroyCurveTree(); ReserveKnotCapacity( ON_KnotCount( m_order, m_cv_count ) ); return ON_MakePeriodicUniformKnotVector( m_order, m_cv_count, m_knot, delta ); } bool ON_NurbsCurve::MakeClampedUniformKnotVector( double delta ) { DestroyCurveTree(); ReserveKnotCapacity( ON_KnotCount( m_order, m_cv_count ) ); return ON_MakeClampedUniformKnotVector( m_order, m_cv_count, m_knot, delta ); } bool ON_NurbsCurve::CreateClampedUniformNurbs( int dimension, int order, int point_count, const ON_3dPoint* point, double knot_delta ) { bool rc = (dimension >= 1 && dimension<=3 && point!=nullptr); if (rc) rc = Create( dimension, false, order, point_count ); if ( rc ) { int i; for (i = 0; i < point_count; i++) SetCV( i, ON::intrinsic_point_style, point[i] ); } if ( rc ) rc = MakeClampedUniformKnotVector( knot_delta ); return rc; } bool ON_NurbsCurve::CreatePeriodicUniformNurbs( int dimension, int order, int point_count, const ON_3dPoint* point, double knot_delta ) { bool rc = (dimension >= 1 && dimension<=3 && point!=nullptr); if (rc) rc = Create( dimension, false, order, point_count+(order-1) ); if ( rc ) { int i; for (i = 0; i < point_count; i++) SetCV( i, ON::intrinsic_point_style, point[i] ); for (i = 0; i <= order-2; i++) SetCV( m_cv_count-m_order+1+i, ON::intrinsic_point_style, CV(i) ); } if ( rc ) rc = MakePeriodicUniformKnotVector( knot_delta ); return rc; } bool ON_NurbsCurve::Create( int dim, // dimension (>= 1) bool is_rat, // true to make a rational NURBS int order, // order (>= 2) int cv_count // cv count (>= order) ) { DestroyCurveTree(); if ( dim < 1 ) return false; if ( order < 2 ) return false; if ( cv_count < order ) return false; m_dim = dim; m_is_rat = (is_rat) ? true : false; m_order = order; m_cv_count = cv_count; m_cv_stride = (m_is_rat) ? m_dim+1 : m_dim; bool rc = ReserveKnotCapacity( KnotCount() ); if ( !ReserveCVCapacity( CVCount()*m_cv_stride ) ) rc = false; return rc; } void ON_NurbsCurve::Destroy() { DestroyCurveTree(); Internal_Destroy(); } void ON_NurbsCurve::EmergencyDestroy() { Internal_InitializeToZero(); } void ON_NurbsCurve::Initialize() { Internal_InitializeToZero(); } ON_NurbsCurve& ON_NurbsCurve::operator=(const ON_BezierCurve& src) { int i; Create(src.m_dim,src.m_is_rat,src.m_order,src.m_order); const int sizeof_cv = src.CVSize()*sizeof(double); for ( i = 0; i < m_cv_count; i++ ) { memcpy( CV(i), src.CV(i), sizeof_cv ); } for ( i = 0; i <= m_order-2; i++ ) m_knot[i] = 0.0; const int knot_count = KnotCount(); for ( i = m_order-1; i < knot_count; i++ ) m_knot[i] = 1.0; return *this; } void ON_NurbsCurve::Dump( ON_TextLog& dump ) const { dump.Print( "ON_NurbsCurve dim = %d is_rat = %d\n" " order = %d cv_count = %d\n", m_dim, m_is_rat, m_order, m_cv_count ); dump.Print( "Knot Vector ( %d knots )\n", KnotCount() ); dump.PrintKnotVector( m_order, m_cv_count, m_knot ); dump.Print( "Control Points %d %s points\n" " index value\n", m_cv_count, (m_is_rat) ? "rational" : "non-rational" ); if ( !m_cv ) { dump.Print(" nullptr cv array\n"); } else { dump.PrintPointList( m_dim, m_is_rat, m_cv_count, m_cv_stride, m_cv, " CV" ); } } static bool ON_NurbsCurveIsNotValid() { return ON_IsNotValid(); // <-- good place for a breakpoint } static bool ON_ControlPointsAreNotValid() { return ON_IsNotValid(); // <-- good place for a breakpoint } static bool ON_ControlPointsAreValid( int cv_size, int cv_count, int cv_stride, const double* cv, ON_TextLog* text_log ) { int i, j; if ( 0 == cv ) { if ( text_log ) { text_log->Print("cv pointer is null.\n"); } return ON_ControlPointsAreNotValid(); } if ( cv_count < 2 ) { if ( text_log ) { text_log->Print("cv_count = %d (must be >= 2).\n",cv_count); } return ON_ControlPointsAreNotValid(); } if ( cv_size < 1 ) { if ( text_log ) { text_log->Print("cv_size = %d (must be >= 1).\n",cv_size); } return ON_ControlPointsAreNotValid(); } if ( cv_stride < cv_size ) { if ( text_log ) { text_log->Print("cv_stride = %d and cv_size = %d (cv_stride must be >= cv_size).\n",cv_stride,cv_size); } return ON_ControlPointsAreNotValid(); } for ( i = 0; i < cv_count; i++, cv += cv_stride ) { for ( j = 0; j < cv_size; j++ ) { if ( !ON_IsValid(cv[j]) ) { if ( text_log ) { text_log->Print("cv[%d*cv_stride + %d] = %g is not valid.\n",i,j,cv[j]); } return ON_ControlPointsAreNotValid(); } } } return true; } bool ON_NurbsCurve::IsValid( ON_TextLog* text_log ) const { if ( m_dim <= 0 ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_dim = %d (should be > 0).\n",m_dim); } return ON_NurbsCurveIsNotValid(); } if (m_order < 2 ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_order = %d (should be >= 2).\n",m_order); } return ON_NurbsCurveIsNotValid(); } if (m_cv_count < m_order ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_cv_count = %d (should be >= m_order=%d).\n",m_cv_count,m_order); } return ON_NurbsCurveIsNotValid(); } if (m_cv_stride < CVSize() ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_cv_stride = %d (should be >= %d).\n",m_cv_stride,CVSize()); } } if (m_cv == nullptr) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_cv is nullptr.\n"); } return ON_NurbsCurveIsNotValid(); } if (m_knot == nullptr) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_knot is nullptr.\n"); } return ON_NurbsCurveIsNotValid(); } if ( !ON_IsValidKnotVector( m_order, m_cv_count, m_knot, text_log ) ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_knot[] is not a valid knot vector.\n"); } return ON_NurbsCurveIsNotValid(); } if ( !ON_ControlPointsAreValid(CVSize(),m_cv_count,m_cv_stride,m_cv,text_log) ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_cv[] is not valid.\n"); } return ON_NurbsCurveIsNotValid(); } if ( m_is_rat ) { // weights at fully multiple knots must be nonzero // partial test for weight function being zero double sign = 0.0; const double* w = &m_cv[m_dim]; int zcount = 0; int i; for ( i = 0; i < m_cv_count; i++, w += m_cv_stride ) { if ( *w == 0.0 ) zcount++; else zcount = 0; if ( zcount >= m_order ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_cv has zero weights for CV[%d],...,CV[%d].\n",i-m_order+1,i); } return ON_NurbsCurveIsNotValid(); // denominator is zero for entire span } if ( m_knot[i] == m_knot[i+m_order-2] ) { if ( *w == 0.0 ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_cv has zero weights for CV[%d].\n",i); } return ON_NurbsCurveIsNotValid(); } if (sign == 0.0) { sign = (*w > 0.0) ? 1.0 : -1.0; } else if ( *w * sign <= 0.0 ) { if ( text_log ) { text_log->Print("ON_NurbsCurve.m_cv has a zero denominator in the parameter interval [%g,%g].\n", m_knot[i-1],m_knot[i]); } return ON_NurbsCurveIsNotValid(); } } } if ( m_dim <= 3 && 2 == m_order && 2 == m_cv_count ) { // fix for RR 21239 // 16 November 2010 Chuck and Dale Lear added m_dim <= 3 // so the 3d checking does not interfer with applications // that use high dimension curves where the start and end // points can be arbitrary. ON_3dPoint P0 = PointAtStart(); ON_3dPoint P1 = PointAtEnd(); if ( P0 == P1 ) { if ( text_log ) { text_log->Print("ON_NurbsCurve is a line with no length.\n"); } return ON_NurbsCurveIsNotValid(); } } } return true; } bool ON_NurbsCurve::GetBBox( // returns true if successful double* boxmin, // minimum double* boxmax, // maximum bool bGrowBox // true means grow box ) const { return ON_GetPointListBoundingBox( m_dim, m_is_rat, m_cv_count, m_cv_stride, m_cv, boxmin, boxmax, bGrowBox?true:false ); } bool ON_NurbsCurve::Transform( const ON_Xform& xform ) { if ( !this->ON_Curve::Transform(xform) ) return false; if ( 0 == m_is_rat ) { if ( xform.m_xform[3][0] != 0.0 || xform.m_xform[3][1] != 0.0 || xform.m_xform[3][2] != 0.0 ) { MakeRational(); } } return ON_TransformPointList( m_dim, m_is_rat, m_cv_count, m_cv_stride, m_cv, xform ); } bool ON_NurbsCurve::IsDeformable() const { return true; } bool ON_NurbsCurve::MakeDeformable() { return true; } bool ON_PolylineCurve::IsDeformable() const { return true; } bool ON_PolylineCurve::MakeDeformable() { return true; } bool ON_NurbsCurve::Write( ON_BinaryArchive& file // open binary file ) const { // NOTE - check legacy I/O code if changed const int minor_version = (file.Archive3dmVersion() >= 60); bool rc = file.Write3dmChunkVersion(1,minor_version); if (rc) { if (rc) rc = file.WriteInt( m_dim ); if (rc) rc = file.WriteInt( m_is_rat ); if (rc) rc = file.WriteInt( m_order ); if (rc) rc = file.WriteInt( m_cv_count ); if (rc) rc = file.WriteInt( 0 ); // reserved - legacy flag values if (rc) rc = file.WriteInt( 0 ); // reserved // write invalid bounding box - may be used in future if (rc) { ON_BoundingBox bbox; rc = file.WriteBoundingBox(bbox); } // write knots int count = (0 != m_knot && m_cv_count >= m_order && m_order >= 2) ? KnotCount() : 0; if (rc) rc = file.WriteInt(count); if (rc) rc = file.WriteDouble( count, m_knot ); // write cvs const int cv_size = CVSize(); count = ( m_cv && cv_size > 0 && m_cv_count > 0 && m_cv_stride >= cv_size ) ? m_cv_count : 0; if (rc) rc = file.WriteInt(count); if (rc && count > 0 ) { int i; for ( i = 0; i < m_cv_count && rc; i++ ) { rc = file.WriteDouble( cv_size, CV(i) ); } } if (rc && minor_version >= 1) { // April 2019 Chunk version 1.1 - added SubDFriendlyTag const bool bSubDFriendlyTag = SubDFriendlyTag(); rc = file.WriteBool(bSubDFriendlyTag); } } return rc; } bool ON_NurbsCurve::Read( ON_BinaryArchive& file // open binary file ) { // NOTE - check legacy I/O code if changed Destroy(); int major_version = 0; int minor_version = 0; bool rc = file.Read3dmChunkVersion(&major_version,&minor_version); if (rc && major_version==1) { // common to all 1.x versions int dim = 0, is_rat = 0, order = 0, cv_count = 0; int reserved1 = 0, reserved2 = 0; if (rc) rc = file.ReadInt( &dim ); if (rc) rc = file.ReadInt( &is_rat ); if (rc) rc = file.ReadInt( &order ); if ( order < 0 ) rc = false; if (rc) rc = file.ReadInt( &cv_count ); if ( cv_count < order ) rc = false; if (rc) rc = file.ReadInt( &reserved1 ); // reserved - legacy flag values if (rc) rc = file.ReadInt( &reserved2 ); // reserved // bounding box if (rc) { // write invalid bounding box - may be used in future ON_BoundingBox bbox; rc = file.ReadBoundingBox( bbox ); } if ( !Create( dim, is_rat, order, cv_count ) ) rc = false; // read knots int count = 0; if (rc) rc = file.ReadInt(&count); if ( count < 0 ) rc = false; // fix crash bug 92841 else if ( 0 != count && count != ON_KnotCount(order,cv_count) ) rc = false; if (rc ) rc = ReserveKnotCapacity(count); if (rc) rc = file.ReadDouble( count, m_knot ); // read cvs count = 0; if (rc) rc = file.ReadInt(&count); const int cv_size = CVSize(); if (rc) rc = ReserveCVCapacity( count*cv_size ); if (count > 0 && cv_size > 0 && rc ) { int i; for ( i = 0; i < m_cv_count && rc; i++ ) { rc = file.ReadDouble( cv_size, CV(i) ); } } if (rc && minor_version >= 1) { // April 2019 Chunk version 1.1 - added SubDFriendlyTag bool bSubDFriendlyTag = false; rc = file.ReadBool(&bSubDFriendlyTag); if (bSubDFriendlyTag) SetSubDFriendlyTag(bSubDFriendlyTag); } } if ( !rc ) Destroy(); return rc; } ON_Interval ON_NurbsCurve::Domain() const { ON_Interval d; if ( !ON_GetKnotVectorDomain( m_order, m_cv_count, m_knot, &d.m_t[0], &d.m_t[1] ) ) d = ON_Interval::EmptyInterval; return d; } bool ON_NurbsCurve::SetDomain( double t0, double t1 ) { bool rc = false; if ( m_order >= 2 && m_cv_count >= m_order && m_knot && t0 < t1 ) { //DestroyCurveTree(); const double k0 = m_knot[m_order-2]; const double k1 = m_knot[m_cv_count-1]; if ( k0 == t0 && k1 == t1 ) rc = true; else if ( k0 < k1 ) { DestroyCurveTree(); const double d = (t1-t0)/(k1-k0); const double km = 0.5*(k0+k1); const int knot_count = KnotCount(); int i; for ( i = 0; i < knot_count; i++ ) { if ( m_knot[i] <= km ) { m_knot[i] = (m_knot[i] - k0)*d + t0; } else { m_knot[i] = (m_knot[i] - k1)*d + t1; } } rc = true; } } return rc; } static ON_NurbsCurve* MoveSeamPeriodicKnot(const ON_NurbsCurve& crv, int knot_index) { int dg = crv.Degree(); if (dg < 2) return 0; if (!crv.IsPeriodic()) return 0; if (knot_index < dg || knot_index >= crv.KnotCount()-dg) return 0; int sc = crv.SpanCount(); if (sc < crv.KnotCount()-2*dg+1)//Not all knots are simple return 0; const double* knots = &crv.m_knot[dg-1]; int distinct_cvc = crv.CVCount()-dg; double dom_len = crv.Domain().Length(); ON_NurbsCurve* pNC = ON_NurbsCurve::New(crv); int curr = dg-1; for (int i=knot_index; iSetKnot(curr, pNC->Knot(i)); curr++; } for (int i=0; i<=knot_index-dg+1; i++){ pNC->SetKnot(curr, knots[i] + dom_len); curr++; } for (int i=0; iSetKnot(curr+i, pNC->Knot(curr+i-1)+pNC->Knot(dg+i) - pNC->Knot(dg+i-1)); pNC->SetKnot(dg-2-i, pNC->Knot(dg-i-1) - pNC->Knot(curr-1-i) + pNC->Knot(curr-2-i)); } int cv_id = knot_index-dg+1; for (int i=0; iCVCount(); i++){ double* pcv = crv.CV(cv_id%distinct_cvc); pNC->SetCV(i, ON::intrinsic_point_style, pcv); cv_id++; } return pNC; } static ON_NurbsCurve* MoveSeamPeriodic(const ON_Curve& crv, double t) { ON_NurbsCurve nc; double s = t; const ON_NurbsCurve*pNC = ON_NurbsCurve::Cast(&crv); if (pNC) nc = *pNC; else { if (!crv.GetNurbFormParameterFromCurveParameter(t, &s)) return 0; if (!crv.GetNurbForm(nc)) return 0; } if (!nc.IsPeriodic()) return 0; //Make sure all interior knots are simple int sc = nc.SpanCount(); if (sc < nc.KnotCount()-2*nc.Degree()+1)//Not all knots are simple return 0; int knot_index = -1; for (int i=0; i s){ knot_index = i; break; } } if (knot_index < nc.Degree() || knot_index > nc.KnotCount()-nc.Degree()) return 0; double k0 = nc.Knot(knot_index-1); double d0 = s-k0; double k1 = nc.Knot(knot_index); double d1 = k1-s; bool bInsert = true; if (d0<=d1){ if (d0 < ON_ZERO_TOLERANCE*fabs(k0)){ knot_index--; bInsert = false; } } else { if (d1 < ON_ZERO_TOLERANCE*fabs(k1)) bInsert = false; } if (bInsert && !nc.InsertKnot(s, 1)) return 0; return MoveSeamPeriodicKnot(nc, knot_index); } bool ON_NurbsCurve::ChangeClosedCurveSeam( double t ) { bool rc = IsClosed(); if ( rc ) { //bool bIsPeriodic = IsPeriodic(); const ON_Interval old_dom = Domain(); double k = t; double s = old_dom.NormalizedParameterAt(t); if ( s < 0.0 || s > 1.0 ) { s = fmod( s, 1.0 ); if ( s < 0.0 ) s += 1.0; k = old_dom.ParameterAt(s); } s = old_dom.NormalizedParameterAt( k); if ( old_dom.Includes(k,true) ) { ON_NurbsCurve left, right; // if periodic - dont' put fully multiple knot in middle. bool bGotIt = false; if ( IsPeriodic() ){//This only works if the curve has only simple knots ON_NurbsCurve* pMovedNC = MoveSeamPeriodic(*this, t); if (pMovedNC){ *this = *pMovedNC; delete pMovedNC; bGotIt = true; } } if (!bGotIt) { ON_Curve* pleft = &left; ON_Curve* pright = &right; rc = Split( k, pleft, pright ); if ( rc ) { //int a = left.IsValid(); //int b = right.IsValid(); right.Append(left); *this = right; } } } else { // k already at start/end of domain rc = true; } if (rc) SetDomain( t, t+old_dom.Length() ); } return rc; } int ON_NurbsCurve::SpanCount() const { // number of smooth spans in curve return ON_KnotVectorSpanCount( m_order, m_cv_count, m_knot ); } bool ON_NurbsCurve::GetSpanVector( // span "knots" double* s // array of length SpanCount() + 1 ) const { return ON_GetKnotVectorSpanVector( m_order, m_cv_count, m_knot, s ); } int ON_NurbsCurve::Degree() const { return m_order >= 2 ? m_order-1 : 0; } // true if curve locus is a line segment // tolerance to use when checking linearity bool ON_NurbsCurve::IsLinear( double tolerance ) const { // 9 March 2009 Dale Lear // Speed up IsLinear() test to accelerate loading curves into Rhino. // if ( !IsClamped() ) return false; // IsClamped returning true insures that 2 <= order <= cv_count // and that the knot vector is clamped. ON_Line L; ON_3dPoint P, Q; ON_3dVector V0, V1, D; double t0, t, d, s; int i; // When m_cv is a null pointer, the GetCV() fails. if ( !GetCV(0,L.from) ) return false; if ( !GetCV(m_cv_count-1,L.to) ) return false; // if CV coordinates are NaNs, infinities or ON_UNSET_VALUE, // then "t" will end failing the ON_IsValid() test. D.x = L.to.x - L.from.x; D.y = L.to.y - L.from.y; D.z = L.to.z - L.from.z; t = D.x*D.x + D.y*D.y + D.z*D.z; if ( !ON_IsValid(t) || !(t > 0.0) ) return false; if ( 2 == m_cv_count ) return true; t0 = 0.0; bool bDefaultTolerance = false; if ( !(tolerance > 0.0) ) { bDefaultTolerance = true; tolerance = ON_ZERO_TOLERANCE; } const double tol2 = tolerance*tolerance; t = 1.0/t; D.x *= t; D.y *= t; D.z *= t; for ( i = 1; i < m_cv_count-1; i++ ) { // This call to GetCV will return true because earlier calls to // GetCV(0,...) and GetCV(m_cv_count-1,...) worked GetCV(i,P); // Set t = parameter of point on line L closest to P V0.x = P.x - L.from.x; V0.y = P.y - L.from.y; V0.z = P.z - L.from.z; V1.x = P.x - L.to.x; V1.y = P.y - L.to.y; V1.z = P.z - L.to.z; if ( V0.x*V0.x + V0.y*V0.y + V0.z*V0.z <= V1.x*V1.x + V1.y*V1.y + V1.z*V1.z ) { // P is closest to L.from t = V0.x*D.x + V0.y*D.y + V0.z*D.z; if ( t < -0.01 ) return false; } else { // P is closest to L.to t = 1.0 + V1.x*D.x + V1.y*D.y + V1.z*D.z; if ( t > 1.01 ) return false; } // set Q = L.PointAt(t) s = 1.0-t; Q.x = s*L.from.x + t*L.to.x; Q.y = s*L.from.y + t*L.to.y; Q.z = s*L.from.z + t*L.to.z; if ( bDefaultTolerance ) { if ( !ON_PointsAreCoincident(3,0,&P.x,&Q.x) ) return false; } else { // verify that the distance from P to Q is <= tolerance s = P.x-Q.x; d = tol2 - s*s; if ( d < 0.0 ) return false; s = P.y-Q.y; d -= s*s; if ( d < 0.0 ) return false; s = P.z-Q.z; d -= s*s; if ( d < 0.0 ) return false; } if ( t > t0 && t0 < 1.0 ) { t0 = (t < 1.0) ? t : 1.0; } if ( !(t >= t0 && t <= 1.0) ) { // 14 Dec 2004 // Chuck and Dale Lear added this test to weed out // garbage curves whose locus is a line but that self intersect. // For example, circles that have been projected onto a plane // perpendicular to their axis are garbage and are not "linear". // either a (nearly) stacked control point or // the curve has reversed direction P = L.PointAt(t0); d = Q.DistanceTo(P); if ( !(d <= tolerance) ) return false; // curve has reversed direction ("garbage") } } return true; } int ON_NurbsCurve::IsPolyline( ON_SimpleArray* pline_points, ON_SimpleArray* pline_t ) const { int i; int rc = 0; if ( pline_points ) pline_points->SetCount(0); if ( pline_t ) pline_t->SetCount(0); if ( IsValid() ) { if ( 2 == m_order ) { rc = m_cv_count; if ( pline_points ) { pline_points->Reserve(m_cv_count); for ( i = 0; i < m_cv_count; i++ ) { GetCV(i,pline_points->AppendNew()); } } if ( pline_t ) { pline_t->Reserve(m_cv_count); for ( i = 0; i < m_cv_count; i++ ) pline_t->Append(m_knot[i]); } } else if ( m_order > 2 && m_dim >= 2 && m_dim <= 3 ) { // 16 April 2003 Dale Lear: // Added to so the high degree polylines get // flagged as polylines. const double *k = m_knot; double t; int i_local, j, span_count = m_cv_count-m_order+1; ON_Line line; ON_3dPoint P, Q; GetCV(0,line.to); for ( i_local = 0; i_local < span_count; i_local++, k++ ) { if ( k[m_order-2] < k[m_order-1] ) { if ( k[0] != k[m_order-2] ) { // not a bezier span return 0; } if ( k[m_order-1] != k[2*m_order-3] ) { // not a bezier span return 0; } // see if this bezier segment is a line // with (roughly) linear parameterization. line.from = line.to; GetCV(i_local+m_order-1,line.to); for ( j = 1; j < m_order-1; j++ ) { GetCV(i_local+j,P); t = 0.0; if ( !line.ClosestPointTo(P,&t) ) return 0; if ( fabs( t*(m_order-1) - j ) > 0.01 ) { // not linearly parameterized. // Please check with Dale Lear before changing // this test. return 0; } Q = line.PointAt(t); // Please check with Dale Lear before changing // this test. // October 2012 Dale Lear // 0.00001 is way too large. For example in bug // http://dev.mcneel.com/bugtrack/?q=115183 it turns // arcs into polylines. I'm changing it to // ON_RELATIVE_TOLERANCE because that makes sense. //const double rel_tol = 0.00001; const double rel_tol = ON_RELATIVE_TOLERANCE; if ( fabs(P.x - Q.x) > (fabs(P.x) + fabs(Q.x))*rel_tol + ON_ZERO_TOLERANCE ) return 0; if ( fabs(P.y - Q.y) > (fabs(P.y) + fabs(Q.y))*rel_tol + ON_ZERO_TOLERANCE ) return 0; if ( fabs(P.z - Q.z) > (fabs(P.z) + fabs(Q.z))*rel_tol + ON_ZERO_TOLERANCE ) return 0; } rc++; } } if (rc > 0 ) { rc++; // it's a polyline if ( 0 != pline_points || 0 != pline_t ) { GetCV(0,P); if ( 0 != pline_points ) { pline_points->Reserve(rc); GetCV(0,pline_points->AppendNew()); } if ( 0 != pline_t ) { pline_t->Reserve(rc); pline_t->Append( m_knot[m_order-2] ); } const double *k_local = m_knot; for ( i_local = 0; i_local < span_count; i_local++, k_local++ ) { if ( k_local[m_order-2] < k_local[m_order-1] ) { // see if this bezier segment is a line // with (roughly) linear parameterization. if ( 0 != pline_points ) GetCV(i_local+m_order-1,pline_points->AppendNew()); if ( 0 != pline_t ) pline_t->Append(k_local[m_order-1]); } } } } } if( IsClosed() && rc >= 4 && 0 != pline_points ) { // GBA 2/26/03 // Make polyline spot on closed. *pline_points->Last() = *pline_points->First(); } } return rc; } bool ON_NurbsCurve::IsArc( const ON_Plane* plane, ON_Arc* arc, double tolerance ) const { // If changes are made, verify that RR 8497 still works. // On 30 April 2003, Mikko changed the strict test to a sloppy // test to fix RR 8497. This change broke other things like // IGES simple entity export tests that require a strict test. // So on 6 May 2003 Dale Lear added in the "bLooseTest" check. // If the tolerance > ON_ZERO_TOLERANCE a fuzzy fit to arc // arc test is performed. If tolerance <= ON_ZERO_TOLERANCE, // a stricter arc test is performed. const bool bLooseTest = (tolerance > ON_ZERO_TOLERANCE); const int knotcount = KnotCount(); const int degree = m_order-1; if ( (2 == m_dim || 3 == m_dim) && m_cv_count >= m_order && degree >= 2 && 0 != m_knot && 0 != m_cv && (bLooseTest || 0 != m_is_rat) ) { if ( bLooseTest || 0 == (knotcount % degree) ) { if ( !bLooseTest ) { for ( int i = 0; i < m_cv_count; i += degree ) { if ( m_knot[i] != m_knot[i+degree-1] ) return false; } } // 24 July 2012 Dale Lear // You can't be a line and an arc. This test prevents // creating arcs with small angles and large radii and // prevents sloppy tolerances from classifying a span // as an arc when it makes no sense. if ( IsLinear(tolerance) ) return false; if ( ON_Curve::IsArc( plane, arc, tolerance ) ) return true; } } return false; } bool ON_NurbsCurve::IsPlanar( ON_Plane* plane, // if not nullptr and true is returned, then plane parameters // are filled in double tolerance // tolerance to use when checking linearity ) const { if ( m_dim == 2 ) { return ON_Curve::IsPlanar(plane,tolerance); } bool rc = false; ON_3dPoint P; ON_3dVector X; EvTangent( Domain()[0], P, X ); if ( IsLinear(tolerance) ) { if ( plane ) { ON_Line line( P, PointAtEnd() ); if ( !line.InPlane( *plane, tolerance) ) line.InPlane( *plane, 0.0 ); } rc = true; } else if ( m_cv_count >= 3 ) { // set P, Q, R to corners of biggest triangle // with corners at control points. ON_Plane test_plane; ON_3dPoint A, B, Q, R; Q = P; R = P; double d, maxd = 0.0; int i, j, k; k = m_cv_count/64; // to keep sample time reasonable on giant NURBS if ( k < 1 ) k = 1; for ( i = 1; i < m_cv_count; i += k ) { GetCV(i,A); for ( j = i+k; j < m_cv_count; j += k ) { GetCV(j,B); d = ON_CrossProduct( A-P, B-P ).Length(); if ( d > maxd ) { maxd = d; Q = A; R = B; } } } if ( test_plane.CreateFromPoints(P,Q,R) ) { ON_2dVector v( X*test_plane.xaxis, X*test_plane.yaxis ); if ( v.Unitize() ) { // Rotate test_plane's x,y axes so its x axis is parallel // to the curve's initial tangent. if ( fabs(v.y) <= ON_SQRT_EPSILON ) { v.x = (v.x >= 0.0) ? 1.0 : -1.0; v.y = 0.0; } else if ( fabs(v.x) <= ON_SQRT_EPSILON ) { v.y = (v.y >= 0.0) ? 1.0 : -1.0; v.x = 0.0; } X = test_plane.xaxis; ON_3dVector Y = test_plane.yaxis; test_plane.xaxis = v.x*X + v.y*Y; test_plane.yaxis = v.x*Y - v.y*X; } rc = IsInPlane( test_plane, tolerance ); if ( rc && plane ) *plane = test_plane; // RH-75060 GBA 9-June-23 // For a planar simple closed curve we should return the plane // whose orientation that matches the curve orientation. if (rc && plane && IsClosed()) { if (ON_ClosedCurveOrientation(*this, *plane) < 0) plane->Flip(); } } } return rc; } bool ON_NurbsCurve::IsInPlane( const ON_Plane& plane, // plane to test double tolerance // tolerance to use when checking linearity ) const { bool rc = IsValid(); ON_3dPoint P; int i; for ( i = 0; rc && i < m_cv_count; i++ ) { GetCV(i,P); if ( fabs( plane.DistanceTo(P)) > tolerance) rc = false; } return rc; } bool ON_NurbsCurve::GetParameterTolerance( // returns tminus < tplus: parameters tminus <= s <= tplus double t, // t = parameter in domain double* tminus, // tminus double* tplus // tplus ) const { bool rc = false; ON_Interval d = Domain(); if ( d.IsIncreasing() ) { const double* knot = Knot(); const int order = Order(); const int cv_count = CVCount(); if ( t < knot[order-1] ) d.m_t[1] = knot[order-1]; else if ( t > knot[cv_count-2] ) d.m_t[0] = knot[cv_count-2]; rc = ON_GetParameterTolerance( d.m_t[0], d.m_t[1], t, tminus, tplus ); } return rc; } bool ON_NurbsCurve::Evaluate( // returns false if unable to evaluate double t, // evaluation parameter int der_count, // number of derivatives (>=0) int v_stride, // v[] array stride (>=Dimension()) double* v, // v[] array of length stride*(ndir+1) int side, // optional - determines which side to evaluate from // 0 = default // < 0 to evaluate from below, // > 0 to evaluate from above int* hint // optional - evaluation hint (int) used to speed // repeated evaluations ) const { bool rc = false; if( m_order<2) // GBA added 01-12-06 to fix crash bug return false; int span_index = ON_NurbsSpanIndex(m_order,m_cv_count,m_knot,t,side,(hint)?*hint:0); if ( -2 == side || 2 == side ) { // 9 November 2010 Dale Lear - ON_TuneupEvaluationParameter fix // When evluation passes through ON_CurveProxy or ON_PolyCurve reparamterization // and the original side parameter was -1 or +1, it is changed to -2 or +2 // to indicate that if t is numerically closed to an end paramter, then // it should be tuned up to be at the end paramter. double a = t; if ( ON_TuneupEvaluationParameter( side, m_knot[span_index+m_order-2], m_knot[span_index+m_order-1], &a) ) { // recalculate span_index t = a; span_index = ON_NurbsSpanIndex(m_order,m_cv_count,m_knot,t,side,span_index); } } rc = ON_EvaluateNurbsSpan( m_dim, m_is_rat, m_order, m_knot + span_index, m_cv_stride, m_cv + (m_cv_stride*span_index), der_count, t, v_stride, v ); if ( hint ) *hint = span_index; return rc; } bool ON_NurbsCurve::IsClosed() const { bool bIsClosed = false; if ( m_dim > 0 && m_cv_count >= 4 ) { if ( IsPeriodic() ) { bIsClosed = true; } else { bIsClosed = ON_Curve::IsClosed(); } } return bIsClosed; } bool ON_NurbsCurve::IsPeriodic() const { bool bIsPeriodic = ON_IsKnotVectorPeriodic( m_order, m_cv_count, m_knot ); if ( bIsPeriodic ) { int i0 = m_order-2; int i1 = m_cv_count-1; const double* cv0 = m_cv + i0*m_cv_stride; const double* cv1 = m_cv + i1*m_cv_stride; for ( /*empty*/; i0 >= 0; i0--, i1-- ) { if ( false == ON_PointsAreCoincident( m_dim, m_is_rat, cv0, cv1 ) ) return false; cv0 -= m_cv_stride; cv1 -= m_cv_stride; } } return bIsPeriodic; } bool ON_IsCurvatureDiscontinuity( const ON_3dVector Km, const ON_3dVector Kp, double cos_angle_tolerance, double curvature_tolerance, double zero_curvature, double radius_tolerance ) { // This function is provided so old code will link. // New code should use the version with an explicit relative_tolerance return ON_IsCurvatureDiscontinuity( Km, Kp, cos_angle_tolerance, curvature_tolerance, zero_curvature, radius_tolerance, 0.001 // relative_tolerance - used to be hard coded in this version of the function ); } bool ON_IsG2CurvatureContinuous( const ON_3dVector Km, const ON_3dVector Kp, double cos_angle_tolerance, double curvature_tolerance ) { // 6 November 2010 Dale Lear // I made up these values used for cos_Kangle_tolerance // and rel_tol today. They may need adjusting as we // get test results. double rel_tol = ON_RELATIVE_CURVATURE_TOLERANCE; // disables using curvature_tolerance to flag unequal scalars double cos_Kangle_tolerance = cos_angle_tolerance; if ( cos_Kangle_tolerance > ON_DEFAULT_ANGLE_TOLERANCE_COSINE ) cos_Kangle_tolerance = ON_DEFAULT_ANGLE_TOLERANCE_COSINE; if ( cos_Kangle_tolerance > 0.95 ) { // double the tangent angle if ( cos_angle_tolerance < 0.0 ) { cos_Kangle_tolerance = -1.0; } else { cos_Kangle_tolerance = 2.0*cos_Kangle_tolerance*cos_Kangle_tolerance - 1.0; // = cos(2*tangent_angle_tolerace) if ( cos_angle_tolerance >= 0.0 && cos_Kangle_tolerance < 0.0 ) cos_Kangle_tolerance = 0.0; } } return !ON_IsCurvatureDiscontinuity(Km,Kp, cos_Kangle_tolerance, curvature_tolerance, ON_ZERO_CURVATURE_TOLERANCE, ON_UNSET_VALUE, // no absolute delta radius tolerance rel_tol ); } bool ON_IsGsmoothCurvatureContinuous( const ON_3dVector Km, const ON_3dVector Kp, double cos_angle_tolerance, double curvature_tolerance ) { double relative_tolerance = 1.0; // disables using relative_tolerance to flag unequal scalars // point of inflection angle >= 90 degrees double cos_Kangle_tolerance = 0.0; // Since cos_Kangle_tolerance and rel_tol are valid settings, // curvature_tolerance is used only to test for equality. return !ON_IsCurvatureDiscontinuity(Km,Kp, cos_Kangle_tolerance, curvature_tolerance, ON_ZERO_CURVATURE_TOLERANCE, ON_UNSET_VALUE, // no absolute delta radius tolerance relative_tolerance ); } bool ON_IsCurvatureDiscontinuity( const ON_3dVector Km, const ON_3dVector Kp, double cos_angle_tolerance, double curvature_tolerance, double zero_curvature, double radius_tolerance, double relative_tolerance ) { const double d = (Km-Kp).Length(); if ( !ON_IsValid(d) ) { // invalid curvatures - call it discontinuous because // there is a good chance the derivative evaluation // failed or the first derivative is zero. return true; } // The d <= 0.0 test handles the case when // curvature_tolerance is ON_UNSET_VALUE if ( d <= 0.0 || d <= curvature_tolerance ) return false; // "equal" curvature vectors // If the scalar curvature is <= zero_curvature, // then K is small enough that we assume it is zero. // The primary reason for doing this is to prevent // reporting a curvature discontinuity when one // or both of the curvatures is small1.0e-300 and 1.0e-200 if ( !(zero_curvature > 7.7037197787136e-34) ) zero_curvature = 7.7037197787136e-34; double km = Km.Length(); double kp = Kp.Length(); // If km or kp are NaNs, I treat them as zero so // the rest of this code can assume they are 0.0 // or positive. if ( !(km > zero_curvature) ) km = 0.0; if ( !(kp > zero_curvature) ) { kp = 0.0; if ( 0.0 == km ) { // both curvatures are "zero" return false; } } if ( !(km > 0.0 && kp > 0.0) ) { // one side is flat and the other is curved. return true; } // Nov 1, 2012 Dale Lear // This test was wrong. Read the description of the curvature_tolerance // parameter in the header file. //////if ( 0.0 == curvature_tolerance ) //////{ ////// // User is asking for exact match. ////// return true; //////} // At this point we know km > 0 and kp > 0. bool bPointOfInflection = (curvature_tolerance > 0.0); bool bDifferentScalars = bPointOfInflection; // NOTE: // At this point either curvature_tolerance was not // specified or |Km - Kp| > curvature_tolerance. Because // it is difficult to come up with a meaningful value for // curvature_tolerance that works at all scales, the // additional tests below are used to determine if // Km and Kp are different enough to matter. If additional // tests are performed, then bTestCurvatureTolerance // is set to false. If no additional tests are performed, // then bTestCurvatureTolerance will remain true and // the |Km-Kp| > curvature_tolerance test is performed // at the end of this function. if ( cos_angle_tolerance >= -1.0 && cos_angle_tolerance <= 1.0 ) { const double KmoKp = Kp*Km; if ( KmoKp < km*kp*cos_angle_tolerance ) return true; // Km and Kp are not parallel bPointOfInflection = false; } // At this point we assume Km and Kp are parallel and we // know km > 0 and kp > 0. if ( radius_tolerance >= 0.0 ) { // This test is if (fabs(rm - rp) > radius_tolerance) return true // where rm = 1.0/km and rp = 1.0/kp. It is coded the way // it is to avoid divides (not that that really matters any more). if ( fabs(km-kp) > kp*km*radius_tolerance ) // radii do not agree return true; bDifferentScalars = false; } if ( relative_tolerance > 0.0 ) { if ( fabs(km-kp) > ((km>kp)?km:kp)*relative_tolerance ) { return true; } bDifferentScalars = false; } if ( bPointOfInflection || bDifferentScalars ) { // |Km - Kp| > curvature_tolerance and we were not asked to // perform and other tests. return true; } return false; // treat Km and Kp as equal curvatures. } bool ON_NurbsCurve::SetWeight( int i, double w ) { DestroyCurveTree(); bool rc = false; if (0 == m_is_rat && w > 0.0 && w < ON_UNSET_POSITIVE_VALUE) { MakeRational(); } if ( m_is_rat ) { double* cv = CV(i); if (cv) { cv[m_dim] = w; rc = true; } } else if ( w == 1.0 ) { rc = true; } return rc; } bool ON_NurbsCurve::SetCV( int i, ON::point_style style, const double* Point ) { bool rc = true; int k; double w; // feeble but fast check for properly initialized class if ( !m_cv || i < 0 || i >= m_cv_count ) return false; double* cv = m_cv + i*m_cv_stride; switch ( style ) { case ON::not_rational: // input Point is not rational memcpy( cv, Point, m_dim*sizeof(*cv) ); if ( IsRational() ) { // NURBS curve is rational - set weight to one cv[m_dim] = 1.0; } break; case ON::homogeneous_rational: // input Point is homogeneous rational if ( IsRational() ) { // NURBS curve is rational memcpy( cv, Point, (m_dim+1)*sizeof(*cv) ); } else { // NURBS curve is not rational w = (Point[m_dim] != 0.0) ? 1.0/Point[m_dim] : 1.0; for ( k = 0; k < m_dim; k++ ) { cv[k] = w*Point[k]; } } break; case ON::euclidean_rational: // input Point is euclidean rational if ( IsRational() ) { // NURBS curve is rational - convert euclean point to homogeneous form w = Point[m_dim]; for ( k = 0; k < m_dim; k++ ) cv[k] = w*Point[k]; // 22 April 2003 - bug fix [i] to [k] cv[m_dim] = w; } else { // NURBS curve is not rational memcpy( cv, Point, m_dim*sizeof(*cv) ); } break; case ON::intrinsic_point_style: memcpy( cv, Point, CVSize()*sizeof(*cv) ); break; default: rc = false; break; } DestroyCurveTree(); return rc; } bool ON_NurbsCurve::SetCV( int i, const ON_3dPoint& point ) { bool rc = false; double* cv = CV(i); if ( cv ) { cv[0] = point.x; if ( m_dim > 1 ) { cv[1] = point.y; if ( m_dim > 2 ) cv[2] = point.z; if ( m_dim > 3 ) { memset( &cv[3], 0, (m_dim-3)*sizeof(*cv) ); } } if ( m_is_rat ) { cv[m_dim] = 1.0; } rc = true; } DestroyCurveTree(); return rc; } bool ON_NurbsCurve::SetCV( int i, const ON_4dPoint& point ) { bool rc = false; double* cv = CV(i); if ( cv ) { if ( m_is_rat ) { cv[0] = point.x; if ( m_dim > 1 ) { cv[1] = point.y; if ( m_dim > 2 ) cv[2] = point.z; if ( m_dim > 3 ) { memset( &cv[3], 0, (m_dim-3)*sizeof(*cv) ); } } cv[m_dim] = point.w; rc = true; } else { double w; if ( point.w != 0.0 ) { w = 1.0/point.w; rc = true; } else { w = 1.0; } cv[0] = w*point.x; if ( m_dim > 1 ) { cv[1] = w*point.y; if ( m_dim > 2 ) { cv[2] = w*point.z; } if ( m_dim > 3 ) { memset( &cv[3], 0, (m_dim-3)*sizeof(*cv) ); } } } } DestroyCurveTree(); return rc; } bool ON_NurbsCurve::GetCV( int i, ON::point_style style, double* Point ) const { const double* cv = CV(i); if ( !cv ) return false; int dim = Dimension(); double w = ( IsRational() ) ? cv[dim] : 1.0; switch(style) { case ON::euclidean_rational: Point[dim] = w; // no break here case ON::not_rational: if ( w == 0.0 ) return false; w = 1.0/w; while(dim--) *Point++ = *cv++ * w; break; case ON::homogeneous_rational: Point[dim] = w; memcpy( Point, cv, dim*sizeof(*Point) ); break; case ON::intrinsic_point_style: memcpy( Point, cv, CVSize()*sizeof(*Point) ); break; default: return false; } return true; } const ON_4dPoint ON_NurbsCurve::ControlPoint( int cv_index ) const { ON_4dPoint cv; if (!GetCV(cv_index, cv)) cv = ON_4dPoint::Nan; return cv; } bool ON_NurbsCurve::GetCV( int i, ON_3dPoint& point ) const { bool rc = false; const double* cv = CV(i); if ( cv ) { if ( m_is_rat ) { if (cv[m_dim] != 0.0) { const double w = 1.0/cv[m_dim]; point.x = cv[0]*w; point.y = (m_dim>1)? cv[1]*w : 0.0; point.z = (m_dim>2)? cv[2]*w : 0.0; rc = true; } } else { point.x = cv[0]; point.y = (m_dim>1)? cv[1] : 0.0; point.z = (m_dim>2)? cv[2] : 0.0; rc = true; } } return rc; } bool ON_NurbsCurve::GetCV( int i, ON_4dPoint& point ) const { bool rc = false; if (m_dim > 0 && i >= 0 && i < m_cv_count) { const double* cv = CV(i); if (cv) { point.x = cv[0]; point.y = (m_dim > 1) ? cv[1] : 0.0; point.z = (m_dim > 2) ? cv[2] : 0.0; point.w = (m_is_rat) ? cv[m_dim] : 1.0; rc = true; } } return rc; } bool ON_NurbsCurve::SetKnot( int knot_index, double k ) { if ( knot_index < 0 || knot_index >= KnotCount() ) return false; m_knot[knot_index] = k; DestroyCurveTree(); return true; } bool ON_NurbsCurve::SetStartPoint( ON_3dPoint start_point ) { bool rc = false; if ( IsValid() ) { if (ON_Curve::SetStartPoint(start_point)) { rc = true; } else { ClampEnd(2); double w = 1.0; if (IsRational()) { w = Weight(0); start_point *= w; } SetCV(0,start_point); if (IsRational()) SetWeight(0, w); DestroyCurveTree(); rc = true; } } return rc; } bool ON_NurbsCurve::SetEndPoint( ON_3dPoint end_point ) { bool rc = false; if ( IsValid() ) { if (ON_Curve::SetEndPoint(end_point)) { rc = true; } else { ClampEnd(2); double w = 1.0; if (IsRational()) { w = Weight(m_cv_count-1); end_point *= w; } SetCV(m_cv_count-1,end_point); if (IsRational()) SetWeight(m_cv_count-1, w); DestroyCurveTree(); rc = true; } } return rc; } bool ON_NurbsCurve::Reverse() { bool rc0 = ON_ReverseKnotVector( m_order, m_cv_count, m_knot ); bool rc1 = ON_ReversePointList( m_dim, m_is_rat, m_cv_count, m_cv_stride, m_cv ); DestroyCurveTree(); return rc0 && rc1; } bool ON_NurbsCurve::SwapCoordinates( int i, int j ) { DestroyCurveTree(); return ON_SwapPointListCoordinates( m_cv_count, m_cv_stride, m_cv, i, j ); } double ON_NurbsCurve::GrevilleAbcissa( int gindex // index (0 <= index < CVCount(dir) ) const { return ON_GrevilleAbcissa( m_order, m_knot+gindex ); } bool ON_NurbsCurve::GetGrevilleAbcissae( // see ON_GetGrevilleAbcissa() for details double* g // g[m_cv_count] ) const { // The "false" for the 4th parameter is on purpose and should not be // replaced with this->IsPeriodic(). The problem // being that when the 4th parameter is true, it is not possible // to determine which subset of the full list of Greville abcissae // was returned. return ON_GetGrevilleAbcissae( m_order, m_cv_count, m_knot, false, g ); } bool ON_NurbsCurve::ZeroCVs() { bool rc = false; int i; if ( m_cv ) { if ( CVCapacity() > 0 ) { memset( m_cv, 0, CVCapacity()*sizeof(*m_cv) ); if ( m_is_rat ) { for ( i = 0; i < m_cv_count; i++ ) { SetWeight( i, 1.0 ); } } rc = true; } else { double* cv; int s = CVSize()*sizeof(*cv); for ( i = 0; i < m_cv_count; i++ ) { cv = CV(i); memset(cv,0,s); if ( m_is_rat ) cv[m_dim] = 1.0; } rc = (i>0) ? true : false; } } DestroyCurveTree(); return rc; } bool ON_NurbsCurve::IsClamped( // determine if knot vector is clamped int end // (default= 2) end to check: 0 = start, 1 = end, 2 = start and end ) const { return ON_IsKnotVectorClamped( m_order, m_cv_count, m_knot, end ); } bool ON_NurbsCurve::IsNatural(int end) const { bool rc = false; ON_3dPoint CV0, CV2, P; ON_3dVector D1, D2; const ON_Interval domain(Domain()); for (int pass = ((0==end||2==end) ? 0 : 1); pass < ((1==end||2==end)?2 : 1); ++pass) { ON_BezierCurve span; if (false == ConvertSpanToBezier(((0 == pass) ? 0 : (m_cv_count - m_order)), span) || span.m_order < 2) return false; int i0 = (0 == pass) ? 0 : span.m_order - 1; int di = (0 == pass) ? 1 : -1; if (span.m_order > 2) di *= 2; if (false == span.GetCV(i0, CV0)) return false; if (false == span.GetCV(i0 + di, CV2)) return false; Ev2Der(domain[pass], P, D1, D2, (0 == pass) ? 1 : -1); const double d2 = D2.Length(); const double tol = CV0.DistanceTo(CV2)*1.0e-8; if (false == (d2 <= tol)) return false; rc = true; } return rc; } int ON_NurbsCurve::CVCapacity() const { // If m_cv_capacity > 0, then m_cv[] was allocated by onmalloc(sz)/onrealloc(...,sz) // where sz = m_cv_capacity*sizeof(double). return m_cv_capacity; // number of doubles in m_cv } bool ON_NurbsCurve::ReserveCVCapacity(int desired_capacity) { if (nullptr != m_cv && 0 == m_cv_capacity) { // If m_cv_capacity == 0 and m_cv != nullptr, then the "expert" user // has hand built the ON_NurbsCurve.m_cv array and is responsible // for making sure it's always big enough. return true; } const int current_capacity = (nullptr != m_cv && m_cv_capacity > 0) ? m_cv_capacity : 0; if ( desired_capacity > current_capacity ) { m_cv = (0 == current_capacity) ? (double*)onmalloc(desired_capacity*sizeof(*m_cv)) : (double*)onrealloc(m_cv,desired_capacity*sizeof(*m_cv)); m_cv_capacity = ( nullptr != m_cv ) ? desired_capacity : 0; } const bool rc = (m_cv_capacity >= desired_capacity); return rc; } int ON_NurbsCurve::KnotCapacity() const { const int knot_capacity = (nullptr == m_knot) ? 0 : ((int)(m_knot_capacity_and_tags & ON_NurbsCurve::masks::knot_capacity)); return knot_capacity; } void ON_NurbsCurve::UnmanageKnotForExperts( int& knot_capacity, double*& knot ) { knot_capacity = KnotCapacity(); knot = m_knot; m_knot_capacity_and_tags &= ON_NurbsCurve::masks::all_tags; // knot_capacity = 0; m_knot = nullptr; } void ON_NurbsCurve::ManageKnotForExperts( int knot_capacity, double* knot ) { // Unconditionally set m_knot and KnotCapacity(). // (Do not free preexisting m_knot.) const unsigned int tags = (m_knot_capacity_and_tags & ON_NurbsCurve::masks::all_tags); const unsigned int new_knot_capacity = (knot_capacity > 0) ? (((unsigned int)knot_capacity) & ON_NurbsCurve::masks::knot_capacity) : 0U; m_knot_capacity_and_tags = (tags | new_knot_capacity); m_knot = knot; } bool ON_NurbsCurve::ReserveKnotCapacity(int desired_capacity) { const int current_knot_capacity = KnotCapacity(); if (nullptr != m_knot && 0 == current_knot_capacity) { // If knot_capacity == 0 and m_knot != nullptr, then the "expert" user // has hand built the ON_NurbsCurve.m_knot array and is responsible // for making sure it's always big enough. return true; } if ( desired_capacity > current_knot_capacity ) { double* knot = ( 0 == current_knot_capacity ) ? (double*)onmalloc(desired_capacity*sizeof(knot[0])) : (double*)onrealloc(m_knot,desired_capacity*sizeof(knot[0])); this->ManageKnotForExperts((nullptr != knot) ? desired_capacity : 0, knot); } bool rc = (nullptr != m_knot && KnotCapacity() >= desired_capacity); return rc; } bool ON_NurbsCurve::ConvertSpanToBezier( int span_index, ON_BezierCurve& bez ) const { bool rc = false; if ( span_index >= 0 && span_index <= m_cv_count-m_order && m_knot && m_cv ) { const int cvdim = CVSize(); const int sizeof_cv = cvdim*sizeof(*bez.m_cv); int i; rc = bez.ReserveCVCapacity( cvdim*m_order ); if ( rc ) { bez.m_dim = m_dim; bez.m_is_rat = m_is_rat; bez.m_order = m_order; bez.m_cv_stride = cvdim; if ( bez.m_cv_stride == m_cv_stride ) { memcpy( bez.m_cv, CV(span_index), bez.m_order*sizeof_cv ); } else { for ( i = 0; i < m_order; i++ ) { memcpy( bez.CV(i), CV(span_index+i), sizeof_cv ); } } const double* knot = m_knot + span_index; if( knot[m_order-2] < knot[m_order-1] ) ON_ConvertNurbSpanToBezier( cvdim, bez.m_order, bez.m_cv_stride, bez.m_cv, knot, knot[m_order-2], knot[m_order-1] ); else rc = false; } } return rc; } bool ON_NurbsCurve::HasBezierSpans() const { return ON_KnotVectorHasBezierSpans( m_order, m_cv_count, m_knot ); } bool ON_NurbsCurve::MakePiecewiseBezier( bool bSetEndWeightsToOne ) { bool rc = HasBezierSpans(); if ( !rc && IsValid() ) { ON_Workspace ws; DestroyRuntimeCache(); if ( !ClampEnd(2) ) return false; int span_count = SpanCount(); //int knot_count = KnotCount(); ReserveKnotCapacity( (span_count + 1)*(m_order-1) ); ReserveCVCapacity( m_cv_stride*(span_count*(m_order-1) + 1) ); double* t = ws.GetDoubleMemory( span_count+1); GetSpanVector( t ); int cvdim = CVSize(); ON_BezierCurve* bez = new ON_BezierCurve[span_count]; int ki, spani, i; for ( ki = m_order-2, spani = 0; ki < m_cv_count-1 && spani < span_count; ki++ ) { if ( m_knot[ki] < m_knot[ki+1] ) { bez[spani].Create(m_dim,m_is_rat,m_order); for ( i = 0; i < m_order; i++ ) bez[spani].SetCV( i, ON::intrinsic_point_style, CV( i + ki - m_order + 2 ) ); ON_ConvertNurbSpanToBezier( cvdim, bez[spani].m_order, bez[spani].m_cv_stride, bez[spani].m_cv, m_knot+ki-m_order+2, m_knot[ki], m_knot[ki+1] ); spani++; } } m_cv_count = span_count*(m_order-1) + 1; for ( spani = 0; spani < span_count; spani++ ) { for ( i = 0; i < m_order; i++ ) { SetCV( spani*(m_order-1) + i, ON::intrinsic_point_style, bez[spani].CV( i ) ); } for ( ki = 0; ki < m_order-1; ki++ ) m_knot[ki+spani*(m_order-1)] = t[spani]; } for ( ki = 0; ki < m_order-1; ki++ ) m_knot[ki+span_count*(m_order-1)] = t[spani]; delete[] bez; rc = true; } if ( rc && bSetEndWeightsToOne && m_is_rat ) { // 2 June 2003 Dale Lear - added support for bSetEndWeightsToOne=true option. double w0, w1; ON_BezierCurve bez; bez.m_dim = m_dim; bez.m_is_rat = m_is_rat; bez.m_order = m_order; bez.m_cv_stride = m_cv_stride; bez.m_cv = CV(0); if ( bez.Weight(0) != 1.0 ) { DestroyRuntimeCache(); w0 = 1.0; w1 = (m_order == m_cv_count) ? 1.0 : bez.Weight(m_order-1); bez.ChangeWeights(0,w0,m_order-1,w1); } bez.m_cv = CV(m_cv_count-m_order); if ( bez.Weight(m_order-1) != 1.0 ) { DestroyRuntimeCache(); w0 = bez.Weight(0); w1 = 1.0; // 23 June 2003 bez.ChangeWeights(0,w0,m_order-1,w1); } bez.m_cv = 0; } return rc; } double ON_NurbsCurve::ControlPolygonLength() const { double length = 0.0; ON_GetPolylineLength( m_dim, m_is_rat, m_cv_count, m_cv_stride, m_cv, &length ); return length; } bool ON_NurbsCurve::InsertKnot( double knot_value, int knot_multiplicity ) { bool rc = false; const int degree = Degree(); double t0, t1; { ON_Interval d = Domain(); if ( !d.IsIncreasing() ) return false; t0 = d[0]; t1 = d[1]; } if ( knot_multiplicity < 1 || knot_multiplicity > degree ) { ON_ERROR("ON_NurbsCurve::ON_InsertKnot(): knot_multiplicity < 1 or knot_multiplicity > degree."); return false; } if( knot_value < t0 || knot_value > t1 ) { ON_ERROR("ON_InsertKnot(): knot_value not in NURBS curve domain."); return false; } if ( knot_value == t0 ) { if ( knot_multiplicity == degree ) { rc = ClampEnd(0); } else if ( knot_multiplicity == 1 ) { rc = true; } else { ON_ERROR("ON_InsertKnot(): knot_value = t0 and 1 < knot_multiplicity < degree."); rc = false; } return rc; } if ( knot_value == t1 ) { if ( knot_multiplicity == degree ) { rc = ClampEnd(1); } else if ( knot_multiplicity == 1 ) { rc = true; } else { ON_ERROR("ON_InsertKnot(): knot_value = t1 and 1 < knot_multiplicity < degree."); rc = false; } return rc; } DestroyCurveTree(); bool bIsPeriodic = (degree>1) ? IsPeriodic() : false; int span_index = ON_NurbsSpanIndex( m_order, m_cv_count, m_knot, knot_value, 0, 0 ); // reserve room for new knots and cvs if ( !ReserveCVCapacity( m_cv_stride*(m_cv_count+knot_multiplicity) ) ) return false; if ( !ReserveKnotCapacity( KnotCount()+knot_multiplicity ) ) return false; if ( bIsPeriodic ) { } rc = true; int span_hint = span_index; int new_knot_count = ON_InsertKnot( knot_value, knot_multiplicity, CVSize(), m_order, m_cv_count, m_cv_stride, m_cv, m_knot, &span_hint ); if ( new_knot_count > 0 ) { m_cv_count += new_knot_count; } if ( bIsPeriodic && rc && !IsPeriodic() ) { // restore periodic form if ( ON_MakeKnotVectorPeriodic( m_order, m_cv_count, m_knot ) ) { int i0, i1; for ( i0 = 0, i1 = m_cv_count-degree; i0 < degree; i0++, i1++ ) { //14 May 2015 - Chuck - Fix for curves with low span count. See RH-30358 //if ( span_index < degree-1) if (i0 > span_index) SetCV( i1, ON::intrinsic_point_style, CV(i0) ); // cv[i1] = cv[i0] else SetCV( i0, ON::intrinsic_point_style, CV(i1) ); // cv[i0] = cv[i1] } } else { ClampEnd(2); } } return rc; } bool ON_NurbsCurve::MakeRational() { if ( !IsRational() ) { const int dim = Dimension(); const int cv_count = CVCount(); if ( cv_count > 0 && m_cv_stride >= dim && dim > 0 ) { const int new_stride = (m_cv_stride == dim) ? dim+1 : m_cv_stride; ReserveCVCapacity( cv_count*new_stride ); const double* old_cv; double* new_cv; int cvi, j; for ( cvi = cv_count-1; cvi>=0; cvi-- ) { old_cv = CV(cvi); new_cv = m_cv+(cvi*new_stride); for ( j = dim-1; j >= 0; j-- ) { new_cv[j] = old_cv[j]; } new_cv[dim] = 1.0; } m_cv_stride = new_stride; m_is_rat = 1; } } return IsRational(); } bool ON_NurbsCurve::MakeNonRational() { if ( IsRational() ) { const int dim = Dimension(); const int cv_count = CVCount(); if ( cv_count > 0 && m_cv_stride >= dim+1 && dim > 0 ) { double w; const double* old_cv; double* new_cv = m_cv; int cvi, j; for ( cvi = 0; cvi < cv_count; cvi++ ) { old_cv = CV(cvi); w = old_cv[dim]; w = ( w != 0.0 ) ? 1.0/w : 1.0; for ( j = 0; j < dim; j++ ) { *new_cv++ = w*(*old_cv++); } } m_is_rat = 0; m_cv_stride = dim; } } DestroyCurveTree(); return ( !IsRational() ) ? true : false; } static bool GetRaisedDegreeCV(int old_order, int cvdim, int old_cvstride, const double* oldCV, //old_cvstride*old_order const double* oldkn, //2*old_degree const double* newkn, //2*old_order int cv_id, //0 <= cv_id <= old_order double* newCV //cvdim ) { int i,j,k; if (!oldCV || !oldkn || !newkn || !newCV || cv_id < 0 || cv_id > old_order) return false; int old_degree = old_order-1; int new_degree = old_degree+1; double* t = (double*)onmalloc(old_degree*sizeof(double)); if (!t) return false; double* P = (double*)onmalloc(cvdim*sizeof(double)); if (!P) { onfree((void*)t); return false; } memset(newCV, 0, cvdim*sizeof(double)); const double* kn = newkn + cv_id; for (i=0; i 0 ) { const int new_cv_capacity = cv_stride1*m_cv_count; m_cv = (double*)onrealloc( m_cv, new_cv_capacity*sizeof(*m_cv) ); if (nullptr != m_cv) m_cv_capacity = new_cv_capacity; } for ( i = CVCount()-1; i >= 0; i-- ) { cv0 = CV(i); cv1 = m_cv + (i*cv_stride1); if ( m_is_rat ) cv1[desired_dimension] = cv0[m_dim]; for ( j = desired_dimension-1; j >= m_dim; j-- ) { cv1[j] = 0.0; } for ( j = m_dim-1; j >= 0; j-- ) { cv1[j] = cv0[j]; } } m_dim = desired_dimension; m_cv_stride = cv_stride1; rc = true; } return rc; } bool ON_NurbsCurve::Append( const ON_NurbsCurve& c ) { bool rc = false; if ( CVCount() == 0 ) { *this = c; return IsValid()?true:false; } if ( c.IsRational() && !IsRational() ) { if ( !MakeRational() ) return false; } if ( c.Degree() > Degree() ) { if ( !IncreaseDegree( c.Degree() ) ) return false; } if ( c.Dimension() > Dimension() ) { if ( !ChangeDimension( c.Dimension() ) ) return false; } if ( (IsRational() && !c.IsRational()) || c.Degree() < Degree() || !c.IsClamped(0) || c.Dimension() < Dimension() ) { ON_NurbsCurve tmp(c); if ( !tmp.IncreaseDegree( Degree() ) ) return false; if ( !tmp.ChangeDimension( Dimension() ) ) return false; if ( IsRational() ) { if ( !tmp.MakeRational() ) return false; } if ( !tmp.ClampEnd(0) ) return false; // make sure we don't reenter this scope if ( tmp.IsRational() != IsRational() ) return false; if ( tmp.Degree() != Degree() ) return false; if ( tmp.Dimension() != Dimension() ) return false; if ( !tmp.IsClamped(0) ) return false; return Append(tmp); } if ( IsValid() && c.IsValid() && Degree() == c.Degree() && IsRational() == c.IsRational() && Dimension() == c.Dimension() ) { if ( !ClampEnd(1) ) return false; const double w0 = c.Weight(0); const double w1 = Weight(CVCount()-1); double w = 1.0; if ( IsRational() && w0 != w1 ) { w = w1/w0; } ReserveCVCapacity( (CVCount()+c.CVCount())*m_cv_stride ); ReserveKnotCapacity( ON_KnotCount(Order(),CVCount()+c.CVCount()) ); const double dk = Knot(CVCount()-1) - c.Knot(c.Order()-2); //const int c_cv_count = c.CVCount(); const int c_knot_count = c.KnotCount(); int i0, i1, i2, j; // i0 and cv index into m_knot and m_cv respectively i0 = KnotCount(); double* cv = CV(CVCount() - 1); const int cv_dim = CVSize(); const int sizeof_cv = cv_dim * sizeof(*cv); // i1 and i2 index c's knots and cvs respectively i2 = 1; for ( i1 = c.Order()-1; i1 < c_knot_count; i1++, i2++ ) { double knot = c.Knot(i1) + dk; // GBA 20-April-2017 Check for span collapse if (knot > m_knot[i0 - Order() + 1]) { m_knot[i0++] = knot; cv += m_cv_stride; m_cv_count++; } memcpy(cv, c.CV(i2), sizeof_cv); if (w != 1.0) { for (j = 0; j < cv_dim; j++) cv[j] *= w; } } rc = true; } return rc; } static bool TweakSplitTrimParameter(double k0, double k1, double& t ) { // [k0,k1] = knots that bracket trim/split parameter t // returns true if parameter is tweaked // The purpose of the tweak is to avoid creating knot // vectors that cause numerical problems during evaluation. bool rc = false; if (k0 < t && t < k1) { // Nov 7, 2008 Lowell Walmsley // Changed this from ON_SQRT_EPSILON to 4*ON_SQRT_EPSILON because // to make it less likely to get very narrow knot spans // Dale says the intention is to round off to 8 sig figs // // 2014-July-7 Dale Lear // Fix RH-21610 (k0 = 5e6, k1 = k0 + 1, t = k1 - 0.2) // I changed the test Lowell referes to above. Lowell's 4.0*ON_SQRT_EPSILON // is now 8.0*ON_EPSILON in the ktol2 initialization value. // I added ktol1 base on the span length. // If you modify this code in the future, please reference a bug with sample // files so any new "fixes" don't break something we've already fixed. const double ktol1 = (k1 - k0)*ON_SQRT_EPSILON; const double ktol2 = (fabs(k0) + fabs(k1))*8.0*ON_EPSILON; const double ktol = (ktol1 > ktol2) ? ktol1 : ktol2; if (t - k0 <= ktol && k1 - t > 16.0*ktol) { t = k0; rc = true; } else if (k1 - t <= ktol && t - k0 > 16.0*ktol) { t = k1; rc = true; } } return rc; } bool ON_NurbsCurve::Trim( const ON_Interval& in ) { if ( !in.IsIncreasing() ) return false; const int cv_dim = CVSize(); const int order = Order(); double t, split_t; int ki, side, i0, i1, i1_max, new_cv_count; //Greg Arden 28 April 2003. Do not change any curve that is trimmed to its entire domain. // This is especiallly important for periodic curves. if(in==Domain()) return true; DestroyCurveTree(); // cut off right end (or extend if in.m_t[1] > Domain.Max() side = -1; t = in.m_t[1]; // trimming parameter ki = ON_NurbsSpanIndex( order, m_cv_count, m_knot, t, side, 0 ); // if t is very close to a knot value, then trim at the knot split_t = t; if ( TweakSplitTrimParameter(m_knot[ki+order-2], m_knot[ki+order-1], split_t ) ) ki = ON_NurbsSpanIndex( order, m_cv_count, m_knot, split_t, side, ki ); if ( !ON_EvaluateNurbsDeBoor( cv_dim, order, m_cv_stride, CV(ki), m_knot + ki, side, 0.0, t ) ) { ON_ERROR("ON_NurbsCurve::Trim() - right end de Boor algorithm failed."); return false; } // clamp right end knots m_cv_count = ki + order; for ( i0 = ON_KnotCount( order, m_cv_count)-1; i0 >= m_cv_count-1; i0--) m_knot[i0] = t; // cut off left end (or extend if in.m_t[0] < Domain.Max() side = 1; t = in.m_t[0]; // trimming parameter ki = ON_NurbsSpanIndex( order, m_cv_count, m_knot, t, side, 0 ); // if t is very close to a knot value, then trim at the knot split_t = t; if (TweakSplitTrimParameter(m_knot[ki + order - 2], m_knot[ki + order - 1], split_t)) ki = ON_NurbsSpanIndex( order, m_cv_count, m_knot, split_t, side, ki ); if ( !ON_EvaluateNurbsDeBoor( cv_dim, order, m_cv_stride, CV(ki), m_knot + ki, side, 0.0, t ) ) { ON_ERROR("ON_NurbsCurve::Trim() - right end de Boor algorithm failed."); return false; } // remove surplus cvs and knots new_cv_count = m_cv_count - ki; if ( new_cv_count < m_cv_count ) { // move cvs and knots over i1_max = m_cv_stride*m_cv_count; for ( i0 = 0, i1 = ki*m_cv_stride; i1 < i1_max; i0++, i1++ ) m_cv[i0] = m_cv[i1]; i1_max = ON_KnotCount( order, m_cv_count ); for ( i0 = 0, i1 = ki; i1 < i1_max; i0++, i1++ ) m_knot[i0] = m_knot[i1]; m_cv_count = new_cv_count; } // clamp left end knots for (i0 = 0; i0 <= order-2; i0++) m_knot[i0] = t; ClampEnd(2); // 26 June 2003 Dale Lear DestroyCurveTree(); return true; } bool ON_NurbsCurve::Extend( const ON_Interval& domain ) { if (IsClosed()) return false; bool is_rat = IsRational() ? true : false; int dim = Dimension(); int cvdim = dim+is_rat; bool changed = false; if (domain[0] < Domain()[0]){ ClampEnd(0); ON_EvaluateNurbsDeBoor(cvdim,Order(),m_cv_stride, CV(0),m_knot,1,0.0,domain[0]); for (int i = 0; i < Order()-1; i++) m_knot[i] = domain[0]; changed = true; } if (domain[1] > Domain()[1]){ ClampEnd(1); int i = CVCount() - Order(); ON_EvaluateNurbsDeBoor(cvdim,Order(),m_cv_stride, CV(i),m_knot + i,-1,0.0,domain[1]); for (i = KnotCount()-1; i >= CVCount()-1; i--) m_knot[i] = domain[1]; changed = true; } if (changed){ DestroyCurveTree(); } return changed; } bool ON_NurbsCurve::Split( double t, // t = curve parameter to split curve at ON_Curve*& left_crv, // left portion returned here (must be an ON_NurbsCurve) ON_Curve*& right_crv // right portion returned here (must be an ON_NurbsCurve) ) const { int i; bool rc = false; if ( left_crv && !ON_NurbsCurve::Cast(left_crv) ) return false; if ( right_crv && !ON_NurbsCurve::Cast(right_crv) ) return false; if ( IsValid() && t > m_knot[m_order-2] && t < m_knot[m_cv_count-1] ) { ON_NurbsCurve* left = (ON_NurbsCurve*)left_crv; ON_NurbsCurve* right = (ON_NurbsCurve*)right_crv; if ( !left ) left = new ON_NurbsCurve(); else if ( left == right ) return false; if ( !right ) right = new ON_NurbsCurve(); left->DestroyCurveTree(); right->DestroyCurveTree(); int span_index = ON_NurbsSpanIndex(m_order,m_cv_count,m_knot,t,1,0); // if t is very close to a knot value, then split at the knot double split_t = t; if (TweakSplitTrimParameter(m_knot[span_index + m_order - 2], m_knot[span_index + m_order - 1], split_t)) { if ( split_t <= m_knot[m_order-2] || split_t >= m_knot[m_cv_count-1] ){ // 22 October - greg added bailout code but didn't clean up??? // chuck fixed leak. if (!left_crv) delete left; if (!right_crv) delete right; return false; } span_index = ON_NurbsSpanIndex(m_order,m_cv_count,m_knot,split_t,1,span_index); } if ( span_index >= 0 && span_index <= m_cv_count - m_order ) { const int cvdim = CVSize(); //23 April 2015 - Chuck - maintain stride const int cv_stride = m_cv_stride; //const int sizeof_cv = cvdim*sizeof(double); const int sizeof_cv = cv_stride*sizeof(double); int left_cv_count = m_order + span_index; if ( span_index > 0 && split_t == m_knot[span_index+m_order-2] ) { // splitting exactly at a knot value int k; for ( k = 0; left_cv_count >= m_order && k <= span_index+m_order-2; k++ ) { if ( split_t == m_knot[span_index+m_order-2-k] ) left_cv_count--; else break; } } int right_cv_count = m_cv_count - span_index; if ( left_cv_count < m_order || right_cv_count < m_order ){ // 22 October - greg added bailout code but didn't clean up??? // chuck fixed leak. if (!left_crv) delete left; if (!right_crv) delete right; return false; } if ( left != this ) { left->m_dim = m_dim; left->m_is_rat = m_is_rat; left->m_order = m_order; left->m_cv_count = left_cv_count; //23 April 2015 - Chuck - maintain stride //left->m_cv_stride = cvdim; left->m_cv_stride = cv_stride; } if ( right != this ) { right->m_dim = m_dim; right->m_is_rat = m_is_rat; right->m_order = m_order; right->m_cv_count = right_cv_count; //23 April 2015 - Chuck - maintain stride //right->m_cv_stride = cvdim; right->m_cv_stride = cv_stride; } // fill in left allowing for possibility that left = this if ( left->m_cv != m_cv ) { //23 April 2015 - Chuck - maintain stride //left->ReserveCVCapacity(cvdim*left_cv_count); left->ReserveCVCapacity(cv_stride*left_cv_count); for( i = 0; i < left_cv_count; i++ ) { //23 April 2015 - Chuck - maintain stride //memcpy( left->m_cv + i*cvdim, CV(i), sizeof_cv ); memcpy( left->m_cv + i*cv_stride, CV(i), sizeof_cv ); } } if ( left->m_knot != m_knot ) { i = ON_KnotCount( m_order, left_cv_count); left->ReserveKnotCapacity( i ); memcpy( left->m_knot, m_knot, i*sizeof(left->m_knot[0]) ); } // fill in right allowing for possibility that right = this if ( right->m_cv != m_cv || span_index > 0 ) { //23 April 2015 - Chuck - maintain stride //right->ReserveCVCapacity(cvdim*right_cv_count); right->ReserveCVCapacity(cv_stride*right_cv_count); for( i = 0; i < right_cv_count; i++ ) { //23 April 2015 - Chuck - maintain stride //memmove( right->m_cv + i*cvdim, CV(i+span_index), sizeof_cv ); memmove( right->m_cv + i*cv_stride, CV(i+span_index), sizeof_cv ); } } if ( right->m_knot != m_knot || span_index > 0 ) { i = ON_KnotCount(m_order, right_cv_count); right->ReserveKnotCapacity( i ); memmove( right->m_knot, m_knot + span_index, i*sizeof(right->m_knot[0]) ); } if ( right == this ) { right->m_cv_count = right_cv_count; //23 April 2015 - Chuck - maintain stride right->m_cv_stride = cv_stride; } if ( left == this ) { left->m_cv_count = left_cv_count; //23 April 2015 - Chuck - maintain stride left->m_cv_stride = cv_stride; } // trim right end of left NURBS i = left->m_cv_count - left->m_order; //23 April 2015 - Chuck - maintain stride // 31 Aug 2022 GBA RH69916 split at split_t not t. Splitting at t produces a gap that can't be right. //ON_EvaluateNurbsDeBoor( cvdim, m_order, cvdim, left->CV(i), left->m_knot + i, -1, 0.0, t ); ON_EvaluateNurbsDeBoor( cvdim, m_order, cv_stride, left->CV(i), left->m_knot + i, -1, 0.0, split_t); for ( i = left->m_cv_count-1; i < ON_KnotCount(left->m_order,left->m_cv_count); i++ ) left->m_knot[i] = t; left->ClampEnd(2); // 26 June 2003 Dale Lear // trim left end of right NURBS //23 April 2015 - Chuck - maintain stride // 31 Aug 2022 GBA RH69916 split at split_t not t. Splitting at t produces a gap that can't be right. //ON_EvaluateNurbsDeBoor( cvdim, m_order, cvdim, right->m_cv, right->m_knot, +1, 0.0, t ); ON_EvaluateNurbsDeBoor( cvdim, m_order, cv_stride, right->m_cv, right->m_knot, +1, 0.0, split_t); for ( i = 0; i <= right->m_order-2; i++ ) right->m_knot[i] = t; right->ClampEnd(2); // 26 June 2003 Dale Lear if ( 0 == left_crv ) left_crv = left; if ( 0 == right_crv ) right_crv = right; rc = true; } } return rc; } int ON_NurbsCurve::GetNurbForm( ON_NurbsCurve& curve, double tolerance, const ON_Interval* subdomain // OPTIONAL subdomain of ON::ProxyCurve::Domain() ) const { int rc = 1; // 4 May 2007 Dale Lear // I'm replacing the call to operator= with a call to // Internal_DeepCopyFrom(). The operator= call // was copying userdata and that does not happen for // any other GetNurbForm overrides. Copying userdata // in GetNurbForm is causing trouble in Make2D and // other places that are creating NURBS copies in // worker memory pools. // curve = *this; // copied user data curve.DestroyRuntimeCache(true); if (this != &curve) { curve.Internal_DeepCopyFrom(*this); // does not copy user data } if ( subdomain ) { if ( !curve.Trim(*subdomain) ) rc = 0; } return rc; } int ON_NurbsCurve::HasNurbForm() const { return 1; } bool ON_NurbsCurve::GetCurveParameterFromNurbFormParameter( double nurbs_t, double* curve_t ) const { *curve_t = nurbs_t; return true; } bool ON_NurbsCurve::GetNurbFormParameterFromCurveParameter( double curve_t, double* nurbs_t ) const { *nurbs_t = curve_t; return true; } bool ON_NurbsCurve::ClampEnd( int end // 0 = clamp start, 1 = clamp end, 2 = clamp start and end ) { // Curve tree doesn't change when you clamp // DestroyCurveTree(); return ON_ClampKnotVector( CVSize(), m_order, m_cv_count, m_cv_stride, m_cv, m_knot, end ); } double ON_NurbsCurve::GetCubicBezierApproximation( double max_deviation, class ON_BezierCurve& bezierCurve ) const { ON_3dPoint bezCV[4]; const double deviation = GetCubicBezierApproximation(max_deviation, bezCV); if (deviation >= 0.0) { bezierCurve.Create(3, false, 4); bezierCurve.SetCV(0, bezCV[0]); bezierCurve.SetCV(1, bezCV[1]); bezierCurve.SetCV(2, bezCV[2]); bezierCurve.SetCV(3, bezCV[3]); } return deviation; } double ON_NurbsCurve::GetCubicBezierApproximation( double max_deviation, ON_3dPoint bezCV[4] ) const { const double failed_rc = ON_DBL_QNAN; if (ThisIsNullptr(false)) return failed_rc; if (m_order < 2) return failed_rc; if (m_cv_count < m_order) return failed_rc; if ( nullptr == bezCV) return failed_rc; if ( 0 == m_is_rat && 4 == m_order && 4 == m_cv_count && m_knot[0] == m_knot[2] && m_knot[3] == m_knot[5] ) { GetCV(0, bezCV[0]); GetCV(1, bezCV[1]); GetCV(2, bezCV[2]); GetCV(3, bezCV[3]); return 0.0; } const ON_Interval domain = Domain(); // bez0CV[] are cubic bezier control points set from end locations and interpolating 2 interior points. ON_3dPoint bez0CV[4] = { ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin }; // bez1CV[] are cubic bezier control points set from end locations and derivatives. ON_3dPoint bez1CV[4] = { ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin, ON_3dPoint::Origin }; // bez1CV[] are cubic bezier control points set from end locations and derivatives. Evaluate(domain[0], 1, 3, &bez1CV[0].x, 0, nullptr); Evaluate(domain[1], 1, 3, &bez1CV[2].x, 0, nullptr); bez0CV[0] = bez1CV[0]; bez0CV[3] = bez1CV[2]; const double c = domain[1] - domain[0]; const ON_3dVector D[2]{ c * bez1CV[1], c * bez1CV[3] }; bez1CV[1] = bez0CV[0] + D[0]/3.0; bez1CV[2] = bez0CV[3] - D[1]/3.0; bez1CV[3] = bez0CV[3]; // bez0CV[] are cubic bezier control points set from end locations and interpolating 2 interior points. ON_3dPoint P[2] = {ON_3dPoint::Origin,ON_3dPoint::Origin}; int hint = 0; Evaluate(domain.ParameterAt(1.0/3.0), 0, 3, &P[0].x, 0, &hint); Evaluate(domain.ParameterAt(2.0/3.0), 0, 3, &P[1].x, 0, &hint); bez0CV[1] = (bez0CV[3] - 2.5*bez0CV[0])/3.0 + (3.0*P[0] - 1.5*P[1]); bez0CV[2] = (bez0CV[0] - 2.5*bez0CV[3])/3.0 + (3.0*P[1] - 1.5*P[0]); double zero_tol = 0.0; double deviation0 = 0.0; double deviation1 = 0.0; if (4 != m_order || 4 != m_cv_count || 0 != m_is_rat) { // g[] = parameters for evaluation test double g32[32]; double* g = (m_cv_count <= 32) ? g32 : (double*)onmalloc(m_cv_count * sizeof(g[0])); GetGrevilleAbcissae(g); if (3 == m_order) { const double* k = m_knot; if (k[0] < k[2]) { g[0] = k[2]; g[1] = (2.0*k[2] + k[3]) / 3.0; } k += (m_cv_count - m_order); if (k[3] < k[5]) { g[m_cv_count - 1] = k[3]; g[m_cv_count - 2] = (2.0*k[3] + k[2]) / 3.0; } } // test evaluation ON_BezierCurve bez; bez.m_dim = 3; bez.m_is_rat = 0; bez.m_order = 4; bez.m_cv_stride = 3; bez.m_cv = &bez0CV[0].x; ON_3dPoint A, B; bez.Evaluate(1.0 / 3.0, 0, 3, &A.x); bez.Evaluate(2.0 / 3.0, 0, 3, &B.x); zero_tol = 16.0*(P[0].DistanceTo(A) + P[1].DistanceTo(B)) + 1.0e-14; double d; for (int i = 1; i < m_cv_count; i++) { for (int pass = 0; pass < 2; pass++) { const double t = (0==pass) ? 0.5*(g[i - 1] + g[i]) : g[i]; const double s = domain.NormalizedParameterAt(t); if (false == (s > 0.0 && s < 1.0)) continue; Evaluate(t, 0, 3, &A.x, 0, &hint); // test bez0CV[] bez.m_cv = &bez0CV[0].x; bez.Evaluate(s, 0, 3, &B.x); d = A.DistanceTo(B); if (d > deviation0) deviation0 = d; // test bez1CV[] bez.m_cv = &bez1CV[0].x; bez.Evaluate(s, 0, 3, &B.x); d = A.DistanceTo(B); if (d > deviation1) deviation1 = d; if (max_deviation > zero_tol && deviation0 > max_deviation && deviation1 > max_deviation) return failed_rc; } } bez.m_cv = nullptr; if (g != g32) onfree(g); } if (deviation1 <= zero_tol || deviation1 <= deviation0) { // use cubic bezier CVs calculated from end locations and interpolating end derivatives. bezCV[0] = bez1CV[0]; bezCV[1] = bez1CV[1]; bezCV[2] = bez1CV[2]; bezCV[3] = bez1CV[3]; return (deviation1 > zero_tol) ? deviation1 : 0.0; } // use cubic bezier CVs calculated from end locations and interpolating 2 interior points. bezCV[0] = bez0CV[0]; bezCV[1] = bez0CV[1]; bezCV[2] = bez0CV[2]; bezCV[3] = bez0CV[3]; return (deviation0 > zero_tol) ? deviation0 : 0.0; } double ON_NurbsSurface::GetCubicBezierApproximation( double max_deviation, class ON_BezierSurface& bezierSurface ) const { ON_3dPoint bezCV[4][4]; const double deviation = GetCubicBezierApproximation(max_deviation, bezCV); if (deviation >= 0.0) { bezierSurface.Create(3, false, 4, 4); for ( int i = 0; i < 4; i++) for ( int j = 0; j < 4; j++) bezierSurface.SetCV(i, j, bezCV[i][j]); } return deviation; } double ON_NurbsSurface::GetCubicBezierApproximation( double max_deviation, ON_3dPoint bezCV[4][4] ) const { const double failed_rc = ON_DBL_QNAN; if ( ThisIsNullptr(false) ) return failed_rc; if (this->m_order[0] < 2 || this->m_order[1] < 2) return failed_rc; if (this->m_cv_count[0] < this->m_order[0] || this->m_cv_count[0] > 64) return failed_rc; if (this->m_cv_count[1] < this->m_order[1] || this->m_cv_count[1] > 64) return failed_rc; ON_3dPoint Q[4]; const ON_Interval domain[2] = { this->Domain(0),this->Domain(1) }; const double c[4] = { domain[1][0],domain[0][1],domain[1][1],domain[0][0] }; const double maximum_deviation = ON_DBL_QNAN; double deviation = 0.0; for (int side = 0; side < 4; side++) { ON_Curve* iso = this->IsoCurve((side%2), c[side]); if (nullptr == iso) return failed_rc; const ON_NurbsCurve* nurbs_curve = ON_NurbsCurve::Cast(iso); double d = (nullptr != nurbs_curve) ? nurbs_curve->GetCubicBezierApproximation( maximum_deviation, (0 == (side % 2)) ? Q : &bezCV[(1 == side) ? 3 : 0][0]) : ON_DBL_QNAN; if (false == (d >= 0.0)) return failed_rc; if (d > deviation) deviation = d; if (0 == (side % 2)) { int j = (0 == side) ? 0 : 3; bezCV[1][j] = Q[1]; bezCV[2][j] = Q[2]; } delete iso; } bezCV[1][1] = ON_3dPoint::Origin; bezCV[1][2] = ON_3dPoint::Origin; bezCV[2][1] = ON_3dPoint::Origin; bezCV[2][2] = ON_3dPoint::Origin; ON_BezierSurface bezS; bezS.m_dim = 3; bezS.m_is_rat = 0; bezS.m_order[0] = 4; bezS.m_order[1] = 4; bezS.m_cv_stride[0] = (int)(&bezCV[1][0].x - &bezCV[0][0].x); bezS.m_cv_stride[1] = (int)(&bezCV[0][1].x - &bezCV[0][0].x); bezS.m_cv = &bezCV[0][0].x; // b[0][] = 27*(bernstein cubics at 1/3) // b[1][] = 27*(bernstein cubics at 2/3) const double b[2][4] = { { 8.0, 12.0, 6.0, 1.0 }, { 1.0, 6.0, 12.0, 8.0 } }; // uv[4] = {{1/3,1/3}, {2/3,1/3}, {1/3,2/3}, {2/3,2/3} } // K[n] = (27*27)*bezCV(uv[n]) // P[n] = this(domain.ParamaterAt(uv[n])); // 36* M * Transpose[C11,C21,C12,C22] = [X0,X1,X2,X3] // uv[4] = {{1/3,1/3{, {2/3,1/3}, {1/3,2/3}, {2/3,2/3} } // X0 = nurbs_srf(1/3,1/3) - K[0]/(27*27) // A[4][4] = 4x4 matrix with rows // 36*A[0][] = {b1[1]*b1[1], b1[2]*b1[1], b1[1]*b1[2], b1[2]*b1[2] } // 36*A[1][] = {b2[1]*b1[1], b2[2]*b1[1], b2[1]*b1[2], b2[2]*b1[2] } // 36*A[2][] = {b1[1]*b2[1], b1[2]*b2[1], b1[1]*b2[2], b1[2]*b2[2] } // 36*A[3][] = {b2[1]*b2[1], b2[2]*b2[1], b2[1]*b2[2], b2[2]*b2[2] } // K[n] + 36*(A[n][0]*C11 + A[n][1]*C21 + A[n][2]*C12 + A[n][3]*C22) = (27*27)P[n] // A*Transpose(C11,C21,C12,C22) = Transpose(X[0],X[1],X[2],X[3]) // X[n] = ((27*27)P[n] - K[n])/36 = 81/4*P[n] - K[n]/36.0 // Inverse(A) = M/9. // Transpose(C11,C21,C12,C22) = M*Transpose(X[0],X[1],X[2],X[3])/9 // Q[n] = X[n]/9 = 2.25*P[n] - K[n]/324.0 // bezCV[cvdex[n].i][cvdex[n].i] = M*Transpose(X[0],X[1],X[2],X[3])/9 const double bezuv[4][2] = { {(1.0 / 3.0), (1.0 / 3.0)}, {(2.0 / 3.0), (1.0 / 3.0)}, {(1.0 / 3.0), (2.0 / 3.0)}, {(2.0 / 3.0), (2.0 / 3.0)} }; ON_3dPoint srfP[4] = { ON_3dPoint::Origin,ON_3dPoint::Origin,ON_3dPoint::Origin,ON_3dPoint::Origin }; int hint[2] = {}; for (int n = 0; n < 4; n++) { this->Evaluate(domain[0].ParameterAt(bezuv[n][0]),domain[1].ParameterAt(bezuv[n][1]), 0, 3, &srfP[n].x, 0, hint); const double* bi = b[n%2]; const double* bj = b[n/2]; ON_3dPoint K(0.0, 0.0, 0.0); ON_3dPoint valQ(0, 0, 0); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) { if ((1 == i || 2 == i) && (1 == j || 2 == j)) continue; K += (bi[i] * bj[j])*bezCV[i][j]; } Q[n] = (2.25*srfP[n]) - (K/324.0); } const ON_2dex cvdex[4]{ON_2dex(1,1),ON_2dex(2,1),ON_2dex(1,2),ON_2dex(2,2)}; ////const double A[4][4] = ////{ //// {4.0, 2.0, 2.0, 1.0}, //// {2.0, 4.0, 1.0, 2.0}, //// {2.0, 1.0, 4.0, 2.0}, //// {1.0, 2.0, 2.0, 4.0} ////}; const double M[4][4] = { {4.0,-2.0,-2.0,1.0}, {-2.0,4.0,1.0,-2.0}, {-2.0,1.0,4.0,-2.0}, {1.0,-2.0,-2.0,4.0} }; for (int n = 0; n < 4; n++) bezCV[cvdex[n].i][cvdex[n].j] = M[n][0] * Q[0] + M[n][1] * Q[1] + M[n][2] * Q[2] + M[n][3] * Q[3]; double zero_tol = 0.0; for (int n = 0; n < 4; n++) { ON_3dPoint bezP(0.0, 0.0, 0.0); bezS.Evaluate(bezuv[n][0], bezuv[n][1], 0, 3, &bezP.x); double d = srfP[n].DistanceTo(bezP); zero_tol += d; } zero_tol = 16.0*zero_tol; double g_buffer[64]; double* g[2]; g[0] = (m_cv_count[0] + m_cv_count[1] <= 32) ? g_buffer : (double*)onmalloc((m_cv_count[0] + m_cv_count[1]) * sizeof(g[0][0])); g[1] = g[0] + m_cv_count[0]; for (int dir = 0; dir < 2; dir++) { this->GetGrevilleAbcissae(dir, g[dir]); if (4 == this->m_order[dir]) { const double* k = this->m_knot[dir]; if (k[0] < k[2]) { g[dir][0] = k[2]; g[dir][1] = (2.0*k[2] + k[3]) / 3.0; } const int cv_count = this->m_cv_count[dir]; k += (cv_count - this->m_order[dir]); if (k[3] < k[5]) { g[dir][cv_count-1] = k[3]; g[dir][cv_count-2] = (2.0*k[3] + k[2]) / 3.0; } } } ON_3dPoint bezP(ON_3dPoint::Origin), nurbsP(ON_3dPoint::Origin); hint[0] = 0; hint[1] = 0; for (int i = 1; i < this->m_cv_count[0]; i++) { for (int passi = 0; passi < 2; passi++) { const double t0 = (0 == passi) ? 0.5*(g[0][i - 1] + g[0][i]) : g[0][i]; const double s0 = domain[0].NormalizedParameterAt(t0); if (false == (s0 > 0.0 && s0 < 1.0)) continue; for (int j = 1; j < this->m_cv_count[1]; j++) { for (int passj = 0; passj < 2; passj++) { const double t1 = (0 == passj) ? 0.5*(g[1][j - 1] + g[1][j]) : g[1][j]; const double s1 = domain[1].NormalizedParameterAt(t1); if (false == (s1 > 0.0 && s1 < 1.0)) continue; bezS.Evaluate(s0,s1, 0, 3, &bezP.x); this->Evaluate(t0,t1, 0, 3, &nurbsP.x, 0, hint); double d = nurbsP.DistanceTo(bezP); if (d > zero_tol && d > deviation) { deviation = d; if (max_deviation >= 0.0 && deviation > max_deviation) return failed_rc; } } } } } return deviation; } /* static bool ON_DuplicateKnots( int order, int cv_count, bool bRevKnot1, double* knot0, double* knot1 ) { return false; } static bool ON_DuplicateContolPoints( int dim, bool is_rat, int cv_count, bool bRevCV1, int stride0, double* cv0, int stride1, double* cv1 ) { return false; } */ static bool ON_IsDuplicateKnotVector( int order, int cv_count, const double* knot, const double* other_knot, bool bIgnoreParameterization ) { bool rc = ( 0 != knot && 0 != other_knot && order >= 2 && cv_count >= order); if (rc) { const int knot_count = ON_KnotCount( order, cv_count ); int i; if ( bIgnoreParameterization ) { const ON_Interval dom(knot[order-2],knot[cv_count-1]); const ON_Interval other_dom(other_knot[order-2],other_knot[cv_count-1]); double k, other_k; for ( i = 0; i < knot_count && rc; i++ ) { k = dom.NormalizedParameterAt(knot[i]); other_k = dom.NormalizedParameterAt(other_knot[i]); rc = (fabs(k-other_k) <= ON_ZERO_TOLERANCE); } } else { for ( i = 0; i < knot_count && rc; i++ ) { rc = (knot[i] == other_knot[i]); } } } return rc; } static bool ON_IsDuplicatePointList( int dim, bool is_rat, int count, int stride, const double* cv, int other_stride, const double* other_cv, double tolerance ) { bool rc = (dim > 0 && count > 0 && std::abs(stride) >= (dim+(is_rat?1:0)) && std::abs(other_stride) >= (dim+(is_rat?1:0)) && 0 != cv && 0 != other_cv); if (rc) { if ( tolerance < 0.0 ) tolerance = 0.0; int i, j; double w = 1.0; double other_w = 1.0; double cv_tol = tolerance; for ( i = 0; i < count && rc; i++ ) { if ( is_rat ) { w = cv[dim]; other_w = other_cv[dim]; cv_tol = fabs(w*tolerance); rc = ( w == other_w ); } for ( j = 0; j < dim && rc; j++ ) { rc = (fabs(cv[j] - other_cv[j]) <= cv_tol); } cv += stride; other_cv += other_stride; } } return rc; } bool ON_NurbsCurve::IsDuplicate( const ON_NurbsCurve& other, bool bIgnoreParameterization, double tolerance ) const { bool rc = (this == &other); if ( !rc && m_dim == other.m_dim && m_is_rat == other.m_is_rat && m_order == other.m_order && m_cv_count == other.m_cv_count ) { // compare knots rc = ON_IsDuplicateKnotVector( m_order, m_cv_count, m_knot, other.m_knot, bIgnoreParameterization ); // compare control points if (rc) rc = ON_IsDuplicatePointList( m_dim, m_is_rat?1:0, m_cv_count, m_cv_stride, m_cv, other.m_cv_stride, other.m_cv, tolerance ); } return rc; } bool ON_NurbsSurface::IsDuplicate( const ON_NurbsSurface& other, bool bIgnoreParameterization, double tolerance ) const { bool rc = (this == &other); if ( !rc && m_dim == other.m_dim && m_is_rat == other.m_is_rat && m_order[0] == other.m_order[0] && m_order[1] == other.m_order[1] && m_cv_count[0] == other.m_cv_count[0] && m_cv_count[1] == other.m_cv_count[1] ) { // compare knots rc = ON_IsDuplicateKnotVector( m_order[0], m_cv_count[0], m_knot[0], other.m_knot[0], bIgnoreParameterization ); if (rc) rc = ON_IsDuplicateKnotVector( m_order[1], m_cv_count[1], m_knot[1], other.m_knot[1], bIgnoreParameterization ); // compare control points int i; for ( i = 0; i < m_cv_count[0] && rc; i++ ) { rc = ON_IsDuplicatePointList( m_dim, m_is_rat?1:0, m_cv_count[1], m_cv_stride[1], CV(i,0), other.m_cv_stride[1], other.CV(i,0), tolerance ); } } return rc; } bool ON_Brep::IsDuplicate( const ON_Brep& other, double tolerance ) const { // OBSOLETE FUNCTION - REMOVE return false; } bool ON_NurbsCurve::Reparameterize(double c) { if ( !ON_IsValid(c) || 0.0 == c ) return false; if ( 1.0 == c ) return true; if ( !MakeRational() ) return false; return ON_ReparameterizeRationalNurbsCurve( c, m_dim,m_order,m_cv_count, m_cv_stride,m_cv,m_knot ); } bool ON_ReparameterizeRationalNurbsCurve( double c, int dim, int order, int cv_count, int cvstride, double* cv, double* knot ) { // Reference // E. T. Y. Lee and M. L. Lucian // Mobius reparameterization of rational B-splines // CAGD Vol8 pp 213-215 1991 const double c1 = c-1.0; double k0, k1, k, d, w0, w1; int i,j; if ( !ON_IsValid(c) || !ON_IsValid(c1) || 0.0 == c ) return false; if ( 1.0 == c ) return true; // change domain to [0,1] and then adjust knots k0 = knot[order-2]; k1 = knot[cv_count-1]; d = k1 - k0; if ( !ON_IsValid(d) || d <= 0.0 ) return false; d = 1.0/d; j = cv_count+order-2; for ( i = 0; i < j; i++ ) { k = knot[i]; k = (k - k0)*d; knot[i] = c*k/(c1*k+1.0); } // adjust cvs order -= 2; cvstride -= (dim+1); for ( i = 0; i < cv_count; i++ ) { d = c - c1*(*knot++); j = order; while(j--) { d *= c - c1*knot[j]; } w0 = cv[dim]; w1 = w0*d; j = dim; while(j--) *cv++ *= d; *cv++ = w1; cv += cvstride; } order += 2; cvstride += (dim+1); cv -= cv_count*cvstride; knot -= cv_count; // change domain back to [k0,k1] j = cv_count+order-2; for ( i = 0; i < j; i++ ) { k = knot[i]; knot[i] = (1.0-k)*k0 + k*k1; } return true; } bool ON_NurbsCurve::ChangeEndWeights(double w0, double w1) { if ( m_cv_count < m_order || m_order < 2 || 0 == m_cv ) return false; if ( !ON_IsValid(w0) || !ON_IsValid(w1) || 0.0 == w0 || 0.0 == w1 ) return false; if ( (w0 < 0.0 && w1 > 0.0) || (w0 > 0.0 && w1 < 0.0) ) return false; if (!ClampEnd(2)) return false; if ( w0 == Weight(0) && w1 == Weight(m_cv_count-1) ) return true; if ( !MakeRational() ) return false; return ON_ChangeRationalNurbsCurveEndWeights( m_dim,m_order, m_cv_count,m_cv_stride,m_cv, m_knot, w0,w1); } bool ON_ChangeRationalNurbsCurveEndWeights( int dim, int order, int cv_count, int cvstride, double* cv, double* knot, double w0, double w1 ) { double r, s, v0, v1; int i, j; if ( !ON_IsValid(w0) || !ON_IsValid(w1) || 0.0 == w0 || 0.0 == w1 ) return false; if ( (w0 < 0.0 && w1 > 0.0) || (w0 > 0.0 && w1 < 0.0) ) return false; if ( !ON_ClampKnotVector( dim+1, order, cv_count, cvstride, cv, knot, 2 ) ) return false; v0 = cv[dim]; v1 = cv[cvstride*(cv_count-1)+dim]; if (!ON_IsValid(v0) || !ON_IsValid(v1) || v0 == 0.0 || v1 == 0.0) return false; if (v0 < 0.0 && v1 > 0.0) return false; if ( v0 > 0.0 && v1 < 0.0) return false; r = w0/v0; s = w1/v1; if ( fabs(r-s) <= fabs(s)*ON_SQRT_EPSILON ) { // simply scale if ( r != s ) s = 0.5*(r+s); r = s; } if ( 1.0 != s && v1 != w1 ) { // scale to get last weight set to w1 dim++; cvstride -= dim; i = cv_count; while(i--) { j = dim; while(j--) *cv++ *= s; cv += cvstride; } cvstride += dim; dim--; cv -= cvstride*cv_count; } if ( r != s ) { v0 = cv[dim]; v1 = cv[cvstride*(cv_count-1)+dim]; if ( ON_IsValid(v0) && ON_IsValid(v1) && 0.0 != v0 ) { // need to scale and reparameterize r = pow(w0/v0,1.0/((double)(order-1))); if ( !ON_IsValid(r) ) return false; if ( !ON_ReparameterizeRationalNurbsCurve(r,dim,order,cv_count,cvstride,cv,knot) ) return false; } } // make sure weights agree to the last bit! cv[dim] = w0; cv[cvstride*(cv_count-1)+dim] = w1; return true; } double ON_Fuzz( double x, double absolute_tolerance ) { double fuzz = fabs(x)*ON_RELATIVE_TOLERANCE; return(fuzz > absolute_tolerance) ? fuzz : absolute_tolerance; } bool ON_NurbsCurve::SpanIsSingular( int span_index ) const { const int cv_size = CVSize(); if ( m_order < 2 || m_cv_count < m_order || m_dim <= 0 || cv_size > m_cv_stride || 0 == m_knot || 0 == m_cv ) { ON_ERROR("Invalid NURBS curve."); return false; } if ( span_index < 0 || span_index > m_cv_count-m_order ) { ON_ERROR("span_index parameter is out of range."); return false; } const double* cv = CV(span_index); const double* knot = m_knot + span_index; if ( !(knot[m_order-2] < knot[m_order-1]) ) { // vacuous question because there is no "span" evaluate. // I chose return false here so people won't try to // remove this empty span. // no call to ON_ERROR here return false; } double* p = 0; int cv_stride = m_cv_stride; if ( knot[0] != knot[m_order-2] || knot[m_order-1] != knot[2*m_order-3] ) { const size_t sizeof_cv = cv_size*sizeof(p[0]); p = (double*)onmalloc(sizeof_cv*m_order); for ( int i = 0; i < m_order; i++ ) memcpy( p+(i*cv_size), cv+(i*cv_stride), sizeof_cv ); ON_ConvertNurbSpanToBezier( cv_size, m_order, cv_size, p, knot, knot[m_order-2], knot[m_order-1] ); cv_stride = cv_size; cv = p; } const bool rc = ON_PointsAreCoincident(m_dim,m_is_rat,m_order,cv_stride,cv); if ( 0 != p ) onfree(p); return rc; } bool ON_NurbsCurve::RemoveSpan( int span_index ) { const int cv_size = CVSize(); if ( m_order < 2 || m_cv_count < m_order || m_dim <= 0 || cv_size > m_cv_stride || 0 == m_knot || 0 == m_cv ) { ON_ERROR("Invalid NURBS curve."); return false; } if ( span_index < 0 || span_index > m_cv_count-m_order ) { ON_ERROR("span_index parameter is out of range."); return false; } if ( m_cv_count == m_order ) { ON_ERROR("Cannot remove the only span from a Bezier NURBS curve."); return false; } const size_t sizeof_cv = cv_size*sizeof(m_cv[0]); int i, j; const double knot0 = m_knot[span_index+m_order-2]; const double knot1 = m_knot[span_index+m_order-1]; const double knot_delta = (knot0 < knot1) ? (knot1 - knot0) : 0.0; const bool bIsPeriodic0 = IsPeriodic()?true:false; if ( span_index <= 0 ) { // remove initial span // set span_index = index of the span to keep. for ( span_index = 1; span_index < m_cv_count-m_order; span_index++ ) { if ( m_knot[span_index+m_order-2] < m_knot[span_index+m_order-1] ) break; } for ( i = 0; i+span_index < m_cv_count; i++ ) memcpy(CV(i),CV(i+span_index),sizeof_cv); for ( i = 0; i+span_index < m_cv_count+m_order-2; i++ ) { m_knot[i] = (knot1 == m_knot[i+span_index]) ? knot0 : (m_knot[i+span_index] - knot_delta); } m_cv_count -= span_index; } else if ( span_index >= m_cv_count-m_order ) { // remove final span // set span_index = index of the span to keep. for ( span_index = m_cv_count-m_order-1; span_index > 0; span_index-- ) { if ( m_knot[span_index+m_order-2] < m_knot[span_index+m_order-1] ) break; } m_cv_count = span_index+m_order; } else { // remove interior span int k0 = span_index+m_order-2; int k1 = span_index+m_order-1; int i0 = k0; int i1 = k1; for ( i0 = k0; i0 > 0; i0-- ) { if ( m_knot[i0-1] < m_knot[k0] ) break; } for ( i1 = k1; i1 < m_cv_count+m_order-3; i1++ ) { if ( m_knot[i1+1] > m_knot[k1] ) break; } int m = (i1-i0+1); if ( !(knot_delta > 0.0) ) { if ( !(m_knot[i0] == m_knot[i1]) || m < m_order ) { ON_ERROR("span_index parameter identifies an empty span."); return false; } } int span_index0 = i0 - (m_order-1); double* cv0 = 0; if ( span_index0 >= 0 && k0 - i0 + 1 < m_order-1 ) { cv0 = (double*)onmalloc( (m_order*cv_size + 2*m_order-2)*sizeof(cv0[0]) ); double* knot0_local = cv0 + (m_order*cv_size); memcpy( knot0_local, m_knot+span_index0, (2*m_order-2)*sizeof(knot0_local[0]) ); for ( i = 0; i < m_order; i++ ) memcpy( cv0 + (i*cv_size), CV(span_index0+i), sizeof_cv ); ON_ClampKnotVector( cv_size, m_order, m_order, cv_size, cv0, knot0_local, 1 ); } if ( m < m_order-1 ) { i = m_order-1 - m; ReserveCVCapacity( m_cv_stride*(m_cv_count+i) ); ReserveKnotCapacity( m_cv_count+m_order-2+i ); for ( j = m_cv_count+m_order-3; j >= i1-m_order+2; j-- ) m_knot[j+i] = m_knot[j]; for ( j = m_cv_count-1; j >= i1-m_order+2; j-- ) memcpy(CV(j+i),CV(j),sizeof_cv); i1 += i; k1 += i; m_cv_count += i; } if ( i1-k1 < m_order-2 ) ON_ClampKnotVector( cv_size, m_order, m_order, m_cv_stride, m_cv + ((i1-m_order+2)*m_cv_stride), m_knot + (i1-m_order+2), 0 ); k0 = i0; k1 = i1-m_order+2; if ( 0 != cv0 ) { for ( i = 0; i < m_order-1; i++ ) memcpy(CV(i+span_index0),cv0 + (i*cv_size),sizeof_cv); onfree(cv0); cv0 = 0; } if ( k0 < k1 ) { for ( i = 0; i + k1 < m_cv_count; i++ ) memcpy(CV(i+k0),CV(i+k1),sizeof_cv); for ( i = 0; i + k1 < m_cv_count+m_order-2; i++ ) { m_knot[i+k0] = (knot1 == m_knot[i+k1]) ? knot0 : (m_knot[i+k1] - knot_delta); } m_cv_count -= (k1-k0); } else if ( k0 == k1 && knot_delta > 0.0 ) { for ( i = k0; i < m_cv_count+m_order-2; i++ ) { m_knot[i] = (knot1 == m_knot[i]) ? knot0 : (m_knot[i] - knot_delta); } } } if ( false == bIsPeriodic0 || false == IsPeriodic() ) ClampEnd(2); return true; } int ON_NurbsCurve::RemoveSingularSpans() { const int cv_size = CVSize(); if ( m_order < 2 || m_cv_count < m_order || m_dim <= 0 || cv_size > m_cv_stride || 0 == m_knot || 0 == m_cv ) { ON_ERROR("Invalid NURBS curve."); return 0; } int singular_span_count = 0; for ( int span_index = 0; m_cv_count > m_order && span_index <= m_cv_count-m_order; span_index++ ) { if ( m_knot[span_index+m_order-2] < m_knot[span_index+m_order-1] && SpanIsSingular(span_index) ) { const int cv_count0 = m_cv_count; if ( RemoveSpan(span_index) ) singular_span_count++; if ( 0 == span_index || m_cv_count < cv_count0 ) span_index--; } } return singular_span_count; } static bool Internal_UnclampedKnots( int order, int cv_count, const double* cv, const double* knot ) { return order >= 3 && cv_count >= order && nullptr != cv && nullptr != knot && knot[0] > ON_UNSET_VALUE && knot[cv_count + order - 3] < ON_UNSET_POSITIVE_VALUE && knot[order - 2] < knot[order - 1] - ON_ZERO_TOLERANCE && knot[cv_count - 2] < knot[cv_count - 1] - ON_ZERO_TOLERANCE && ((knot[0] < knot[order - 2] - ON_ZERO_TOLERANCE) || (knot[cv_count - 1] < knot[cv_count + order - 3] - ON_ZERO_TOLERANCE)) ; } bool ON_NurbsCurve::UnclampedTagForExperts() const { if ( 0 != (m_knot_capacity_and_tags & ON_NurbsCurve::masks::unclamped_knots_tag) && Internal_UnclampedKnots(m_order,m_cv_count,m_cv,m_knot) && false == IsPeriodic() ) { return true; } return false; } /* Description: Set the curve's UnclampedTag() property. Parameters: bUnclampedTag - [in] If bUnclampedTag is true, the curve has unclamped knots, and the curve is not periodic, then the UnclampedTag() property is set to true. Otherwise the UnclampedTag() property is set to false. */ void ON_NurbsCurve::SetUnclampedTagForExperts( bool bUnclampedTag ) { if ( bUnclampedTag && Internal_UnclampedKnots(m_order, m_cv_count, m_cv, m_knot) && false == this->IsPeriodic() ) m_knot_capacity_and_tags |= ON_NurbsCurve::masks::unclamped_knots_tag; else m_knot_capacity_and_tags &= ~ON_NurbsCurve::masks::unclamped_knots_tag; } bool ON_NurbsCurve::SubDFriendlyTag() const { if (0 != (m_knot_capacity_and_tags & ON_NurbsCurve::masks::subdfriendly_tag)) { if (IsSubDFriendly(true)) return true; // Something modified NURBS properties after the tag was set. const_cast(this)->SetSubDFriendlyTag(false); } return false; } void ON_NurbsCurve::SetSubDFriendlyTag( bool bSubDFriendlyTag ) { if (bSubDFriendlyTag && IsSubDFriendly(true)) m_knot_capacity_and_tags |= ON_NurbsCurve::masks::subdfriendly_tag; else m_knot_capacity_and_tags &= ~ON_NurbsCurve::masks::subdfriendly_tag; } static bool Internal_IsSubDFriendlyEnd( int endex, const double* cubic_cpan_knots, const ON_3dPoint& cv0, const ON_3dPoint& cv1, const ON_3dPoint& cv2 ) { ON_3dPoint Q; if (0 == endex) { Q = (cubic_cpan_knots[0] == cubic_cpan_knots[2] && cubic_cpan_knots[3] < cubic_cpan_knots[5]) ? ((2.0*cv0 + cv2) / 3.0) : (0.5*(cv0 + cv2)) ; } else { Q = (cubic_cpan_knots[0] < cubic_cpan_knots[2] && cubic_cpan_knots[3] == cubic_cpan_knots[5]) ? ((cv0 + 2.0*cv2) / 3.0) : (0.5*(cv0 + cv2)) ; } const double d = cv0.DistanceTo(cv2); const double tol = 1.0e-6*d; const double e = cv1.DistanceTo(Q); return (e <= tol); } bool ON_NurbsCurve::IsSubDFriendly( bool bPermitCreases ) const { for (;;) { if (m_dim <= 0) break; if (false != m_is_rat) break; if (4 != m_order) break; if (m_cv_count < 4) break; if (nullptr == m_knot) break; if (m_cv_stride < m_dim) break; if (nullptr == m_cv) break; // knots must be piecewise uniform const ON_SubD::SubDFriendlyKnotType knot_type = ON_SubD::NurbsKnotType(m_order, m_cv_count, m_knot); if (ON_SubD::SubDFriendlyKnotType::Unset == knot_type || ON_SubD::SubDFriendlyKnotType::Unfriendly == knot_type) return false; if (ON_SubD::SubDFriendlyKnotType::ClampedPiecewiseUniform == knot_type && false == bPermitCreases) return false; ON_3dPoint CV[2] = { ON_3dPoint::NanPoint,ON_3dPoint::NanPoint }; if (false == GetCV(0, CV[0])) break; if (false == GetCV(1, CV[1])) break; bool bAllEqual = CV[0] == CV[1]; if (bAllEqual) { // All CVs must be equal - curve is a "point" used in lofting to a point. for (int i = 2; bAllEqual && i < m_cv_count; ++i) { bAllEqual = GetCV(i, CV[1]) && CV[0] == CV[1]; } if (false == bAllEqual) break; } // curve must be periodic or natural bool bNaturalEnds = (m_cv_count >= 6 && IsPeriodic()); if ( false == bNaturalEnds) { // test ends for natural end conditions ON_3dPoint P[3]; bNaturalEnds = true; for (int endex = 0; endex < 2 && bNaturalEnds; ++endex) { const int cv_dex = (0 == endex) ? 0 : ( CVCount() - 3); for (int j = 0; j < 3 && bNaturalEnds; ++j) bNaturalEnds = GetCV(cv_dex+j, P[j]); if (false == bNaturalEnds) break; const double* span_knots = m_knot + ((0 == endex) ? 0 : (m_cv_count - m_order)); bNaturalEnds = Internal_IsSubDFriendlyEnd(endex, span_knots, P[0], P[1], P[2]); } } if ( bNaturalEnds && ON_SubD::SubDFriendlyKnotType::ClampedPiecewiseUniform == knot_type ) { // Interior triple knots must be natural on either side of the triple knot. // Interior triple knots are used to place kinks inside the curve. ON_3dPoint P[5]; const unsigned int knot_count = (unsigned int)KnotCount(); const double* knot = m_knot; for (unsigned int knot_dex = 3; knot_dex < knot_count - 4U && bNaturalEnds; ++knot_dex) { if (knot[knot_dex] == knot[knot_dex + 1]) { // This tests that the 2nd derivative is zero when evaluated from both below and above knot[knot_dex]. bNaturalEnds = knot[knot_dex - 1] < knot[knot_dex] // tested above to enter this scope // && knot[knot_dex] == knot[knot_dex+1] && knot[knot_dex + 1] == knot[knot_dex + 2] && knot[knot_dex + 2] < knot[knot_dex + 3] && GetCV(knot_dex - 2, P[0]) && GetCV(knot_dex - 1, P[1]) && GetCV(knot_dex, P[2]) && GetCV(knot_dex + 1, P[3]) && GetCV(knot_dex + 2, P[4]) && Internal_IsSubDFriendlyEnd(1,knot+knot_dex-3, P[0], P[1], P[2]) && Internal_IsSubDFriendlyEnd(0,knot+knot_dex, P[2], P[3], P[4]); ++knot_dex; } } } if (false == bNaturalEnds) break; return true; } return false; } ON_SubD::SubDFriendlyKnotType ON_SubD::NurbsKnotType( int order, int cv_count, const double* knots ) { return ON_SubD::NurbsKnotType(order, cv_count, knots, nullptr); } ON_SubD::SubDFriendlyKnotType ON_SubD::NurbsKnotType( int order, int cv_count, const double* knots, ON_SimpleArray* triple_knots ) { if (nullptr != triple_knots) triple_knots->SetCount(0); for (;;) { if (4 != order || cv_count < order || nullptr == knots) break; const double delta = knots[3] - knots[2]; if (false == (delta > 0.0)) break; const double deltatol = ON_SQRT_EPSILON * delta; const bool bClamped0 = knots[0] == knots[1] && knots[1] == knots[2]; const bool bClamped1 = knots[cv_count - 1] == knots[cv_count] && knots[cv_count] == knots[cv_count + 1]; if (bClamped0 && nullptr != triple_knots) triple_knots->Append(knots[2]); const bool bClamped = bClamped0 && bClamped1; const bool bUnclamped = (false == bClamped) && fabs(knots[1] - knots[0] - delta) <= deltatol && fabs(knots[2] - knots[1] - delta) <= deltatol && fabs(knots[cv_count] - knots[cv_count - 1] - delta) <= deltatol && fabs(knots[cv_count + 1] - knots[cv_count] - delta) <= deltatol ; if ( bClamped == bUnclamped) break; bool bPiecewiseUniform = false; for (int knot_dex = 3; knot_dex < cv_count - 1; ++knot_dex) { const double d = knots[knot_dex + 1] - knots[knot_dex]; if (0.0 == d) { if (bClamped && knots[knot_dex + 2] == knots[knot_dex]) { bPiecewiseUniform = true; ++knot_dex; if (bClamped1 && nullptr != triple_knots) triple_knots->Append(knots[knot_dex]); continue; } } else { if (fabs(d - delta) <= deltatol) continue; } return ON_SubD::SubDFriendlyKnotType::Unfriendly; } if (bClamped1 && nullptr != triple_knots) triple_knots->Append(knots[cv_count - 1]); return bClamped ? (bPiecewiseUniform ? ON_SubD::SubDFriendlyKnotType::ClampedPiecewiseUniform : ON_SubD::SubDFriendlyKnotType::ClampedUniform) : ON_SubD::SubDFriendlyKnotType::UnclampedUniform; } return ON_SubD::SubDFriendlyKnotType::Unfriendly; }