Files
opennurbs/opennurbs_curve.cpp
2025-08-12 12:33:43 -07:00

4390 lines
117 KiB
C++

//
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
#include "opennurbs.h"
#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif
ON_VIRTUAL_OBJECT_IMPLEMENT(ON_Curve,ON_Geometry,"4ED7D4D7-E947-11d3-BFE5-0010830122F0");
ON_Curve::ON_Curve() ON_NOEXCEPT
: ON_Geometry()
{}
ON_Curve::ON_Curve(const ON_Curve& src)
: ON_Geometry(src)
{}
ON_Curve& ON_Curve::operator=(const ON_Curve& src)
{
if ( this != &src )
{
this->DestroyCurveTree();
ON_Geometry::operator=(src);
}
return *this;
}
#if defined(ON_HAS_RVALUEREF)
ON_Curve::ON_Curve( ON_Curve&& src ) ON_NOEXCEPT
: ON_Geometry(std::move(src))
{
}
ON_Curve& ON_Curve::operator=( ON_Curve&& src )
{
if ( this != &src )
{
this->DestroyCurveTree();
ON_Geometry::operator=(std::move(src));
}
return *this;
}
#endif
ON_Curve::~ON_Curve()
{
// Do not call the (virtual) DestroyRuntimeCache or
// DestroyCurveTree (which calls DestroyRuntimeCache()
// because it opens the potential for crashes in a
// "dirty" destructors of classes derived from ON_Curve
// that to not use DestroyRuntimeCache() in their
// destructors and to not set deleted pointers to zero.
}
unsigned int ON_Curve::SizeOf() const
{
unsigned int sz = ON_Geometry::SizeOf();
sz += (sizeof(*this) - sizeof(ON_Geometry));
// Currently, the size of m_ctree is not included
// because this is cached runtime information.
// Applications that care about object size are
// typically storing "inactive" objects for potential
// future use and should call DestroyRuntimeCache(true)
// to remove any runtime cache information.
return sz;
}
ON_Curve* ON_Curve::DuplicateCurve() const
{
// ON_CurveProxy overrides this virtual function.
return Duplicate();
}
ON::object_type ON_Curve::ObjectType() const
{
return ON::curve_object;
}
bool ON_Curve::GetDomain(double* s0,double* s1) const
{
bool rc = false;
ON_Interval d = Domain();
if ( d.IsIncreasing() ) {
if(s0) *s0 = d.Min();
if (s1) *s1 = d.Max();
rc = true;
}
return rc;
}
void ON_Curve::DestroyCurveTree()
{
DestroyRuntimeCache(true);
}
bool ON_Curve::GetTightBoundingBox(
ON_BoundingBox& tight_bbox,
bool bGrowBox,
const ON_Xform* xform
) const
{
if ( bGrowBox && !tight_bbox.IsValid() )
{
bGrowBox = false;
}
if ( !bGrowBox )
{
tight_bbox.Destroy();
}
// In general, putting start and end point in the box lets me avoid
// testing lots of nodes.
ON_3dPoint P = PointAtStart();
if ( xform )
P = (*xform)*P;
tight_bbox.Set( P, bGrowBox );
bGrowBox = true;
P = PointAtEnd();
if ( xform )
P = (*xform)*P;
tight_bbox.Set( P, bGrowBox );
ON_BoundingBox curve_bbox = BoundingBox();
if ( ON_WorldBBoxIsInTightBBox( tight_bbox, curve_bbox, xform ) )
{
// Curve is inside tight_bbox
return true;
}
ON_NurbsCurve N;
if ( 0 == GetNurbForm(N) )
return false;
if ( N.m_order < 2 || N.m_cv_count < N.m_order )
return false;
ON_BezierCurve B;
for ( int span_index = 0; span_index <= N.m_cv_count - N.m_order; span_index++ )
{
if ( !(N.m_knot[span_index + N.m_order-2] < N.m_knot[span_index + N.m_order-1]) )
continue;
if ( !N.ConvertSpanToBezier( span_index, B ) )
continue;
if ( !B.GetTightBoundingBox(tight_bbox,bGrowBox,xform) )
continue;
bGrowBox = true;
}
return (0!=bGrowBox);
}
// overrides virtual ON_Geometry::Transform()
bool ON_Curve::Transform(
const ON_Xform& xform
)
{
if ( !this->ON_Geometry::Transform(xform) )
return false;
this->DestroyCurveTree();
return true;
}
bool ON_Curve::SetDomain( ON_Interval domain )
{
return ( domain.IsIncreasing() && SetDomain( domain[0], domain[1] )) ? true : false;
}
bool ON_Curve::SetDomain( double, double )
{
// this virtual function is overridden by curves that can change their domain.
return false;
}
bool ON_Curve::ChangeClosedCurveSeam( double t, double min_dist)
{
ON_3dPoint P = PointAt(t);
if (min_dist <= 0.0 || P.DistanceTo(PointAtStart()) >= min_dist)
return ChangeClosedCurveSeam(t);
return false;
}
bool ON_Curve::ChangeClosedCurveSeam( double t )
{
// this virtual function is overridden by curves that can be closed
return false;
}
//virtual
bool ON_Curve::ChangeDimension( int desired_dimension )
{
return (desired_dimension > 0 && desired_dimension == Dimension() );
}
//virtual
bool ON_Curve::GetSpanVectorIndex(
double t, // [IN] t = evaluation parameter
int side, // [IN] side 0 = default, -1 = from below, +1 = from above
int* span_vector_i, // [OUT] span vector index
ON_Interval* span_domain // [OUT] domain of the span containing "t"
) const
{
bool rc = false;
int i;
int span_count = SpanCount();
if ( span_count > 0 ) {
double* span_vector = (double*)onmalloc((span_count+1)*sizeof(span_vector[0]));
rc = GetSpanVector( span_vector );
if (rc) {
i = ON_NurbsSpanIndex( 2, span_count+1, span_vector, t, side, 0 );
if ( i >= 0 && i < span_count ) {
if ( span_vector_i )
*span_vector_i = i;
if ( span_domain )
span_domain->Set( span_vector[i], span_vector[i+1] );
}
else
rc = false;
}
onfree(span_vector);
}
return rc;
}
const ON_SimpleArray<double> ON_Curve::SpanVector() const
{
ON_SimpleArray<double> span_vector;
const int span_count = this->SpanCount();
if (span_count >= 1)
{
span_vector.Reserve(span_count + 1);
if (this->GetSpanVector(span_vector.Array()))
span_vector.SetCount(span_count + 1);
else
span_vector.Destroy();
}
return span_vector; // uses Rvalue clone - no array copied.
}
bool ON_Curve::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() )
rc = ON_GetParameterTolerance( d[0], d[1], t, tminus, tplus );
return rc;
}
int ON_Curve::IsPolyline(
ON_SimpleArray<ON_3dPoint>* pline_points, // default = nullptr
ON_SimpleArray<double>* pline_t // default = nullptr
) const
{
// virtual function that is overridden
return 0;
}
bool ON_Curve::IsLinear( double tolerance ) const
{
bool rc = false;
if ( Dimension() == 2 || Dimension() == 3 ) {
const int span_count = SpanCount();
const int span_degree = Degree();
if ( span_count > 0 ) {
ON_SimpleArray<double> s(span_count+1);
s.SetCount(span_count+1);
if ( GetSpanVector( s.Array() ) ) {
if ( tolerance == 0.0 )
tolerance = ON_ZERO_TOLERANCE;
ON_Line line( PointAtStart(), PointAtEnd() );
if ( line.Length() > tolerance ) {
double t, t0, d, delta;
ON_Interval sp;
int n, i, span_index;
rc = true;
t0 = 0; // Domain()[0];
ON_3dPoint P;
for ( span_index = 0; span_index < span_count; span_index++ ) {
sp.Set( s[span_index], s[span_index+1] );
n = 2*span_degree+1;
delta = 1.0/n;
for ( i = (span_index)?0:1; i < n; i++ ) {
P = PointAt( sp.ParameterAt(i*delta) );
if ( !line.ClosestPointTo( P, &t ) )
rc = false;
else if ( t < t0 )
rc = false;
else if (t > 1.0 + ON_SQRT_EPSILON)
rc = false;
d = P.DistanceTo( line.PointAt(t) );
if ( d > tolerance )
rc = false;
t0 = t;
}
}
}
}
}
}
return rc;
}
bool ON_Curve::IsEllipse(
const ON_Plane* plane,
ON_Ellipse* ellipse,
double tolerance
) const
{
// virtual function
ON_Arc arc;
bool rc = IsArc(plane,&arc,tolerance)?true:false;
if (rc && ellipse)
{
ellipse->plane = arc.plane;
ellipse->radius[0] = arc.radius;
ellipse->radius[1] = arc.radius;
}
return rc;
}
bool ON_Curve::IsArcAt(
double t,
const ON_Plane* plane,
ON_Arc* arc,
double tolerance,
double* t0,
double* t1
) const
{
double k, k0, k1;
int hint;
if ( !GetDomain(&k0,&k1) )
return false;
if ( 0 != t0 )
*t0 = k0;
if ( 0 != t1 )
*t1 = k1;
if ( !ON_IsValid(t) )
return false;
if ( ! (t <= k1) )
return false;
if ( IsArc(plane,arc,tolerance) )
return true; // entire curve is an arc
// check sub-segments
hint = 0;
for ( k = k0; k0 <= t && GetNextDiscontinuity(ON::continuity::G2_locus_continuous, k0, k1, &k, &hint); k0 = k )
{
if ( !(k > k0) )
break; // sanity check to prevent infinite loops
if( t <= k )
{
if ( 0 != t0 )
*t0 = k0;
if ( 0 != t1 )
*t1 = k1;
ON_CurveProxy subcrv(this,ON_Interval(k0,k));
if ( subcrv.IsArc(plane,arc,tolerance) )
return true;
// NOTE WELL:
// When t == k, we need to check the next segment as well
// (happens when t is the parameter between a line and arc segment.)
// The "k0 <= t" test is in the for() condition will
// terminate the loop when t < k
}
}
return false;
}
bool ON_Curve::IsArc( const ON_Plane* plane, ON_Arc* arc, double tolerance ) const
{
bool rc = false;
double c0, c, t, delta;
int n, i, span_index;
ON_Plane pln;
ON_Arc a;
ON_3dPoint P, C;
if ( !plane ) {
if ( !IsPlanar(&pln,tolerance) )
return false;
plane = &pln;
}
if ( !arc )
arc = &a;
const int span_count = SpanCount();
const int span_degree = Degree();
if ( span_count < 1 )
return false;
ON_SimpleArray<double> d(span_count+1);
d.SetCount(span_count+1);
if ( !GetSpanVector(d.Array()) )
return false;
const bool bIsClosed = IsClosed();
ON_3dPoint P0 = PointAt( d[0] );
t = bIsClosed ? 0.5*d[0] + 0.5*d[span_count] : d[span_count];
ON_3dPoint P1 = PointAt( 0.5*d[0] + 0.5*t );
ON_3dPoint P2 = PointAt( t );
if ( !arc->Create(P0,P1,P2) )
return false;
if ( bIsClosed )
arc->SetAngleRadians(2.0*ON_PI);
ON_Interval arc_domain = arc->Domain();
ON_3dPoint A0 = arc->PointAt(arc_domain[0]);
ON_3dPoint A1 = arc->PointAt(arc_domain[1]);
ON_3dPoint C0 = PointAtStart();
ON_3dPoint C1 = PointAtEnd();
if ( false == ON_PointsAreCoincident(3,0,&A0.x,&C0.x)
|| false == ON_PointsAreCoincident(3,0,&A1.x,&C1.x)
)
{
return false;
}
if ( tolerance == 0.0 )
tolerance = ON_ZERO_TOLERANCE;
rc = true;
c0 = 0.0;
for ( span_index = 0; rc && span_index < span_count; span_index++ ) {
n = 2*span_degree+1;
if ( n < 4 )
n = 4;
delta = 1.0/n;
for ( i = 0; i < n; i++ ) {
t = i*delta;
P = PointAt( (1.0-t)*d[span_index] + t*d[span_index+1] );
if ( !arc->ClosestPointTo(P,&c) ) {
rc = false;
break;
}
if ( c < c0 ) {
rc = false;
break;
}
C = arc->PointAt(c);
if ( C.DistanceTo(P) > tolerance ) {
rc = 0;
break;
}
c0 = c;
}
}
return rc;
}
bool ON_Curve::IsPlanar( ON_Plane* plane, double tolerance ) const
{
bool rc = false;
const int dim = Dimension();
if ( dim == 2 )
{
// all 2d curves use this code to set the plane
// so that there is consistent behavior.
rc = true;
if ( plane )
{
*plane = ON_xy_plane;
//plane->CreateFromFrame( PointAtStart(), ON_3dVector::XAxis, ON_3dVector::YAxis );
}
}
else if ( IsLinear(tolerance) )
{
rc = true;
if ( plane )
{
ON_Line line( PointAtStart(), PointAtEnd() );
if ( !line.InPlane( *plane, tolerance ) )
line.InPlane( *plane, 0.0 );
}
}
else if ( dim == 3 )
{
const int span_count = SpanCount();
if ( span_count < 1 )
return false;
const int span_degree = Degree();
if ( span_degree < 1 )
return false;
ON_SimpleArray<double> s(span_count+1);
s.SetCount(span_count+1);
if ( !GetSpanVector(s.Array()) )
return false;
ON_Interval d = Domain();
// use initial point, tangent, and evaluated spans to guess a plane
ON_3dPoint pt = PointAt(d.ParameterAt(0.0));
ON_3dVector x = TangentAt(d.ParameterAt(0.0));
if ( x.Length() < 0.95 ) {
return false;
}
int n = (span_degree > 1) ? span_degree+1 : span_degree;
double delta = 1.0/n;
int i, span_index, hint = 0;
ON_3dPoint q;
ON_3dVector y;
bool bNeedY = true;
for ( span_index = 0; span_index < span_count && bNeedY; span_index++ ) {
d.Set(s[span_index],s[span_index+1]);
for ( i = span_index ? 0 : 1; i < n && bNeedY; i++ ) {
if ( !EvPoint( d.ParameterAt(i*delta), q, 0, &hint ) )
return false;
y = q-pt;
y = y - (y*x)*x;
bNeedY = ( y.Length() <= 1.0e-6 );
}
}
if ( bNeedY )
y.PerpendicularTo(x);
ON_Plane pln( pt, x, y );
if ( plane )
*plane = pln;
// test
rc = true;
n = 2*span_degree + 1;
delta = 1.0/n;
double h = pln.plane_equation.ValueAt(PointAtEnd());
if ( fabs(h) > tolerance )
rc = false;
hint = 0;
for ( span_index = 0; rc && span_index < span_count; span_index++ ) {
d.Set(s[span_index],s[span_index+1]);
for ( i = 0; rc && i < n; i++ ) {
if ( !EvPoint( d.ParameterAt(i*delta), q, 0, &hint ) )
rc = false;
else {
h = pln.plane_equation.ValueAt(q);
if ( fabs(h) > tolerance )
rc = false;
}
}
}
// RH-75060 GBA 9-June-23
// For a planar simple closed curve we should return the plane
// whose orientation that matches the curve orientation.
if (rc && plane && IsClosed())
{
if (ON_ClosedCurveOrientation(*this, *plane) < 0)
plane->Flip();
}
}
return rc;
}
bool ON_Curve::IsClosed() const
{
bool rc = false;
double *a, *b, *c, *p, w[12];
const int dim = Dimension();
a = 0;
if ( dim > 1 )
{
ON_Interval d = Domain();
a = (dim>3) ? (double*)onmalloc(dim*4*sizeof(*a)) : w;
b = a+dim;
c = b+dim;
p = c+dim;
if ( Evaluate( d.ParameterAt(0.0), 0, dim, a, 1 )
&& Evaluate( d.ParameterAt(1.0), 0, dim, p,-1 )
)
{
// Note: The point compare test should be the same
// as the one used in ON_PolyCurve::HasGap().
// June 2019 - sometime in the past decade ON_PolyCurve::HasGap()
// changed and the test there is different from this test.
// The initial "Note" no longer applies because it's no longer
// clear why the current ON_PolyCurve::HasGap() was changed.
if ( ON_PointsAreCoincident( dim, false, a, p ) )
{
if ( Evaluate( d.ParameterAt(1.0/3.0), 0, dim, b, 0 )
&& Evaluate( d.ParameterAt(2.0/3.0), 0, dim, c, 0 )
)
{
if ( false == ON_PointsAreCoincident( dim, false, a, b )
&& false == ON_PointsAreCoincident( dim, false, a, c )
&& false == ON_PointsAreCoincident( dim, false, p, b )
&& false == ON_PointsAreCoincident( dim, false, p, c )
)
{
rc = true;
}
}
}
}
if ( dim > 3 && 0 != a )
onfree(a);
}
return rc;
}
bool ON_Curve::IsPeriodic() const
{
// curve types that may be periodic override this virtual function
return false;
}
bool ON_Curve::GetNextDiscontinuity(
ON::continuity c,
double t0,
double t1,
double* t,
int* hint,
int* dtype,
double cos_angle_tolerance,
double curvature_tolerance
) const
{
// this function must be overridden by curve objects that
// can have parametric discontinuities on the interior of the curve.
bool rc = false;
if ( dtype )
*dtype = 0;
if ( t0 != t1 )
{
bool bTestC0 = false;
bool bTestD1 = false;
bool bTestD2 = false;
bool bTestT = false;
bool bTestK = false;
switch(c)
{
case ON::continuity::C0_locus_continuous:
bTestC0 = true;
break;
case ON::continuity::C1_locus_continuous:
bTestC0 = true;
bTestD1 = true;
break;
case ON::continuity::C2_locus_continuous:
bTestC0 = true;
bTestD1 = true;
bTestD2 = true;
break;
case ON::continuity::G1_locus_continuous:
bTestC0 = true;
bTestT = true;
break;
case ON::continuity::G2_locus_continuous:
bTestC0 = true;
bTestT = true;
bTestK = true;
break;
default:
// other values ignored on purpose.
break;
}
if ( bTestC0 )
{
// 20 March 2003 Dale Lear:
// Have to look for locus discontinuities at ends.
// Must test both ends because t0 > t1 is valid input.
// In particular, for ON_CurveProxy::GetNextDiscontinuity()
// to work correctly on reversed "real" curves, the
// t0 > t1 must work right.
ON_Interval domain = Domain();
if ( t0 < domain[1] && t1 >= domain[1] )
t1 = domain[1];
else if ( t0 > domain[0] && t1 <= domain[0] )
t1 = domain[0];
if ( (t0 < domain[1] && t1 >= domain[1]) || (t0 > domain[0] && t1 <= domain[0]) )
{
if ( IsClosed() )
{
if ( bTestD1 || bTestT )
{
// need to check locus continuity at start/end of closed curve.
ON_3dPoint Pa, Pb;
ON_3dVector D1a, D1b, D2a, D2b;
if ( Ev2Der(domain[0],Pa,D1a,D2a,1,nullptr)
&& Ev2Der(domain[1],Pb,D1b,D2b,-1,nullptr) )
{
Pb = Pa; // IsClosed() = true means assume Pa=Pb;
if ( bTestD1 )
{
if ( !(D1a-D1b).IsTiny(D1b.MaximumCoordinate()*ON_SQRT_EPSILON ) )
{
if ( dtype )
*dtype = 1;
*t = t1;
rc = true;
}
else if ( bTestD2 && !(D2a-D2b).IsTiny(D2b.MaximumCoordinate()*ON_SQRT_EPSILON) )
{
if ( dtype )
*dtype = 2;
*t = t1;
rc = true;
}
}
else if ( bTestT )
{
ON_3dVector Ta, Tb, Ka, Kb;
ON_EvCurvature( D1a, D2a, Ta, Ka );
ON_EvCurvature( D1b, D2b, Tb, Kb );
if ( Ta*Tb < cos_angle_tolerance )
{
if ( dtype )
*dtype = 1;
*t = t1;
rc = true;
}
else if ( bTestK )
{
// NOTE:
// This test must exactly match the one
// used in ON_NurbsCurve::GetNextDiscontinuity()
if ( !ON_IsG2CurvatureContinuous( Ka, Kb,
cos_angle_tolerance,
curvature_tolerance
)
)
{
if ( dtype )
*dtype = 2;
*t = t1;
rc = true;
}
}
}
}
}
}
else
{
// open curves are not locus continuous at ends.
if (dtype )
*dtype = 0; // locus C0 discontinuity
*t = t1;
rc = true;
}
}
}
}
return rc;
}
bool ON_Curve::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
{
ON_Interval domain = Domain();
if ( !domain.IsIncreasing() )
{
return true;
}
ON_3dPoint Pm, Pp;
ON_3dVector D1m, D1p, D2m, D2p, Tm, Tp, Km, Kp;
bool bIsClosed = false;
// 20 March 2003 Dale Lear
// I added this preable to handle the new
// locus continuity values.
double t0 = t;
double t1 = t;
switch(desired_continuity)
{
case ON::continuity::C0_locus_continuous:
case ON::continuity::C1_locus_continuous:
case ON::continuity::C2_locus_continuous:
case ON::continuity::G1_locus_continuous:
case ON::continuity::G2_locus_continuous:
if ( t <= domain[0] )
{
// By convention - see comments by ON::continuity enum.
return true;
}
if ( t == domain[1] )
{
if ( !IsClosed() )
{
// open curves are not locus continuous at the end parameter
// see comments by ON::continuity enum
return false;
}
else
{
if ( ON::continuity::C0_locus_continuous == desired_continuity )
{
return true;
}
bIsClosed = true;
}
t0 = domain[0];
t1 = domain[1];
}
break;
case ON::continuity::unknown_continuity:
case ON::continuity::C0_continuous:
case ON::continuity::C1_continuous:
case ON::continuity::C2_continuous:
case ON::continuity::G1_continuous:
case ON::continuity::G2_continuous:
case ON::continuity::Cinfinity_continuous:
case ON::continuity::Gsmooth_continuous:
default:
// does not change pre 20 March behavior - just skips the out
// of domain evaluation on parametric queries.
if ( t <= domain[0] || t >= domain[1] )
return true;
break;
}
// at this point, no difference between parametric and locus tests.
desired_continuity = ON::ParametricContinuity((int)desired_continuity);
// this is slow and uses evaluation
// virtual overrides on curve classes that can have multiple spans
// are much faster because the avoid evaluation
switch ( desired_continuity )
{
case ON::continuity::unknown_continuity:
break;
case ON::continuity::C0_continuous:
if ( !EvPoint( t1, Pm, -1, hint ) )
return false;
if ( !EvPoint( t0, Pp, 1, hint ) )
return false;
if ( bIsClosed )
Pm = Pp;
if ( !(Pm-Pp).IsTiny(point_tolerance) )
return false;
break;
case ON::continuity::C1_continuous:
if ( !Ev1Der( t1, Pm, D1m, -1, hint ) )
return false;
if ( !Ev1Der( t0, Pp, D1p, 1, hint ) )
return false;
if ( bIsClosed )
Pm = Pp;
if ( !(Pm-Pp).IsTiny(point_tolerance) || !(D1m-D1p).IsTiny(d1_tolerance) )
return false;
break;
case ON::continuity::G1_continuous:
if ( !EvTangent( t1, Pm, Tm, -1, hint ) )
return false;
if ( !EvTangent( t0, Pp, Tp, 1, hint ) )
return false;
if ( bIsClosed )
Pm = Pp;
if ( !(Pm-Pp).IsTiny(point_tolerance) || Tm*Tp < cos_angle_tolerance )
return false;
break;
case ON::continuity::C2_continuous:
if ( !Ev2Der( t1, Pm, D1m, D2m, -1, hint ) )
return false;
if ( !Ev2Der( t0, Pp, D1p, D2p, 1, hint ) )
return false;
if ( bIsClosed )
Pm = Pp;
if ( !(Pm-Pp).IsTiny(point_tolerance) || !(D1m-D1p).IsTiny(d1_tolerance) || !(D2m-D2p).IsTiny(d2_tolerance) )
return false;
break;
case ON::continuity::G2_continuous:
case ON::continuity::Gsmooth_continuous:
if ( !EvCurvature( t1, Pm, Tm, Km, -1, hint ) )
return false;
if ( !EvCurvature( t0, Pp, Tp, Kp, 1, hint ) )
return false;
if ( !bIsClosed && !(Pm-Pp).IsTiny(point_tolerance) )
return false;
if ( Tm*Tp < cos_angle_tolerance )
return false; // tangent discontinuity
if ( desired_continuity == ON::continuity::Gsmooth_continuous )
{
if ( !ON_IsGsmoothCurvatureContinuous(Km,Kp,cos_angle_tolerance,curvature_tolerance) )
return false;
}
else
{
if ( !ON_IsG2CurvatureContinuous(Km,Kp,cos_angle_tolerance,curvature_tolerance) )
return false;
}
break;
case ON::continuity::C0_locus_continuous:
case ON::continuity::C1_locus_continuous:
case ON::continuity::C2_locus_continuous:
case ON::continuity::G1_locus_continuous:
case ON::continuity::G2_locus_continuous:
case ON::continuity::Cinfinity_continuous:
break;
}
return true;
}
ON_3dPoint ON_Curve::PointAt( double t ) const
{
ON_3dPoint p(0.0,0.0,0.0);
if ( !EvPoint(t,p) )
p = ON_3dPoint::UnsetPoint;
return p;
}
ON_3dPoint ON_Curve::PointAtStart() const
{
return PointAt(Domain().Min());
}
ON_3dPoint ON_Curve::PointAtEnd() const
{
return PointAt(Domain().Max());
}
bool ON_Curve::SetStartPoint(ON_3dPoint start_point)
{
ON_3dPoint S = PointAtStart();
return (S == start_point) ? true : false;
}
bool ON_Curve::SetEndPoint(ON_3dPoint end_point)
{
ON_3dPoint E = PointAtEnd();
return (E == end_point) ? true : false;
}
ON_3dVector ON_Curve::DerivativeAt( double t ) const
{
ON_3dPoint p(0.0,0.0,0.0);
ON_3dVector d(0.0,0.0,0.0);
Ev1Der(t,p,d);
return d;
}
ON_3dVector ON_Curve::TangentAt( double t ) const
{
ON_3dPoint point;
ON_3dVector tangent;
EvTangent( t, point, tangent );
return tangent;
}
ON_3dVector ON_Curve::CurvatureAt( double t ) const
{
ON_3dPoint point;
ON_3dVector tangent, kappa;
EvCurvature( t, point, tangent, kappa );
return kappa;
}
double ON_Curve::SignedCurvatureAt(double t, const ON_3dVector* plane_normal) const
{
ON_3dPoint point;
ON_3dVector tangent;
double kappa;
EvSignedCurvature(t, point, tangent, kappa, plane_normal);
return kappa;
}
bool ON_Curve::EvTangent(
double t,
ON_3dPoint& point,
ON_3dVector& tangent,
int side,
int* hint
) const
{
ON_3dVector D1, D2;//, K;
tangent = ON_3dVector::ZeroVector;
int rc = Ev1Der( t, point, tangent, side, hint );
if ( rc && !tangent.Unitize() )
{
if ( Ev2Der( t, point, D1, D2, side, hint ) )
{
// Use l'Hopital's rule to show that if the unit tangent
// exists, the 1rst derivative is zero, and the 2nd
// derivative is nonzero, then the unit tangent is equal
// to +/-the unitized 2nd derivative. The sign is equal
// to the sign of D1(s) o D2(s) as s approaches the
// evaluation parameter.
tangent = D2;
rc = tangent.Unitize();
if ( rc )
{
ON_Interval domain = Domain();
double tminus = 0.0;
double tplus = 0.0;
if ( domain.IsIncreasing() && GetParameterTolerance( t, &tminus, &tplus ) )
{
ON_3dPoint p;
ON_3dVector d1, d2;
double eps = 0.0;
double d1od2tol = 0.0; //1.0e-10; // 1e-5 is too big
double d1od2;
double tt = t;
//double dt = 0.0;
if ( (t < domain[1] && side >= 0) || (t == domain[0]) )
{
eps = tplus-t;
if ( eps <= 0.0 || t+eps > domain.ParameterAt(0.1) )
return rc;
}
else if ( (t > domain[0] && side < 0) || (t == domain[1]) )
{
eps = tminus - t;
if ( eps >= 0.0 || t+eps < domain.ParameterAt(0.9) )
return rc;
}
int i, negative_count=0, zero_count=0;
int test_count = 3;
for ( i = 0; i < test_count; i++, eps *= 0.5 )
{
tt = t + eps;
if ( tt == t )
break;
if (!Ev2Der( tt, p, d1, d2, side, 0 ))
break;
d1od2 = d1*d2;
if ( d1od2 > d1od2tol )
break;
if ( d1od2 < d1od2tol )
negative_count++;
else
zero_count++;
}
if ( negative_count > 0 && test_count == negative_count+zero_count )
{
// all sampled d1od2 values were <= 0
// and at least one was strictly < 0.
tangent = -tangent;
}
}
}
}
}
return rc;
}
bool ON_Curve::EvCurvature(
double t,
ON_3dPoint& point,
ON_3dVector& tangent,
ON_3dVector& kappa,
int side,
int* hint
) const
{
ON_3dVector d1, d2;
int rc = Ev2Der( t, point, d1, d2, side, hint );
if ( rc )
rc = ON_EvCurvature( d1, d2, tangent, kappa );
return rc;
}
bool ON_Curve::EvSignedCurvature(
double t,
ON_3dPoint& point,
ON_3dVector& tangent,
double& kappa,
const ON_3dVector* plane_normal,
int side,
int* hint
) const
{
ON_3dVector K;
ON_3dVector zdir(0, 0, 1);
const ON_3dVector* N = plane_normal;
if (!N)
N = &zdir;
bool rc = EvCurvature(t, point, tangent, K, side, hint);
if (rc)
{
kappa = ON_TripleProduct(tangent, K, *N);
}
return rc;
}
bool ON_Curve::FrameAt( double t, ON_Plane& plane) const
{
bool rc = false;
ON_Interval domain = Domain();
if( t < domain[0] - ON_EPSILON || t > domain[1] + ON_EPSILON)
return false;
ON_3dPoint pt;
ON_3dVector d1, d2;
rc = Ev2Der( t, pt, d1, d2 );
if (rc)
{
ON_3dVector T, K;
rc = ON_EvCurvature(d1, d2, T, K);
if (rc)
{
if (!K.Unitize())
{
K.PerpendicularTo(T);
K.Unitize();
}
plane.origin = pt;
plane.xaxis = T;
plane.yaxis = K;
plane.zaxis = ON_CrossProduct(plane.xaxis, plane.yaxis);
if (!plane.zaxis.Unitize())
return false;
plane.UpdateEquation();
rc = plane.IsValid();
if (!rc)
{
//26 Aug 2015 - Chuck - If K is extremely short, but can still be unitized, the result may not be perpendicular to T.
plane.yaxis = ON_CrossProduct(plane.zaxis, plane.xaxis);
plane.yaxis.Unitize();
rc = plane.UpdateEquation();
}
}
}
return rc;
}
bool ON_Curve::EvPoint( // returns false if unable to evaluate
double t, // evaluation parameter
ON_3dPoint& point, // returns value of curve
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 used to speed repeated
// evaluations
) const
{
bool rc = false;
double ws[128];
double* v;
if ( Dimension() <= 3 ) {
v = &point.x;
point.x = 0.0;
point.y = 0.0;
point.z = 0.0;
}
else if ( Dimension() <= 128 ) {
v = ws;
}
else {
v = (double*)onmalloc(Dimension()*sizeof(*v));
}
rc = Evaluate( t, 0, Dimension(), v, side, hint );
if ( Dimension() > 3 ) {
point.x = v[0];
point.y = v[1];
point.z = v[2];
if ( Dimension() > 128 )
onfree(v);
}
return rc;
}
bool ON_Brep::EvaluatePoint( const class ON_ObjRef& objref, ON_3dPoint& P ) const
{
// TODO
return false;
}
bool ON_Surface::EvaluatePoint( const class ON_ObjRef& objref, ON_3dPoint& P ) const
{
// TODO
return false;
}
bool ON_PolyCurve::EvaluatePoint( const class ON_ObjRef& objref, ON_3dPoint& P ) const
{
// TODO
return false;
}
bool ON_Curve::EvaluatePoint( const class ON_ObjRef& objref, ON_3dPoint& P ) const
{
// virtual function default
bool rc = false;
ON_3dPoint Q = ON_3dPoint::UnsetPoint;
if ( 1 == objref.m_evp.m_t_type )
{
if ( !EvPoint(objref.m_evp.m_t[0],Q) )
Q = ON_3dPoint::UnsetPoint;
}
switch( objref.m_osnap_mode )
{
case ON::os_center:
{
ON_Ellipse ellipse;
if ( IsEllipse(0,&ellipse) )
{
P = ellipse.plane.origin;
rc = true;
}
else
{
ON_SimpleArray<ON_3dPoint> pline;
if ( IsClosed() && IsPolyline(&pline) && pline.Count() >= 4 )
{
P = pline[0];
int i;
for ( i = pline.Count()-2; i > 0; i-- )
{
Q = pline[i];
P.x += Q.x; P.y += Q.y; P.z += Q.z;
}
double s = 1.0/(pline.Count()-1.0);
P.x *= s; P.y *= s; P.z *= s;
rc = true;
}
else if ( Q.IsValid() )
{
ON_3dVector T, K;
if ( EvCurvature(objref.m_evp.m_t[0],Q,T,K) )
{
double k = K.Length();
if ( k > 0.0 )
{
P = Q + (1.0/(k*k))*K;
rc = true;
}
}
}
}
}
break;
case ON::os_focus:
{
ON_Ellipse ellipse;
if ( IsEllipse(0,&ellipse) )
{
ON_3dPoint F1, F2;
if ( ellipse.GetFoci(F1,F2) )
{
P = ( F1.DistanceTo(Q) <= F2.DistanceTo(Q)) ? F1 : F2;
rc = true;
}
}
}
break;
case ON::os_midpoint:
{
}
break;
case ON::os_end:
{
ON_SimpleArray<ON_3dPoint> pline;
if ( IsPolyline(&pline) )
{
P = pline[0];
double d = P.DistanceTo(Q);
int i;
for ( i = 1; i < pline.Count(); i++ )
{
double d1 = pline[i].DistanceTo(Q);
if ( d1 < d )
{
d = d1;
P = pline[i];
rc = true;
}
}
}
else
{
P = PointAtStart();
rc = true;
if ( !IsClosed() )
{
ON_3dPoint P1 = PointAtEnd();
if ( P.DistanceTo(Q) > P1.DistanceTo(Q) )
{
P = P1;
}
}
}
}
break;
default:
if ( Q.IsValid() )
{
P = Q;
rc = true;
}
break;
}
return rc;
}
bool ON_Curve::Ev1Der( // returns false if unable to evaluate
double t, // evaluation parameter
ON_3dPoint& point,
ON_3dVector& derivative,
int side, // optional - determines which side to evaluate from
// <= 0 to evaluate from below,
// > 0 to evaluate from above
int* hint // optional - evaluation hint used to speed repeated
// evaluations
) const
{
bool rc = false;
const int dim = Dimension();
double ws[2*64];
double* v;
point.x = 0.0;
point.y = 0.0;
point.z = 0.0;
derivative.x = 0.0;
derivative.y = 0.0;
derivative.z = 0.0;
if ( dim <= 64 ) {
v = ws;
}
else {
v = (double*)onmalloc(2*dim*sizeof(*v));
}
rc = Evaluate( t, 1, dim, v, side, hint );
point.x = v[0];
derivative.x = v[dim];
if ( dim > 1 ) {
point.y = v[1];
derivative.y = v[dim+1];
if ( dim > 2 ) {
point.z = v[2];
derivative.z = v[dim+2];
if ( dim > 64 )
onfree(v);
}
}
return rc;
}
bool ON_Curve::Ev2Der( // returns false if unable to evaluate
double t, // evaluation parameter
ON_3dPoint& point,
ON_3dVector& firstDervative,
ON_3dVector& secondDervative,
int side, // optional - determines which side to evaluate from
// <= 0 to evaluate from below,
// > 0 to evaluate from above
int* hint // optional - evaluation hint used to speed repeated
// evaluations
) const
{
bool rc = false;
const int dim = Dimension();
double ws[3*64];
double* v;
point.x = 0.0;
point.y = 0.0;
point.z = 0.0;
firstDervative.x = 0.0;
firstDervative.y = 0.0;
firstDervative.z = 0.0;
secondDervative.x = 0.0;
secondDervative.y = 0.0;
secondDervative.z = 0.0;
if ( dim <= 64 ) {
v = ws;
}
else {
v = (double*)onmalloc(3*dim*sizeof(*v));
}
rc = Evaluate( t, 2, dim, v, side, hint );
point.x = v[0];
firstDervative.x = v[dim];
secondDervative.x = v[2*dim];
if ( dim > 1 ) {
point.y = v[1];
firstDervative.y = v[dim+1];
secondDervative.y = v[2*dim+1];
if ( dim > 2 ) {
point.z = v[2];
firstDervative.z = v[dim+2];
secondDervative.z = v[2*dim+2];
if ( dim > 64 )
onfree(v);
}
}
return rc;
}
////////////////////////////////////////////////////////////////////////////////////////
//
// ON_Curve::IsShort()
//
static
ON::eCurveType ON_CurveType( const ON_Curve* curve )
{
const ON_ClassId* curve_id = &ON_CLASS_RTTI(ON_Curve);
const ON_ClassId* id = curve->ClassId();
// "fake virtual" handling of fast/easy special cases
while (0 != id && curve_id != id )
{
if ( &ON_CLASS_RTTI(ON_ArcCurve) == id )
return ON::ctArc;
if ( &ON_CLASS_RTTI(ON_LineCurve) == id )
return ON::ctLine;
if ( &ON_CLASS_RTTI(ON_PolylineCurve) == id )
return ON::ctPolyline;
if ( &ON_CLASS_RTTI(ON_CurveProxy) == id )
return ON::ctProxy;
if ( &ON_CLASS_RTTI(ON_PolyCurve) == id )
return ON::ctPolycurve;
if ( &ON_CLASS_RTTI(ON_NurbsCurve) == id )
return ON::ctNurbs;
if ( &ON_CLASS_RTTI(ON_CurveOnSurface) == id )
return ON::ctOnsurface;
id = id->BaseClass();
}
return ON::ctCurve;
}
/*
Description:
Carefully match curve ends.
Parameters:
curve0 - [in]
end0 - [in] 0=match start of curve0, 1 = match end of curve0
curve1 - [in]
end1 - [in] 0=match start of curve1, 1 = match end of curve1
gap_tolerance - [in]
The match is not performed if the initial gap is <= gap_tolerance.
If gap_tolerance < 0, then ON_ComparePoint() is used to
compare the points.
Returns:
True if ends of curves are matched to requested gap_tolerance.
*/
bool ON_MatchCurveEnds( ON_Curve* curve0,
int end0,
ON_Curve* curve1,
int end1,
double gap_tolerance = 0.0
);
bool ON_MatchCurveEnds( ON_Curve* curve0,
int end0,
ON_Curve* curve1,
int end1,
double gap_tolerance )
{
bool rc = false;
if ( 0 != curve0 && 0 != curve1
&& end0 >= 0 && end0 <= 1
&& end1 >= 0 && end1 <= 1 )
{
ON_3dPoint P0 = end0 ? curve0->PointAtEnd() : curve0->PointAtStart();
ON_3dPoint P1 = end1 ? curve1->PointAtEnd() : curve1->PointAtStart();
rc = ( gap_tolerance < 0.0 )
? (0==ON_ComparePoint( 3, false, &P0.x, &P1.x ) )
: (P0.DistanceTo(P1) <= gap_tolerance);
if ( !rc )
{
// try to close the gap
ON_Curve* seg[2] = {0,0};
int fix[2] = {0,0};
ON_3dPoint fixPoint[2];
fixPoint[0] = ON_3dPoint::UnsetPoint;
fixPoint[1] = ON_3dPoint::UnsetPoint;
// heuristic for deciding which point gets moved
int i;
for ( i = 0; i <= 1; i++ )
{
ON_3dPoint Q0 = ON_3dPoint::UnsetPoint;
ON_3dPoint Q1 = ON_3dPoint::UnsetPoint;
ON_Curve* c = i ? curve1 : curve0;
ON::eCurveType ct = ON_CurveType(c);
int e = i ? end1 : end0;
while ( ON::ctPolycurve == ct )
{
c->DestroyRuntimeCache();
ON_PolyCurve* polycurve = ON_PolyCurve::Cast(c);
if ( 0 == polycurve )
break;
c = polycurve->SegmentCurve(e?(polycurve->Count()-1):0);
if( 0 == c )
return false;
ct = ON_CurveType(c);
}
seg[i] = c;
switch(ct)
{
case ON::ctArc: // arc
if ( c->IsClosed() )
fix[i] = 200;
else
{
fix[i] = 100;
}
Q0 = c->PointAtStart();
Q1 = c->PointAtEnd();
break;
case ON::ctLine: // line
fix[i] = 20;
Q0 = c->PointAtStart();
Q1 = c->PointAtEnd();
break;
case ON::ctPolyline: // polyline
fix[i] = 10;
if ( c->SpanCount() == 1 )
{
// same as a line
fix[i] = 20;
Q0 = c->PointAtStart();
Q1 = c->PointAtEnd();
}
else
fix[i] = 10;
break;
case ON::ctProxy: // proxy
fix[i] = 1000; // cannot change
break;
case ON::ctPolycurve: // polycurve
fix[i] = 1000; // if this happens, something is bad
break;
case ON::ctNurbs: // nurbs
{
if ( c->Degree() == 1 )
{
if ( c->SpanCount() == 1 )
{
// same as a line
fix[i] = 20;
Q0 = c->PointAtStart();
Q1 = c->PointAtEnd();
}
else
{
// same as a polyline
fix[i] = 10;
}
}
else
fix[i] = 0;
}
break;
case ON::ctOnsurface: // curve on surface
fix[i] = 1000; // cannot change
break;
default: // ???
fix[i] = 50;
break;
}
int j = 0;
if ( ON_UNSET_VALUE != Q0.x && Q0.x == Q1.x )
{
fixPoint[i].x = Q0.x;
j++;
}
if ( ON_UNSET_VALUE != Q0.y && Q0.y == Q1.y )
{
fixPoint[i].y = Q0.y;
j++;
}
if ( ON_UNSET_VALUE != Q0.z && Q0.z == Q1.z )
{
fixPoint[i].z = Q0.z;
j++;
}
if ( 2 == j )
fix[i] += 9;
else if ( 1 == j )
fix[i] += 1;
}
if ( fix[0] >= 1000 || fix[1] < fix[0] )
{
if ( fix[1] >= 1000 )
return false;
rc = end1
? curve1->SetEndPoint(P0)
: curve1->SetStartPoint(P0);
}
else if ( fix[1] >= 1000 || fix[0] < fix[1] )
{
rc = end0
? curve0->SetEndPoint(P1)
: curve0->SetStartPoint(P1);
}
else
{
ON_3dPoint P = 0.5*(P0+P1);
if ( P0.x == P1.x )
P.x = P0.x;
else if ( fixPoint[0].x != ON_UNSET_VALUE && fixPoint[1].x == ON_UNSET_VALUE )
P.x = fixPoint[0].x;
else if ( fixPoint[0].x == ON_UNSET_VALUE && fixPoint[1].x != ON_UNSET_VALUE )
P.x = fixPoint[1].x;
if ( P0.y == P1.y )
P.y = P0.y;
else if ( fixPoint[0].y != ON_UNSET_VALUE && fixPoint[1].y == ON_UNSET_VALUE )
P.y = fixPoint[0].y;
else if ( fixPoint[0].y == ON_UNSET_VALUE && fixPoint[1].y != ON_UNSET_VALUE )
P.y = fixPoint[1].y;
if ( P0.z == P1.z )
P.z = P0.z;
else if ( fixPoint[0].z != ON_UNSET_VALUE && fixPoint[1].z == ON_UNSET_VALUE )
P.z = fixPoint[0].z;
else if ( fixPoint[0].z == ON_UNSET_VALUE && fixPoint[1].z != ON_UNSET_VALUE )
P.z = fixPoint[1].z;
bool rc0 = end0
? curve0->SetEndPoint(P)
: curve0->SetStartPoint(P);
bool rc1 = end1
? curve1->SetEndPoint(P)
: curve1->SetStartPoint(P);
rc = (rc0 && rc1);
}
if ( rc )
{
P0 = end0 ? curve0->PointAtEnd() : curve0->PointAtStart();
P1 = end1 ? curve1->PointAtEnd() : curve1->PointAtStart();
double d = P0.DistanceTo(P1);
rc = ( gap_tolerance <= 0.0 ) // <= is correct here
? (0==ON_ComparePoint( 3, false, &P0.x, &P1.x ) )
: (d <= gap_tolerance);
}
}
}
return rc?true:false;
}
static bool ForceMatchArcs(ON_ArcCurve& Arc0, int end0, ON_ArcCurve& Arc1, int end1)
{
ON_3dPoint P0 = (end0) ? Arc0.PointAtEnd() : Arc0.PointAtStart();
ON_3dPoint P1 = (end1) ? Arc1.PointAtEnd() : Arc1.PointAtStart();
ON_3dPoint P = 0.5*(P0+P1);
bool bMove[2] = {true,true};
bool rc = true;
if (bMove[0]){
bool brc = (end0) ? Arc0.SetEndPoint(P) : Arc0.SetStartPoint(P);
if (!brc)
rc = false;
}
if (bMove[1]){
bool brc = (end1) ? Arc1.SetEndPoint(P) : Arc1.SetStartPoint(P);
if (!brc)
rc = false;
}
return rc;
}
static bool FastIsShort(const ON_Curve& crv, double tol)
{
ON_3dPoint P[5];
P[0] = crv.PointAtStart();
P[4] = crv.PointAtEnd();
if (P[0].DistanceTo(P[4]) >= tol)
return false;
double d = 0.0;
for (int i=1; i<4; i++){
P[i] = crv.PointAt(crv.Domain().ParameterAt(0.25*(double)i));
d += P[i].DistanceTo(P[i-1]);
if (d >= tol)
return false;
}
d += P[4].DistanceTo(P[3]);
return (d < tol) ? true : false;
}
bool ON_ForceMatchCurveEnds(ON_Curve& Crv0, int end0, ON_Curve& Crv1, int end1)
{
if (end0)
end0 = 1;
if (end1)
end1 = 1;
int i;
ON::eCurveType ct[2];
ON_Curve* seg[2];
bool bIsArc[2];
for ( i = 0; i<2; i++ )
{
ON_Curve* c = i ? &Crv1 : &Crv0;
ct[i] = ON_CurveType(c);
int e = i ? end1 : end0;
while ( ON::ctPolycurve == ct[i] )
{
c->DestroyRuntimeCache();
ON_PolyCurve* polycurve = ON_PolyCurve::Cast(c);
if ( 0 == polycurve )
break;
c = polycurve->SegmentCurve(e?(polycurve->Count()-1):0);
if( 0 == c )
return false;
ct[i] = ON_CurveType(c);
}
if (c->IsClosed())
return false;
if (ct[i] == ON::ctArc)
bIsArc[i] = true;
else {
if (ct[i] == ON::ctLine || ct[i] == ON::ctNurbs || ct[i] == ON::ctPolyline)
bIsArc[i] = false;
else
return false;
}
seg[i] = c;
}
ON_3dPoint P;
bool bMove[2] = {true,true};
if (bIsArc[0]){
if (!bIsArc[1]){
P = (end0) ? seg[0]->PointAtEnd() : seg[0]->PointAtStart();
bMove[0] = false;
}
else {
ON_ArcCurve* pArc0 = ON_ArcCurve::Cast(seg[0]);
if (!pArc0)
return false;
ON_ArcCurve* pArc1 = ON_ArcCurve::Cast(seg[1]);
if (!pArc1)
return false;
return ForceMatchArcs(*pArc0, end0, *pArc1, end1);
}
}
else {
if (bIsArc[1]){
P = (end1) ? seg[1]->PointAtEnd() : seg[1]->PointAtStart();
bMove[1] = false;
}
else {
ON_3dPoint P0 = (end0) ? seg[0]->PointAtEnd() : seg[0]->PointAtStart();
ON_3dPoint P1 = (end1) ? seg[1]->PointAtEnd() : seg[1]->PointAtStart();
P = 0.5*(P0+P1);
}
}
bool rc = true;
bool bTryAgain = false;
if (bMove[0]){
bool brc = (end0) ? seg[0]->SetEndPoint(P) : seg[0]->SetStartPoint(P);
if (!brc)
rc = false;
else {
//23 Jan 2018 - Chuck - If yanking a polycurve segment at the join causes
//that seg to be tiny, remove it and try again. See RH-43661
if (ON_CurveType(&Crv0) == ON::ctPolycurve){
ON_PolyCurve* polycurve = ON_PolyCurve::Cast(&Crv0);
if (polycurve->Count() > 1 && FastIsShort(*seg[0], 10.0*ON_ZERO_TOLERANCE))
bTryAgain = (end0) ? polycurve->Remove() : polycurve->Remove(0);
}
}
}
if (bMove[1]){
bool brc = (end1) ? seg[1]->SetEndPoint(P) : seg[1]->SetStartPoint(P);
if (!brc)
rc = false;
else {
//23 Jan 2018 - Chuck - If yanking a polycurve segment at the join causes
//that seg to be tiny, remove it and try again. See RH-43661
if (ON_CurveType(&Crv1) == ON::ctPolycurve){
ON_PolyCurve* polycurve = ON_PolyCurve::Cast(&Crv1);
if (polycurve->Count() > 1 && FastIsShort(*seg[1], 10.0*ON_ZERO_TOLERANCE)){
bool bRem = (end1) ? polycurve->Remove() : polycurve->Remove(0);
if (bRem)
bTryAgain = true;
}
}
}
}
//23 Jan 2018 - Chuck - If yanking a polycurve segment at the join causes
//that seg to be tiny, remove it and try again. See RH-43661
if (bTryAgain)//One of these is a polycurve with one less segment thane before, so no infinite recursion.
return ON_ForceMatchCurveEnds(Crv0, end0, Crv1, end1);
return rc;
}
static bool Internal_IsUniformCubic(const ON_NurbsCurve& curve)
{
if (4 != curve.m_order)
return false;
if (curve.m_cv_count < curve.m_order)
return false;
if (0 != curve.m_is_rat)
return false;
if (nullptr == curve.m_knot)
return false;
const int knot_count = curve.KnotCount();
for (int i = 0; i < knot_count; i++)
{
if (curve.m_knot[i] != (double)(i - 2))
return false;
}
return true;
}
bool ON_NurbsCurve::RepairBadKnots( double knot_tolerance, bool bRepair )
{
bool rc = false;
if ( m_order >= 2 && m_cv_count > m_order
&& 0 != m_cv && 0 != m_knot
&& m_dim > 0
&& (m_cv_stride >= m_is_rat ? m_dim + 1 : m_dim)
&& m_knot[m_cv_count-1] - m_knot[m_order-2] > knot_tolerance
)
{
//const int cv_count0 = m_cv_count;
const int sizeof_cv = CVSize()*sizeof(*m_cv);
//int knot_count = KnotCount();
int i, j0, j1;
const bool bIsPeriodic = IsPeriodic();
const bool bIsUnclamped = this->UnclampedTagForExperts();
const bool bUnclampedUniformCubic = Internal_IsUniformCubic(*this);
const bool bClampEnds =
false == bIsPeriodic
&& false == bIsUnclamped
&& false == bUnclampedUniformCubic;
if (bClampEnds)
{
if ( m_knot[0] != m_knot[m_order-2] || m_knot[m_cv_count-1] != m_knot[m_cv_count+m_order-3] )
{
rc = true;
if (bRepair)
ClampEnd(2);
else
return rc;
}
}
// make sure last span has m_knot[m_cv_count-1] - m_knot[m_cv_count-2] > knot_tolerance
for ( i = m_cv_count-2; i > m_order-2; i-- )
{
if ( m_knot[m_cv_count-1] - m_knot[i] > knot_tolerance )
{
if ( i < m_cv_count-2 )
{
// the last span is invalid
rc = true;
if ( bRepair )
{
// 15-June-2020
// Remove degenerate and small spans at the end, with out changing the domain
// and trying to maintain parametric curve ( i.e, C: Domain->R^d is invariant).
DestroyRuntimeCache();
if (knot_tolerance > 0)
{
// Tune up knots at end with 0< spacing <knot_tolerance, by setting to domain end.
for (j0 = i + 1; j0 < m_cv_count - 1; j0++)
m_knot[j0] = m_knot[m_cv_count - 1];
}
m_cv_count = i + 2; // Eliminate knots and CV's from the end.
// Now that final span is non-degenerate we can ClampEnd
ClampEnd(1);
}
else
return rc;
}
break; // the last span is non degenerate
}
}
// make sure first span has m_knot[m_order-1] - m_knot[m_order-2] > knot_tolerance
for ( i = m_order-1; i < m_cv_count-1; i++ )
{
if ( m_knot[i] - m_knot[m_order-2] > knot_tolerance )
{
if ( i > m_order-1 )
{
// the first span is invalid
rc = true;
if ( bRepair )
{
// 15-June-2020
// Remove degenerate and small spans at the end, with out changing the domain
// and trying to maintain parametric curve ( i.e, C: Domain->R^d is invariant).
DestroyRuntimeCache();
if (knot_tolerance > 0)
{
// Tune up knots at start with 0< spacing <knot_tolerance, by setting to domain start.
for (j0 = i - 1; j0 > m_order - 2; j0--)
m_knot[j0] = m_knot[m_order - 2];
}
// This is a hack way of eliminating knots and CV's from the start.
// Warning!! m_cv and m_knot need to be restored before we exit this function
i = i - (m_order - 1);
m_cv += m_cv_stride * i ;
m_knot += i;
m_cv_count -= i;
ClampEnd(0); // ...This hack works because ClampEnd does not reallocate cv or knot arrays
m_cv -= m_cv_stride * i ;
m_knot -= i ;
for (j0 = 0, j1 = i; j0 < m_cv_count; j0++, j1++)
memcpy(CV(j0), CV(j1), sizeof_cv);
for (j0 = 0, j1 = i; j0 < m_cv_count+m_order-2; j0++, j1++)
m_knot[j0] = m_knot[j1];
}
else
return rc;
}
break; // there at least one valid span
}
}
if ( m_knot[m_order-1]-m_knot[m_order-2] > knot_tolerance
&& m_knot[m_cv_count-1]-m_knot[m_cv_count-2] > knot_tolerance )
{
// Remove interior knots with multiplicity >= m_order
for ( i = m_cv_count-m_order-1; i >= m_order; i-- )
{
if ( m_knot[i+m_order-1] - m_knot[i] <= knot_tolerance )
{
rc = true;
if ( bRepair )
{
// empty evaluation span - remove CV and knot
DestroyRuntimeCache();
for ( j0 = i, j1 = i+1; j1 < m_cv_count; j0++, j1++ )
memcpy( CV(j0), CV(j1), sizeof_cv );
for ( j0 = i, j1 = i+1; j1 < m_cv_count+m_order-2; j0++, j1++ )
m_knot[j0] = m_knot[j1];
m_cv_count--;
}
else
{
// query mode
return rc;
}
}
}
}
if ( bRepair && bIsPeriodic && rc )
{
if (!IsPeriodic())
{
// the repairs broke being periodic.
ClampEnd(2);
}
}
}
return rc;
}
bool ON_Curve::FirstSpanIsLinear(
double min_length,
double tolerance
) const
{
return FirstSpanIsLinear(min_length,tolerance,0);
}
bool ON_Curve::FirstSpanIsLinear(
double min_length,
double tolerance,
ON_Line* span_line
) const
{
const ON_NurbsCurve* nurbs_curve = ON_NurbsCurve::Cast(this);
if ( 0 != nurbs_curve )
{
return nurbs_curve->SpanIsLinear(0,min_length,tolerance,span_line);
}
const ON_PolylineCurve* polyline_curve = ON_PolylineCurve::Cast(this);
if ( 0 != polyline_curve )
{
bool rc = (polyline_curve->PointCount() >= 2);
if (rc && 0 != span_line )
{
span_line->from = polyline_curve->m_pline[0];
span_line->to = polyline_curve->m_pline[1];
}
return rc;
}
const ON_LineCurve* line_curve = ON_LineCurve::Cast(this);
if ( 0 != line_curve )
{
if ( span_line )
*span_line = line_curve->m_line;
return true;
}
const ON_PolyCurve* poly_curve = ON_PolyCurve::Cast(this);
if ( 0 != poly_curve )
{
const ON_Curve* segment = poly_curve->SegmentCurve(0);
return (0 != segment) ? segment->FirstSpanIsLinear(min_length,tolerance,span_line) : false;
}
const ON_CurveProxy* proxy_curve = ON_CurveProxy::Cast(this);
if ( 0 != proxy_curve )
{
const ON_Curve* curve = proxy_curve->ProxyCurve();
if ( 0 == curve )
return false;
bool bProxyCurveIsReversed = proxy_curve->ProxyCurveIsReversed();
bool rc = bProxyCurveIsReversed
? curve->FirstSpanIsLinear(min_length,tolerance,span_line)
: curve->LastSpanIsLinear(min_length,tolerance,span_line);
if ( rc && bProxyCurveIsReversed && 0 != span_line )
span_line->Reverse();
return rc;
}
return false;
}
bool ON_Curve::LastSpanIsLinear(
double min_length,
double tolerance
) const
{
return LastSpanIsLinear(min_length,tolerance,0);
}
bool ON_Curve::LastSpanIsLinear(
double min_length,
double tolerance,
ON_Line* span_line
) const
{
const ON_NurbsCurve* nurbs_curve = ON_NurbsCurve::Cast(this);
if ( 0 != nurbs_curve )
{
return nurbs_curve->SpanIsLinear(nurbs_curve->m_cv_count-nurbs_curve->m_order,min_length,tolerance,span_line);
}
const ON_PolylineCurve* polyline_curve = ON_PolylineCurve::Cast(this);
if ( 0 != polyline_curve )
{
int count = polyline_curve->PointCount();
if ( count >= 2 && 0 != span_line )
{
span_line->from = polyline_curve->m_pline[count-2];
span_line->to = polyline_curve->m_pline[count-1];
}
return ( count >= 2);
}
const ON_LineCurve* line_curve = ON_LineCurve::Cast(this);
if ( 0 != line_curve )
{
if ( span_line )
*span_line = line_curve->m_line;
return true;
}
const ON_PolyCurve* poly_curve = ON_PolyCurve::Cast(this);
if ( 0 != poly_curve )
{
const ON_Curve* segment = poly_curve->SegmentCurve(poly_curve->Count()-1);
return (0 != segment) ? segment->LastSpanIsLinear(min_length,tolerance,span_line) : false;
}
const ON_CurveProxy* proxy_curve = ON_CurveProxy::Cast(this);
if ( 0 != proxy_curve )
{
const ON_Curve* curve = proxy_curve->ProxyCurve();
if ( 0 == curve )
return false;
bool bProxyCurveIsReversed = proxy_curve->ProxyCurveIsReversed();
bool rc = bProxyCurveIsReversed
? curve->LastSpanIsLinear(min_length,tolerance,span_line)
: curve->FirstSpanIsLinear(min_length,tolerance,span_line);
if ( rc && bProxyCurveIsReversed && 0 != span_line )
span_line->Reverse();
return rc;
}
return false;
}
bool ON_NurbsCurve::SpanIsLinear(
int span_index,
double min_length,
double tolerance
) const
{
return SpanIsLinear(span_index,min_length,tolerance,0);
}
bool ON_NurbsCurve::SpanIsLinear(
int span_index,
double min_length,
double tolerance,
ON_Line* span_line
) const
{
if ( m_dim < 2 || m_dim > 3 )
return false;
if ( -1 == span_index && m_cv_count-m_order+1+span_index >= 0 )
{
// negative span indices work from the back
span_index += m_cv_count-m_order+1;
}
else if ( span_index < 0 || span_index > m_cv_count-m_order )
{
ON_ERROR("span_index out of range.");
return false;
}
if ( !(m_knot[span_index+m_order-2] < m_knot[span_index+m_order-1]) )
{
// empty span
ON_ERROR("empty span.");
return false;
}
if ( m_knot[span_index] == m_knot[span_index+m_order-2]
&& m_knot[span_index+m_order-1] == m_knot[span_index+2*m_order-3]
)
{
ON_3dPoint P, Q;
ON_Line line;
const int i1 = span_index+m_order-1;
if ( !GetCV(span_index,line.from) )
return false;
if ( !GetCV(i1,line.to) )
return false;
if ( !(line.Length() >= min_length) )
return false;
double t0, t, d;
t0 = t = 0.0;
for ( int i = span_index+1; i < i1; i++ )
{
if ( !GetCV(i,P) )
return false;
if ( !line.ClosestPointTo( P, &t ) )
return false;
if ( !(t0 < t) )
return false;
if ( !(t <= 1.0 + ON_SQRT_EPSILON) )
return false;
Q = line.PointAt(t);
if ( false == ON_PointsAreCoincident(3,0,&P.x,&Q.x) )
{
d = P.DistanceTo( line.PointAt(t) );
if ( !(d <= tolerance) )
return false;
}
t0 = t;
}
if ( span_line )
*span_line = line;
return true;
}
return false;
}
bool ON_Curve::Trim( const ON_Interval& in )
{
// TODO - make this pure virtual
return false;
}
bool ON_Curve::Extend(
const ON_Interval& domain
)
{
return false;
}
ON_Curve* ON_TrimCurve(
const ON_Curve& curve,
ON_Interval trim_parameters
)
{
ON_Curve* trimmed_curve = 0;
const ON_Interval curve_domain = curve.Domain();
bool bDecreasing = trim_parameters.IsDecreasing();
trim_parameters.Intersection( curve_domain ); // trim_parameters will be increasing or empty
if ( bDecreasing )
{
trim_parameters.Swap();
if ( trim_parameters[0] == curve_domain[1] )
{
if ( trim_parameters[1] == curve_domain[0] )
return 0;
trim_parameters[0] = curve_domain[0];
}
else if ( trim_parameters[1] == curve_domain[0] )
trim_parameters[1] = curve_domain[1];
else if ( !trim_parameters.IsDecreasing() )
return 0;
}
if ( trim_parameters.IsDecreasing() && curve.IsClosed() )
{
ON_Curve* left_crv = curve.DuplicateCurve();
if ( !left_crv->Trim(ON_Interval(trim_parameters[0],curve_domain[1])) )
{
delete left_crv;
return 0;
}
ON_Curve* right_crv = curve.DuplicateCurve();
if ( !right_crv->Trim(ON_Interval(curve_domain[0],trim_parameters[1])) )
{
delete left_crv;
delete right_crv;
return 0;
}
ON_PolyCurve* polycurve = ON_PolyCurve::Cast(left_crv);
if ( polycurve == nullptr )
{
polycurve = new ON_PolyCurve();
polycurve->Append( left_crv );
}
ON_PolyCurve* ptmp = ON_PolyCurve::Cast(right_crv);
if ( ptmp )
{
int i;
for ( i = 0; i < ptmp->Count(); i++ )
{
ON_Interval sdom = ptmp->SegmentDomain(i);
ON_Curve* segment = ptmp->HarvestSegment(i);
segment->SetDomain(sdom[0],sdom[1]); // to keep relative parameterization unchanged
polycurve->Append( segment );
}
delete right_crv;
ptmp = 0;
right_crv = 0;
}
else
{
polycurve->Append( right_crv );
}
polycurve->SetDomain( trim_parameters[0], trim_parameters[1] + curve_domain.Length() );
trimmed_curve = polycurve;
}
else if ( trim_parameters.IsIncreasing() )
{
trimmed_curve = curve.DuplicateCurve();
if(!trimmed_curve || !trimmed_curve->Trim(trim_parameters) )
{
delete trimmed_curve;
trimmed_curve = 0;
}
}
return trimmed_curve;
}
bool ON_Curve::Split(
double, // t = curve parameter to split curve at
ON_Curve*&, // left portion returned here (can pass "this" as the pointer)
ON_Curve*& // right portion returned here (can pass "this" as the pointer)
) const
{
// override if curve can split itself
return false;
}
// virtual
int ON_Curve::GetNurbForm(
ON_NurbsCurve& nurbs_curve,
double tolerance,
const ON_Interval* subdomain
) const
{
return 0;
}
// virtual
int ON_Curve::HasNurbForm() const
{
return 0;
}
ON_NurbsCurve* ON_Curve::NurbsCurve(
ON_NurbsCurve* pNurbsCurve,
double tolerance,
const ON_Interval* subdomain
) const
{
ON_NurbsCurve* nurbs_curve = pNurbsCurve;
if ( !nurbs_curve )
nurbs_curve = new ON_NurbsCurve();
int rc = GetNurbForm( *nurbs_curve, tolerance, subdomain );
if ( !rc )
{
if (!pNurbsCurve)
delete nurbs_curve;
nurbs_curve = nullptr;
}
return nurbs_curve;
}
bool ON_Curve::GetCurveParameterFromNurbFormParameter(
double nurbs_t,
double* curve_t
) const
{
*curve_t = nurbs_t;
return false;
}
bool ON_Curve::GetNurbFormParameterFromCurveParameter(
double curve_t,
double* nurbs_t
) const
{
*nurbs_t = curve_t;
return false;
}
ON_CurveArray::ON_CurveArray( int initial_capacity )
: ON_SimpleArray<ON_Curve*>(initial_capacity)
{}
ON_CurveArray::~ON_CurveArray()
{
Destroy();
}
void ON_CurveArray::Destroy()
{
int i = m_capacity;
while ( i-- > 0 ) {
if ( m_a[i] ) {
delete m_a[i];
m_a[i] = nullptr;
}
}
Empty();
}
bool ON_CurveArray::Duplicate( ON_CurveArray& dst ) const
{
dst.Destroy();
dst.SetCapacity( Capacity() );
const int count = Count();
int i;
ON_Curve* curve;
for ( i = 0; i < count; i++ )
{
curve = 0;
if ( m_a[i] )
{
curve = m_a[i]->Duplicate();
}
dst.Append(curve);
}
return true;
}
bool ON_CurveArray::Write( ON_BinaryArchive& file ) const
{
bool rc = file.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 0 );
if (rc) rc = file.Write3dmChunkVersion(1,0);
if (rc ) {
int i;
rc = file.WriteInt( Count() );
for ( i = 0; rc && i < Count(); i++ ) {
if ( m_a[i] ) {
rc = file.WriteInt(1);
if ( rc )
rc = file.WriteObject( *m_a[i] ); // polymorphic curves
}
else {
// nullptr curve
rc = file.WriteInt(0);
}
}
if ( !file.EndWrite3dmChunk() )
rc = false;
}
return rc;
}
bool ON_CurveArray::Read( ON_BinaryArchive& file )
{
int major_version = 0;
int minor_version = 0;
ON__UINT32 tcode = 0;
ON__INT64 big_value = 0;
int flag;
Destroy();
bool rc = file.BeginRead3dmBigChunk( &tcode, &big_value );
if (rc)
{
rc = ( tcode == TCODE_ANONYMOUS_CHUNK );
if (rc) rc = file.Read3dmChunkVersion(&major_version,&minor_version);
if (rc && major_version == 1)
{
ON_Object* p;
int count;
rc = file.ReadInt( &count );
if (rc)
{
SetCapacity(count);
SetCount(count);
Zero();
int i;
for ( i = 0; rc && i < count && rc; i++ )
{
flag = 0;
rc = file.ReadInt(&flag);
if (rc && flag==1)
{
p = 0;
rc = file.ReadObject( &p ) ? true : false; // polymorphic curves
m_a[i] = ON_Curve::Cast(p);
if ( !m_a[i] )
delete p;
}
}
}
}
else
{
rc = false;
}
if ( !file.EndRead3dmChunk() )
{
rc = false;
}
}
return rc;
}
///////////////////////////////////////////////////////////////////////////////////////
////////////////////////// utilities for curve joining ///////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
static void ReverseSegs(ON_SimpleArray<CurveJoinSeg>& SArray)
{
int i;
for (i=0; i<SArray.Count(); i++)
SArray[i].bRev = !SArray[i].bRev;
SArray.Reverse();
return;
}
//distance from curve[id[0] end[end[0]] to curve[id[1]] end[end[1]] is dist.
struct CurveJoinEndData {
int id[2]; //index into array of curves
int end[2]; //0 for start, 1 for end
double dist;
double tan_dot;//If start-to-start or end-to-end, this will be -Tan0*Tan1. Otherwise Tan0*Tan1.
};
struct JoinEndCompareContext
{
public:
double dist_tol;
double dot_tol;
bool bUseTan;
};
static bool CJEDIsMatch(const CurveJoinEndData* a, const CurveJoinEndData* b)
//Do these represent a pair of possible joins in the same location?
{
for (int i=0; i<2; i++){
for (int j=0; j<2; j++){
if (a->id[i] == b->id[j] && a->end[i] == b->end[j])
return true;
}
}
return false;
}
static int CompareJoinEnds(void* ctext, const void* aA, const void* bB)
{
//The greater tan_dot is, the more tangent the ends are.
// Be sure that they have been adjusted for start meets start or end mets end.
JoinEndCompareContext* context = (JoinEndCompareContext*)ctext;
const CurveJoinEndData* a = (CurveJoinEndData*)aA;
const CurveJoinEndData* b = (CurveJoinEndData*)bB;
//If not comparing two matches at the same end of a curve, sort by id.
if (!CJEDIsMatch(a, b)){
if (a->id[0] < b->id[0]) return -1;
if (a->id[0] > b->id[0]) return 1;
if (a->id[1] < b->id[1]) return -1;
if (a->id[1] > b->id[1]) return 1;
return 0;
}
if (context->bUseTan){
//If one is real close and the other isn't,take the close one.
if (a->dist < context->dist_tol && b->dist >= context->dist_tol) return -1;
if (a->dist >= context->dist_tol && b->dist < context->dist_tol) return 1;
//If one is tangent and the other isn't, take the tangent one.
if (a->tan_dot > context->dot_tol && b->tan_dot <= context->dot_tol) return -1;
if (a->tan_dot <= context->dot_tol && b->tan_dot > context->dot_tol) return 1;
//If both are close, take the more tangent one
if (a->dist < context->dist_tol && b->dist < context->dist_tol){
if (a->tan_dot > b->tan_dot) return -1;
if (a->tan_dot < b->tan_dot) return 1;
}
//either both or neither are tangent. Take the closest.
if (a->dist < b->dist)
return -1;
if (a->dist > b->dist)
return 1;
if (a->id[0] < b->id[0]) return -1;
if (a->id[0] > b->id[0]) return 1;
if (a->id[1] < b->id[1]) return -1;
if (a->id[1] > b->id[1]) return 1;
return 0;
}
else {
if (a->dist < b->dist) return -1;
if (a->dist > b->dist) return 1;
if (a->tan_dot > b->tan_dot) return -1;
if (a->tan_dot < b->tan_dot) return 1;
if (a->id[0] < b->id[0]) return -1;
if (a->id[0] > b->id[0]) return 1;
if (a->id[1] < b->id[1]) return -1;
if (a->id[1] > b->id[1]) return 1;
return 0;
}
}
///////////////////////////////////////////////////////////////////////////////////////
////////////////////////// end of utilities for curve joining ///////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//This next bunch of code is to allow V5 style curve joining.
// The special case line/polyline code has been removed,
// as has the code to pick the most tangent result when
// multiple possibilities are present
///////////////////////////////////////////////
struct OldCurveJoinSeg {
int id;
bool bRev;
};
//distance from curve[id[0] end[end[0]] to curve[id[1]] end[end[1]] is dist.
struct OldCurveJoinEndData {
int id[2]; //index into array of curves
int end[2]; //0 for start, 1 for end
double dist;
};
static int OldCompareEndData(const OldCurveJoinEndData* a, const OldCurveJoinEndData* b)
{
if (a->dist < b->dist) return -1;
if (a->dist > b->dist) return 1;
return 0;
}
static void OldReverseSegs(ON_SimpleArray<OldCurveJoinSeg>& SArray)
{
int i;
for (i=0; i<SArray.Count(); i++)
SArray[i].bRev = !SArray[i].bRev;
SArray.Reverse();
return;
}
int
ON_JoinCurvesOld(const ON_SimpleArray<const ON_Curve*>& InCurves,
ON_SimpleArray<ON_Curve*>& OutCurves,
double join_tol,
bool bPreserveDirection, // = false
ON_SimpleArray<int>* key //=0
)
{
int i, count = OutCurves.Count();
if (InCurves.Count() < 1)
return 0;
int dim = InCurves[0]->Dimension();
for (i=1; i<InCurves.Count(); i++){
if (InCurves[i]->Dimension() != dim) return 0;
}
if (key) {
key->Reserve(InCurves.Count());
for (i=0; i<InCurves.Count(); i++) key->Append(-1);
}
//Copy curves, take out closed curves.
OutCurves.Reserve(InCurves.Count());
ON_SimpleArray<ON_Curve*> IC(InCurves.Count());
ON_SimpleArray<int> cmap(InCurves.Count());
for (i=0; i<InCurves.Count(); i++){
ON_Curve* C = InCurves[i]->DuplicateCurve();
if (!C) continue;
if (C->IsClosed()) {
if (key) (*key)[i] = OutCurves.Count();
OutCurves.Append(C);
}
else {
cmap.Append(i);
IC.Append(C);
}
}
//IC is a list of copies of all open curves. match endpoints and join into polycurves.
//copy curves that are not joined.
ON_3dPointArray Start(IC.Count());
Start.SetCount(IC.Count());
ON_3dPointArray End(IC.Count());
End.SetCount(IC.Count());
for (i=0; i<IC.Count(); i++){
Start[i] = IC[i]->PointAtStart();
End[i] = IC[i]->PointAtEnd();
}
//get a list of all possible joins
ON_SimpleArray<OldCurveJoinEndData> EData(IC.Count());
for (i=0; i<IC.Count(); i++){
int j;
for (j=0; j<IC.Count(); j++){
if (i==j) continue;
double dist = Start[i].DistanceTo(End[j]);
if (dist <= join_tol){
OldCurveJoinEndData& ED = EData.AppendNew();
ED.id[0] = i;
ED.end[0] = 0;
ED.id[1] = j;
ED.end[1] = 1;
ED.dist = dist;
}
if (!bPreserveDirection && i<j){
dist = Start[i].DistanceTo(Start[j]);
if (dist <= join_tol){
OldCurveJoinEndData& ED = EData.AppendNew();
ED.id[0] = i;
ED.end[0] = 0;
ED.id[1] = j;
ED.end[1] = 0;
ED.dist = dist;
}
dist = End[i].DistanceTo(End[j]);
if (dist <= join_tol){
OldCurveJoinEndData& ED = EData.AppendNew();
ED.id[0] = i;
ED.end[0] = 1;
ED.id[1] = j;
ED.end[1] = 1;
ED.dist = dist;
}
}
}
}
//sort possibilities by distance
EData.QuickSort(OldCompareEndData);
int* endspace = (int*)onmalloc(2*sizeof(int)*IC.Count());
memset(endspace, 0, 2*sizeof(int)*IC.Count());
int** endarray = (int**)onmalloc(sizeof(int*)*IC.Count());
//endarray[i] is an int[2]. if endarray[i][0] > 0, then IC[i] is part of
//polycurve endarray[i][0] - 1, and the start of IC[i] is interior to the polycurve.
//if endarray[i][1] > 0, then the end of IC[i] is interior. if both endarray[i][0] > 0
//and endarray[i][1] > 0, then they are equal.
for (i=0; i<IC.Count(); i++)
endarray[i] = &endspace[2*i];
ON_ClassArray<ON_SimpleArray<OldCurveJoinSeg> > SegsArray(IC.Count());
for (i=0; i<EData.Count(); i++){
const OldCurveJoinEndData& ED = EData[i];
if (endarray[ED.id[0]][ED.end[0]] > 0 || endarray[ED.id[1]][ED.end[1]] > 0)
continue; //one of these endpoints has already been join to a closer end
if (endarray[ED.id[0]][1 - ED.end[0]] == 0){
if (endarray[ED.id[1]][1 - ED.end[1]] == 0){//new curve
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = SegsArray.Count()+1;
ON_SimpleArray<OldCurveJoinSeg>& SArray = SegsArray.AppendNew();
SArray.Reserve(4);
OldCurveJoinSeg& Seg0 = SArray.AppendNew();
OldCurveJoinSeg& Seg1 = SArray.AppendNew();
if (ED.end[0]) {
Seg0.id = ED.id[0];
Seg0.bRev = false;
Seg1.id = ED.id[1];
Seg1.bRev = (ED.end[1]) ? true : false;
}
else {
Seg1.id = ED.id[0];
Seg1.bRev = false;
Seg0.id = ED.id[1];
Seg0.bRev = (ED.end[1]) ? false : true;
}
}
else {
//second curve is part of an existing sequence. Insert or append first curve.
ON_SimpleArray<OldCurveJoinSeg>& SArray = SegsArray[endarray[ED.id[1]][1 - ED.end[1]] - 1];
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] =
endarray[ED.id[1]][1 - ED.end[1]];
if (SArray[0].id == ED.id[1]){
OldCurveJoinSeg Seg;
Seg.id = ED.id[0];
Seg.bRev = (ED.end[0]) ? false : true;
SArray.Insert(0, Seg);
}
else {
OldCurveJoinSeg& Seg = SArray.AppendNew();
Seg.id = ED.id[0];
Seg.bRev = (ED.end[0]) ? true : false;
}
}
}
else if (endarray[ED.id[1]][1 - ED.end[1]] == 0){
//first curve is part of an existing sequence. Insert or append second curve.
ON_SimpleArray<OldCurveJoinSeg>& SArray = SegsArray[endarray[ED.id[0]][1 - ED.end[0]] - 1];
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] =
endarray[ED.id[0]][1 - ED.end[0]];
if (SArray[0].id == ED.id[0]){
OldCurveJoinSeg Seg;
Seg.id = ED.id[1];
Seg.bRev = (ED.end[1]) ? false : true;
SArray.Insert(0, Seg);
}
else {
OldCurveJoinSeg& Seg = SArray.AppendNew();
Seg.id = ED.id[1];
Seg.bRev = (ED.end[1]) ? true : false;
}
}
else {
//both are in existing sequences. join the sequences.
if (endarray[ED.id[0]][1 - ED.end[0]] == endarray[ED.id[1]][1 - ED.end[1]])
//closes off this curve
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] =
endarray[ED.id[0]][1 - ED.end[0]];
else {
int segid0 = endarray[ED.id[0]][1 - ED.end[0]];
int segid1 = endarray[ED.id[1]][1 - ED.end[1]];
ON_SimpleArray<OldCurveJoinSeg>& SArray0 = SegsArray[segid0 - 1];
ON_SimpleArray<OldCurveJoinSeg>& SArray1 = SegsArray[segid1 - 1];
if (SArray0[0].id == ED.id[0]){
if (SArray1[0].id == ED.id[1]){
OldReverseSegs(SArray0);
int j;
for (j=0; j<SArray1.Count(); j++){
if (endarray[SArray1[j].id][0] > 0) endarray[SArray1[j].id][0] = segid0;
if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0;
SArray0.Append(SArray1[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0;
SArray1.SetCount(0);
}
else {
int j;
for (j=0; j<SArray0.Count(); j++){
if (endarray[SArray0[j].id][0] > 0) endarray[SArray0[j].id][0] = segid1;
if (endarray[SArray0[j].id][1] > 0) endarray[SArray0[j].id][1] = segid1;
SArray1.Append(SArray0[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid1;
SArray0.SetCount(0);
}
}
else if (SArray1[0].id == ED.id[1]){
int j;
for (j=0; j<SArray1.Count(); j++){
if (endarray[SArray1[j].id][0] > 0) endarray[SArray1[j].id][0] = segid0;
if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0;
SArray0.Append(SArray1[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0;
SArray1.SetCount(0);
}
else {
OldReverseSegs(SArray1);
int j;
for (j=0; j<SArray1.Count(); j++) {
if (endarray[SArray1[j].id][0] > 0) endarray[SArray1[j].id][0] = segid0;
if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0;
SArray0.Append(SArray1[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0;
SArray1.SetCount(0);
}
}
}
}
//make polycurves out of sequences
for (i=0; i<SegsArray.Count(); i++){
ON_SimpleArray<OldCurveJoinSeg>& SArray = SegsArray[i];
if (SArray.Count() < 2) continue;
if (!bPreserveDirection){//if number of reversed segs is more than half, reverse.
int scount= 0;
int j;
for (j=0; j<SArray.Count(); j++) {
if (SArray[j].bRev)
scount++;
}
if (2*scount > SArray.Count())
OldReverseSegs(SArray);
}
ON_PolyCurve* PC = new ON_PolyCurve(SArray.Count());
bool pc_added = false;
int j;
int min_seg = 0;
int min_id = -1;
for (j=0; j<SArray.Count(); j++){
if (key)
(*key)[cmap[SArray[j].id]] = OutCurves.Count();
ON_Curve* C = IC[SArray[j].id];
if (min_id < 0 || SArray[j].id < min_id){
min_id = SArray[j].id;
min_seg = j;
}
if (SArray[j].bRev) C->Reverse();
if (PC->Count()){
bool bSet = true;
if (!ON_ForceMatchCurveEnds(*PC, 1, *C, 0)) {
ON_3dPoint P = PC->PointAtEnd();
ON_3dPoint Q = C->PointAtStart();
P = 0.5*(P+Q);
if (!PC->SetEndPoint(P) || !C->SetStartPoint(P)) {
ON_NurbsCurve* NC = C->NurbsCurve();
if (NC && NC->SetStartPoint(P)){
delete C;
C = NC;
}
else {
bSet = false;
delete NC;
if (PC->Count()) {
pc_added = true;
OutCurves.Append(PC);
}
if (key)
(*key)[cmap[SArray[j].id]]++;
OutCurves.Append(C);
int k;
for (k=j+1; k<SArray.Count(); k++){
if (key)
(*key)[cmap[SArray[k].id]] = OutCurves.Count();
OutCurves.Append(IC[SArray[k].id]);
}
break;
}
}
}
}
ON_PolyCurve* pPoly = ON_PolyCurve::Cast(C);
if( pPoly){
int si;
for (si=0; si<pPoly->Count(); si++){
const ON_Curve* SC = pPoly->SegmentCurve(si);
ON_Curve* SCCopy = SC->DuplicateCurve();
if (SCCopy) PC->Append(SCCopy);
}
delete pPoly;
}
else PC->Append(C);
}
if (!PC->Count()) delete PC;
else if (!pc_added) {
if (!PC->IsClosed() && PC->IsClosable(join_tol)){
if (!ON_ForceMatchCurveEnds(*PC, 0, *PC, 1))
PC->SetEndPoint(PC->PointAtStart());
}
if (PC->IsClosed() && min_id >= 0){
//int sc = PC->SpanCount();
double t = PC->SegmentDomain(min_seg)[0];
PC->ChangeClosedCurveSeam(t);
}
// 28 October 2010 Dale Lear
// I added the RemoveNesting() and SynchronizeSegmentDomains()
// lines so Rhino will create higher quality geometry.
PC->RemoveNesting();
PC->SynchronizeSegmentDomains();
OutCurves.Append(PC);
}
}
//add in singletons
for (i=0; i<IC.Count(); i++){
if (endarray[i][0] == 0 && endarray[i][1] == 0){
if (key) (*key)[cmap[i]] = OutCurves.Count();
OutCurves.Append(IC[i]);
}
}
/* This was added by greg to fix big curves that are nearly, but not quite, closed.
It causes problems when the curve is tiny.
for(i=0; i<OutCurves.Count(); i++){
if(!OutCurves[i]->IsClosed()){
ON_3dPoint s= OutCurves[i]->PointAtStart();
ON_3dPoint e = OutCurves[i]->PointAtEnd();
if(s.DistanceTo(e)<join_tol)
OutCurves[i]->SetEndPoint( s );
}
}
*/
//Chuck added this, 1/16/03.
for(i=0; i<OutCurves.Count(); i++){
ON_Curve* C = OutCurves[i];
if (!C || C->IsClosed()) continue;
if (C->IsClosable(join_tol))
C->SetEndPoint(C->PointAtStart());
}
onfree((void*)endarray);
onfree((void*)endspace);
return OutCurves.Count() - count;
}
bool ON_Curve::IsClosable(double tolerance,
double min_abs_size, //0.0
double min_rel_size //10.0
) const
{
if (IsClosed()) return true;
if (Degree() + SpanCount() < 4) return false;
ON_3dPoint P[6];
P[0] = PointAtStart();
P[5] = PointAtEnd();
double gap = P[0].DistanceTo(P[5]);
if (gap > tolerance) return false;
bool abs_ok = (min_abs_size < 0.0) ? true : false;
bool rel_ok = (min_rel_size <= 1.0) ? true : false;
bool ok = abs_ok && rel_ok;
if (!ok){
//make sure curve is long enough to close off.
int i;
double len = 0.0;
for (i=1; i<6; i++){
if (i!=5)
P[i] = PointAt(Domain().ParameterAt(0.2*i));
if (!abs_ok && P[i].DistanceTo(P[0]) > min_abs_size)
abs_ok = true;
if (!rel_ok){
len += P[i-1].DistanceTo(P[i]);
if (len >= min_rel_size*gap)
rel_ok = true;
}
ok = abs_ok && rel_ok;
if (ok) break;
}
}
return ok;
}
//Makes continuous ON_PolyCurves and ON_Polycurves. Appends results to OutCurves.
//returns number of curves appended.
int
ON_JoinCurves(const ON_SimpleArray<const ON_Curve*>& InCurves,
ON_SimpleArray<ON_Curve*>& OutCurves,
double join_tol,
bool bPreserveDirection, // = false
ON_SimpleArray<int>* key //=0
)
{
return ON_JoinCurves(InCurves, OutCurves, join_tol, ON_UNSET_VALUE, false, bPreserveDirection, key);
}
//////////////////////////////////////
class JoinCurveEnd
{
public:
JoinCurveEnd();
JoinCurveEnd(const JoinCurveEnd& src);
void Create(int cid, const ON_Curve& crv, int end, bool bDoTan);
~JoinCurveEnd();
JoinCurveEnd& operator=(const JoinCurveEnd& src);
int m_cid;//into curve array. -1 if not valid
int m_end;//0, 1
ON_3dPoint m_P;
ON_3dVector m_T;
bool m_bTanOK;
};
JoinCurveEnd::JoinCurveEnd()
:m_cid(-1), m_end(0), m_P(ON_3dPoint::Origin), m_T(ON_3dVector::ZeroVector), m_bTanOK(false)
{}
JoinCurveEnd::JoinCurveEnd(const JoinCurveEnd& src)
{
*this = src;
}
void JoinCurveEnd::Create(int cid, const ON_Curve& crv, int end, bool bGetTan)
{
m_cid = cid;
m_end = (end) ? 1 : 0;
m_bTanOK = false;
if (bGetTan){
if (!crv.EvTangent(crv.Domain()[m_end], m_P, m_T))
m_P = crv.PointAt(crv.Domain()[m_end]);
else
m_bTanOK = true;
}
else
m_P = crv.PointAt(crv.Domain()[m_end]);
}
JoinCurveEnd::~JoinCurveEnd()
{}
JoinCurveEnd& JoinCurveEnd::operator=(const JoinCurveEnd& src)
{
if ( this != &src )
{
m_cid = src.m_cid;
m_end = src.m_end;
m_bTanOK = src.m_bTanOK;
m_P = src.m_P;
m_T = src.m_T;
}
return *this;
}
struct JoinEndPair
{
JoinCurveEnd* a;
JoinCurveEnd* b;
double dist;
double dot;
};
#if defined(ON_DLL_TEMPLATE)
#pragma ON_PRAGMA_WARNING_PUSH
#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4231 )
ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray<JoinEndPair>;
#pragma ON_PRAGMA_WARNING_POP
#endif
struct JoinTreeContext
{
ON_SimpleArray<JoinEndPair>* Pairs;
bool bPreserveDirection;
bool bCheckDot;
double dot_tol;//Only used if bCheckDot is true
};
void ON_CALLBACK_CDECL JoinEndCallback(void* a_context, ON__INT_PTR a_idA, ON__INT_PTR a_idB)
{
JoinTreeContext* JTC = (JoinTreeContext*)a_context;
JoinEndPair JCP;
JCP.a = (JoinCurveEnd*)a_idA;
JCP.b = (JoinCurveEnd*)a_idB;
if (JCP.a->m_cid < 0 || JCP.b->m_cid < 0 || JCP.a->m_cid == JCP.b->m_cid)
return;
if (JTC->bPreserveDirection && JCP.a->m_end == JCP.b->m_end)
return;
if (JTC->bCheckDot){
if (!JCP.a->m_bTanOK || !JCP.b->m_bTanOK)
return;
double dot = JCP.a->m_T*JCP.b->m_T;
if (JCP.a->m_end == JCP.b->m_end) dot *= -1.0;
if (dot < JTC->dot_tol)
return;
JCP.dot = dot;
}
else
JCP.dot = JCP.a->m_T*JCP.b->m_T;
JCP.dist = JCP.a->m_P.DistanceTo(JCP.b->m_P);
JTC->Pairs->Append(JCP);
}
class JoinCurveEndArray
{
public:
JoinCurveEndArray();
~JoinCurveEndArray();
bool Create(const ON_SimpleArray<ON_Curve*>& Curves,
double dtol,
bool bPreserveDirection,
bool bGetTan,
bool bCheckDot,
double dot_tol);//Only used if bCheckDot is true
void Destroy();
int m_CurveCount;
JoinCurveEnd* m_Ends[2];//2*m_CurveCount
ON_SimpleArray<JoinEndPair> m_Pairs;
};
JoinCurveEndArray::JoinCurveEndArray()
:m_CurveCount(0)
{
m_Ends[0] = m_Ends[1] = 0;
}
JoinCurveEndArray::~JoinCurveEndArray()
{
Destroy();
}
void JoinCurveEndArray::Destroy()
{
m_Pairs.Empty();
for (int i=0; i<2; i++){
delete [] m_Ends[i];
m_Ends[i] = 0;
}
}
bool JoinCurveEndArray::Create(const ON_SimpleArray<ON_Curve*>& Curves,
double dtol,
bool bPreserveDirection,
bool bGetTan,
bool bCheckDot,
double dot_tol//Only used if bCheckDot is true
)
{
Destroy();
if (Curves.Count() == 0)
return false;
for (int i=0; i<2; i++){
m_Ends[i] = new JoinCurveEnd[Curves.Count()];
if (!m_Ends[i])
return false;
}
bool bGotOne = false;
for (int i=0; i<Curves.Count(); i++){
for (int j=0; j<2; j++){
JoinCurveEnd& J = m_Ends[j][i];
if (Curves[i]){
J.Create(i, *Curves[i], j, bGetTan);
bGotOne = true;
}
}
}
m_CurveCount = Curves.Count();
if (!bGotOne)
return false;
ON_RTree Tree;
for (int i=0; i<m_CurveCount; i++){
for (int j=0; j<2; j++){
const JoinCurveEnd& J = m_Ends[j][i];
if (J.m_cid < 0)
continue;
double min[3], max[3];
for (int k=0; k<3; k++)
min[k] = max[k] = J.m_P[k];
if (!Tree.Insert(min, max, (void*)&J))
return false;
}
}
JoinTreeContext JTC;
JTC.bCheckDot = bCheckDot;
JTC.bPreserveDirection = bPreserveDirection;
JTC.dot_tol = dot_tol;
JTC.Pairs = &m_Pairs;
if (!Tree.Search(dtol, JoinEndCallback, (void*)&JTC))
return false;
return true;
}
/////////////////////////////////////////
static bool GetCurveEndData(const ON_SimpleArray<ON_Curve*>& IC,
double join_tol, double dot_tol,
bool bUseTanAngle,
bool bPreserveDirection,
ON_SimpleArray<CurveJoinEndData>& EData)
{
JoinCurveEndArray JCA;
if (!JCA.Create(IC, join_tol, bPreserveDirection, bUseTanAngle, (dot_tol > 0.0) ? true : false, dot_tol))
return false;
for (int i=0; i<JCA.m_Pairs.Count(); i++){
JoinEndPair& Pair = JCA.m_Pairs[i];
CurveJoinEndData& ED = EData.AppendNew();
ED.dist = Pair.dist;
ED.end[0] = Pair.a->m_end;
ED.end[1] = Pair.b->m_end;
ED.id[0] = Pair.a->m_cid;
ED.id[1] = Pair.b->m_cid;
ED.tan_dot = Pair.dot;
if (ED.end[0] == ED.end[1])
ED.tan_dot *= -1.0;
}
return true;
}
static bool GetCurveEndData(int count,
const ON_3dPoint* StartPoints, const ON_3dPoint* EndPoints,//size count
const ON_3dVector* StartTans, const ON_3dVector* EndTans,//nullptr or size count
double join_tol, double dot_tol,
bool bUseTanAngle,
bool bPreserveDirection,
ON_SimpleArray<CurveJoinEndData>& EData
)
{
join_tol = join_tol * join_tol;
EData.Reserve(count);
bool bHaveTans = (StartTans && EndTans) ? true : false;
if (dot_tol < 0.0)
dot_tol = 0.0;
bool bCheckDot = (dot_tol > 0.0) ? true : false;
int i;
for (i=1; i<count; i++){
int j;
for (j=0; j<i; j++){
bool bDoIt[2][2];
double dist[2][2];
double dot[2][2];
for (int endi=0; endi<2; endi++){
for (int endj=0; endj<2; endj++){
bDoIt[endi][endj] = (!bPreserveDirection || endi != endj) ? true : false;
dist[endi][endj] = dot[endi][endj] = 0.0;
}
}
for (int endi=0; endi<2; endi++){
const ON_3dPoint& Pi = (endi) ? EndPoints[i] : StartPoints[i];
for (int endj=0; endj<2; endj++){
const ON_3dPoint& Pj = (endj) ? EndPoints[j] : StartPoints[j];
dist[endi][endj] = (Pi-Pj).LengthSquared();
if (dist[endi][endj] >= join_tol)
bDoIt[endi][endj] = false;
}
}
if (dist[0][0] < dist[0][1])
bDoIt[0][1] = false;
else if (dist[0][0] > dist[0][1])
bDoIt[0][0] = false;
if (dist[1][0] < dist[1][1])
bDoIt[1][1] = false;
else if (dist[1][0] > dist[1][1])
bDoIt[1][0] = false;
if (bHaveTans){
for (int endi=0; endi<2; endi++){
const ON_3dVector& Ti = (endi) ? EndTans[i] : StartTans[i];
for (int endj=0; endj<2; endj++){
if (!bDoIt[endi][endj])
continue;
const ON_3dVector& Tj = (endj) ? EndTans[j] : StartTans[j];
dot[endi][endj] = Ti*Tj;
if (endi==endj)
dot[endi][endj] *= -1.0;
if (bCheckDot && dot[endi][endj] <= dot_tol)
bDoIt[endi][endj] = false;
}
}
}
for (int endi=0; endi<2; endi++){
for (int endj=0; endj<2; endj++){
if (!bDoIt[endi][endj])
continue;
CurveJoinEndData& ED = EData.AppendNew();
ED.id[0] = i;
ED.end[0] = endi;
ED.id[1] = j;
ED.end[1] = endj;
ED.dist = dist[endi][endj];
ED.tan_dot = dot[endi][endj];
}
}
}
}
return true;
}
static void SortCurveEndData(int count, ON_SimpleArray<CurveJoinEndData>& EData,
double dist_tol, double dot_tol, bool bUseTanAngle,
ON_ClassArray<ON_SimpleArray<CurveJoinSeg> >& SegsArray,
ON_SimpleArray<int>& Singles)
{
//sort possibilities by distance
JoinEndCompareContext context;
context.bUseTan = bUseTanAngle;
context.dot_tol = dot_tol;
context.dist_tol = dist_tol;
ON_qsort((void*)EData.Array(), EData.Count(), sizeof(CurveJoinEndData), CompareJoinEnds, (void*)&context);
int* endspace = (int*)onmalloc(2*sizeof(int)*count);
memset(endspace, 0, 2*sizeof(int)*count);
int** endarray = (int**)onmalloc(sizeof(int*)*count);
//endarray[i] is an int[2]. if endarray[i][0] > 0, then IC[i] is part of
//polycurve endarray[i][0] - 1, and the start of IC[i] is interior to the polycurve.
//if endarray[i][1] > 0, then the end of IC[i] is interior. if both endarray[i][0] > 0
//and endarray[i][1] > 0, then they are equal.
for (int i=0; i<count; i++)
endarray[i] = &endspace[2*i];
SegsArray.Reserve(count);
for (int i=0; i<EData.Count(); i++){
const CurveJoinEndData& ED = EData[i];
if (endarray[ED.id[0]][ED.end[0]] > 0 || endarray[ED.id[1]][ED.end[1]] > 0)
continue; //one of these endpoints has already been join to a closer end
if (endarray[ED.id[0]][1 - ED.end[0]] == 0){
if (endarray[ED.id[1]][1 - ED.end[1]] == 0){//new curve
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = SegsArray.Count()+1;
ON_SimpleArray<CurveJoinSeg>& SArray = SegsArray.AppendNew();
SArray.Reserve(4);
CurveJoinSeg& Seg0 = SArray.AppendNew();
CurveJoinSeg& Seg1 = SArray.AppendNew();
if (ED.end[0]) {
Seg0.id = ED.id[0];
Seg0.bRev = false;
Seg1.id = ED.id[1];
Seg1.bRev = (ED.end[1]) ? true : false;
}
else {
Seg1.id = ED.id[0];
Seg1.bRev = false;
Seg0.id = ED.id[1];
Seg0.bRev = (ED.end[1]) ? false : true;
}
}
else {
//second curve is part of an existing sequence. Insert or append first curve.
ON_SimpleArray<CurveJoinSeg>& SArray = SegsArray[endarray[ED.id[1]][1 - ED.end[1]] - 1];
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] =
endarray[ED.id[1]][1 - ED.end[1]];
if (SArray[0].id == ED.id[1]){
CurveJoinSeg Seg;
Seg.id = ED.id[0];
Seg.bRev = (ED.end[0]) ? false : true;
SArray.Insert(0, Seg);
}
else {
CurveJoinSeg& Seg = SArray.AppendNew();
Seg.id = ED.id[0];
Seg.bRev = (ED.end[0]) ? true : false;
}
}
}
else if (endarray[ED.id[1]][1 - ED.end[1]] == 0){
//first curve is part of an existing sequence. Insert or append second curve.
ON_SimpleArray<CurveJoinSeg>& SArray = SegsArray[endarray[ED.id[0]][1 - ED.end[0]] - 1];
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] =
endarray[ED.id[0]][1 - ED.end[0]];
if (SArray[0].id == ED.id[0]){
CurveJoinSeg Seg;
Seg.id = ED.id[1];
Seg.bRev = (ED.end[1]) ? false : true;
SArray.Insert(0, Seg);
}
else {
CurveJoinSeg& Seg = SArray.AppendNew();
Seg.id = ED.id[1];
Seg.bRev = (ED.end[1]) ? true : false;
}
}
else {
//both are in existing sequences. join the sequences.
if (endarray[ED.id[0]][1 - ED.end[0]] == endarray[ED.id[1]][1 - ED.end[1]])
//closes off this curve
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] =
endarray[ED.id[0]][1 - ED.end[0]];
else {
int segid0 = endarray[ED.id[0]][1 - ED.end[0]];
int segid1 = endarray[ED.id[1]][1 - ED.end[1]];
ON_SimpleArray<CurveJoinSeg>& SArray0 = SegsArray[segid0 - 1];
ON_SimpleArray<CurveJoinSeg>& SArray1 = SegsArray[segid1 - 1];
if (SArray0[0].id == ED.id[0]){
if (SArray1[0].id == ED.id[1]){
ReverseSegs(SArray0);
int j;
for (j=0; j<SArray1.Count(); j++){
if (endarray[SArray1[j].id][0] > 0) endarray[SArray1[j].id][0] = segid0;
if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0;
SArray0.Append(SArray1[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0;
SArray1.SetCount(0);
}
else {
int j;
for (j=0; j<SArray0.Count(); j++){
if (endarray[SArray0[j].id][0] > 0) endarray[SArray0[j].id][0] = segid1;
if (endarray[SArray0[j].id][1] > 0) endarray[SArray0[j].id][1] = segid1;
SArray1.Append(SArray0[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid1;
SArray0.SetCount(0);
}
}
else if (SArray1[0].id == ED.id[1]){
int j;
for (j=0; j<SArray1.Count(); j++){
if (endarray[SArray1[j].id][0] > 0) endarray[SArray1[j].id][0] = segid0;
if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0;
SArray0.Append(SArray1[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0;
SArray1.SetCount(0);
}
else {
ReverseSegs(SArray1);
int j;
for (j=0; j<SArray1.Count(); j++) {
if (endarray[SArray1[j].id][0] > 0) endarray[SArray1[j].id][0] = segid0;
if (endarray[SArray1[j].id][1] > 0) endarray[SArray1[j].id][1] = segid0;
SArray0.Append(SArray1[j]);
}
endarray[ED.id[0]][ED.end[0]] = endarray[ED.id[1]][ED.end[1]] = segid0;
SArray1.SetCount(0);
}
}
}
}
for (int i=0; i<count; i++){
if (endarray[i][0] == 0 && endarray[i][1] == 0)
Singles.Append(i);
}
onfree((void*)endarray);
onfree((void*)endspace);
}
static bool SortEnds(int count,
const ON_3dPoint* StartPoints, const ON_3dPoint* EndPoints,//size count
const ON_3dVector* StartTans, const ON_3dVector* EndTans,//nullptr or size count
double join_tol, double kink_tol,
bool bUseTanAngle,
bool bPreserveDirection,
ON_ClassArray<ON_SimpleArray<CurveJoinSeg> >& SegsArray,
ON_SimpleArray<int>& Singles
)
{
if (!StartPoints || !EndPoints)
return false;
//get a list of all possible joins
ON_SimpleArray<CurveJoinEndData> EData;
double dot_tol = (kink_tol > 0.0) ? cos(kink_tol) : 0.0;
GetCurveEndData(count, StartPoints, EndPoints, StartTans, EndTans,
join_tol, dot_tol, bUseTanAngle, bPreserveDirection, EData);
SortCurveEndData(count, EData, 0.3*join_tol, 0.99984, bUseTanAngle, SegsArray, Singles);
return true;
}
ON_DECL bool SortEnds(const ON_SimpleArray<ON_Curve*>& IC,//Open, non-NULL
double join_tol, double kink_tol,
bool bUseTanAngle,
bool bPreserveDirection,
ON_ClassArray<ON_SimpleArray<CurveJoinSeg> >& SegsArray,
ON_SimpleArray<int>& Singles
)
{
//get a list of all possible joins
ON_SimpleArray<CurveJoinEndData> EData;
double dot_tol = (kink_tol > 0.0) ? cos(kink_tol) : 0.0;
if (!GetCurveEndData(IC,join_tol, dot_tol, bUseTanAngle, bPreserveDirection, EData))
return false;
SortCurveEndData(IC.Count(), EData, 0.3*join_tol, 0.99984, bUseTanAngle, SegsArray, Singles);
return true;
}
int
ON_JoinCurves(const ON_SimpleArray<const ON_Curve*>& InCurves,
ON_SimpleArray<ON_Curve*>& OutCurves,
double join_tol,
double kink_tol,
bool bUseTanAngle,
bool bPreserveDirection, // = false
ON_SimpleArray<int>* key //=0
)
{
bool bGetTans = (bUseTanAngle || kink_tol > 0.0) ? true : false;
double dot_tol = (kink_tol > 0.0) ? cos(kink_tol) : 0.0;
if (dot_tol < 0.0)
dot_tol = 0.0;
int i, count = OutCurves.Count();
if (InCurves.Count() < 1)
return 0;
int dim = InCurves[0]->Dimension();
for (i=1; i<InCurves.Count(); i++){
if (0 != InCurves[i] && InCurves[i]->Dimension() != dim) return 0;
}
if (key) {
key->Reserve(InCurves.Count());
for (i=0; i<InCurves.Count(); i++) key->Append(-1);
}
//Copy curves, take out closed curves.
OutCurves.Reserve(InCurves.Count());
ON_SimpleArray<ON_Curve*> IC(InCurves.Count());
ON_SimpleArray<int> cmap(InCurves.Count());
for (i=0; i<InCurves.Count(); i++){
if (0 == InCurves[i]) // 8 April, 2014 - Lowell - Rh-26021
continue;
ON_Curve* C = InCurves[i]->DuplicateCurve();
if (!C) continue;
if (C->IsClosed()) {
if (key) (*key)[i] = OutCurves.Count();
OutCurves.Append(C);
}
else {
cmap.Append(i);
IC.Append(C);
}
}
if (IC.Count() < 1)
return OutCurves.Count() - count;
ON_ClassArray<ON_SimpleArray<CurveJoinSeg> > SegsArray;
ON_SimpleArray<int> Singles;
//IC is a list of copies of all open curves. match endpoints and join into polycurves.
//copy curves that are not joined.
bool bNewWay = true;
if (!bNewWay){
ON_3dPointArray Start(IC.Count());
Start.SetCount(IC.Count());
ON_SimpleArray<ON_3dVector> StartTan;
if (bGetTans){
StartTan.Reserve(IC.Count());
StartTan.SetCount(IC.Count());
}
ON_3dPointArray End(IC.Count());
End.SetCount(IC.Count());
ON_SimpleArray<ON_3dVector> EndTan;
if (bGetTans){
EndTan.Reserve(IC.Count());
EndTan.SetCount(IC.Count());
}
for (i=0; i<IC.Count(); i++){
Start[i] = IC[i]->PointAtStart();
End[i] = IC[i]->PointAtEnd();
if (bGetTans){
StartTan[i] = IC[i]->TangentAt(IC[i]->Domain()[0]);
EndTan[i] = IC[i]->TangentAt(IC[i]->Domain()[1]);
}
}
SortEnds(IC.Count(), Start.Array(), End.Array(),
(bGetTans) ? StartTan.Array() : 0, (bGetTans) ? EndTan.Array() : 0,
join_tol, kink_tol, bUseTanAngle, bPreserveDirection, SegsArray, Singles);
}
else
SortEnds(IC,
join_tol, kink_tol, bUseTanAngle, bPreserveDirection, SegsArray, Singles);
//make polycurves out of sequences
for (i=0; i<SegsArray.Count(); i++){
ON_SimpleArray<CurveJoinSeg>& SArray = SegsArray[i];
if (SArray.Count() < 2) continue;
if (!bPreserveDirection)
{
//if number of reversed segs is more than half, reverse.
int count_local= 0;
int j;
for (j=0; j<SArray.Count(); j++) {
if (SArray[j].bRev)
count_local++;
}
if (2*count_local > SArray.Count())
ReverseSegs(SArray);
}
ON_PolyCurve* PC = new ON_PolyCurve(SArray.Count());
bool pc_added = false;
int j;
int min_seg = 0;
int min_id = -1;
for (j=0; j<SArray.Count(); j++){
if (key)
(*key)[cmap[SArray[j].id]] = OutCurves.Count();
ON_Curve* C = IC[SArray[j].id];
if (min_id < 0 || SArray[j].id < min_id){
min_id = SArray[j].id;
min_seg = j;
}
if (SArray[j].bRev) C->Reverse();
if (PC->Count()){
bool bSet = true;
if (!ON_ForceMatchCurveEnds(*PC, 1, *C, 0)) {
ON_3dPoint P = PC->PointAtEnd();
ON_3dPoint Q = C->PointAtStart();
P = 0.5*(P+Q);
if (!PC->SetEndPoint(P) || !C->SetStartPoint(P)) {
ON_NurbsCurve* NC = C->NurbsCurve();
if (NC && NC->SetStartPoint(P)){
delete C;
C = NC;
}
else {
bSet = false;
delete NC;
if (PC->Count()) {
pc_added = true;
OutCurves.Append(PC);
}
if (key)
(*key)[cmap[SArray[j].id]]++;
OutCurves.Append(C);
int k;
for (k=j+1; k<SArray.Count(); k++){
if (key)
(*key)[cmap[SArray[k].id]] = OutCurves.Count();
OutCurves.Append(IC[SArray[k].id]);
}
break;
}
}
}
}
ON_PolyCurve* pPoly = ON_PolyCurve::Cast(C);
if( pPoly){
int si;
for (si=0; si<pPoly->Count(); si++){
const ON_Curve* SC = pPoly->SegmentCurve(si);
ON_Curve* SCCopy = SC->DuplicateCurve();
if (SCCopy) PC->Append(SCCopy);
}
delete pPoly;
}
else PC->Append(C);
}
if (!PC->Count()) delete PC;
else if (!pc_added) {
if (!PC->IsClosed() && PC->IsClosable(join_tol)){
if (!ON_ForceMatchCurveEnds(*PC, 0, *PC, 1))
PC->SetEndPoint(PC->PointAtStart());
}
if (PC->IsClosed() && min_id >= 0){
//int sc = PC->SpanCount();
double t = PC->SegmentDomain(min_seg)[0];
PC->ChangeClosedCurveSeam(t);
}
// 28 October 2010 Dale Lear
// I added the RemoveNesting() and SynchronizeSegmentDomains()
// lines so Rhino will create higher quality geometry.
PC->RemoveNesting();
PC->SynchronizeSegmentDomains();
OutCurves.Append(PC);
}
}
//add in singletons
for (i=0; i<Singles.Count(); i++){
if (key) (*key)[cmap[Singles[i]]] = OutCurves.Count();
OutCurves.Append(IC[Singles[i]]);
}
/* This was added by greg to fix big curves that are nearly, but not quite, closed.
It causes problems when the curve is tiny.
for(i=0; i<OutCurves.Count(); i++){
if(!OutCurves[i]->IsClosed()){
ON_3dPoint s= OutCurves[i]->PointAtStart();
ON_3dPoint e = OutCurves[i]->PointAtEnd();
if(s.DistanceTo(e)<join_tol)
OutCurves[i]->SetEndPoint( s );
}
}
*/
//Chuck added this, 1/16/03.
for(i=0; i<OutCurves.Count(); i++){
ON_Curve* C = OutCurves[i];
if (!C || C->IsClosed()) continue;
if (C->IsClosable(join_tol))
C->SetEndPoint(C->PointAtStart());
}
return OutCurves.Count() - count;
}
static bool PolylineIsClosable(const ON_Polyline& pline, double tol)
{
if (pline.PointCount() < 4 || pline.IsClosed())
return false;
int id[6];
id[0] = 0;
id[5] = pline.PointCount()-1;
double gap = pline[id[0]].DistanceTo(pline[id[5]]);
if (gap > tol)
return false;
double min_rel_size = 10.0;
if (pline.PointCount() < 6)
return (pline.Length() < min_rel_size*gap) ? false : true;
int i;
double len = 0.0;
for (i=1; i<6; i++){
if (i!=5)
id[i] = (pline.PointCount()*i)/5;
len += pline[id[i-1]].DistanceTo(pline[id[i]]);
if (len >= min_rel_size*gap)
return true;
}
return false;
}
int ON_JoinPolylines(
const ON_SimpleArray<const ON_Polyline*>& InPlines,
ON_SimpleArray<ON_Polyline*>& OutPlines,
double join_tol,
double kink_tol,
bool bUseTanAngle,
bool bPreserveDirection, // = false
ON_SimpleArray<int>* key //=0
)
{
bool bGetTans = (bUseTanAngle || kink_tol > 0.0) ? true : false;
double dot_tol = (kink_tol > 0.0) ? cos(kink_tol) : 0.0;
if (dot_tol < 0.0)
dot_tol = 0.0;
int i, count = OutPlines.Count();
if (InPlines.Count() < 1)
return 0;
if (key) {
key->Reserve(InPlines.Count());
for (i=0; i<InPlines.Count(); i++) key->Append(-1);
}
//Copy curves, take out closed curves.
OutPlines.Reserve(InPlines.Count());
ON_SimpleArray<ON_Polyline*> IC(InPlines.Count());
ON_SimpleArray<int> cmap(InPlines.Count());
for (i=0; i<InPlines.Count(); i++){
if (0 == InPlines[i] || InPlines[i]->PointCount() < 2) // 8 April, 2014 - Lowell - Rh-26021
continue;
ON_Polyline* C = new ON_Polyline(*InPlines[i]);
if (!C) continue;
if (C->IsClosed()) {
if (key) (*key)[i] = OutPlines.Count();
OutPlines.Append(C);
}
else {
cmap.Append(i);
IC.Append(C);
}
}
if (IC.Count() < 1)
return OutPlines.Count() - count;
//IC is a list of copies of all open curves. match endpoints and join into polycurves.
//copy curves that are not joined.
ON_3dPointArray Start(IC.Count());
Start.SetCount(IC.Count());
ON_SimpleArray<ON_3dVector> StartTan;
if (bGetTans){
StartTan.Reserve(IC.Count());
StartTan.SetCount(IC.Count());
}
ON_3dPointArray End(IC.Count());
End.SetCount(IC.Count());
ON_SimpleArray<ON_3dVector> EndTan;
if (bGetTans){
EndTan.Reserve(IC.Count());
EndTan.SetCount(IC.Count());
}
for (i=0; i<IC.Count(); i++){
Start[i] = IC[i]->PointAt(0.0);
End[i] = IC[i]->PointAt((double)(IC[i]->PointCount())-1.0);
if (bGetTans){
StartTan[i] = IC[i]->TangentAt(0.0);
EndTan[i] = IC[i]->TangentAt((double)(IC[i]->PointCount())-1.0);
}
}
ON_ClassArray<ON_SimpleArray<CurveJoinSeg> > SegsArray;
ON_SimpleArray<int> Singles;
SortEnds(IC.Count(), Start.Array(), End.Array(),
(bGetTans) ? StartTan.Array() : 0, (bGetTans) ? EndTan.Array() : 0,
join_tol, kink_tol, bUseTanAngle, bPreserveDirection, SegsArray, Singles);
//make polylines out of sequences
for (i=0; i<SegsArray.Count(); i++){
ON_SimpleArray<CurveJoinSeg>& SArray = SegsArray[i];
if (SArray.Count() < 2) continue;
if (!bPreserveDirection)
{
//if number of reversed segs is more than half, reverse.
int count_local= 0;
int j;
for (j=0; j<SArray.Count(); j++) {
if (SArray[j].bRev)
count_local++;
}
if (2*count_local > SArray.Count())
ReverseSegs(SArray);
}
ON_Polyline* Pline = 0;
int j;
int min_seg = 0;
int min_id = -1;
for (j=0; j<SArray.Count(); j++){
if (key)
(*key)[cmap[SArray[j].id]] = OutPlines.Count();
ON_Polyline* C = IC[SArray[j].id];
if (min_id < 0 || SArray[j].id < min_id){
min_id = SArray[j].id;
min_seg = j;
}
if (SArray[j].bRev) C->Reverse();
if (Pline){
ON_3dPoint P = (*Pline)[Pline->PointCount()-1];
ON_3dPoint& Q = (*C)[0];
Q = 0.5*(P+Q);
Pline->Remove();
Pline->Append(C->PointCount(), C->Array());
delete C;
IC[SArray[j].id] = 0;
}
else
Pline = C;
}
if (Pline)
OutPlines.Append(Pline);
}
//add in singletons
for (i=0; i<Singles.Count(); i++){
if (key) (*key)[cmap[Singles[i]]] = OutPlines.Count();
OutPlines.Append(IC[Singles[i]]);
}
for(i=0; i<OutPlines.Count(); i++){
ON_Polyline* C = OutPlines[i];
if (!C || C->IsClosed()) continue;
if (PolylineIsClosable(*C, join_tol))
(*C)[C->PointCount()-1] = (*C)[0];
}
return OutPlines.Count() - count;
}
int ON_JoinLines(
const ON_SimpleArray<ON_Line>& InLines,
ON_ClassArray<ON_Polyline>& OutPolylines,
double tolerance,
bool bPreserveDirection,
ON_SimpleArray<int>* pKey
)
{
const int inlines_count = InLines.Count();
if (0 == inlines_count)
return 0;
if (pKey)
{
pKey->Reserve(inlines_count);
pKey->SetCount(inlines_count);
memset((void*)pKey->Array(), -1, (size_t)pKey->SizeOfArray());
}
const int outpolylines_count = OutPolylines.Count();
OutPolylines.Reserve(InLines.Count());
ON_SimpleArray<int> index_map(inlines_count);
index_map.SetCount(inlines_count);
for (int i = 0; i < inlines_count; i++)
index_map[i] = i;
ON_3dPointArray Start(inlines_count);
Start.SetCount(inlines_count);
ON_3dPointArray End(inlines_count);
End.SetCount(inlines_count);
for (int i = 0; i < inlines_count; i++)
{
Start[i] = InLines[i].from;
End[i] = InLines[i].to;
}
ON_ClassArray<ON_SimpleArray<CurveJoinSeg>> SegsArray;
ON_SimpleArray<int> Singles;
SortEnds(
inlines_count,
Start.Array(),
End.Array(),
nullptr,
nullptr,
tolerance,
ON_UNSET_VALUE,
false,
bPreserveDirection,
SegsArray,
Singles
);
for (int i = 0; i < SegsArray.Count(); i++)
{
ON_SimpleArray<CurveJoinSeg>& SArray = SegsArray[i];
if (SArray.Count() < 2)
continue;
if (!bPreserveDirection)
{
// If number of reversed segs is more than half, reverse.
int count_local = 0;
for (int j = 0; j < SArray.Count(); j++)
{
if (SArray[j].bRev)
count_local++;
}
if (2 * count_local > SArray.Count())
ReverseSegs(SArray);
}
ON_Polyline& polyline = OutPolylines.AppendNew();
int min_seg = 0;
int min_id = -1;
for (int j = 0; j < SArray.Count(); j++)
{
if (pKey)
(*pKey)[index_map[SArray[j].id]] = OutPolylines.Count();
ON_Line line = InLines[SArray[j].id];
if (min_id < 0 || SArray[j].id < min_id)
{
min_id = SArray[j].id;
min_seg = j;
}
if (SArray[j].bRev)
line.Reverse();
if (0 == polyline.Count())
polyline.Append(line.from);
polyline.Append(line.to);
}
}
for (int i = 0; i < OutPolylines.Count(); i++)
{
ON_Polyline& polyline = OutPolylines[i];
if (!polyline.IsClosed() && PolylineIsClosable(polyline, tolerance))
polyline.Append(polyline[0]);
}
return OutPolylines.Count() - outpolylines_count;
}
// returns true if t is sufficiently close to m_t[index]
// -1 <= index <= m_t.Count()
bool ON_Curve::ParameterSearch(double t, int& index, bool bEnableSnap,
const ON_SimpleArray<double>& m_t, double RelTol) const{
// 24 October 2003 Dale Lear - added comments and fixed bugs when t < m_t[0]
// If you make changes to this code, please discuss them with Dale Lear.
bool rc = false;
int count = m_t.Count();
ON_Interval c_dom = Domain();
index = -1;
if(count>1 && ON_IsValid(t))
{
index = ON_SearchMonotoneArray(m_t, count, t);
// index < 0 : means t < m_t[0]
// index == count-1: means t == m_t[count-1]
// index == count : means t > m_t[count-1]
rc = (index>=0 && index<=count-1 && t == m_t[index]);
if( bEnableSnap && !rc)
{
// see if t is within "ktol" of a value in m_t[]
double ktol = RelTol*( ON_Max(fabs(c_dom[0]) ,fabs(c_dom[1])));
if (index >= 0 && index < count-1 )
{
// If we get here, then m_t[index] < t < m_t[index+1]
double middle_t = 0.5*(m_t[index] + m_t[index+1]);
if( t < middle_t && t - m_t[index] <= ktol)
{
// t is a hair bigger than m_t[index]
rc = true;
}
else if( t > middle_t && m_t[index+1]-t <= ktol)
{
// t is a hair smaller than m_t[index+1]
rc = true;
index ++;
}
}
else if (index == count)
{
// If we get here, then t > m_t[count-1]
if( t-m_t[count-1]<=ktol)
{
// t is a hair bigger than m_t[count-1]
rc = true;
index = count-1;
}
}
else if (index<0)
{
// 22 October 2003 Dale Lear - added this case to match the index==count case above.
// If we get here, then t < m_t[0]
if( m_t[0]-t <= ktol)
{
// t is a hair smaller than m_t[count-1]
rc = true;
index = 0;
}
}
}
}
return rc;
}
bool ON_SortLines(
int line_count,
const ON_Line* line_list,
int* index,
bool* bReverse
)
{
ON_3dPoint StartP, EndP, Q;
double d, startd, endd;
int Ni, start_i, start_end, end_i, end_end, i, end;
if ( index )
{
for ( i = 0; i < line_count; i++ )
index[i] = i;
}
if ( bReverse )
{
for ( i = 0; i < line_count; i++ )
bReverse[i] = false;
}
if ( line_count < 1 || 0 == line_list || 0 == index || 0 == bReverse )
{
ON_ERROR("ON_SortLines - illegal input");
return false;
}
if ( 1 == line_count )
{
return true;
}
// sort lines
for ( Ni = 1; Ni < line_count; Ni++ )
{
/* index[] = some permutation of {0,...,line_count-1}
// N[index[0]], ..., N[index[Ni-1]] are in order
// N[index[j]] needs to be reversed if bReverse[j] is true
*/
start_i = end_i = Ni;
start_end = end_end = 0;
StartP = line_list[ index[0] ][(bReverse[0]) ? 1 : 0];
EndP = line_list[ index[Ni-1] ][(bReverse[Ni-1]) ? 0 : 1];
startd = StartP.DistanceTo( line_list[index[start_i]].from ); // "from" is correct here
endd = EndP.DistanceTo( line_list[index[end_i]].from ); // "from" is correct here
for ( i = Ni; i < line_count; i++ )
{
Q = line_list[index[i]].from;
for ( end = 0; end < 2; end++ )
{
d = StartP.DistanceTo( Q );
if ( d < startd )
{
start_i = i;
start_end = end;
startd = d;
}
d = EndP.DistanceTo( Q );
if ( d < endd )
{
end_i = i;
end_end = end;
endd = d;
}
Q = line_list[index[i]].to;
}
}
if ( startd < endd )
{
// N[index[start_i]] will be first in list
i = index[Ni];
index[Ni] = index[start_i];
index[start_i] = i;
start_i = index[Ni];
for ( i = Ni; i > 0; i-- )
{
index[i] = index[i-1];
bReverse[i] = bReverse[i-1];
}
index[0] = start_i;
bReverse[0] = (start_end != 1);
}
else
{
// N[index[end_i]] will be next in the list
i = index[Ni];
index[Ni] = index[end_i];
index[end_i] = i;
bReverse[Ni] = (end_end == 1);
}
}
return true;
}
bool ON_SortLines(
const ON_SimpleArray<ON_Line>& line_list,
int* index,
bool* bReverse
)
{
return ON_SortLines(line_list.Count(),line_list.Array(),index,bReverse);
}
bool ON_SortCurves( int curve_count, const ON_Curve* const* curve_list, int* index, bool* bReverse )
{
int i;
if ( curve_count < 1 || 0 == curve_list || 0 == curve_list[0] || 0 == index || 0 == bReverse )
{
if ( index )
{
for ( i = 0; i < curve_count; i++ )
index[i] = i;
}
if ( bReverse )
{
for ( i = 0; i < curve_count; i++ )
bReverse[i] = false;
}
ON_ERROR("ON_SortCurves - illegal input");
return false;
}
if ( 1 == curve_count )
{
index[0] = 0;
bReverse[0] = false;
return true;
}
// get start and end points
ON_SimpleArray< ON_Line > line_list(curve_count);
ON_Interval d;
bool rc = true;
for ( i = 0; i < curve_count; i++ )
{
index[i] = i;
bReverse[0] = false;
if ( !rc )
continue;
const ON_Curve* curve = curve_list[i];
if ( !curve )
{
rc = false;
continue;
}
d = curve->Domain();
if ( !d.IsIncreasing() )
{
rc = false;
continue;
}
ON_Line& line = line_list.AppendNew();
if ( !curve->EvPoint(d[0],line.from,1) || !curve->EvPoint(d[1],line.to,-1) )
{
rc = false;
}
}
if ( !rc )
{
ON_ERROR("ON_SortCurves - illegal input curve");
}
else
{
rc = ON_SortLines( curve_count, line_list, index, bReverse );
}
return rc;
}
bool ON_SortCurves( const ON_SimpleArray<const ON_Curve*>& curves, ON_SimpleArray<int>& index, ON_SimpleArray<bool>& bReverse )
{
const int curve_count = curves.Count();
index.Reserve(curve_count);
index.SetCount(curve_count);
bReverse.Reserve(curve_count);
bReverse.SetCount(curve_count);
return ON_SortCurves( curve_count,curves.Array(),index.Array(),bReverse.Array());
}
bool ON_SortCurves( const ON_SimpleArray<ON_Curve*>& curves, ON_SimpleArray<int>& index, ON_SimpleArray<bool>& bReverse )
{
const int curve_count = curves.Count();
index.Reserve(curve_count);
index.SetCount(curve_count);
bReverse.Reserve(curve_count);
bReverse.SetCount(curve_count);
return ON_SortCurves( curve_count,curves.Array(),index.Array(),bReverse.Array());
}