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

#include "opennurbs.h"

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

ON_OBJECT_IMPLEMENT(ON_ArcCurve,ON_Curve,"CF33BE2A-09B4-11d4-BFFB-0010830122F0");

ON_ArcCurve::ON_ArcCurve() ON_NOEXCEPT
{}

ON_ArcCurve::~ON_ArcCurve()
{}

ON_ArcCurve::ON_ArcCurve( const ON_ArcCurve& src )
  : ON_Curve(src)
  , m_arc(src.m_arc)
  , m_t(src.m_t)
  , m_dim(src.m_dim)
{}

ON_ArcCurve& ON_ArcCurve::operator=( const ON_ArcCurve& src )
{
  if ( this != &src ) 
  {
    ON_Curve::operator=(src);
    m_arc = src.m_arc;
    m_t = src.m_t;
    m_dim  = src.m_dim;
  }
  return *this;
}

#if defined(ON_HAS_RVALUEREF)

ON_ArcCurve::ON_ArcCurve( ON_ArcCurve&& src) ON_NOEXCEPT
  : ON_Curve(std::move(src))
  , m_arc(std::move(src.m_arc))
  , m_t(src.m_t)
  , m_dim(src.m_dim)
{
}

ON_ArcCurve& ON_ArcCurve::operator=( ON_ArcCurve&& src)
{
  if ( this != &src ) 
  {
    ON_Curve::operator=(std::move(src));
    m_arc = std::move(src.m_arc);
    m_t = src.m_t;
    m_dim  = src.m_dim;
  }
  return *this;
}

#endif


ON_ArcCurve::ON_ArcCurve( const ON_Arc& A )
{
  m_arc = A;
  m_t.m_t[0] = 0.0;
  m_t.m_t[1] = m_arc.Length();
  if ( m_t.m_t[1] <= 0.0 )
    m_t.m_t[1] = 1.0;
  m_dim = 3;
}

ON_ArcCurve::ON_ArcCurve( const ON_Circle& circle )
{
  ON_ArcCurve::operator=(circle);
}

ON_ArcCurve::ON_ArcCurve( const ON_Arc& A, double t0, double t1 )
{
  m_arc = A;
  m_t.m_t[0] = t0;
  m_t.m_t[1] = t1;
  m_dim = 3;
}

ON_ArcCurve::ON_ArcCurve( const ON_Circle& circle, double t0, double t1 )
{
  m_arc = circle;
  m_t.m_t[0] = t0;
  m_t.m_t[1] = t1;
  m_dim = 3;
}



unsigned int ON_ArcCurve::SizeOf() const
{
  unsigned int sz = ON_Curve::SizeOf();
  sz += sizeof(*this) - sizeof(ON_Curve);
  return sz;
}

ON__UINT32 ON_ArcCurve::DataCRC(ON__UINT32 current_remainder) const
{
  current_remainder = ON_CRC32(current_remainder,sizeof(m_arc),&m_arc);
  current_remainder = ON_CRC32(current_remainder,sizeof(m_t),&m_t);
  current_remainder = ON_CRC32(current_remainder,sizeof(m_dim),&m_dim);
  return current_remainder;
}

ON_ArcCurve& ON_ArcCurve::operator=( const ON_Arc& A )
{
  m_arc = A;
  m_t.m_t[0] = 0.0;
  m_t.m_t[1] = A.Length();
  if ( m_t.m_t[1] == 0.0 )
    m_t.m_t[1] = 1.0;
  m_dim = 3;
  return *this;
}

ON_ArcCurve& ON_ArcCurve::operator=(const ON_Circle& circle)
{
  m_arc = circle;
  m_t.m_t[0] = 0.0;
  m_t.m_t[1] = m_arc.Length();
  if ( m_t.m_t[1] <= 0.0 )
    m_t.m_t[1] = 1.0;
  m_dim = 3;
  return *this;
}

int ON_ArcCurve::Dimension() const
{
  return m_dim;
}

bool ON_ArcCurve::GetBBox( // returns true if successful
         double* boxmin,    // minimum
         double* boxmax,    // maximum
         bool bGrowBox
         ) const
{
  bool rc = m_arc.IsValid();
  if (rc) {
    ON_BoundingBox bbox = m_arc.BoundingBox();
    if ( bGrowBox ) {
      if ( boxmin[0] > bbox.m_min.x ) boxmin[0] = bbox.m_min.x;
      if ( boxmin[1] > bbox.m_min.y ) boxmin[1] = bbox.m_min.y;
      if ( boxmax[0] < bbox.m_max.x ) boxmax[0] = bbox.m_max.x;
      if ( boxmax[1] < bbox.m_max.y ) boxmax[1] = bbox.m_max.y;
      if ( m_dim > 2 ) {
        if ( boxmin[2] > bbox.m_min.z ) boxmin[2] = bbox.m_min.z;
        if ( boxmax[2] < bbox.m_max.z ) boxmax[2] = bbox.m_max.z;
      }
    }
    else {
      boxmin[0] = bbox.m_min.x;
      boxmin[1] = bbox.m_min.y;
      boxmax[0] = bbox.m_max.x;
      boxmax[1] = bbox.m_max.y;
      if ( m_dim > 2 ) {
        boxmin[2] = bbox.m_min.z;
        boxmax[2] = bbox.m_max.z;
      }
    }
  }
  return rc;
}

bool
ON_ArcCurve::Transform( const ON_Xform& xform )
{
  TransformUserData(xform);
	DestroyCurveTree();
  return m_arc.Transform( xform );
}

bool ON_ArcCurve::IsValid( ON_TextLog* text_log ) const
{
  if ( !m_t.IsIncreasing() )
  {
    if ( 0 != text_log )
      text_log->Print("ON_ArcCurve - m_t=(%g,%g) - it should be an increasing interval.\n",m_t[0],m_t[1]);
    return false;
  }

  if ( !m_arc.IsValid() )
  {
    if ( 0 != text_log )
      text_log->Print("ON_ArcCurve m_arc is not valid\n");
    return false;
  }
  // 7-May-21.  GBA. Added conditions to define a degenerate ON_ArcCurve
  // Note: these conditions should be enforced by Trim and Split
  if (m_arc.radius < ON_ZERO_TOLERANCE )
  {
    if (0 != text_log)
      text_log->Print("ON_ArcCurve m_arc.radius < ON_ZERO_TOLERANCE\n");
    return false;
  }
  ON_3dPoint S = PointAtStart();
  ON_3dPoint E = PointAtEnd();
  if( S.IsCoincident(E) != IsCircle())
  {
    if (0 != text_log)
    {
      if (IsCircle() )
        text_log->Print("ON_ArcCurve !Start.IsCoincident(End) an a circle\n");
      else
        text_log->Print("ON_ArcCurve Start.IsCoincident(End) on open arc curve\n");
    }
    return false;
  }


  return true;
}

void ON_ArcCurve::Dump( ON_TextLog& dump ) const
{
  dump.Print( "ON_ArcCurve:  domain = [%g,%g]\n",m_t[0],m_t[1]);
  dump.PushIndent();
  dump.Print( "center = ");
  dump.Print( m_arc.plane.origin );
  dump.Print( "\nradius = %g\n",m_arc.radius);
  dump.Print( "length = %g\n",m_arc.Length());
  ON_3dPoint start = PointAtStart();
  ON_3dPoint end = PointAtEnd();
  dump.Print( "start = "); dump.Print(start);
  dump.Print( "\nend = "); dump.Print(end); dump.Print("\n");
  dump.PopIndent();
}

bool ON_ArcCurve::Write(
       ON_BinaryArchive& file // open binary file
     ) const
{
  bool rc = file.Write3dmChunkVersion(1,0);
  if (rc) 
  {
    rc = file.WriteArc( m_arc );
    if (rc) rc = file.WriteInterval( m_t );
    if (rc) rc = file.WriteInt(m_dim);
  }
  return rc;
}

bool ON_ArcCurve::Read(
       ON_BinaryArchive& file // open binary file
     )
{
  int major_version = 0;
  int minor_version = 0;
  bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
  if (rc)
  {
    if (major_version==1) 
    {
      // common to all 1.x  versions
      rc = file.ReadArc( m_arc );
      if (rc) 
        rc = file.ReadInterval( m_t );
      if (rc) 
        rc = file.ReadInt(&m_dim);
      if ( m_dim != 2 && m_dim != 3 )
        m_dim = 3;
    }
    else
      rc = 0;
  }
  return rc;
}


bool ON_ArcCurve::SetDomain( double t0, double t1 )
{
  bool rc = false;
  if ( t0 < t1 )
  {
    m_t.Set(t0,t1);
    rc = true;
  }
	DestroyCurveTree();
  return rc;
}


bool ON_ArcCurve::ChangeDimension( int desired_dimension )
{
  bool rc = (desired_dimension>=2 && desired_dimension<=3);
  if ( rc && m_dim != desired_dimension )
  {
  	DestroyCurveTree();
    if ( desired_dimension == 2 )
      m_dim = 2;
    else
      m_dim = 3;
  }
  return rc;
}


ON_Interval ON_ArcCurve::Domain() const
{
  return m_t;
}

bool ON_ArcCurve::ChangeClosedCurveSeam( 
            double t ){
	bool rc = false;
	if( IsCircle() ){
		double angle_delta = m_t.NormalizedParameterAt(t);
		angle_delta*= 2*ON_PI;
		
		m_arc.Rotate(angle_delta, m_arc.plane.Normal());
		m_t = ON_Interval( t, m_t[1] + t - m_t[0]);
		rc = true;
	}
	return rc;
}


int ON_ArcCurve::SpanCount() const
{
  return 1;
}

bool ON_ArcCurve::GetSpanVector( double* s ) const
{
  s[0] = m_t[0];
  s[1] = m_t[1];
  return m_t.IsIncreasing();
}

int ON_ArcCurve::Degree() const
{
  return 2;
}


bool
ON_ArcCurve::IsLinear(  // true if curve locus is a line segment
      double // tolerance - formal parameter intentionally ignored in this virtual function
      ) const
{
  return false;  // GBA 23 May 23.  This just seems wrong.  Maybe we should change this very early in WIP and see if anyone notices.
                 //  I think this is the correct implementation
 // return m_arc.IsLinear(tolerance);

}

bool
ON_ArcCurve::IsArc( // true if curve locus in an arc or circle
      const ON_Plane* plane, // if not nullptr, test is performed in this plane
      ON_Arc* arc,         // if not nullptr and true is returned, then arc
                              // arc parameters are filled in
      double tolerance // tolerance to use when checking linearity
      ) const
{
  bool rc = (plane) ? IsInPlane(*plane,tolerance) : true;
  if (arc) 
    *arc = m_arc;
  if (rc)
    rc = IsValid();
  return rc;
}

bool
ON_ArcCurve::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);
  }
  
  if ( plane ) 
    *plane = m_arc.plane;

  return true;
}

bool
ON_ArcCurve::IsInPlane(
      const ON_Plane& plane, // plane to test
      double tolerance // tolerance to use when checking linearity
      ) const
{
  return m_arc.IsInPlane( plane, tolerance );
}

bool 
ON_ArcCurve::IsClosed() const
{
  return m_arc.IsCircle();
}

bool 
ON_ArcCurve::IsPeriodic() const
{
  return m_arc.IsCircle();
}

bool
ON_ArcCurve::Reverse()
{
  bool rc = m_arc.Reverse();
  if (rc)
	{
    m_t.Reverse();
		DestroyCurveTree();
	}	
  return true;
}


bool ON_ArcCurve::SetStartPoint(ON_3dPoint start_point)
{
  if (ON_Curve::SetStartPoint(start_point))
    return true;
  if (IsCircle())
    return false;
  bool rc = false;
  if ( m_dim == 3 || start_point.z == 0.0 )
  {
    ON_3dPoint P;
    ON_3dVector T;
    double t = Domain()[1];
    Ev1Der( t, P, T );
    T = -T;
    ON_Arc a;
    rc = a.Create( P, T, start_point );
    if ( rc )
    {
      a.Reverse();
      m_arc = a;
    }
    else {
      ON_3dPoint end_point = PointAt(Domain()[1]);
      if (end_point.DistanceTo(start_point) < ON_ZERO_TOLERANCE*m_arc.Radius()){
        //make arc into circle
        m_arc.plane.xaxis = end_point - m_arc.Center();
        m_arc.plane.xaxis.Unitize();
        m_arc.plane.yaxis = ON_CrossProduct(m_arc.Normal(), m_arc.plane.xaxis);
        m_arc.plane.yaxis.Unitize();
        m_arc.SetAngleRadians(2.0*ON_PI);
        rc = true;
      }
    }
  }
	DestroyCurveTree();
  return rc;  
}


bool ON_ArcCurve::SetEndPoint(ON_3dPoint end_point)
{
  if (ON_Curve::SetEndPoint(end_point))
    return true;
  if (IsCircle())
    return false;
  bool rc = false;
  if ( m_dim == 3 || end_point.z == 0.0 )
  {
    ON_3dPoint P;
    ON_3dVector T;
    double t = Domain()[0];
    Ev1Der( t, P, T );
    ON_Arc a;
    rc = a.Create( P, T, end_point );
    if ( rc )
    {
      m_arc = a;
    }
    else {
      ON_3dPoint start_point = PointAt(Domain()[0]);
      if (end_point.DistanceTo(start_point) < ON_ZERO_TOLERANCE*m_arc.Radius()){
        //make arc into circle
        m_arc.plane.xaxis = start_point - m_arc.Center();
        m_arc.plane.xaxis.Unitize();
        m_arc.plane.yaxis = ON_CrossProduct(m_arc.Normal(), m_arc.plane.xaxis);
        m_arc.plane.yaxis.Unitize();
        m_arc.SetAngleRadians(2.0*ON_PI);
        rc = true;
      }
    }
  }
	DestroyCurveTree();
  return rc;  
}

bool ON_ArcCurve::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 - formal parameter intentionally ignored in this virtual function
       int* // hint - formal parameter intentionally ignored in this virtual function
       ) const
{
  // The issue here is that ON_PI is a rational approximation of the "real" pi. 
  // Ideally sin(N*pi) would be zero and the bugs of July 2012 and RH-26341 that are
  // discussed below would not exist.  When N is large, N*ON_PI isn't close by any 
  // measure to a multiple of "real" pi.  But, for smallish N that we are likely to
  // encounter in practice, we want sin(N*ON_PI) to be zero in this evaluator.
  // The multiple 4 is used because we felt is was reasonable for somebody to want 
  // this evaluator to apply the same special case handing to a and a + 2.0*ON_PI
  // when fabs(a) <= 2.0*ON_PI.
  static const double sin_of_pi = fabs(sin(4.0*ON_PI)) > fabs(sin(ON_PI))
    ? fabs(sin(4.0*ON_PI)) // the fabs(sin(4.0*ON_PI)) values is used in the tests we performed.
    : fabs(sin(ON_PI));

  static const double cos_of_pi_over_2 = fabs(cos(4.5*ON_PI)) > fabs(cos(0.5*ON_PI))
    ? fabs(cos(4.5*ON_PI)) // the fabs(cos(4.5*ON_PI)) values is used in the tests we performed.
    : fabs(cos(0.5*ON_PI)); 

  ON_3dVector d;
  bool rc = false;
  if ( m_t[0] < m_t[1] ) 
  {
    double rat = m_arc.DomainRadians().Length()/m_t.Length();
    double scale = 1.0;
    double a = m_arc.DomainRadians().ParameterAt( m_t.NormalizedParameterAt(t) );

    // 12 July 2012 Dale Lear
    //   When evaluating circles centered at the origin 
    //   a = ON_PI = 3.1415926535897931, c = -1.0 and s = 1.2246467991473532e-016.
    //   As a result the y coordinates that "should" be zero comes out as
    //   radius*1.2246467991473532e-016.  When the radius is large (1.0e9 in the
    //   bug I was looking at), the y coordinate is big enough to cause other problems.
    //   When I added this comment I failed to insert the bug number, so I cannot
    //   provide more details as of May 6, 2014 and the changes for bug RH-26341.
    double c = cos(a);
    double s = sin(a);


    // This test turned out to be too crude.  The bug RH-26341
    // is one example.  The issue is that the trig function with the
    // largest derivative has more precise sensitivity to changes in
    // angle and in order to get as precise an evaluation as possible,
    // it is important to allow non-zero values of one trig function
    // even when the other is being rounded to +1 or -1.
    //
    ////if ( fabs(c) < ON_EPSILON || fabs(s) > 1.0-ON_EPSILON )
    ////{
    ////  c = 0.0;
    ////  s = s < 0.0 ? -1.0 : 1.0;
    ////}
    ////else if ( fabs(s) < ON_EPSILON || fabs(c) > 1.0-ON_EPSILON )
    ////{
    ////  s = 0.0;
    ////  c = c < 0.0 ? -1.0 : 1.0;
    ////}

    if (fabs(c) <= cos_of_pi_over_2)
    {
      c = 0.0;
      s = s < 0.0 ? -1.0 : 1.0;
    }
    else if (fabs(s) <= sin_of_pi)
    {
      s = 0.0;
      c = c < 0.0 ? -1.0 : 1.0;
    }

    c *= m_arc.radius;
    s *= m_arc.radius;

    ON_3dPoint p = m_arc.plane.origin + c*m_arc.plane.xaxis + s*m_arc.plane.yaxis;
    v[0] = p.x;
    v[1] = p.y;
    if ( m_dim == 3 )
      v[2] = p.z;
    for ( int di = 1; di <= der_count; di++ ) {
      scale*=rat;
      a =  c;
      c = -s;
      s =  a;
      d =  c*m_arc.plane.xaxis + s*m_arc.plane.yaxis;
      v += v_stride;
      v[0] = d.x*scale;
      v[1] = d.y*scale;
      if ( m_dim == 3 )
        v[2] = d.z*scale;
    }
    rc = true;
  }
  return rc;
}

bool ON_ArcCurve::Trim( const ON_Interval& trimt )
{
  bool rc = false;
  if (trimt.IsIncreasing())
  {
    if (m_t.Includes(trimt, true))
    {
      ON_Interval normt = m_t.NormalizedParameterAt(trimt);
      ON_3dPoint S = PointAt(trimt[0]);
      ON_3dPoint E = PointAt(trimt[1]);
      ON_Interval angle = m_arc.DomainRadians().ParameterAt(normt);
      // 7-May-21.  GBA.   New definition of IsValid() enforced.
      // Resulting ON_Arc and ON_ArcCurve must pass IsValid()
      if (angle.Length() > ON_ZERO_TOLERANCE && !S.IsCoincident(E)
        && m_arc.SetAngleIntervalRadians(angle))
      {
        m_t = trimt;
        DestroyCurveTree();
        rc = true;
      }
    }
    /* Allow trim where nothing is removed RH-64768 */
    else if (m_t == trimt)
      rc = true;
  }
  return rc;
}

bool ON_ArcCurve::Extend(
  const ON_Interval& domain
  )

{
  if (IsClosed()) return false;

  double s0, s1;
  bool changed = false;
  GetDomain(&s0, &s1);
  if (domain[0] < s0){
    s0 = domain[0];
    changed = true;
  }
  if (domain[1] > s1){
    s1 = domain[1];
    changed = true;
  }
  if (!changed) return false;

  DestroyCurveTree();

  double a0 = m_arc.Domain().ParameterAt(Domain().NormalizedParameterAt(s0));
  double a1 = m_arc.Domain().ParameterAt(Domain().NormalizedParameterAt(s1));
  if (a1 > a0+2.0*ON_PI) {
    a1 = a0+2.0*ON_PI;
    s1 = Domain().ParameterAt(m_arc.Domain().NormalizedParameterAt(a1));
  }
  m_arc.Trim(ON_Interval(a0, a1));
  SetDomain(s0, s1);
  return true;
}

bool ON_ArcCurve::Split(
    double t,
    ON_Curve*& left_side,
    ON_Curve*& right_side
  ) const
{
  // make sure t is strictly inside the arc's domain
  ON_Interval arc_domain = Domain();
  ON_Interval arc_angles = m_arc.DomainRadians();
  if ( !arc_domain.Includes(t) )
    return false;
  double a = (arc_domain == arc_angles)
           ? t
           : arc_angles.ParameterAt(arc_domain.NormalizedParameterAt(t));
  if ( !arc_angles.Includes(a) )
    return false;

  // make sure input curves are ok.
  ON_ArcCurve* left_arc = 0;
  ON_ArcCurve* right_arc = 0;

  if ( 0 != left_side )
  {
    if ( left_side == right_side )
      return false;
    left_arc = ON_ArcCurve::Cast(left_side);
    if ( 0 == left_arc )
      return false;
    left_arc->DestroyCurveTree();
  }

  if ( 0 != right_side )
  {
    right_arc = ON_ArcCurve::Cast(right_side);
    if ( 0 == right_arc )
      return false;
    right_arc->DestroyCurveTree();
  }

  if ( 0 == left_arc )
  {
    left_arc = new ON_ArcCurve( *this );
  }
  else if ( this != left_arc )
  {
    left_arc->operator=(*this);
  }

  if ( 0 == right_arc )
  {
    right_arc = new ON_ArcCurve( *this );
  }
  else if ( this != right_arc )
  {
    right_arc->operator=(*this);
  }

  bool rc = false;
  if ( this != left_arc )
  {
    rc = left_arc->Trim( ON_Interval( arc_domain[0], t ) );
    if (rc)
     rc = right_arc->Trim( ON_Interval( t, arc_domain[1] ) );
  }
  else
  {
    rc = right_arc->Trim( ON_Interval( t, arc_domain[1] ) );
    if (rc)
      rc = left_arc->Trim( ON_Interval( arc_domain[0], t ) );
  }

  if ( rc )
  {
    if ( 0 == left_side )
      left_side = left_arc;
    if ( 0 == right_side )
      right_side = right_arc;
  }
  else
  {
    if ( 0 == left_side && this != left_arc )
    {
      delete left_arc;
      left_arc = 0;
    }
    if ( 0 == right_side && this != right_arc )
    {
      delete right_arc;
      right_arc = 0;
    }
  }
  return rc;
}




static double ArcDeFuzz( double d )
{
  // 0.0078125 = 1.0/128.0 exactly
  // Using 2^n scale factors insures no loss of precision
  // but preserves fractional values that are multiples of 1/128.
  //
  // Fuzz tol should be scale * 2^m * ON_EPSILON for m >= 1

  double f, i;
  f = modf( d*128.0, &i );
  if ( f != 0.0 && fabs(f) <= 1024.0*ON_EPSILON ) {
    d = i*0.0078125;
  }  
  return d;
}

static bool NurbsCurveArc ( const ON_Arc& arc, int dim, ON_NurbsCurve& nurb )
{ 
  if ( !arc.IsValid() )
    return false;
  // makes a quadratic nurbs arc
  const ON_3dPoint center = arc.Center();
  double angle = arc.AngleRadians();
  ON_Interval dom = arc.DomainRadians();
  const double angle0 = dom[0];
  const double angle1 = dom[1];
  ON_3dPoint start_point = arc.StartPoint();
  //ON_3dPoint mid_point   = arc.PointAt(angle0 + 0.5*angle);
  ON_3dPoint end_point   = arc.IsCircle() ? start_point : arc.EndPoint();

  ON_4dPoint CV[9];
  double knot[10];

	double a, b, c, w, winv;
	double *cv;
	int    j, span_count, cv_count;

	a = (0.5 + ON_SQRT_EPSILON)*ON_PI;

	if (angle <= a)
		span_count = 1;
	else if (angle <= 2.0*a)
		span_count = 2;
	else if (angle <= 3.0*a)
		span_count = 4; // TODO - make a 3 span case
	else
		span_count = 4;

	cv_count = 2*span_count + 1;
	
	switch(span_count) {
	case 1:
    CV[0] = start_point;
    CV[1] = arc.PointAt(angle0 + 0.50*angle);
    CV[2] = end_point;
		break;
	case 2:
    CV[0] = start_point;
    CV[1] = arc.PointAt(angle0 + 0.25*angle);
    CV[2] = arc.PointAt(angle0 + 0.50*angle);
    CV[3] = arc.PointAt(angle0 + 0.75*angle);
    CV[4] = end_point;
		angle *= 0.5;
		break;
	default: // 4 spans
    CV[0] = start_point;
    CV[1] = arc.PointAt(angle0 + 0.125*angle);
    CV[2] = arc.PointAt(angle0 + 0.250*angle);
    CV[3] = arc.PointAt(angle0 + 0.375*angle);
    CV[4] = arc.PointAt(angle0 + 0.500*angle);
    CV[5] = arc.PointAt(angle0 + 0.625*angle);
    CV[6] = arc.PointAt(angle0 + 0.750*angle);
    CV[7] = arc.PointAt(angle0 + 0.875*angle);
    CV[8] = end_point;
		angle *= 0.25;
		break;
	}

	a = cos(0.5*angle);
	b = a - 1.0;
	//c = (radius > 0.0) ? radius*angle : angle;
  c = angle;

	span_count *= 2;
	knot[0] = knot[1] = angle0; //0.0;
	for (j = 1; j < span_count; j += 2) {
    CV[j].x += b * center.x;
    CV[j].y += b * center.y;
    CV[j].z += b * center.z;
    CV[j].w = a;
		CV[j+1].w = 1.0;
		knot[j+1] = knot[j+2] = knot[j-1] + c;
	}
  knot[cv_count-1] = knot[cv_count] = angle1;
  for ( j = 1; j < span_count; j += 2 ) {
    w = CV[j].w;
    winv = 1.0/w;
    a = CV[j].x*winv;
    b = ArcDeFuzz(a);
    if ( a != b ) {
      CV[j].x = b*w;
    }
    a = CV[j].y*winv;
    b = ArcDeFuzz(a);
    if ( a != b ) {
      CV[j].y = b*w;
    }
    a = CV[j].z*winv;
    b = ArcDeFuzz(a);
    if ( a != b ) {
      CV[j].z = b*w;
    }
  }

  nurb.m_dim = (dim==2) ? 2 : 3;
  nurb.m_is_rat = 1;
  nurb.m_order = 3;
  nurb.m_cv_count = cv_count;
  nurb.m_cv_stride = (dim==2 ? 3 : 4);
  nurb.ReserveCVCapacity( nurb.m_cv_stride*cv_count );
  nurb.ReserveKnotCapacity( cv_count+1 );
  for ( j = 0; j < cv_count; j++ ) {
    cv = nurb.CV(j);
    cv[0] = CV[j].x;
    cv[1] = CV[j].y;
    if ( dim == 2 ) {
      cv[2] = CV[j].w;
    }
    else {
      cv[2] = CV[j].z;
      cv[3] = CV[j].w;
    }
    nurb.m_knot[j] = knot[j];
  }
  nurb.m_knot[cv_count] = knot[cv_count];
  return true;
}


int ON_Arc::GetNurbForm( ON_NurbsCurve& nurbscurve ) const

{
  bool rc = NurbsCurveArc ( *this, 3, nurbscurve );
  return (rc) ? 2 : 0;
}

bool ON_Arc::GetRadianFromNurbFormParameter(double NurbParameter, double* RadianParameter  ) const
{
	//  TRR#53994.
	// 16-Sept-09  Replaced this code so we dont use LocalClosestPoint.
	// In addition to being slower than necessary the old method suffered from getting the
	// wrong answer at the seam of a full circle,  This probably only happened with large 
	// coordinates where many digits of precision get lost.

	ON_NurbsCurve crv;
	
	if( !IsValid()|| RadianParameter==nullptr) 
		return false;

	ON_Interval dom= Domain();

	if( fabs(NurbParameter- dom[0])<=2.0*ON_EPSILON*fabs(dom[0]))
	{
		*RadianParameter=dom[0];
		return true;
	} 
	else if(  fabs(NurbParameter- dom[1])<=2.0*ON_EPSILON*fabs(dom[1]))
	{
		*RadianParameter=dom[1];
		return true;
	}

	if( !dom.Includes(NurbParameter) )
		return false;

	if( !GetNurbForm(crv) )
		return false;
		
	ON_3dPoint cp;
	cp = crv.PointAt(NurbParameter);
	cp -= Center();

	double x = ON_DotProduct(Plane().Xaxis(), cp);
	double y = ON_DotProduct(Plane().Yaxis(), cp);
	double theta = atan2(y,x);

	theta -= floor( (theta-dom[0])/(2*ON_PI)) * 2* ON_PI;
	if( theta<dom[0] || theta>dom[1])
	{
		// 24-May-2010 GBA 
		// We got outside of the domain because of a numerical error somewhere.
		// The only case that matters is because we are right near an endpoint.
		// So we need to decide which endpoint to return.  (Other possibilities 
		// are that the radius is way to small relative to the coordinates of the center.
		// In this case the circle is just numerical noise around the center anyway.)
		if( NurbParameter< (dom[0]+dom[1])/2.0)
			theta = dom[0];
		else 
			theta = dom[1];
	}


	// Carefully handle the potential discontinuity of this function
	//  when the domain is a full circle
	if(dom.Length()>.99999*2.0*ON_PI)
	{
		double np_theta = dom.NormalizedParameterAt(theta);
		double np_nurb = dom.NormalizedParameterAt(NurbParameter);
		if( np_nurb<.01 && np_theta>.99)
			theta = dom[0];
		else if( np_nurb>.99 && np_theta<.01)
			theta = dom[1];
	}

	*RadianParameter = theta;

	return true;
}


bool ON_Arc::GetNurbFormParameterFromRadian(double RadianParameter, double* NurbParameter ) const
{
	if(!IsValid() || NurbParameter==nullptr) 
		return false;

  ON_Interval ADomain = DomainRadians();

  double endtol = 10.0*ON_EPSILON*(fabs(ADomain[0]) + fabs(ADomain[1]));

  double del = RadianParameter - ADomain[0];
	if(del <= endtol && del >= -ON_SQRT_EPSILON)
  {
		*NurbParameter=ADomain[0];
		return true;
	} 
  else {
    del = ADomain[1] - RadianParameter;
    if(del <= endtol && del >= -ON_SQRT_EPSILON){
		  *NurbParameter=ADomain[1];
		  return true;
    }
	}

	if( !ADomain.Includes(RadianParameter ) )
		return false;


	ON_NurbsCurve crv;

	if( !GetNurbForm(crv))
		return false;

	//Isolate a bezier that contains the solution
	int cnt = crv.SpanCount();	
	int si =0;	//get span index
	int ki=0;		//knot index
	double ang = ADomain[0];
	ON_3dPoint cp;
	cp = crv.PointAt( crv.Knot(0) ) - Center();
	double x = ON_DotProduct(Plane().Xaxis(),cp);
	double y = ON_DotProduct(Plane().Yaxis(),cp);
	double at = atan2( y, x);	//todo make sure we dont go to far

	for( si=0, ki=0; si<cnt; si++, ki+=crv.KnotMultiplicity(ki) ){
		cp = crv.PointAt( crv.Knot(ki+2)) - Center();
		x = ON_DotProduct(Plane().Xaxis(),cp);
		y = ON_DotProduct(Plane().Yaxis(),cp);
		double at2 = atan2(y,x);
		if(at2>at)
			ang+=(at2-at);
		else
			ang += (2*ON_PI + at2 - at);
		at = at2;
		if( ang>RadianParameter)
			break;
	} 

	// Crash Protection trr#55679
	if( ki+2>= crv.KnotCount())
	{
		 *NurbParameter=ADomain[1];
		 return true;		
	}
	ON_Interval BezDomain(crv.Knot(ki), crv.Knot(ki+2));

	ON_BezierCurve bez;
	if(!crv.ConvertSpanToBezier(ki,bez))
		return false;

 	ON_Xform COC;
	COC.ChangeBasis( ON_Plane(),Plane());   

	
	bez.Transform(COC);	// change coordinates to circles local frame
	double a[3];							// Bez coefficients of a quadratic to solve
	for(int i=0; i<3; i++)
		a[i] = tan(RadianParameter)* bez.CV(i)[0] - bez.CV(i)[1];

	//Solve the Quadratic
	double descrim = (a[1]*a[1]) - a[0]*a[2];
	double squared = a[0]-2*a[1]+a[2];
	double tbez;
	if(fabs(squared)> ON_ZERO_TOLERANCE){
		ON_ASSERT(descrim>=0);
		descrim = sqrt(descrim);
		tbez = (a[0]-a[1] + descrim)/(a[0]-2*a[1]+a[2]);
		if( tbez<0 || tbez>1){
			double tbez2 = (a[0]-a[1]-descrim)/(a[0] - 2*a[1] + a[2]);
			if( fabs(tbez2 - .5)<fabs(tbez-.5) )
				tbez = tbez2;
		}

		ON_ASSERT(tbez>=-ON_ZERO_TOLERANCE && tbez<=1+ON_ZERO_TOLERANCE);
	}
	else{
		// Quadratic degenerates to linear
		tbez = 1.0;
		if(a[0]-a[2])
			tbez = a[0]/(a[0]-a[2]);
	}	
	if(tbez<0)
		tbez=0.0;
	else if(tbez>1.0)
		tbez=1.0;


		//Debug ONLY Code  - check the result
//		double aa = a[0]*(1-tbez)*(1-tbez)  + 2*a[1]*tbez*(1-tbez) + a[2]*tbez*tbez;
//		double tantheta= tan(RadianParameter);
//		ON_3dPoint bezp;
//		bez.Evaluate(tbez, 0, 3, bezp);
//		double yx = bezp.y/bezp.x;


	*NurbParameter = BezDomain.ParameterAt(tbez);
	return true;

}


int ON_ArcCurve::GetNurbForm( // returns 0: unable to create NURBS representation
                 //            with desired accuracy.
                 //         1: success - returned NURBS parameterization
                 //            matches the curve's to wthe desired accuracy
                 //         2: success - returned NURBS point locus matches
                 //            the curve's to the desired accuracy but, on
                 //            the interior of the curve's domain, the 
                 //            curve's parameterization and the NURBS
                 //            parameterization may not match to the 
                 //            desired accuracy.
      ON_NurbsCurve& c,
      double tolerance,
      const ON_Interval* subdomain  // OPTIONAL subdomain of arc
      ) const
{
  int rc = 0;
  if ( subdomain ) 
  {
    ON_ArcCurve trimmed_arc(*this);
    if ( trimmed_arc.Trim(*subdomain) ) 
    {
      rc = trimmed_arc.GetNurbForm( c, tolerance, nullptr );
    }
  }
  else if ( m_t.IsIncreasing() && m_arc.IsValid() ) 
  {
    if ( NurbsCurveArc( m_arc, m_dim, c ) )
    {
      rc = 2;
      c.SetDomain( m_t[0], m_t[1] );
    }
  }
  return rc;
}

int ON_ArcCurve::HasNurbForm( // returns 0: unable to create NURBS representation
                 //            with desired accuracy.
                 //         1: success - returned NURBS parameterization
                 //            matches the curve's to wthe desired accuracy
                 //         2: success - returned NURBS point locus matches
                 //            the curve's to the desired accuracy but, on
                 //            the interior of the curve's domain, the 
                 //            curve's parameterization and the NURBS
                 //            parameterization may not match to the 
                 //            desired accuracy.
                 ) const

{
  if (!IsValid())
    return 0;
  return 2;
}

bool ON_ArcCurve::GetCurveParameterFromNurbFormParameter(
      double nurbs_t,
      double* curve_t
      ) const
{
  double radians;

  double arcnurb_t = m_arc.DomainRadians().ParameterAt(m_t.NormalizedParameterAt(nurbs_t));

  bool rc = m_arc.GetRadianFromNurbFormParameter(arcnurb_t,&radians);
  *curve_t = m_t.ParameterAt( m_arc.DomainRadians().NormalizedParameterAt(radians) );
  
  return rc;
}

bool ON_ArcCurve::GetNurbFormParameterFromCurveParameter(
      double curve_t,
      double* nurbs_t
      ) const
{
  double radians = m_arc.DomainRadians().ParameterAt(m_t.NormalizedParameterAt(curve_t));
  double arcnurb_t;
  bool rc = m_arc.GetNurbFormParameterFromRadian(radians,&arcnurb_t);
  if (rc)        // Oct 29, 2009 - Dale Lear added condition to set *nurbs_t = curve_t
    *nurbs_t = m_t.ParameterAt(m_arc.DomainRadians().NormalizedParameterAt(arcnurb_t));
  else
    *nurbs_t = curve_t;
  return rc;
}

bool ON_ArcCurve::IsCircle() const
{
  return m_arc.IsCircle() ? true : false;
}

double ON_ArcCurve::Radius() const
{
	return m_arc.Radius();
}
  
double ON_ArcCurve::AngleRadians() const
{
	return m_arc.AngleRadians();
}

double ON_ArcCurve::AngleDegrees() const
{
	return m_arc.AngleDegrees();
}

/*
Description:
  ON_CircleCurve is obsolete.  
  This code exists so v2 files can be read.
*/
class ON__OBSOLETE__CircleCurve : public ON_ArcCurve
{
public: 
  static const ON_ClassId m_ON__OBSOLETE__CircleCurve_class_rtti;
  const ON_ClassId* ClassId() const;
  bool Read(
         ON_BinaryArchive&  // open binary file
       );
};

static ON_Object* CreateNewON_CircleCurve() 
{

  // must create an ON_CircleCurve so virtual 
  // ON_CircleCurve::Read will be used to read
  // archive objects with uuid CF33BE29-09B4-11d4-BFFB-0010830122F0
  return new ON__OBSOLETE__CircleCurve();
} 

const ON_ClassId ON__OBSOLETE__CircleCurve::m_ON__OBSOLETE__CircleCurve_class_rtti(
    "ON__OBSOLETE__CircleCurve",
    "ON_ArcCurve",
    CreateNewON_CircleCurve,
    "CF33BE29-09B4-11d4-BFFB-0010830122F0"
    );

const ON_ClassId* ON__OBSOLETE__CircleCurve::ClassId() const 
{
  // so write will save ON_ArcCurve uuid
  return &ON_CLASS_RTTI(ON_ArcCurve);
}

bool ON__OBSOLETE__CircleCurve::Read(
       ON_BinaryArchive& file // open binary file
     )
{
  int major_version = 0;
  int minor_version = 0;
  bool rc = file.Read3dmChunkVersion(&major_version,&minor_version);
  if (rc)
  {
    if (major_version==1) 
    {
      // common to all 1.x versions
      ON_Circle circle;
      rc = file.ReadCircle( circle );
      m_arc = circle;
      if (rc) 
        rc = file.ReadInterval( m_t );
      if (rc) 
        rc = file.ReadInt(&m_dim);
      if ( m_dim != 2 && m_dim != 3 )
        m_dim = 3;
    }
  }

  return rc;
}
