/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

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
*/
static void ON_NurbsCurve_copy_member_data( const ON_NurbsCurve& src, ON_NurbsCurve& dest )
{
  int i;
  dest.m_dim       = src.m_dim;
  dest.m_is_rat    = src.m_is_rat;
  dest.m_order     = src.m_order;
  dest.m_cv_count  = src.m_cv_count;
  dest.m_cv_stride = dest.m_is_rat ? dest.m_dim+1 : dest.m_dim;
  if ( src.m_knot ) 
  {
    // copy knot array
    i = dest.KnotCount();
    dest.ReserveKnotCapacity( i );
    memcpy( dest.m_knot, src.m_knot, i*sizeof(*dest.m_knot) );
  }
  if ( src.m_cv ) 
  {
    // copy cv array
    dest.ReserveCVCapacity( dest.m_cv_stride*dest.m_cv_count );
    const int dst_cv_size = dest.CVSize()*sizeof(*dest.m_cv);
    const int src_stride = src.m_cv_stride;
    const int dst_stride = dest.m_cv_stride;
    const double *src_cv = src.CV(0);
    double *dst_cv = dest.m_cv;
    if ( src_stride == dst_stride ) 
    {
      memcpy( dst_cv, src_cv, dest.m_cv_count*dst_stride*sizeof(*dst_cv) );
    }
    else 
    {
      for ( i = 0; i < dest.m_cv_count; i++ )
      {
        memcpy( dst_cv, src_cv, dst_cv_size );
        dst_cv += dst_stride;
        src_cv += src_stride;
      }
    }
  }
}

/*
Description:
  Helper to move (shallow copy) from src to dest
*/
#if defined(ON_HAS_RVALUEREF)
static void ON_NurbsCurve_move_member_data( const ON_NurbsCurve& src, ON_NurbsCurve& dest )
{
  dest.m_dim           = src.m_dim;
  dest.m_is_rat        = src.m_is_rat;
  dest.m_order         = src.m_order;
  dest.m_cv_count      = src.m_cv_count;
  dest.m_knot_capacity = src.m_knot_capacity;
  dest.m_knot          = src.m_knot;
  dest.m_cv_stride     = src.m_cv_stride;
  dest.m_cv_capacity   = src.m_cv_capacity;
  dest.m_cv            = src.m_cv;
}
#endif

/*
Description:
  Helper to clear (set all member vars to zero)
*/
static void ON_NurbsCurve_zero_member_data( ON_NurbsCurve& dest )
{
  dest.m_dim = 0;
  dest.m_is_rat = 0;
  dest.m_order = 0;
  dest.m_cv_count = 0;
  dest.m_knot_capacity = 0;
  dest.m_knot = 0;
  dest.m_cv_stride = 0;
  dest.m_cv_capacity = 0;
  dest.m_cv = 0;
}

/*
Description:
  Helper to make a deep copy (duplicate memory) from src to dest
*/
static void ON_NurbsCurve_delete_member_data( ON_NurbsCurve& dest )
{
  double* cv = ( dest.m_cv && dest.m_cv_capacity ) ? dest.m_cv : 0;
  double* knot = ( dest.m_knot && dest.m_knot_capacity ) ? dest.m_knot : 0;
  ON_NurbsCurve_zero_member_data(dest);
  if ( cv )
    onfree(cv);
  if ( knot )
    onfree(knot);
}

ON_NurbsCurve::ON_NurbsCurve() ON_NOEXCEPT
{
  ON__SET__THIS__PTR(m_s_ON_NurbsCurve_ptr);
  ON_NurbsCurve_zero_member_data(*this);
}

ON_NurbsCurve::ON_NurbsCurve( const ON_NurbsCurve& src )
  : ON_Curve(src)
{
  ON__SET__THIS__PTR(m_s_ON_NurbsCurve_ptr);
  ON_NurbsCurve_zero_member_data(*this);
  ON_NurbsCurve_copy_member_data(src,*this);
}

ON_NurbsCurve& ON_NurbsCurve::operator=( const ON_NurbsCurve& src )
{
  if ( this != &src ) 
  {
    ON_Curve::operator=(src);
    ON_NurbsCurve_copy_member_data(src,*this);
  }
  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);
  ON_NurbsCurve_move_member_data(src,*this);
  ON_NurbsCurve_zero_member_data(src);
}

ON_NurbsCurve& ON_NurbsCurve::operator=( ON_NurbsCurve&& src)
{
  if ( this != &src )
  {
    ON_NurbsCurve_delete_member_data(*this);
    ON_Curve::operator=(std::move(src));
    ON_NurbsCurve_move_member_data(src,*this);
    ON_NurbsCurve_zero_member_data(src);
  }
  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 += m_knot_capacity*sizeof(*m_knot);
  sz += m_cv_capacity*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();
  ON_NurbsCurve_delete_member_data(*this);
}

void ON_NurbsCurve::EmergencyDestroy()
{
  ON_NurbsCurve_zero_member_data(*this);
}


void ON_NurbsCurve::Initialize()
{
  ON_NurbsCurve_zero_member_data(*this);
}

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
  bool rc = file.Write3dmChunkVersion(1,0);
  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) );
      }
    }

  }
  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 )
    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;
}

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;
      // TODO - if periodic - dont' put multi knot in middle
      //if ( IsPeriodic() ){} else
      {
        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<ON_3dPoint>* pline_points,
      ON_SimpleArray<double>* 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;
    }
  }
  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 ( m_cv_capacity > 0 ) {
      memset( m_cv, 0, m_cv_capacity*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::ReserveCVCapacity(int desired_capacity)
{
  // If m_cv_capacity == 0 and m_cv != nullptr, then the user
  // has hand built the ON_NurbsCurve.m_cv array and is responsible
  // for making sure it's always big enough.
  bool rc = true;
  if ( desired_capacity > m_cv_capacity ) {
    if ( !m_cv ) {
      // no cv array - allocate one
      m_cv = (double*)onmalloc(desired_capacity*sizeof(*m_cv));
      if ( !m_cv ) {
        rc = false;
      }
      else {
        m_cv_capacity = desired_capacity;
      }
    }
    else if ( m_cv_capacity > 0 ) {
      // existing m_cv[] is too small and the fact that
      // m_cv_capacity > 0 indicates that ON_NurbsCurve is
      // managing the m_cv[] memory, so we need to grow
      // the m_cv[] array.
      m_cv = (double*)onrealloc(m_cv,desired_capacity*sizeof(*m_cv));
      if ( !m_cv ) {
        rc = false;
        m_cv_capacity = 0;
      }
      else {
        m_cv_capacity = desired_capacity;
      }
    }
  }
  return rc;
}

bool ON_NurbsCurve::ReserveKnotCapacity(int desired_capacity)
{
  // If m_knot_capacity == 0 and m_knot != nullptr, then the user
  // has hand built the ON_NurbsCurve.m_knot array and is responsible
  // for making sure it's always big enough.
  bool rc = true;
  if ( desired_capacity > m_knot_capacity ) {
    if ( !m_knot ) {
      // no knot array - allocate one
      m_knot = (double*)onmalloc(desired_capacity*sizeof(*m_knot));
      if ( !m_knot ) {
        m_knot_capacity = 0;
        rc = false;
      }
      else {
        m_knot_capacity = desired_capacity;
      }
    }
    else if ( m_knot_capacity > 0 ) {
      // existing m_knot[] is too small and the fact that
      // m_knot_capacity > 0 indicates that ON_NurbsCurve is
      // managing the m_knot[] memory, so we need to grow
      // the m_knot[] array.
      m_knot = (double*)onrealloc(m_knot,desired_capacity*sizeof(*m_knot));
      if ( !m_knot ) {
        rc = false;
        m_knot_capacity = 0;
      }
      else {
        m_knot_capacity = 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<new_degree; i++){
    k=0;
    for (j=0; j<new_degree; j++){
      if (j != i) {
        t[k] = kn[j];
        k++;
      }
    }
    if (!ON_EvaluateNurbsBlossom(cvdim, old_order, 
      old_cvstride, oldCV, oldkn, t, P)){
      onfree((void*)t);
      onfree((void*)P);
      return false;
    }
    for (k=0; k<cvdim; k++) newCV[k] += P[k];
  }

  double denom = (double)new_degree;
  for (i=0; i<cvdim; i++) 
    newCV[i] /= denom;

  onfree((void*)t);
  onfree((void*)P);

  return true;
}


static bool IncrementNurbDegree(ON_NurbsCurve& N)
//for use only in ON_NurbsCurve::IncreaseDegree().  No validation done.
//N is assumed to have sufficient knot and cv capacities, clamped ends

{
  ON_NurbsCurve M = N;
  int span_count = M.SpanCount();
  int new_kcount = M.KnotCount() + span_count + 1;
  N.m_order = M.Order()+1;
  N.m_cv_count = new_kcount - N.Order() + 2;

  //set N's knots
  int i=0;
  int k=0;
  int j;
  while (i<M.CVCount()){
    double kn = M.Knot(i);
    int mult = M.KnotMultiplicity(i);
    for (j=0; j<=mult; j++) {
      N.SetKnot(k, kn);
      k++;
    }
    i+=mult;
  }
  //zero out N's cvs
  memset(N.m_cv, 0, N.m_cv_capacity*sizeof(double));
  int cvdim = N.CVSize();
  const double* cvM;
  double* cvN;
  const double* knotN;
  const double* knotM;
  int siN = 0;
  int siM = 0;

  for (i=0; i<span_count; i++){
    knotN = &N.m_knot[siN];
    knotM = &M.m_knot[siM];
    cvM = M.CV(siM);
    cvN = N.CV(siN);
    int span_mult = N.KnotMultiplicity(siN+N.Degree()-1);
    int skip = N.Order()-span_mult;
    cvN += skip*N.m_cv_stride;
    for (j=skip; j<N.Order(); j++){//calculate each cv of the span
      GetRaisedDegreeCV(M.Order(), cvdim, M.m_cv_stride, cvM, knotM, knotN, j, cvN);
      cvN += N.m_cv_stride;
    }
    siN = ON_NextNurbsSpanIndex(N.Order(), N.CVCount(), N.m_knot, siN);
    siM = ON_NextNurbsSpanIndex(M.Order(), M.CVCount(), M.m_knot, siM);
  }

  //set first and last equal to original
  cvM = M.CV(0);
  cvN = N.CV(0);
  for (i=0; i<cvdim; i++) cvN[i] = cvM[i];

  cvM = M.CV(M.CVCount()-1);
  cvN = N.CV(N.CVCount()-1);
  for (i=0; i<cvdim; i++) cvN[i] = cvM[i];

  return true;
}

bool ON_NurbsCurve::IncreaseDegree( int desired_degree )
{
  if ( desired_degree < 1 || desired_degree < m_order-1 ) return false;
  if ( desired_degree == m_order-1 ) return true;
  if (!ClampEnd(2)) return false;

  int del = desired_degree - Degree();
  int new_order = Order()+del;
  int span_count = SpanCount();
  int new_kcount = KnotCount()+(span_count+1)*del;
  int new_cvcount = new_kcount - new_order + 2;

  if (!ReserveKnotCapacity(new_kcount)) return false;
  if (!ReserveCVCapacity(new_cvcount*m_cv_stride)) return false;

  for (int i=0; i<del; i++) {
    if (!IncrementNurbDegree(*this)) return false;
  }

  return true;
}

bool ON_NurbsCurve::ChangeDimension( int desired_dimension )
{
  bool rc = false;
  int i, j;
  if ( desired_dimension < 1 )
    return false;
  if ( desired_dimension == m_dim )
    return true;

  DestroyCurveTree();

  if ( desired_dimension < m_dim ) 
  {
    if ( m_is_rat ) {
      double* cv;
      for ( i = 0; i < m_cv_count; i++ ) {
        cv = CV(i);
        cv[desired_dimension] = cv[m_dim];
      }
    }
    m_dim = desired_dimension;
    rc = true;
  }
  else 
  {
    const double* cv0;
    double* cv1;
    //const int cv_size0 = CVSize();
    const int cv_size1 = m_is_rat ? desired_dimension + 1 : desired_dimension;
    const int cv_stride1 = (m_cv_stride < cv_size1) ? cv_size1 : m_cv_stride;
    if ( m_cv_stride < cv_stride1 && m_cv_capacity > 0 ) {
      m_cv_capacity = cv_stride1*m_cv_count;
      m_cv = (double*)onrealloc( m_cv, m_cv_capacity*sizeof(*m_cv) );
    }
    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
      //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, 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
      //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, 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
  //   ON_NurbsCurveCopyHelper().  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 )
    ON_NurbsCurve_copy_member_data(*this,curve); // 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 (nullptr == this)
    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 (nullptr == this)
    return failed_rc;

  if (nullptr == this)
    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;
}
