// // Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved. // OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert // McNeel & Associates. // // THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. // ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF // MERCHANTABILITY ARE HEREBY DISCLAIMED. // // For complete openNURBS copyright information see . // //////////////////////////////////////////////////////////////// #include "opennurbs.h" #if !defined(ON_COMPILING_OPENNURBS) // This check is included in all opennurbs source .c and .cpp files to insure // ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled. // When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined // and the opennurbs .h files alter what is declared and how it is declared. #error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs #endif ON_VIRTUAL_OBJECT_IMPLEMENT(ON_Surface,ON_Geometry,"4ED7D4E1-E947-11d3-BFE5-0010830122F0"); ON_Surface::ON_Surface() : ON_Geometry() {} ON_Surface::ON_Surface(const ON_Surface& src) : ON_Geometry(src) {} unsigned int ON_Surface::SizeOf() const { unsigned int sz = ON_Geometry::SizeOf(); sz += (sizeof(*this) - sizeof(ON_Geometry)); // Currently, the size of m_stree 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_Surface& ON_Surface::operator=(const ON_Surface& src) { DestroySurfaceTree(); ON_Geometry::operator=(src); return *this; } ON_Surface::~ON_Surface() { // Do not call the (virtual) DestroyRuntimeCache or // DestroySurfaceTree (which calls DestroyRuntimeCache() // because it opens the potential for crashes in a // "dirty" destructors of classes derived from ON_Surface // that to not use DestroyRuntimeCache() in their // destructors and to not set deleted pointers to zero. } ON_Surface* ON_Surface::DuplicateSurface() const { return Duplicate(); } ON::object_type ON_Surface::ObjectType() const { return ON::surface_object; } bool ON_Surface::GetDomain( int dir, double* t0, double* t1 ) const { ON_Interval d = Domain(dir); if ( t0 ) *t0 = d[0]; if ( t1 ) *t1 = d[1]; return d.IsIncreasing(); } bool ON_Surface::GetSurfaceSize( double* width, double* height ) const { if ( width ) *width = 0.0; if ( height ) *height = 0.0; return false; } bool ON_Surface::SetDomain( int dir, ON_Interval domain ) { return ( dir >= 0 && dir <= 1 && domain.IsIncreasing() && SetDomain( dir, domain[0], domain[1] )) ? true : false; } bool ON_Surface::SetDomain( int, // 0 sets first parameter's domain, 1 gets second parameter's domain double, double ) { // TODO make this pure virtual when all the source code is available return false; } const ON_SimpleArray ON_Surface::SpanVector(int dir) const { ON_SimpleArray span_vector; const int span_count = this->SpanCount(dir); if (span_count >= 1) { span_vector.Reserve(span_count + 1); if (this->GetSpanVector(dir, span_vector.Array())) span_vector.SetCount(span_count + 1); else span_vector.Destroy(); } return span_vector; // uses Rvalue clone - no array copied. } ////////// // If t is in the domain of the surface, GetSpanVectorIndex() returns the // span vector index "i" such that span_vector[i] <= t <= span_vector[i+1]. // The "side" parameter determines which span is selected when t is at the // end of a span. // //virtual bool ON_Surface::GetSpanVectorIndex( int dir, // 0 gets first parameter's domain, 1 gets second parameter's domain 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(dir); if ( span_count > 0 ) { double* span_vector = (double*)onmalloc((span_count+1)*sizeof(span_vector[0])); rc = GetSpanVector( dir, span_vector ); if (rc) { i = ON_NurbsSpanIndex( 2, span_count, 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; } bool ON_Surface::GetParameterTolerance( // returns tminus < tplus: parameters tminus <= s <= tplus int dir, double t, // t = parameter in domain double* tminus, // tminus double* tplus // tplus ) const { bool rc = false; ON_Interval d = Domain( dir ); if ( d.IsIncreasing() ) rc = ON_GetParameterTolerance( d.Min(), d.Max(), t, tminus, tplus ); return rc; } ON_Surface::ISO ON_Surface::IsIsoparametric( const ON_Curve& curve, const ON_Interval* subdomain ) const { ISO iso = not_iso; if ( subdomain ) { ON_Interval cdom = curve.Domain(); double t0 = cdom.NormalizedParameterAt(subdomain->Min()); double t1 = cdom.NormalizedParameterAt(subdomain->Max()); if ( t0 < t1-ON_SQRT_EPSILON ) { if ( (t0 > ON_SQRT_EPSILON && t0 < 1.0-ON_SQRT_EPSILON) || (t1 > ON_SQRT_EPSILON && t1 < 1.0-ON_SQRT_EPSILON) ) { cdom.Intersection(*subdomain); if ( cdom.IsIncreasing() ) { ON_NurbsCurve nurbs_curve; if ( curve.GetNurbForm( nurbs_curve, 0.0,&cdom) ) { return IsIsoparametric( nurbs_curve, 0 ); } } } } } ON_BoundingBox bbox; double tolerance = 0.0; const int dim = curve.Dimension(); if ( (dim == 2 || dim==3) && curve.GetBoundingBox(bbox) ) { iso = IsIsoparametric( bbox ); switch (iso) { case x_iso: case W_iso: case E_iso: // make sure curve is a (nearly) vertical line // and weed out vertical scribbles tolerance = bbox.m_max.x - bbox.m_min.x; if ( tolerance < ON_ZERO_TOLERANCE && ON_ZERO_TOLERANCE*1024.0 <= (bbox.m_max.y-bbox.m_min.y) ) { // 26 March 2007 Dale Lear // If tolerance is tiny, then use ON_ZERO_TOLERANCE // This fixes cases where iso curves where not getting // the correct flag because tol=1e-16 and the closest // point to line had calculation errors of 1e-15. tolerance = ON_ZERO_TOLERANCE; } if ( !curve.IsLinear( tolerance ) ) iso = not_iso; break; case y_iso: case S_iso: case N_iso: // make sure curve is a (nearly) horizontal line // and weed out horizontal scribbles tolerance = bbox.m_max.y - bbox.m_min.y; if ( tolerance < ON_ZERO_TOLERANCE && ON_ZERO_TOLERANCE*1024.0 <= (bbox.m_max.x-bbox.m_min.x) ) { // 26 March 2007 Dale Lear // If tolerance is tiny, then use ON_ZERO_TOLERANCE // This fixes cases where iso curves where not getting // the correct flag because tol=1e-16 and the closest // point to line had calculation errors of 1e-15. tolerance = ON_ZERO_TOLERANCE; } if ( !curve.IsLinear( tolerance ) ) iso = not_iso; break; default: // nothing here break; } } return iso; } ON_Surface::ISO ON_Surface::IsIsoparametric( const ON_BoundingBox& bbox ) const { ISO iso = not_iso; if ( bbox.m_min.z == bbox.m_max.z ) { const double ds = bbox.m_max.x - bbox.m_min.x; const double dt = bbox.m_max.y - bbox.m_min.y; double a, b, s0, s1, t0, t1; ON_Interval d = Domain(0); s0 = d.Min(); s1 = d.Max(); d = Domain(1); t0 = d.Min(); t1 = d.Max(); double stol = (s1-s0)/32.0; double ttol = (t1-t0)/32.0; if ( s0 < s1 && t0 < t1 && ( ds <= stol || dt <= ttol) ) { if ( ds*(t1-t0) <= dt*(s1-s0) ) { // check for s = constant iso if ( bbox.m_max.x <= s0+stol ) { // check for west side iso GetParameterTolerance( 0, s0, &a, &b); if ( a <= bbox.m_min.x && bbox.m_max.x <= b ) iso = W_iso; } else if ( bbox.m_min.x >= s1-stol ) { // check for east side iso GetParameterTolerance( 0, s1, &a, &b); if ( a <= bbox.m_min.x && bbox.m_max.x <= b ) iso = E_iso; } if ( iso == not_iso && (s0 < bbox.m_max.x || bbox.m_min.x < s1) ) { // check for interior "u = constant" iso GetParameterTolerance( 0, 0.5*(bbox.m_min.x+bbox.m_max.x), &a, &b); if ( a <= bbox.m_min.x && bbox.m_max.x <= b ) iso = x_iso; } } else { // check for t = constant iso if ( bbox.m_max.y <= t0+ttol ) { // check for south side iso GetParameterTolerance( 1, t0, &a, &b); if ( a < bbox.m_min.y && bbox.m_max.y <= b ) iso = S_iso; } else if ( bbox.m_min.y >= t1-ttol ) { // check for north side iso GetParameterTolerance( 1, t1, &a, &b); if ( a < bbox.m_min.y && bbox.m_max.y <= b ) iso = N_iso; } if ( iso == not_iso && (t0 < bbox.m_max.x || bbox.m_min.x < t1) ) { // check for interior "t = constant" iso GetParameterTolerance( 1, 0.5*(bbox.m_min.y+bbox.m_max.y), &a, &b); if ( a < bbox.m_min.y && bbox.m_max.y <= b ) iso = y_iso; } } } } return iso; } bool ON_Surface::IsPlanar( ON_Plane* plane, double tolerance ) const { return false; } bool ON_Surface::IsClosed(int dir) const { ON_Interval d = Domain(dir); if ( d.IsIncreasing() && Dimension() <= 3 ) { const int span_count = SpanCount(dir?0:1); const int span_degree = Degree(dir?0:1); if ( span_count > 0 && span_degree > 0 ) { ON_SimpleArray s(span_count+1); s.SetCount(span_count+1); int n = 2*span_degree+1; double delta = 1.0/n; ON_3dPoint P(ON_3dPoint::Origin); ON_3dPoint Q(ON_3dPoint::Origin); int hintP[2] = {0,0}; int hintQ[2] = {0,0}; double *u0, *u1, *v0, *v1; double t; ON_Interval sp; if ( dir ) { v0 = &d.m_t[0]; v1 = &d.m_t[1]; u0 = &t; u1 = &t; } else { u0 = &d.m_t[0]; u1 = &d.m_t[1]; v0 = &t; v1 = &t; } if ( GetSpanVector( dir?0:1, s.Array() ) ) { int span_index, i; for ( span_index = 0; span_index < span_count; span_index++ ) { sp.Set(s[span_index],s[span_index+1]); for ( i = 0; i < n; i++ ) { t = sp.ParameterAt(i*delta); if ( !Evaluate( *u0, *v0, 1, 3, P, 0, hintP ) ) return false; if ( !Evaluate( *u1, *v1, 2, 3, Q, 0, hintQ ) ) return false; if ( false == ON_PointsAreCoincident( 3, 0, &P.x, &Q.x ) ) return false; } } return true; } } } return false; } bool ON_Surface::IsPeriodic(int dir) const { return false; } bool ON_Surface::GetNextDiscontinuity( int dir, ON::continuity c, double t0, double t1, double* t, int* hint, int* dtype, double cos_angle_tolerance, double curvature_tolerance ) const { // 28 Jan 2005 - untested code // this function must be overridden by surface objects that // can have parametric discontinuities on the interior of the curve. bool rc = false; // using tmp_dtype means I don't have to keep checking for a null dtype; int tmp_dtype = 0; if ( !dtype ) dtype = &tmp_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. int hinta[2], hintb[2], span_index, j; ON_Interval domain = Domain(dir); ON_Interval span_domain; ON_2dPoint st0, st1; ON_3dVector Da[6], Db[6]; ON_3dVector& D1a = Da[1+dir]; ON_3dVector& D1b = Db[1+dir]; ON_3dVector& D2a = Da[3+2*dir]; ON_3dVector& D2b = Db[3+2*dir]; 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(dir) ) { int span_count = SpanCount(1-dir); double* span_vector = (span_count>0) ? ((double*)onmalloc((span_count+1)*sizeof(*span_vector))) : 0; if (!GetSpanVector(1-dir,span_vector)) span_count = 0; st0[dir] = domain[0]; st1[dir] = domain[1]; for ( span_index = 0; span_index < span_count && 1 != *dtype; span_index++ ) { span_domain.Set(span_vector[span_index],span_vector[span_index+1]); for ( j = (span_index?1:0); j <= 2 && 1 != *dtype; j++ ) { st0[1-dir] = span_domain.ParameterAt(0.5*j); st1[1-dir] = st0[1-dir]; if ( bTestD1 || bTestT ) { // need to check locus continuity at start/end of closed surface. if ( Evaluate(st0.x,st0.y,2,3,&Da[0].x,1,hinta) && Evaluate(st1.x,st1.y,2,3,&Db[0].x,2,hintb) ) { 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 && (Ka-Kb).Length() > curvature_tolerance ) { if ( dtype ) *dtype = 2; *t = t1; rc = true; } } } } } } if ( span_vector) { onfree(span_vector); } } else { // open curves are not locus continuous at ends. *dtype = 0; // locus C0 discontinuity *t = t1; rc = true; } } } } return rc; } void ON_IsG1Closed(const ON_Surface& Srf, bool closed[2]) { ON_Interval dom[2]; double t; dom[0] = Srf.Domain(0); dom[1] = Srf.Domain(1); closed[0] = Srf.IsClosed(0) && !Srf.GetNextDiscontinuity(0, ON::continuity::G1_locus_continuous, dom[0][0], dom[0][1], &t); closed[1] = Srf.IsClosed(1) && !Srf.GetNextDiscontinuity(1, ON::continuity::G1_locus_continuous, dom[1][0], dom[1][1], &t); } static bool PrincipalCurvaturesAreContinuous( bool bSmoothTest, double k1a, double k2a, // side "a" principal curvatures double k1b, double k2b, // side "b" principal curvatures double curvature_tolerance ) { ON_3dVector K[2]; K[0].x = k1a; K[0].y = 0.0; K[0].z = 0.0; K[1].x = k1b; K[1].y = 0.0; K[1].z = 0.0; // compare the first principal curvatures bool rc = ( bSmoothTest ) ? ON_IsGsmoothCurvatureContinuous(K[0],K[1],0.0,curvature_tolerance) : ON_IsG2CurvatureContinuous(K[0],K[1],0.0,curvature_tolerance); if ( rc ) { // compare the second principal curvatures K[0].x = k2a; K[1].x = k2b; rc = ( bSmoothTest ) ? ON_IsGsmoothCurvatureContinuous(K[0],K[1],0.0,curvature_tolerance) : ON_IsG2CurvatureContinuous(K[0],K[1],0.0,curvature_tolerance); } return rc; } bool ON_Surface::IsContinuous( ON::continuity desired_continuity, double s, 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 { int qi, span_count[2]; span_count[0] = SpanCount(0); span_count[1] = SpanCount(1); if ( span_count[0] <= 1 && span_count[1] <= 1 ) return true; ON_3dPoint P[4]; ON_3dVector Ds[4], Dt[4], Dss[4], Dst[4], Dtt[4], N[4], K1[4], K2[4]; double gauss[4], mean[4], kappa1[4], kappa2[4], sq[4], tq[4]; 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: { // 7 April 2005 Dale Lear // This locus continuity test was added. Prior to // this time, this function ignored the word "locus". // The reason for the change is that Chuck's filleting code // needs to query the continuity at the seams of closed surfaces. // See ON::continuity comments. The different sq[] values // must NOT be used when s == Domain(0)[0] and must always // be used when s == Domain(0)[1]. In particular, if a surface // is not closed in the "s" direction and s == Domain(1)[1], then // the answer to any locus query is false. ON_Interval d = Domain(0); if ( s == d[1] ) { sq[0] = sq[1] = d[0]; sq[2] = sq[3] = d[1]; } else { sq[0] = sq[1] = sq[2] = sq[3] = s; } d = Domain(1); // See ON::continuity comments. The different tq[] values // must NOT be used when t == Domain(1)[0] and must always // be used when t == Domain(1)[1]. In particular, if a surface // is not closed in the "t" direction and t == Domain(1)[1], then // the answer to any locus query is false. if ( t == d[1] ) { tq[0] = tq[3] = d[0]; tq[1] = tq[2] = d[1]; } else { tq[0] = tq[1] = tq[2] = tq[3] = t; } } break; default: sq[0] = sq[1] = sq[2] = sq[3] = s; tq[0] = tq[1] = tq[2] = tq[3] = t; break; } 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::C0_continuous: for ( qi = 0; qi < 4; qi++ ) { if ( !EvPoint( sq[qi], tq[qi], P[qi], qi+1 ) ) return false; if ( qi ) { if ( !(P[qi]-P[qi-1]).IsTiny(point_tolerance) ) return false; } } if ( !(P[3]-P[0]).IsTiny(point_tolerance) ) return false; break; case ON::continuity::C1_continuous: for ( qi = 0; qi < 4; qi++ ) { if ( !Ev1Der( sq[qi], tq[qi], P[qi], Ds[qi], Dt[qi], qi+1, hint ) ) return false; if ( qi ) { if ( !(P[qi]-P[qi-1]).IsTiny(point_tolerance) ) return false; if ( !(Ds[qi]-Ds[qi-1]).IsTiny(d1_tolerance) ) return false; if ( !(Dt[qi]-Dt[qi-1]).IsTiny(d1_tolerance) ) return false; } } if ( !(P[3]-P[0]).IsTiny(point_tolerance) ) return false; if ( !(Ds[3]-Ds[0]).IsTiny(d1_tolerance) ) return false; if ( !(Dt[3]-Dt[0]).IsTiny(d1_tolerance) ) return false; break; case ON::continuity::C2_continuous: for ( qi = 0; qi < 4; qi++ ) { if ( !Ev2Der( sq[qi], tq[qi], P[qi], Ds[qi], Dt[qi], Dss[qi], Dst[qi], Dtt[qi], qi+1, hint ) ) return false; if ( qi ) { if ( !(P[qi]-P[qi-1]).IsTiny(point_tolerance) ) return false; if ( !(Ds[qi]-Ds[qi-1]).IsTiny(d1_tolerance) ) return false; if ( !(Dt[qi]-Dt[qi-1]).IsTiny(d1_tolerance) ) return false; if ( !(Dss[qi]-Dss[qi-1]).IsTiny(d2_tolerance) ) return false; if ( !(Dst[qi]-Dst[qi-1]).IsTiny(d2_tolerance) ) return false; if ( !(Dtt[qi]-Dtt[qi-1]).IsTiny(d2_tolerance) ) return false; } } if ( !(P[3]-P[0]).IsTiny(point_tolerance) ) return false; if ( !(Ds[3]-Ds[0]).IsTiny(d1_tolerance) ) return false; if ( !(Dt[3]-Dt[0]).IsTiny(d1_tolerance) ) return false; if ( !(Dss[3]-Dss[0]).IsTiny(d2_tolerance) ) return false; if ( !(Dst[3]-Dst[0]).IsTiny(d2_tolerance) ) return false; if ( !(Dtt[3]-Dtt[0]).IsTiny(d2_tolerance) ) return false; break; case ON::continuity::G1_continuous: for ( qi = 0; qi < 4; qi++ ) { if ( !EvNormal( sq[qi], tq[qi], P[qi], N[qi], qi+1 ) ) return false; if ( qi ) { if ( !(P[qi]-P[qi-1]).IsTiny(point_tolerance) ) return false; if ( N[qi]*N[qi-1] < cos_angle_tolerance ) return false; } } if ( !(P[3]-P[0]).IsTiny(point_tolerance) ) return false; if ( N[3]*N[0] < cos_angle_tolerance ) return false; break; case ON::continuity::G2_continuous: case ON::continuity::Gsmooth_continuous: { bool bSmoothCon = (ON::continuity::Gsmooth_continuous == desired_continuity); for ( qi = 0; qi < 4; qi++ ) { if ( !Ev2Der( sq[qi], tq[qi], P[qi], Ds[qi], Dt[qi], Dss[qi], Dst[qi], Dtt[qi], qi+1, hint ) ) return false; if (!ON_EvNormal(qi+1, Ds[qi], Dt[qi], Dss[qi], Dst[qi], Dtt[qi], N[qi])) return false; ON_EvPrincipalCurvatures( Ds[qi], Dt[qi], Dss[qi], Dst[qi], Dtt[qi], N[qi], &gauss[qi], &mean[qi], &kappa1[qi], &kappa2[qi], K1[qi], K2[qi] ); if ( qi ) { if ( !(P[qi]-P[qi-1]).IsTiny(point_tolerance) ) return false; if ( N[qi]*N[qi-1] < cos_angle_tolerance ) return false; if ( !PrincipalCurvaturesAreContinuous(bSmoothCon,kappa1[qi],kappa2[qi],kappa1[qi-1],kappa2[qi-1],curvature_tolerance) ) return false; } } if ( !(P[3]-P[0]).IsTiny(point_tolerance) ) return false; if ( N[3]*N[0] < cos_angle_tolerance ) return false; if ( !PrincipalCurvaturesAreContinuous(bSmoothCon,kappa1[3],kappa2[3],kappa1[0],kappa2[0],curvature_tolerance) ) return false; } break; default: // intentionally ignoring other ON::continuity enum values break; } return true; } bool ON_Surface::IsSingular(int side) const { return false; } bool ON_Surface::IsSolid() const { const bool bIsClosed0 = ( IsClosed(0) || ( IsSingular(1) && IsSingular(3) ) ); const bool bIsClosed1 = ( IsClosed(1) || ( IsSingular(0) && IsSingular(2) ) ); if ( bIsClosed0 && bIsClosed1 ) return true; const ON_Extrusion* extrusion = ON_Extrusion::Cast(this); if ( 0 != extrusion && extrusion->IsSolid() ) return true; return false; } bool ON_Surface::IsAtSingularity(double s, double t, bool bExact //true by default ) const { if (bExact){ if (s == Domain(0)[0]){ if (IsSingular(3)) return true; } else if (s == Domain(0)[1]){ if (IsSingular(1)) return true; } if (t == Domain(1)[0]){ if (IsSingular(0)) return true; } else if (t == Domain(1)[1]){ if (IsSingular(2)) return true; } return false; } if (IsAtSingularity(s, t, true)) return true; bool bCheckPartials[2] = {false, false}; int i; double m[2]; for (i=0; i<2; i++) m[i] = Domain(i).Mid(); if (s < m[0]){ if (IsSingular(3)) bCheckPartials[1] = true; } else { if (IsSingular(1)) bCheckPartials[1] = true; } if (!bCheckPartials[0] && !bCheckPartials[1]){ if (t < m[1]){ if (IsSingular(0)) bCheckPartials[0] = true; } else { if (IsSingular(2)) bCheckPartials[0] = true; } } if (!bCheckPartials[0] && !bCheckPartials[1]) return false; ON_3dPoint P; ON_3dVector M[2], S[2]; if (!Ev1Der(s, t, P, S[0], S[1])) return false; if (!Ev1Der(m[0], m[1], P, M[0], M[1])) return false; for (i=0; i<2; i++){ if (!bCheckPartials[i]) continue; if (S[i].Length() < 1.0e-6 * M[i].Length()) return true; } return false; } int ON_Surface::IsAtSeam(double s, double t) const { int rc = 0; int i; for (i=0; i<2; i++){ if (!IsClosed(i)) continue; double p = (i) ? t : s; if (p == Domain(i)[0] || p == Domain(i)[1]) rc += (i+1); } return rc; } ON_3dPoint ON_Surface::PointAt( double s, double t ) const { ON_3dPoint p(0.0,0.0,0.0); EvPoint( s, t, p ); return p; } ON_3dVector ON_Surface::NormalAt( double s, double t ) const { ON_3dVector N(0.0,0.0,0.0); EvNormal( s, t, N ); return N; } bool ON_Surface::FrameAt( double u, double v, ON_Plane& frame) const { bool rc = false; ON_3dPoint origin; ON_3dVector udir, vdir, normal; if( EvNormal( u, v, origin, udir, vdir, normal)) { if ( udir.Unitize() ) vdir = ON_CrossProduct( normal, udir); else if ( vdir.Unitize() ) udir = ON_CrossProduct( vdir, normal); frame.CreateFromFrame( origin, udir, vdir); rc = frame.IsValid(); } return rc; } bool ON_Surface::EvPoint( // returns false if unable to evaluate double s, double t, // evaluation parameters ON_3dPoint& point, int side, // optional - determines which side to evaluate from // 0 = default // 1 from NE quadrant // 2 from NW quadrant // 3 from SW quadrant // 4 from SE quadrant int* hint // optional - evaluation hint (int[2]) 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( s, 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_Surface::Ev1Der( // returns false if unable to evaluate double s, double t, // evaluation parameters ON_3dPoint& point, ON_3dVector& ds, ON_3dVector& dt, int side, // optional - determines which side to evaluate from // 0 = default // 1 from NE quadrant // 2 from NW quadrant // 3 from SW quadrant // 4 from SE quadrant int* hint // optional - evaluation hint (int[2]) used to speed // repeated evaluations ) const { bool rc = false; const int dim = Dimension(); double ws[3*32]; double* v; point.x = 0.0; point.y = 0.0; point.z = 0.0; ds.x = 0.0; ds.y = 0.0; ds.z = 0.0; dt.x = 0.0; dt.y = 0.0; dt.z = 0.0; if ( dim <= 32 ) { v = ws; } else { v = (double*)onmalloc(3*dim*sizeof(*v)); } rc = Evaluate( s, t, 1, dim, v, side, hint ); point.x = v[0]; ds.x = v[dim]; dt.x = v[2*dim]; if ( dim > 1 ) { point.y = v[1]; ds.y = v[dim+1]; dt.y = v[2*dim+1]; if ( dim > 2 ) { point.z = v[2]; ds.z = v[dim+2]; dt.z = v[2*dim+2]; if ( dim > 32 ) onfree(v); } } return rc; } bool ON_Surface::Ev2Der( // returns false if unable to evaluate double s, double t, // evaluation parameters ON_3dPoint& point, ON_3dVector& ds, ON_3dVector& dt, ON_3dVector& dss, ON_3dVector& dst, ON_3dVector& dtt, int side, // optional - determines which side to evaluate from // 0 = default // 1 from NE quadrant // 2 from NW quadrant // 3 from SW quadrant // 4 from SE quadrant int* hint // optional - evaluation hint (int[2]) used to speed // repeated evaluations ) const { bool rc = false; const int dim = Dimension(); double ws[6*16]; double* v; point.x = 0.0; point.y = 0.0; point.z = 0.0; ds.x = 0.0; ds.y = 0.0; ds.z = 0.0; dt.x = 0.0; dt.y = 0.0; dt.z = 0.0; dss.x = 0.0; dss.y = 0.0; dss.z = 0.0; dst.x = 0.0; dst.y = 0.0; dst.z = 0.0; dtt.x = 0.0; dtt.y = 0.0; dtt.z = 0.0; if ( dim <= 16 ) { v = ws; } else { v = (double*)onmalloc(6*dim*sizeof(*v)); } rc = Evaluate( s, t, 2, dim, v, side, hint ); point.x = v[0]; ds.x = v[dim]; dt.x = v[2*dim]; dss.x = v[3*dim]; dst.x = v[4*dim]; dtt.x = v[5*dim]; if ( dim > 1 ) { point.y = v[1]; ds.y = v[dim+1]; dt.y = v[2*dim+1]; dss.y = v[3*dim+1]; dst.y = v[4*dim+1]; dtt.y = v[5*dim+1]; if ( dim > 2 ) { point.z = v[2]; ds.z = v[dim+2]; dt.z = v[2*dim+2]; dss.z = v[3*dim+2]; dst.z = v[4*dim+2]; dtt.z = v[5*dim+2]; if ( dim > 16 ) onfree(v); } } return rc; } bool ON_Surface::EvNormal( // returns false if unable to evaluate double s, double t, // evaluation parameters (s,t) ON_3dVector& normal, // unit normal int side, // optional - determines which side to evaluate from // 0 = default // 1 from NE quadrant // 2 from NW quadrant // 3 from SW quadrant // 4 from SE quadrant int* hint // optional - evaluation hint (int[2]) used to speed // repeated evaluations ) const { ON_3dPoint point; ON_3dVector ds, dt; return EvNormal( s, t, point, ds, dt, normal, side, hint ); } bool ON_Surface::EvNormal( // returns false if unable to evaluate double s, double t, // evaluation parameters (s,t) ON_3dPoint& point, // returns value of surface ON_3dVector& normal, // unit normal int side, // optional - determines which side to evaluate from // 0 = default // 1 from NE quadrant // 2 from NW quadrant // 3 from SW quadrant // 4 from SE quadrant int* hint // optional - evaluation hint (int[2]) used to speed // repeated evaluations ) const { ON_3dVector ds, dt; return EvNormal( s, t, point, ds, dt, normal, side, hint ); } bool ON_Surface::EvNormal( // returns false if unable to evaluate double s, double t, // evaluation parameters (s,t) ON_3dPoint& point, // returns value of surface ON_3dVector& ds, // first partial derivatives (Ds) ON_3dVector& dt, // (Dt) ON_3dVector& normal, // unit normal int side, // optional - determines which side to evaluate from // 0 = default // 1 from NE quadrant // 2 from NW quadrant // 3 from SW quadrant // 4 from SE quadrant int* hint // optional - evaluation hint (int[2]) used to speed // repeated evaluations ) const { // simple cross product normal - override to support singular surfaces bool rc = Ev1Der( s, t, point, ds, dt, side, hint ); if ( rc ) { const double len_ds = ds.Length(); const double len_dt = dt.Length(); // do not reduce the tolerance used here - there is a retry in the code // below. //17 Oct 2022 - Chuck - Added test for parallel partials. if ( len_ds > ON_SQRT_EPSILON*len_dt && len_dt > ON_SQRT_EPSILON*len_ds && ds.IsParallelTo(dt, 0.01*ON_DEFAULT_ANGLE_TOLERANCE) == 0) { ON_3dVector a = ds/len_ds; ON_3dVector b = dt/len_dt; normal = ON_CrossProduct( a, b ); rc = normal.Unitize(); } else { // see if we have a singular point ON_3dVector v[6]; int normal_side = side; bool bOnSide = false; ON_Interval sdom = Domain(0); ON_Interval tdom = Domain(1); if (s == sdom.Min()) { normal_side = (normal_side >= 3) ? 4 : 1; bOnSide = true; } else if (s == sdom.Max()) { normal_side = (normal_side >= 3) ? 3 : 2; bOnSide = true; } if (t == tdom.Min()) { normal_side = (normal_side == 2 || normal_side == 3) ? 2 : 1; bOnSide = true; } else if (t == tdom.Max()) { normal_side = (normal_side == 2 || normal_side == 3) ? 3 : 4; bOnSide = true; } if ( !bOnSide ) { // 2004 November 11 Dale Lear // Added a retry again with a more generous tolerance if ( len_ds > ON_EPSILON*len_dt && len_dt > ON_EPSILON*len_ds ) { ON_3dVector a = ds/len_ds; ON_3dVector b = dt/len_dt; normal = ON_CrossProduct( a, b ); rc = normal.Unitize(); } else { rc = false; } } else { rc = Evaluate( s, t, 2, 3, &v[0].x, normal_side, hint ); if ( rc ) { rc = ON_EvNormal( normal_side, v[1], v[2], v[3], v[4], v[5], normal); } } } } if ( !rc ) { normal = ON_3dVector::ZeroVector; } return rc; } //virtual ON_Curve* ON_Surface::IsoCurve( int dir, // 0 first parameter varies and second parameter is constant // e.g., point on IsoCurve(0,c) at t is srf(t,c) - Horizontal // 1 first parameter is constant and second parameter varies // e.g., point on IsoCurve(1,c) at t is srf(c,t) - Vertical double c // value of constant parameter ) const { return nullptr; } //virtual bool ON_Surface::Trim( int dir, const ON_Interval& domain ) { return false; } //virtual bool ON_Surface::Extend( int dir, const ON_Interval& domain ) { return false; } //virtual bool ON_Surface::Split( int dir, double c, ON_Surface*& west_or_south_side, ON_Surface*& east_or_north_side ) const { return false; } // virtual int ON_Surface::GetNurbForm( ON_NurbsSurface& nurbs_surface, double tolerance ) const { return 0; } // virtual int ON_Surface::HasNurbForm( ) const { return 0; } bool ON_Surface::GetSurfaceParameterFromNurbFormParameter( double nurbs_s, double nurbs_t, double* surface_s, double* surface_t ) const { // NOTE: ON_SumSurface and ON_RevSurface override this virtual function *surface_s = nurbs_s; *surface_t = nurbs_t; return true; } bool ON_Surface::GetNurbFormParameterFromSurfaceParameter( double surface_s, double surface_t, double* nurbs_s, double* nurbs_t ) const { // NOTE: ON_SumSurface and ON_RevSurface override this virtual function *nurbs_s = surface_s; *nurbs_t = surface_t; return true; } ON_NurbsSurface* ON_Surface::NurbsSurface( ON_NurbsSurface* pNurbsSurface, double tolerance, const ON_Interval* s_subdomain, const ON_Interval* t_subdomain ) const { ON_NurbsSurface* nurbs_surface = pNurbsSurface; if ( !nurbs_surface ) nurbs_surface = new ON_NurbsSurface(); int rc = GetNurbForm( *nurbs_surface, tolerance ); if ( !rc ) { if (!pNurbsSurface) delete nurbs_surface; nurbs_surface = nullptr; } return nurbs_surface; } ON_SurfaceArray::ON_SurfaceArray( int initial_capacity ) : ON_SimpleArray(initial_capacity) {} ON_SurfaceArray::~ON_SurfaceArray() { Destroy(); } void ON_SurfaceArray::Destroy() { int i = m_capacity; while ( i-- > 0 ) { if ( m_a[i] ) { delete m_a[i]; m_a[i] = nullptr; } } Empty(); } bool ON_SurfaceArray::Duplicate( ON_SurfaceArray& dst ) const { dst.Destroy(); dst.SetCapacity( Capacity() ); const int count = Count(); int i; ON_Surface* surface; for ( i = 0; i < count; i++ ) { surface = 0; if ( m_a[i] ) { surface = m_a[i]->Duplicate(); } dst.Append(surface); } return true; } bool ON_SurfaceArray::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 surfaces } else { // nullptr surface rc = file.WriteInt(0); } } if ( !file.EndWrite3dmChunk() ) rc = false; } return rc; } bool ON_SurfaceArray::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 = 0; rc = file.ReadInt( &count ); if (rc && count < 0) rc = false; 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 ); // polymorphic surfaces m_a[i] = ON_Surface::Cast(p); if ( !m_a[i] ) delete p; } } } } else { rc = false; } if ( !file.EndRead3dmChunk() ) rc = false; } return rc; } bool ON_Surface::HasBrepForm() const { return true; } ON_Brep* ON_Surface::BrepForm( ON_Brep* brep ) const { ON_Brep* pBrep = nullptr; if ( brep ) brep->Destroy(); // 26 August 2008 Dale Lear - fixed bug // When this function was called on an ON_SurfaceProxy //ON_Surface* pSurface = Duplicate(); ON_Surface* pSurface = DuplicateSurface(); if ( pSurface ) { if ( brep ) pBrep = brep; else pBrep = new ON_Brep(); if ( !pBrep->Create(pSurface) ) { if ( pSurface ) { delete pSurface; pSurface = nullptr; } if ( !brep ) delete pBrep; pBrep = nullptr; } } return pBrep; } void ON_Surface::DestroySurfaceTree() { DestroyRuntimeCache(true); } ON_SurfaceProperties::ON_SurfaceProperties() { memset(this,0,sizeof(*this)); } void ON_SurfaceProperties::Set( const ON_Surface* surface ) { m_surface = surface; if ( 0 == m_surface ) { m_bIsSet = false; m_bHasSingularity = false; m_bIsSingular[0] = false; m_bIsSingular[1] = false; m_bIsSingular[2] = false; m_bIsSingular[3] = false; m_bHasSeam = false; m_bIsClosed[0] = false; m_bIsClosed[1] = false; m_domain[0].Set(0.0,0.0); m_domain[1].Set(0.0,0.0); } else { m_bIsSet = true; m_bIsSingular[0] = m_surface->IsSingular(0)?true:false; m_bIsSingular[1] = m_surface->IsSingular(1)?true:false; m_bIsSingular[2] = m_surface->IsSingular(2)?true:false; m_bIsSingular[3] = m_surface->IsSingular(3)?true:false; m_bHasSingularity = (m_bIsSingular[0] || m_bIsSingular[1] || m_bIsSingular[2] || m_bIsSingular[3]); m_bIsClosed[0] = m_surface->IsClosed(0)?true:false; m_bIsClosed[1] = m_surface->IsClosed(1)?true:false; m_bHasSeam = (m_bIsClosed[0] || m_bIsClosed[1]); m_domain[0] = m_surface->Domain(0); m_domain[1] = m_surface->Domain(1); } }