// // Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// #include "opennurbs.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif ON_OBJECT_IMPLEMENT(ON_PolylineCurve, ON_Curve, "4ED7D4E6-E947-11d3-BFE5-0010830122F0"); ON_PolylineCurve::ON_PolylineCurve() ON_NOEXCEPT : m_dim(3) {} ON_PolylineCurve::~ON_PolylineCurve() {} ON_PolylineCurve::ON_PolylineCurve(const ON_PolylineCurve& src) : ON_Curve(src) // copies user data , m_pline(src.m_pline) , m_t(src.m_t) , m_dim(src.m_dim) {} ON_PolylineCurve& ON_PolylineCurve::operator=(const ON_PolylineCurve& src) { if (this != &src) { ON_Curve::operator=(src); m_pline = src.m_pline; m_t = src.m_t; m_dim = src.m_dim; } return *this; } #if defined(ON_HAS_RVALUEREF) ON_PolylineCurve::ON_PolylineCurve(ON_PolylineCurve&& src) ON_NOEXCEPT : ON_Curve(std::move(src)) // moves userdata , m_pline(std::move(src.m_pline)) , m_t(std::move(src.m_t)) , m_dim(src.m_dim) {} ON_PolylineCurve& ON_PolylineCurve::operator=(ON_PolylineCurve&& src) { if (this != &src) { ON_Curve::operator=(std::move(src)); // moves userdata m_pline = std::move(src.m_pline); m_t = std::move(src.m_t); m_dim = src.m_dim; } return *this; } #endif ON_PolylineCurve::ON_PolylineCurve(const ON_3dPointArray& points, const ON_SimpleArray& params) { *this = points; if (points.Count() == params.Count()) { for (int i = 1; i < params.Count(); i++) { if (params[i-1] >= params[i]) return; } m_t = params; } } ON_PolylineCurve::ON_PolylineCurve( const ON_3dPointArray& L ) { *this = L; } unsigned int ON_PolylineCurve::SizeOf() const { unsigned int sz = ON_Curve::SizeOf(); sz += (sizeof(*this) - sizeof(ON_Curve)); sz += m_pline.SizeOfArray(); sz += m_t.SizeOfArray(); return sz; } ON__UINT32 ON_PolylineCurve::DataCRC(ON__UINT32 current_remainder) const { current_remainder = m_pline.DataCRC(current_remainder); current_remainder = m_t.DataCRC(current_remainder); current_remainder = ON_CRC32(current_remainder,sizeof(m_dim),&m_dim); return current_remainder; } void ON_PolylineCurve::EmergencyDestroy() { m_pline.EmergencyDestroy(); m_t.EmergencyDestroy(); } ON_PolylineCurve& ON_PolylineCurve::operator=( const ON_3dPointArray& src ) { m_pline = src; m_dim = 3; const int count = src.Count(); m_t.Reserve(count); m_t.SetCount(count); int i; for (i = 0; i < count; i++) { m_t[i] = (double)i; } return *this; } int ON_PolylineCurve::Dimension() const { return m_dim; } bool ON_PolylineCurve::GetBBox( // returns true if successful double* boxmin, // minimum double* boxmax, // maximum bool bGrowBox ) const { return ON_GetPointListBoundingBox( m_dim, false, PointCount(), 3, m_pline[0], boxmin, boxmax, bGrowBox?true:false ); } bool ON_PolylineCurve::Transform( const ON_Xform& xform ) { TransformUserData(xform); DestroyCurveTree(); return m_pline.Transform( xform ); } bool ON_PolylineCurve::SwapCoordinates( int i, int j ) { DestroyCurveTree(); return m_pline.SwapCoordinates(i,j); } bool ON_PolylineCurve::IsValid( ON_TextLog* text_log ) const { const int count = PointCount(); bool rc = (count >= 2 && count == m_t.Count()); if (rc) { if ( !m_pline.IsValid() ) { if ( 0 != text_log ) { text_log->Print("PolylineCurve m_pline[] is not valid.\n"); } return ON_IsNotValid(); } int i; for ( i = 1; i < count; i++ ) { if ( m_t[i] <= m_t[i-1] ) { if ( 0 != text_log ) { text_log->Print("PolylineCurve m_t[%d]=%g should be less than m_t[%d]=(%g).\n", i-1,m_t[i-1],i,m_t[i]); } return ON_IsNotValid(); } // 7-May-21. GBA Added this condition for IsValid if (m_pline[i].IsCoincident(m_pline[i - 1])) { if (0 != text_log) { text_log->Print("PolylineCurve m_pline[%d].IsCoincident( m_pline[%d]).\n", i - 1, i ); } return ON_IsNotValid(); } } if (m_dim < 2 || m_dim > 3 ) { if (0 != text_log ) text_log->Print("PolylineCurve m_dim = %d (should be 2 or 3).\n",m_dim); return ON_IsNotValid(); } } else if ( 0 != text_log ) { if ( count < 2 ) text_log->Print("PolylineCurve has %d points (should be >= 2)\n",count); else text_log->Print("PolylineCurve m_t.Count() = %d and PointCount() = %d (should be equal)\n", m_t.Count(),count); return ON_IsNotValid(); } return rc; } void ON_PolylineCurve::Dump( ON_TextLog& dump ) const { ON_Interval d = Domain(); dump.Print( "ON_PolylineCurve: domain = [%g,%g]\n",d[0],d[1]); for ( int i = 0; i < PointCount(); i++ ) { dump.Print( " point[%2d] = ",i); dump.Print( m_pline[i] ); dump.Print( ", %g\n",m_t[i]); } } bool ON_PolylineCurve::Write( ON_BinaryArchive& file ) const { bool rc = file.Write3dmChunkVersion(1,0); if (rc) { if (rc) rc = file.WriteArray( m_pline ); if (rc) rc = file.WriteArray( m_t ); if (rc) rc = file.WriteInt(m_dim); } return rc; } bool ON_PolylineCurve::Read( ON_BinaryArchive& file ) { 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 if (rc) rc = file.ReadArray( m_pline ); if (rc) rc = file.ReadArray( m_t ); if (rc) rc = file.ReadInt(&m_dim); } return rc; } ON_Interval ON_PolylineCurve::Domain() const { ON_Interval d; //bool rc = false; const int count = PointCount(); if ( count >= 2 && m_t[0] < m_t[count-1] ) { d.Set(m_t[0],m_t[count-1]); } return d; } bool ON_PolylineCurve::SetDomain( double t0, double t1 ) { bool rc = false; const int count = m_t.Count()-1; if ( count >= 1 ) { if ( t0 == m_t[0] && t1 == m_t[count] ) rc = true; else if ( t0 < t1 ) { const ON_Interval old_domain = Domain(); const ON_Interval new_domain(t0,t1); m_t[0] = t0; m_t[count] = t1; for ( int i = 1; i < count; i++ ) { m_t[i] = new_domain.ParameterAt( old_domain.NormalizedParameterAt(m_t[i]) ); } rc=true; } } DestroyCurveTree(); return rc; } bool ON_PolylineCurve::ChangeDimension( int desired_dimension ) { bool rc = (desired_dimension>=2 && desired_dimension<=3); if ( rc && m_dim != desired_dimension ) { DestroyCurveTree(); int i, count = m_pline.Count(); if ( 2 == desired_dimension ) { if ( count > 0 ) { // 7 April 2003 Dale Lear: // If x coord of first point is set, then // zero all z coords. if ( ON_UNSET_VALUE != m_pline[0].x ) { for ( i = 0; i < count; i++ ) m_pline[i].z = 0.0; } } m_dim = 2; } else { if ( count > 0 ) { // 7 April 2003 Dale Lear: // If first point x coord is set and z is unset, then // zero all z coords. if ( ON_UNSET_VALUE != m_pline[0].x && ON_UNSET_VALUE == m_pline[0].z ) { for ( i = 0; i < count; i++ ) m_pline[i].z = 0.0; } } m_dim = 3; } } return rc; } bool ON_PolylineCurve::ChangeClosedCurveSeam( double t ) { const ON_Interval old_dom = Domain(); bool rc = IsClosed(); if ( rc ) { double k = t; if ( !old_dom.Includes(t) ) { // If you know why this is here, please add // a bug reference so we can retest when this code // is cleaned up. It needs to be cleaned up. double s = old_dom.NormalizedParameterAt(t); s = fmod(s,1.0); if ( s < 0.0 ) s += 1.0; k = old_dom.ParameterAt(s); } if ( old_dom.Includes(k,true) ) { int old_count = PointCount(); int i = ON_NurbsSpanIndex(2,old_count,m_t.Array(),k,0,0); if ( k < m_t[i] ) return false; if ( k >= m_t[i+1] ) return false; // 20 Feb 2014 Dale L & Lowell - This was making a new point in the polyline // when the seam was changed to a t within eps of an existing point. // This change snaps the t to existing points if it is within normalized_span_tol // of one that already exists. ON_EPSILON didn't quite work but 8 * ON_EPSILON does // comparing t = 4149.8519999999990 to m_t[1] = 4149.8519999999980 in RH-24591 ON_Interval span_domain(m_t[i],m_t[i+1]); double s = span_domain.NormalizedParameterAt(k); double normalized_span_tol = 8.0*ON_EPSILON; if ( s <= normalized_span_tol ) { k = span_domain[0]; } else if ( s >= 1.0 - normalized_span_tol ) { k = span_domain[1]; i = ON_NurbsSpanIndex(2,old_count,m_t.Array(),k,0,0); } if ( k == old_dom[0] || k == old_dom[1] ) { // k already at start end of this curve rc = true; } else { int new_count = (k==m_t[i]) ? old_count : old_count+1; ON_SimpleArray new_pt(new_count); ON_SimpleArray new_t(new_count); ON_3dPoint new_start = (k==m_t[i]) ? m_pline[i] : PointAt(k); new_pt.Append( new_start ); new_t.Append(k); int n = old_count-i-1; new_pt.Append( n, m_pline.Array() + i+1 ); new_t.Append( n, m_t.Array() + i+1 ); int j = new_t.Count(); n = new_count-old_count+i-1; new_pt.Append( n, m_pline.Array() + 1 ); new_t.Append( n, m_t.Array() + 1 ); new_pt.Append( new_start ); new_t.Append(k); double d = old_dom.Length(); while ( j < new_t.Count() ) { new_t[j] += d; j++; } m_pline = new_pt; m_t = new_t; } } else { // k already at start end of this curve rc = true; } if ( rc && t != old_dom[0] ) SetDomain( t, t + old_dom.Length() ); } return rc; } int ON_PolylineCurve::SpanCount() const { return m_pline.SegmentCount(); } bool ON_PolylineCurve::GetSpanVector( // span "knots" double* s // array of length SpanCount() + 1 ) const { bool rc = false; const int count = PointCount(); if ( count >= 1 ) { memcpy( s, m_t.Array(), count*sizeof(*s) ); rc = true; } return rc; } int ON_PolylineCurve::Degree() const { return 1; } bool ON_PolylineCurve::IsLinear( // true if curve locus is a line segment double tolerance // tolerance to use when checking linearity ) const { bool rc = false; ON_NurbsCurve nurbs_curve; nurbs_curve.m_dim = m_dim; nurbs_curve.m_is_rat = 0; nurbs_curve.m_order = 2; nurbs_curve.m_cv_count = m_pline.Count(); if ( nurbs_curve.m_cv_count >= 2 ) { nurbs_curve.m_cv = const_cast(&m_pline[0].x); nurbs_curve.m_cv_stride = (int)(&m_pline[1].x - nurbs_curve.m_cv); // the int converts 64 bit size_t nurbs_curve.m_knot = const_cast(m_t.Array()); // using ptr to make sure we go through vtable const ON_Curve* ptr = &nurbs_curve; rc = ptr->IsLinear(tolerance); nurbs_curve.m_cv = 0; nurbs_curve.m_knot = 0; } return rc; } int ON_PolylineCurve::IsPolyline( ON_SimpleArray* pline_points, ON_SimpleArray* pline_t ) const { if ( pline_points ) pline_points->SetCount(0); if ( pline_t ) pline_t->SetCount(0); int rc = this->PointCount(); if ( rc >= 2 ) { if ( pline_points ) pline_points->operator=(m_pline); if ( pline_t ) pline_t->operator=(m_t); } else rc = 0; return rc; } bool ON_PolylineCurve::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 { return false; } bool ON_PolylineCurve::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 { bool rc = false; ON_NurbsCurve nurbs_curve; nurbs_curve.m_dim = m_dim; nurbs_curve.m_is_rat = 0; nurbs_curve.m_order = 2; nurbs_curve.m_cv_count = m_pline.Count(); if ( nurbs_curve.m_cv_count >= 2 ) { if (m_dim == 2 ) { rc = ON_Curve::IsPlanar(plane,tolerance); } else { nurbs_curve.m_cv = const_cast(&m_pline[0].x); nurbs_curve.m_cv_stride = (int)(&m_pline[1].x - nurbs_curve.m_cv); // the (int) converts 64 bit size_t nurbs_curve.m_knot = const_cast(m_t.Array()); // using ptr to make sure we go through vtable const ON_Curve* ptr = &nurbs_curve; rc = ptr->IsPlanar(plane,tolerance); nurbs_curve.m_cv = 0; nurbs_curve.m_knot = 0; } } return rc; } bool ON_PolylineCurve::IsInPlane( const ON_Plane& plane, // plane to test double tolerance // tolerance to use when checking linearity ) const { bool rc = false; ON_NurbsCurve nurbs_curve; nurbs_curve.m_dim = m_dim; nurbs_curve.m_is_rat = 0; nurbs_curve.m_order = 2; nurbs_curve.m_cv_count = m_pline.Count(); if ( nurbs_curve.m_cv_count >= 2 ) { nurbs_curve.m_cv = const_cast(&m_pline[0].x); nurbs_curve.m_cv_stride = (int)(&m_pline[1].x - nurbs_curve.m_cv); nurbs_curve.m_knot = const_cast(m_t.Array()); rc = nurbs_curve.IsInPlane(plane,tolerance); nurbs_curve.m_cv = 0; nurbs_curve.m_knot = 0; } return rc; } bool ON_PolylineCurve::IsClosed() const { return m_pline.IsClosed(0.0); } bool ON_PolylineCurve::IsPeriodic() const { return false; } bool ON_PolylineCurve::GetNextDiscontinuity( ON::continuity c, double t0, double t1, double* t, int* hint, int* dtype, double cos_angle_tolerance, double curvature_tolerance ) const { bool rc = false; const int segment_count = m_pline.SegmentCount(); if ( segment_count > 0 && t0 != t1 ) { ON_Interval domain = Domain(); if ( t0 < t1 ) { if ( t0 < domain[0] ) t0 = domain[0]; if ( t1 > domain[1] ) t1 = domain[1]; if ( t0 >= t1 ) return false; } else if ( t0 > t1 ) { if ( t1 < domain[0] ) t1 = domain[0]; if ( t0 > domain[1] ) t0 = domain[1]; if ( t1 >= t0 ) return false; } if ( t0 != t1 ) { ON_3dPoint Pm, Pp; ON_3dVector D1m, D1p, Tm, Tp; if ( dtype ) *dtype = 0; c = ON::PolylineContinuity((int)c); ON::continuity parametric_c = ON::ParametricContinuity((int)c); if ( segment_count >= 2 && parametric_c != ON::continuity::C0_continuous ) { int i = 0; int delta_i = 1; double s0 = t0; double s1 = t1; i = ON_NurbsSpanIndex(2,PointCount(),m_t,t0,0,(hint)?*hint:0); double segtol = (fabs(m_t[i]) + fabs(m_t[i+1]) + fabs(m_t[i+1]-m_t[i]))*ON_SQRT_EPSILON; if ( t0 < t1 ) { if ( t0 < m_t[i+1] && t1 > m_t[i+1] && (m_t[i+1]-t0) <= segtol && i+1 < PointCount() ) { t0 = m_t[i+1]; i = ON_NurbsSpanIndex(2,PointCount(),m_t,t0,0,(hint)?*hint:0); } if ( hint ) *hint = i; i++; // start checking at first m_t[i] > t0 } else if ( t0 > t1 ) { // Check backwards (have to handle this case so // ON_CurveProxy::GetNextDiscontinuity() works on // reversed proxy curves. if ( t0 > m_t[i] && t1 < m_t[i] && (t0-m_t[i]) <= segtol && i > 0 ) { t0 = m_t[i]; i = ON_NurbsSpanIndex(2,PointCount(),m_t,t0,0,(hint)?*hint:0); } if ( hint ) *hint = i; if ( t0 == m_t[i] ) i--; delta_i = -1; s0 = t1; s1 = t0; } for ( /*empty*/; !rc && 0 < i && i < segment_count && s0 < m_t[i] && m_t[i] < s1; i += delta_i ) { Ev1Der(m_t[i], Pm, D1m, -1, hint ); Ev1Der(m_t[i], Pp, D1p, +1, hint ); if ( parametric_c == ON::continuity::C1_continuous || parametric_c == ON::continuity::C2_continuous ) { if ( !(D1m-D1p).IsTiny(D1m.MaximumCoordinate()*ON_SQRT_EPSILON) ) rc = true; } else if ( parametric_c == ON::continuity::G1_continuous || parametric_c == ON::continuity::G2_continuous || parametric_c == ON::continuity::Gsmooth_continuous ) { Tm = D1m; Tp = D1p; Tm.Unitize(); Tp.Unitize(); if ( Tm*Tp < cos_angle_tolerance ) rc = true; } if ( rc ) { if ( dtype ) *dtype = 1; if ( t ) *t = m_t[i]; break; } } } if ( !rc && segment_count > 0 && parametric_c != c ) { // 20 March 2003 Dale Lear: // Let base class test for locus continuities at start/end. rc = ON_Curve::GetNextDiscontinuity( c, t0, t1, t, hint, dtype, cos_angle_tolerance, curvature_tolerance ); } } } return rc; } bool ON_PolylineCurve::IsContinuous( ON::continuity desired_continuity, double t, int* hint, // default = nullptr, double point_tolerance, // default=ON_ZERO_TOLERANCE double d1_tolerance, // default==ON_ZERO_TOLERANCE double d2_tolerance, // default==ON_ZERO_TOLERANCE double cos_angle_tolerance, // default==ON_DEFAULT_ANGLE_TOLERANCE_COSINE double curvature_tolerance // default==ON_SQRT_EPSILON ) const { bool rc = true; const int segment_count = m_pline.SegmentCount(); if ( segment_count >= 1 ) { bool bPerformTest = false; desired_continuity = ON::PolylineContinuity((int)desired_continuity); if ( t <= m_t[0] || t >= m_t[segment_count] ) { // 20 March 2003 Dale Lear // Consistently handles locus case and out of domain case. switch(desired_continuity) { case ON::continuity::C0_locus_continuous: case ON::continuity::C1_locus_continuous: case ON::continuity::G1_locus_continuous: bPerformTest = true; break; default: // intentionally ignoring other ON::continuity enum values break; } } else { if ( segment_count >= 2 && desired_continuity != ON::continuity::C0_continuous ) { int i = ON_NurbsSpanIndex(2,PointCount(),m_t,t,0,(hint)?*hint:0); { // 20 March 2003 Dale Lear: // If t is very near interior m_t[] value, see if it // should be set to that value. A bit or two of // precision sometimes gets lost in proxy // domain to real curve domain conversions on the interior // of a curve domain. double segtol = (fabs(m_t[i]) + fabs(m_t[i+1]) + fabs(m_t[i+1]-m_t[i]))*ON_SQRT_EPSILON; if ( m_t[i]+segtol < m_t[i+1]-segtol ) { if ( fabs(t-m_t[i]) <= segtol && i > 0 ) { t = m_t[i]; } else if ( fabs(t-m_t[i+1]) <= segtol && i+1 < PointCount() ) { t = m_t[i+1]; i = ON_NurbsSpanIndex(2,PointCount(),m_t,t,0,(hint)?*hint:0); } } } if ( hint ) *hint = i; if ( i > 0 && i < segment_count && t == m_t[i] ) { // "locus" and "parametric" tests are the same at this point. desired_continuity = ON::ParametricContinuity((int)desired_continuity); bPerformTest = true; } } } if ( bPerformTest ) { // need to evaluate and test rc = ON_Curve::IsContinuous( desired_continuity, t, hint, point_tolerance, d1_tolerance, d2_tolerance, cos_angle_tolerance, curvature_tolerance ); } } return rc; } bool ON_PolylineCurve::Reverse() { bool rc = false; const int count = PointCount(); if ( count >= 2 ) { m_pline.Reverse(); m_t.Reverse(); double* t = m_t.Array(); for ( int i = 0; i < count; i++ ) { t[i] = -t[i]; } rc = true; } DestroyCurveTree(); return rc; } bool ON_PolylineCurve::SetStartPoint( ON_3dPoint start_point ) { // 10 March 2009 Dale Lear // I'm using exact compare instead of the fuzzy IsClosed() // check to permit setting the start point. This fixes // a bug Mikko reported that prevented making polylines // exactly closed when the end points were almost exactly // equal. At this point, I don't remember why we don't allow // SetStartPoint() the start point of a closed curve. if (ON_Curve::SetStartPoint(start_point)) return true; bool rc = false; int count = m_pline.Count(); if ( count >= 2 && ( !m_pline[0].IsValid() || m_pline[count-1].x != m_pline[0].x // used to call IsClosed() || m_pline[count-1].y != m_pline[0].y || m_pline[count-1].z != m_pline[0].z ) ) { m_pline[0] = start_point; rc = true; } DestroyCurveTree(); return rc; } bool ON_PolylineCurve::SetEndPoint( ON_3dPoint end_point ) { // 10 March 2009 Dale Lear // I'm using exact compare instead of the fuzzy IsClosed() // check to permit setting the start point. This fixes // a bug Mikko reported that prevented making polylines // exactly closed when the end points were almost exactly // equal. At this point, I don't remember why we don't allow // SetEndPoint() the end point of a closed curve. if (ON_Curve::SetEndPoint(end_point)) return true; bool rc = false; int count = m_pline.Count(); if ( count >= 2 && ( !m_pline[count-1].IsValid() || m_pline[count-1].x != m_pline[0].x // used to call IsClosed() || m_pline[count-1].y != m_pline[0].y || m_pline[count-1].z != m_pline[0].z ) ) { m_pline[count-1] = end_point; rc = true; } DestroyCurveTree(); return rc; } bool ON_PolylineCurve::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; const int count = PointCount(); if ( count >= 2 ) { int segment_index = ON_NurbsSpanIndex(2,count,m_t,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_t[segment_index], m_t[segment_index+1], &a) ) { // recalculate segment index t = a; segment_index = ON_NurbsSpanIndex(2,count,m_t,t,side,segment_index); } } const double t0 = m_t[segment_index]; const double t1 = m_t[segment_index+1]; double s = (t == t1) ? 1.0 : (t-t0)/(t1-t0); const ON_3dPoint p = (1.0-s)*m_pline[segment_index] + s*m_pline[segment_index+1]; v[0] = p.x; v[1] = p.y; if ( m_dim == 3 ) v[2] = p.z; if ( der_count >= 1 ) { v += v_stride; ON_3dVector d = 1.0/(t1-t0)*(m_pline[segment_index+1] - m_pline[segment_index]); v[0] = d.x; v[1] = d.y; if ( m_dim == 3 ) v[2] = d.z; } for ( int di = 2; di <= der_count; di++ ) { v += v_stride; v[0] = 0.0; v[1] = 0.0; if ( m_dim == 3 ) v[2] = 0.0; } if ( hint ) *hint = segment_index; rc = true; } return rc; } int ON_PolylineCurve::PointCount() const { return m_pline.PointCount(); } bool ON_PolylineCurve::Append( const ON_PolylineCurve& c ) { if ( PointCount() == 0 ) { *this = c; return IsValid() ? true : false; } if (!IsValid() || !c.IsValid()) return false; if ( c.Dimension() == 3 && Dimension() == 2) m_dim = 3; m_pline.Remove(); m_pline.Append(c.m_pline.Count(), c.m_pline.Array()); m_t.Reserve(m_t.Count()+c.m_t.Count()-1); double del = *m_t.Last() - c.m_t[0]; int i; for (i=1; i= 0 && s0 <= segment_count) { actual_trim_domain[0]=m_t[s0]; } } if ( ParameterSearch(output_domain[1], s1, true ) ) { if (s1 >= 0 && s1 <= segment_count ) { // ParameterSearch says domain[1] is within "microtol" of // m_t[s1]. So we will actually trim at m_t[s1]. actual_trim_domain[1]=m_t[s1]; s1--; } } if ( !actual_trim_domain.IsIncreasing() ) { // After microtol snapping, there is not enough curve left to trim. return false; } if ( s0 < 0 || s0 > s1 || s1 >= segment_count ) { // Because output_domain is a subinterval of original_polyline_domain, // the only way that (s0 < 0 || s0 > s1 || s1 >= segment_count) can be true // is if something is seriously wrong with the m_t[] values. return false; } // we will begin modifying the polyline DestroyCurveTree(); if ( actual_trim_domain == original_polyline_domain ) { // ParameterSearch says that the ends of output_domain // were microtol away from being the entire curve. // Set the domain and return. m_t[0] = output_domain[0]; m_t[segment_count] = output_domain[1]; return true; } if ( s1 < segment_count-1 ) { m_t.SetCount(s1+2); m_pline.SetCount(s1+2); segment_count = s1+1; } if ( s0 > 0 ) { double* tmp_t = m_t.Array(); ON_3dPoint* tmp_P = m_pline.Array(); for ( i = 0, j = s0; j <= segment_count; i++, j++ ) { tmp_t[i] = tmp_t[j]; tmp_P[i] = tmp_P[j]; } s1 -= s0; s0 = 0; m_t.SetCount(s1+2); m_pline.SetCount(s1+2); segment_count = s1+1; } bool bTrimFirstSegment = ( m_t[0] < actual_trim_domain[0] || (0 == s1 && actual_trim_domain[1] < m_t[1]) ); bool bTrimLastSegment = (s1>s0 && m_t[s1] < actual_trim_domain[1] && actual_trim_domain[1] < m_t[s1+1]); if ( bTrimFirstSegment ) { ON_Interval seg_domain(m_t[0],m_t[1]); ON_3dPoint Q0 = m_pline[0]; ON_3dPoint Q1 = m_pline[1]; ON_Line seg_chord(Q0,Q1); double np0 = 0.0; double np1 = 1.0; bool bSet0 = false; bool bSet1 = false; if ( m_t[0] < actual_trim_domain[0] && actual_trim_domain[0] < m_t[1] ) { np0 = seg_domain.NormalizedParameterAt(actual_trim_domain[0]); Q0 = seg_chord.PointAt( np0 ); bSet0 = true; } if ( 0 == s1 && m_t[0] < actual_trim_domain[1] && actual_trim_domain[1] < m_t[1] ) { np1 = seg_domain.NormalizedParameterAt(actual_trim_domain[1]); Q1 = seg_chord.PointAt( np1 ); bSet1 = true; } if ( np0 >= np1 ) return false; // trim is not viable if ( bSet0 ) { if ( np0 >= 1.0-ON_SQRT_EPSILON && Q0.DistanceTo(Q1) <= ON_ZERO_TOLERANCE && s1>0 && m_t[1] < actual_trim_domain[1] ) { // trim will leave a micro segment at the start - just remove the first segment m_t.Remove(0); m_pline.Remove(0); s1--; segment_count--; actual_trim_domain[0] = m_t[0]; } m_t[0] = actual_trim_domain[0]; m_pline[0] = Q0; } if ( bSet1 ) { m_t[1] = actual_trim_domain[1]; m_pline[1] = Q1; } } if ( bTrimLastSegment ) { ON_Interval seg_domain(m_t[s1],m_t[s1+1]); ON_3dPoint Q0 = m_pline[s1]; ON_3dPoint Q1 = m_pline[s1+1]; ON_Line seg_chord(Q0,Q1); double np = seg_domain.NormalizedParameterAt(actual_trim_domain[1]); Q1 = seg_chord.PointAt(np); if ( np <= ON_SQRT_EPSILON && Q1.DistanceTo(Q0) <= ON_ZERO_TOLERANCE && s1 > 0 ) { // trim will leave a micro segment at the end - just remove the last segment m_pline.SetCount(s1+1); m_t.SetCount(s1+1); s1--; segment_count--; actual_trim_domain[1] = m_t[s1+1]; } m_t[s1+1] = actual_trim_domain[1]; m_pline[s1+1] = Q1; } // If we get this far, trims were is successful. // The following makes potential tiny adjustments // that need to happen when trims get snapped to // input m_t[] values that are within fuzz of the // output_domain[] values. m_t[0] = output_domain[0]; m_t[m_t.Count()-1] = output_domain[1]; return true; } bool ON_PolylineCurve::Extend( const ON_Interval& domain ) { if (IsClosed()) return false; if (PointCount() < 2) return false; if ( !domain.IsIncreasing() ) return false; bool changed = false; if ( domain == Domain() ) return true; if (domain[0] < m_t[0]){ changed = true; double len = m_t[1] - m_t[0]; if ( len <= 0.0 ) return false; ON_3dVector V = m_pline[1] - m_pline[0]; ON_3dPoint Q0 = m_pline[0]; Q0 += (domain[0]-m_t[0])/len*V; m_t[0] = domain[0]; m_pline[0] = Q0; } int last = PointCount()-1; if (domain[1] > m_t[last]){ changed = true; double len = m_t[last] - m_t[last-1]; if ( len <= 0.0 ) return false; ON_3dVector V = m_pline[last] - m_pline[last-1]; ON_3dPoint Q1 = m_pline[last]; Q1 += (domain[1]-m_t[last])/len*V; m_t[last] = domain[1]; m_pline[last] = Q1; } if (changed){ DestroyCurveTree(); } return changed; } bool ON_PolylineCurve::Split( double t, ON_Curve*& left_side, ON_Curve*& right_side ) const { bool rc = false; ON_PolylineCurve* left_pl=0; ON_PolylineCurve* right_pl=0; if ( left_side ) { left_pl = ON_PolylineCurve::Cast(left_side); if (!left_pl) return false; } if ( right_side ) { right_pl = ON_PolylineCurve::Cast(right_side); if (!right_pl) return false; } // count = number of polyline segments const int count = m_t.Count()-1; if ( count >= 1 && m_t[0] < t && t < m_t[count] ) { // March 26 2003 Greg Arden // Use new function ParameterSearch() to snap parameter value // when close to break point. int segment_index; bool split_at_break = ParameterSearch(t, segment_index, true); // 22 August 2008 Dale Lear // Added segment_index checks to fix bug when // t = m_t[count]-epsilon and segment_index is // returned as count. In this case right_point_count // go set to 1 and an invalid polyline was returned. // The || in the first expression prevents splitting // when (t=m_t[0]+epsilon and epsilon is small enough // that parameter search considers t to be nearly equal // to m_t[0]. if ( ( segment_index >= 1 || (false==split_at_break && 0 == segment_index) ) && segment_index < count && m_t[0] < t && t < m_t[count] ) { int left_point_count = (split_at_break) ? segment_index+1 : segment_index+2; int right_point_count = m_t.Count() - segment_index; if ( left_pl != this ) { if ( !left_pl ) left_pl = new ON_PolylineCurve(); left_pl->m_t.Reserve(left_point_count); left_pl->m_t.SetCount(left_point_count); left_pl->m_pline.Reserve(left_point_count); left_pl->m_pline.SetCount(left_point_count); memcpy( left_pl->m_t.Array(), m_t.Array(), left_point_count*sizeof(double) ); memcpy( left_pl->m_pline.Array(), m_pline.Array(), left_point_count*sizeof(ON_3dPoint) ); if(split_at_break) { // reparameterize the last segment *left_pl->m_t.Last()= t; } left_pl->m_dim = m_dim; } if ( right_pl != this ) { if ( !right_pl ) right_pl = new ON_PolylineCurve(); right_pl->m_t.Reserve(right_point_count); right_pl->m_t.SetCount(right_point_count); right_pl->m_pline.Reserve(right_point_count); right_pl->m_pline.SetCount(right_point_count); memcpy( right_pl->m_t.Array(), m_t.Array() + m_t.Count() - right_point_count, right_point_count*sizeof(double) ); memcpy( right_pl->m_pline.Array(), m_pline.Array() + m_pline.Count() - right_point_count, right_point_count*sizeof(ON_3dPoint) ); if( split_at_break) { // Reparameterize the first segment right_pl->m_t[0] = t; } right_pl->m_dim = m_dim; } left_pl->Trim( ON_Interval( left_pl->m_t[0], t ) ); right_pl->Trim( ON_Interval( t, *right_pl->m_t.Last() ) ); rc = true; } } left_side = left_pl; right_side = right_pl; return rc; } int ON_PolylineCurve::GetNurbForm( ON_NurbsCurve& nurb, double tol, const ON_Interval* subdomain // OPTIONAL subdomain of ON::ProxyCurve::Domain() ) const { int rc = 0; const int count = PointCount(); if ( count < 2 ) nurb.Destroy(); else if ( nurb.Create( Dimension(), false, 2, count) ) { int i; for ( i = 0; i < count; i++ ) { nurb.SetKnot( i, m_t[i] ); nurb.SetCV( i, m_pline[i] ); } if ( subdomain && *subdomain != Domain() ) nurb.Trim(*subdomain); if ( nurb.IsValid() ) rc = 1; } return rc; } int ON_PolylineCurve::HasNurbForm() const { if (PointCount() < 2) return 0; if (!IsValid()) return 0; return 1; } bool ON_PolylineCurve::GetCurveParameterFromNurbFormParameter( double nurbs_t, double* curve_t ) const { *curve_t = nurbs_t; return true; } bool ON_PolylineCurve::GetNurbFormParameterFromCurveParameter( double curve_t, double* nurbs_t ) const { *nurbs_t = curve_t; return true; } void ON_PolylineCurve::SetArcLengthParameterization(double tolerance) { double d, mind = tolerance; m_t[0] = 0; const int count = m_pline.Count(); for (int i = 1; i < count; i++) { d = (m_pline[i] - m_pline[i - 1]).Length(); if (d < mind) d = mind; if (d < fabs(m_t[i - 1]) * 1e-5) d = fabs(m_t[i - 1]) * 1e-5; m_t[i] = m_t[i - 1] + d; } }