//---------------------------------------------------------------------------- // EgalTech 2013-2014 //---------------------------------------------------------------------------- // File : CurveBezier.cpp Data : 26.03.14 Versione : 1.5c9 // Contenuto : Implementazione della classe Curva di Bezier. // // // // Modifiche : 28.12.12 DS Creazione modulo. // 26.03.14 DS Modif. Save/Load, ora ogni punto su una linea. // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include "CurveBezier.h" #include "CurveComposite.h" #include "DistPointCrvBezier.h" #include "BiArcs.h" #include "GeoConst.h" #include "PolygonPlane.h" #include "GeoObjFactory.h" #include "NgeWriter.h" #include "NgeReader.h" #include "PolynomialPoint3d.h" #include "Bernstein.h" #include "deCasteljau.h" #include "Voronoi.h" #include "IntersLineLine.h" #include "/EgtDev/Include/EGkCurveArc.h" #include "/EgtDev/Include/EGkDistPointLine.h" #include "/EgtDev/Include/EGkStringUtils3d.h" #include "/EgtDev/Include/EGkUiUnits.h" #include "/EgtDev/Include/ENkPolynomial.h" #include "/EgtDev/Include/EgtNumUtils.h" #include "/EgtDev/Include/EgtPointerOwner.h" #include "/EgtDev/Include/EGkGeoPoint3d.h" #include "/EgtDev/Include/EGkCurveLine.h" #include using namespace std ; //---------------------------------------------------------------------------- static const double MIN_EXT_WEIGHT = 0.25 ; //---------------------------------------------------------------------------- GEOOBJ_REGISTER( CRV_BEZIER, NGE_C_BEZ, CurveBezier) ; //---------------------------------------------------------------------------- CurveBezier::CurveBezier( void) : m_nStatus( TO_VERIFY), m_nDeg(), m_bRat( false), m_dParSing( -2), m_VtExtr(), m_dThick(), m_nTempProp{0,0}, m_dTempParam{0.0,0.0}, m_pVoronoiObj( nullptr) { } //---------------------------------------------------------------------------- CurveBezier::~CurveBezier( void) { ResetVoronoiObject() ; } //---------------------------------------------------------------------------- bool CurveBezier::Init( int nDeg, bool bIsRational) { // verifico validità grado if ( nDeg < 1 || nDeg > MAXDEG) return false ; // imposto grado, flag di razionale e singolarità non studiata m_nDeg = nDeg ; m_bRat = bIsRational ; m_dParSing = - 2 ; // dimensiono i vettori dei punti e dei pesi m_vPtCtrl.assign( nDeg + 1, ORIG) ; if ( bIsRational) m_vWeCtrl.assign( nDeg + 1, 1) ; else m_vWeCtrl.clear() ; m_nStatus = TO_VERIFY ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; return Validate() ; } //---------------------------------------------------------------------------- bool CurveBezier::SetControlPoint( int nInd, const Point3d& ptCtrl) { // verifico validità indice if ( m_nStatus != OK || m_bRat || nInd < 0 || nInd > m_nDeg) return false ; // assegno il valore m_vPtCtrl[nInd] = ptCtrl ; // se razionale, metto il peso a 1 if ( m_bRat) m_vWeCtrl[nInd] = 1 ; // annullo analisi presenza singolarità m_dParSing = - 2 ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::SetControlPoint( int nInd, const Point3d& ptCtrl, double dW) { // verifico validità, razionalità e indice if ( m_nStatus != OK || ! m_bRat || nInd < 0 || nInd > m_nDeg) return false ; // verifico che il peso non sia nullo o negativo if ( dW < EPS_SMALL) return false ; // assegno il valore e il peso m_vPtCtrl[nInd] = ptCtrl ; m_vWeCtrl[nInd] = dW ; // annullo analisi presenza singolarità m_dParSing = - 2 ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::FromArc( const ICurveArc& crArc) { const double MAX_ANG_CEN_DEG = 120 ; if ( ! crArc.IsValid()) return false ; double dAngCen = crArc.GetAngCenter() ; if ( abs( dAngCen) > MAX_ANG_CEN_DEG) return false ; double dCosAhalf = cos( 0.5 * dAngCen * DEGTORAD) ; // se arco piatto, basta quadrica razionale // algoritmo di Sederberg (BYU) CAGD cap. 2 pag. 33 if ( abs( crArc.GetDeltaN()) < EPS_ZERO) { // peso del punto di controllo intermedio double dW = dCosAhalf ; // calcolo dei punti di controllo Point3d ptStart ; crArc.GetStartPoint( ptStart) ; Point3d ptEnd ; crArc.GetEndPoint( ptEnd) ; Point3d ptMed = Media( ptStart, ptEnd, 0.5) ; Vector3d vtDir = ptMed - crArc.GetCenter() ; Point3d ptNew = crArc.GetCenter() + vtDir / ( dCosAhalf * dCosAhalf) ; // inserimento nella curva dei punti di controllo con i pesi Init( 2, true) ; SetControlPoint( 0, ptStart, 1) ; SetControlPoint( 1, ptNew, dW) ; SetControlPoint( 2, ptEnd, 1) ; } // altrimenti per elica serve cubica razionale // algoritmo di Juhasz HelixApproxByCubicBezier CAD 1995 else { // peso dei punti di controllo intermedi double dW = ( 1 + 2 * dCosAhalf) / 3 ; // calcolo dei punti di controllo Point3d ptStart ; crArc.GetStartPoint( ptStart) ; Point3d ptEnd ; crArc.GetEndPoint( ptEnd) ; Vector3d vtDeltaN = crArc.GetDeltaN() * crArc.GetNormVersor() ; Point3d ptEndNoDN = ptEnd - vtDeltaN ; Point3d ptMed = Media( ptStart, ptEndNoDN, 0.5) ; Vector3d vtDir = ptMed - crArc.GetCenter() ; Point3d ptNew = crArc.GetCenter() + vtDir / ( dCosAhalf * dCosAhalf) ; Point3d ptNew1 = ( ptStart + 2 * dCosAhalf * ptNew) / ( 1 + 2 * dCosAhalf) + 0.5 * ( 1 - dCosAhalf / ( 2 + dCosAhalf)) * vtDeltaN ; Point3d ptNew2 = ( ptEndNoDN + 2 * dCosAhalf * ptNew) / ( 1 + 2 * dCosAhalf) + 0.5 * ( 1 + ( dCosAhalf / ( 2 + dCosAhalf))) * vtDeltaN ; // inserimento nella curva dei punti di controllo con i pesi Init( 3, true) ; SetControlPoint( 0, ptStart, 1) ; SetControlPoint( 1, ptNew1, dW) ; SetControlPoint( 2, ptNew2, dW) ; SetControlPoint( 3, ptEnd, 1) ; //// anziché usare una bezier cubica approssimo le eliche con più bezier quadratiche razionali. //// più è grande l'angolo al centro dell'arco e maggiore sarà l'errore di approssimazione // // // quadratica razionale // // peso del punto di controllo intermedio // double dW = dCosAhalf ; // // calcolo dei punti di controllo // Point3d ptStart ; // crArc.GetStartPoint( ptStart) ; // Point3d ptEnd ; // crArc.GetEndPoint( ptEnd) ; // // Point3d ptMed = Media( ptStart, ptEnd, 0.5) ; // //Vector3d vtDir = ptMed - crArc.GetCenter() ; // // Point3d ptNew = crArc.GetCenter() + vtDir / ( dCosAhalf * dCosAhalf) ; // Vector3d vtStart ; crArc.GetStartDir( vtStart) ; // Vector3d vtEnd ; crArc.GetEndDir( vtEnd) ; // PtrOwner pCrvLine1( CreateBasicCurveLine()) ; // pCrvLine1->SetPVL( ptStart, vtStart, 10) ; // PtrOwner pCrvLine2( CreateBasicCurveLine()) ; // pCrvLine2->SetPVL( ptEnd, vtEnd, 10) ; // IntersLineLine ill( *pCrvLine1, *pCrvLine2, false) ; // IntCrvCrvInfo iccInfo ; ill.GetIntCrvCrvInfo( iccInfo) ; // Point3d ptNew = 0.5 * (iccInfo.IciA->ptI + iccInfo.IciB->ptI) ; // // inserimento nella curva dei punti di controllo con i pesi // Init( 2, true) ; // SetControlPoint( 0, ptStart, 1) ; // SetControlPoint( 1, ptNew, dW) ; // SetControlPoint( 2, ptEnd, 1) ; } // copio estrusione e spessore crArc.GetExtrusion( m_VtExtr) ; crArc.GetThickness( m_dThick) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::FromLine( const ICurveLine& crLine) { if ( ! crLine.IsValid()) return false ; double dWeight = 1 ; int nCount = 0 ; Point3d ptStart ; crLine.GetStartPoint( ptStart) ; SetControlPoint( nCount, ptStart, dWeight) ; ++nCount ; double dPart = 1. / m_nDeg ; for ( int i = 1 ; i < m_nDeg ; ++i) { double dU = i * dPart ; Point3d ptMid ; crLine.GetPointD1D2( dU, ICurve::FROM_MINUS, ptMid) ; SetControlPoint( nCount, ptMid, dWeight) ; ++nCount ; } Point3d ptEnd ; crLine.GetEndPoint( ptEnd) ; SetControlPoint( nCount, ptEnd, dWeight) ; ++nCount ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::IsAPoint( void) const { // verifico lo stato if ( m_nStatus != OK) return false ; // ciclo sui punti for ( int i = 1 ; i <= m_nDeg ; ++ i) { if ( ! AreSamePointApprox( m_vPtCtrl[0], m_vPtCtrl[i])) return false ; } return true ; } //---------------------------------------------------------------------------- const Point3d& CurveBezier::GetControlPoint( int nInd, bool* pbOk) const { // verifico validità e indice if ( m_nStatus != OK || nInd < 0 || nInd > m_nDeg) { if ( pbOk != NULL) *pbOk = false ; return ORIG ; } // ritorno i dati if ( pbOk != NULL) *pbOk = true ; return m_vPtCtrl[nInd] ; } //---------------------------------------------------------------------------- double CurveBezier::GetControlWeight( int nInd, bool* pbOk) const { // verifico validità, razionalità e indice if ( m_nStatus != OK || ! m_bRat || nInd < 0 || nInd > m_nDeg) { if ( pbOk != NULL) *pbOk = false ; return 0 ; } // ritorno i dati if ( pbOk != NULL) *pbOk = true ; return m_vWeCtrl[nInd] ; } //---------------------------------------------------------------------------- CurveBezier* CurveBezier::Clone( void) const { // alloco oggetto CurveBezier* pCrv = new( nothrow) CurveBezier ; if ( pCrv != nullptr) { if ( ! pCrv->CopyFrom( *this)) { delete pCrv ; return nullptr ; } } return pCrv ; } //---------------------------------------------------------------------------- bool CurveBezier::CopyFrom( const IGeoObj* pGObjSrc) { const CurveBezier* pCB = GetBasicCurveBezier( pGObjSrc) ; if ( pCB == nullptr) return false ; return CopyFrom( *pCB) ; } //---------------------------------------------------------------------------- bool CurveBezier::CopyFrom( const CurveBezier& cbSrc) { if ( &cbSrc == this) return true ; if ( ! Init( cbSrc.m_nDeg, cbSrc.m_bRat)) return false ; m_vPtCtrl = cbSrc.m_vPtCtrl ; if ( cbSrc.m_bRat) m_vWeCtrl = cbSrc.m_vWeCtrl ; m_VtExtr = cbSrc.m_VtExtr ; m_dThick = cbSrc.m_dThick ; m_nTempProp[0] = cbSrc.m_nTempProp[0] ; m_nTempProp[1] = cbSrc.m_nTempProp[1] ; m_dTempParam[0] = cbSrc.m_dTempParam[0] ; m_dTempParam[1] = cbSrc.m_dTempParam[1] ; return true ; } //---------------------------------------------------------------------------- GeoObjType CurveBezier::GetType( void) const { return static_cast( GEOOBJ_GETTYPE( CurveBezier)) ; } //---------------------------------------------------------------------------- const string& CurveBezier::GetTitle( void) const { static const string sTitle = "CurveBezier" ; return sTitle ; } //---------------------------------------------------------------------------- bool CurveBezier::Dump( string& sOut, bool bMM, const char* szNewLine) const { // dati generali di una curva if ( ! CurveDump( *this, sOut, bMM, szNewLine)) return false ; // parametri : flag razionale sOut += ( m_bRat ? "Rat " : "Int ") ; // grado sOut += "Deg=" + ToString( m_nDeg) + szNewLine ; // ciclo sui punti di controllo ( con pesi se razionale) for ( int i = 0 ; i <= m_nDeg ; ++ i) { sOut += "PC(" + ToString( GetInUiUnits( m_vPtCtrl[i], bMM), 3) ; if ( m_bRat) sOut += "," + ToString( m_vWeCtrl[i], 3) ; sOut += string( ")") + szNewLine ; } return true ; } //---------------------------------------------------------------------------- int CurveBezier::GetNgeId( void) const { return GEOOBJ_GETNGEID( CurveBezier) ; } //---------------------------------------------------------------------------- bool CurveBezier::Save( NgeWriter& ngeOut) const { // flag razionale if ( ! ngeOut.WriteBool( m_bRat, ";")) return false ; // grado if ( ! ngeOut.WriteInt( m_nDeg, ";", true)) return false ; // ciclo sui punti di controllo ( con pesi se razionale) for ( int i = 0 ; i <= m_nDeg ; ++ i) { if ( ! m_bRat) { if ( ! ngeOut.WritePoint( m_vPtCtrl[i], ";", true)) return false ; } else { if ( ! ngeOut.WritePointW( m_vPtCtrl[i], m_vWeCtrl[i], ";", true)) return false ; } } // da versione 1008 : linea con VtEstrusione e Spessore if ( ! ngeOut.WriteVector( m_VtExtr, ";")) return false ; if ( ! ngeOut.WriteDouble( m_dThick, ";", true)) return false ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::Load( NgeReader& ngeIn) { // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // leggo la prossima linea ( 2 parametri) // recupero il flag razionale bool bIsRat ; if ( ! ngeIn.ReadBool( bIsRat, ";")) return false ; // recupero il grado int nDeg ; if ( ! ngeIn.ReadInt( nDeg, ";", true)) return false ; // inizializzo la curva di Bezier if ( ! Init( nDeg, bIsRat)) return false ; // se integrale if ( ! bIsRat) { // recupero e setto punti di controllo Point3d ptP ; for ( int i = 0 ; i <= nDeg ; ++ i) { // leggo la prossima linea ( un punto) if ( ! ngeIn.ReadPoint( ptP, ";", true)) return false ; // lo assegno if ( ! SetControlPoint( i, ptP)) return false ; } } // altrimenti razionale else { // recupero e setto punti di controllo Point3d ptP ; double dW ; for ( int i = 0 ; i <= nDeg ; ++ i) { // leggo la prossima linea ( un punto con peso) if ( ! ngeIn.ReadPointW( ptP, dW, ";", true)) return false ; // lo assegno if ( ! SetControlPoint( i, ptP, dW)) return false ; } } // da versione 1008 : linea con VtEstrusione e Spessore if ( ngeIn.GetFileVersion() >= NGE_VER_1008) { if ( ! ngeIn.ReadVector( m_VtExtr, ";")) return false ; if ( ! ngeIn.ReadDouble( m_dThick, ";", true)) return false ; } // eseguo validazione return Validate() ; } //---------------------------------------------------------------------------- bool CurveBezier::Validate( void) { if ( m_nStatus == TO_VERIFY) { for ( const auto& ptP : m_vPtCtrl) { if ( ! ptP.IsValid()) { m_nStatus = ERR ; break ; } } } if ( m_nStatus == TO_VERIFY) { for ( const auto& dWe : m_vWeCtrl) { if ( ! isfinite( dWe)) { m_nStatus = ERR ; break ; } } } if ( m_nStatus == TO_VERIFY) m_nStatus = ( ( m_nDeg >= 1 && m_vPtCtrl.size() >= 2) ? OK : ERR) ; return ( m_nStatus == OK) ; } //---------------------------------------------------------------------------- bool CurveBezier::GetLocalBBox( BBox3d& b3Loc, int nFlag) const { // verifico lo stato if ( m_nStatus != OK) return false ; // assegno il box in locale b3Loc.Reset() ; // basta approssimato if ( ( nFlag & BBF_EXACT) == 0) { for ( int i = 0 ; i <= m_nDeg ; ++ i) b3Loc.Add( m_vPtCtrl[i]) ; } // deve essere preciso else { // costruisco una approssimazione lineare PolyLine PL ; if ( ! ApproxWithLines( LIN_TOL_MIN, ANG_TOL_APPROX_DEG, APL_STD, PL)) return false ; // ciclo sui punti della approssimazione Point3d ptTemp ; for ( bool bFound = PL.GetFirstPoint( ptTemp) ; bFound ; bFound = PL.GetNextPoint( ptTemp)) { b3Loc.Add( ptTemp) ; } } // se c'è estrusione, devo tenerne conto if ( ! m_VtExtr.IsSmall() && abs( m_dThick) > EPS_SMALL) { Point3d ptMinExtr = b3Loc.GetMin() + m_VtExtr * m_dThick ; Point3d ptMaxExtr = b3Loc.GetMax() + m_VtExtr * m_dThick ; b3Loc.Add( ptMinExtr) ; b3Loc.Add( ptMaxExtr) ; } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::GetBBox( const Frame3d& frRef, BBox3d& b3Ref, int nFlag) const { // verifico lo stato if ( m_nStatus != OK) return false ; // verifico validità del frame if ( frRef.GetType() == Frame3d::ERR) return false ; // assegno il box nel riferimento b3Ref.Reset() ; // basta approssimato if ( ( nFlag & BBF_EXACT) == 0) { for ( int i = 0 ; i <= m_nDeg ; ++ i) { Point3d ptTemp = m_vPtCtrl[i] ; ptTemp.ToGlob( frRef) ; b3Ref.Add( ptTemp) ; } } // deve essere preciso else { // costruisco una approssimazione lineare PolyLine PL ; if ( ! ApproxWithLines( LIN_TOL_MIN, ANG_TOL_APPROX_DEG, APL_STD, PL)) return false ; // ciclo sui punti della approssimazione Point3d ptTemp ; for ( bool bFound = PL.GetFirstPoint( ptTemp) ; bFound ; bFound = PL.GetNextPoint( ptTemp)) { ptTemp.ToGlob( frRef) ; b3Ref.Add( ptTemp) ; } } // se c'è estrusione, devo tenerne conto if ( ! m_VtExtr.IsSmall() && abs( m_dThick) > EPS_SMALL) { Vector3d vtFrExtr = m_VtExtr ; vtFrExtr.ToGlob( frRef) ; Point3d ptMinExtr = b3Ref.GetMin() + vtFrExtr * m_dThick ; Point3d ptMaxExtr = b3Ref.GetMax() + vtFrExtr * m_dThick ; b3Ref.Add( ptMinExtr) ; b3Ref.Add( ptMaxExtr) ; } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::IsFlat( Plane3d& plPlane, bool bUseExtrusion, double dToler) const { // verifico lo stato if ( m_nStatus != OK) return false ; // polilinea dei punti rappresentativi PolyLine shPL ; int nLastPC = m_nDeg ; for ( int i = 0 ; i <= nLastPC ; ++ i) { double dU = i / double( nLastPC) ; if ( ! shPL.AddUPoint( dU, GetControlPoint( i))) return false ; } // recupero dati sulla planarità int nRank ; Point3d ptCen ; Vector3d vtDir ; bool bFlat = shPL.IsFlat( nRank, ptCen, vtDir, dToler) ; // se punto switch ( nRank) { case 0 : // punto if ( bFlat) plPlane.Set( ptCen, ( m_VtExtr.IsSmall() ? Z_AX : m_VtExtr)) ; else plPlane.Set( 0, ( m_VtExtr.IsSmall() ? V_NULL : m_VtExtr)) ; break ; case 1 : // linea if ( m_VtExtr.IsSmall()) plPlane.Set( ptCen, FromUprightOrtho( vtDir)) ; else { Vector3d vtN = OrthoCompo( m_VtExtr, vtDir) ; if ( ! vtN.Normalize()) vtN = FromUprightOrtho( vtDir) ; plPlane.Set( ptCen, vtN) ; bFlat = bFlat && ( ! bUseExtrusion || AreSameOrOppositeVectorApprox( m_VtExtr, vtN)) ; } break ; default : // piana o 3d if ( m_VtExtr.IsSmall()) plPlane.Set( ptCen, vtDir) ; else { plPlane.Set( ptCen, (( m_VtExtr * vtDir) > 0 ? vtDir : - vtDir)) ; bFlat = bFlat && ( ! bUseExtrusion || AreSameOrOppositeVectorApprox( m_VtExtr, vtDir)) ; } break ; } return bFlat ; } //---------------------------------------------------------------------------- bool CurveBezier::GetStartPoint( Point3d& ptStart) const { // verifico lo stato if ( m_nStatus != OK) return false ; // assegno il punto ptStart = m_vPtCtrl[0] ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::GetEndPoint( Point3d& ptEnd) const { // verifico lo stato if ( m_nStatus != OK) return false ; // assegno il punto ptEnd = m_vPtCtrl[m_nDeg] ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::GetMidPoint( Point3d& ptMid) const { // verifico lo stato if ( m_nStatus != OK) return false ; // determino il valore del parametro a metà lunghezza double dLen, dMid ; if ( ! GetLengthAtParam( 1, dLen) || ! GetParamAtLength( 0.5 * dLen, dMid)) return false ; // calcolo il punto return GetPointD1D2( dMid, ptMid) ; } //---------------------------------------------------------------------------- bool CurveBezier::GetCentroid( Point3d& ptCen) const { // verifico lo stato if ( m_nStatus != OK) return false ; // approssimo la curva con una polilinea PolyLine PL ; if ( ! ApproxWithLines( LIN_TOL_STD, ANG_TOL_STD_DEG, APL_STD, PL)) return false ; // calcolo il centro mediante PolygonPlane Point3d ptP ; PolygonPlane PolyPlane ; for ( bool bFound = PL.GetFirstPoint( ptP) ; bFound ; bFound = PL.GetNextPoint( ptP)) PolyPlane.AddPoint( ptP) ; if ( PolyPlane.GetCentroid( ptCen)) return true ; // se non riuscito, uso il punto medio return GetMidPoint( ptCen) ; } //---------------------------------------------------------------------------- bool CurveBezier::GetMidDir( Vector3d& vtDir) const { // verifico lo stato if ( m_nStatus != OK) return false ; // determino il valore del parametro a metà lunghezza double dLen, dMid ; if ( ! GetLengthAtParam( 1, dLen) || ! GetParamAtLength( 0.5 * dLen, dMid)) return false ; // calcolo la direzione return ::GetTang( *this, dMid, FROM_MINUS, vtDir) ; } //---------------------------------------------------------------------------- bool CurveBezier::GetPointD1D2( double dU, Side nS, Point3d& ptPos, Vector3d* pvtDer1, Vector3d* pvtDer2) const { // la curva deve essere validata if ( m_nStatus != OK) return false ; // il parametro U deve essere compreso tra 0 e 1 dU = Clamp( dU, 0., 1.) ; // verifico se punto singolare double dSingU ; if ( GetSingularParam( dSingU) > 0 && abs( dU - dSingU) < EPS_PARAM) { if ( nS == ICurve::FROM_MINUS) dU -= 100 * EPS_PARAM ; else dU += 100 * EPS_PARAM ; } return GetPointD1D2( dU, ptPos, pvtDer1, pvtDer2) ; } //---------------------------------------------------------------------------- // Calcolo i polinomi di Bernstein e da questi il punto ( vedi Piegl-Tiller) //---------------------------------------------------------------------------- bool CurveBezier::GetPointD1D2( double dU, Point3d& ptPos, Vector3d* pvtDer1, Vector3d* pvtDer2) const { // la curva deve essere validata if ( m_nStatus != OK) return false ; // il parametro U deve essere compreso tra 0 e 1 dU = Clamp( dU, 0., 1.) ; // se forma polinomiale (o integrale) if ( ! m_bRat) { // calcolo dei polinomi di Bernstein di grado opportuno DBLVECTOR vBern( m_nDeg + 1) ; GetAllBernstein( dU, m_nDeg - 2, vBern) ; // se richiesto, calcolo della derivata seconda if ( pvtDer2 != nullptr && pvtDer1 != nullptr) { *pvtDer2 = V_NULL ; for ( int i = 0 ; i <= m_nDeg - 2 ; ++ i) { *pvtDer2 += vBern[i] * ( m_vPtCtrl[i+2] + m_vPtCtrl[i] - 2 * m_vPtCtrl[i+1]) ; } *pvtDer2 *= m_nDeg * ( m_nDeg - 1) ; } // aumento il grado IncreaseAllBernsteinOneDegree( dU, m_nDeg - 1, vBern) ; // se richiesto, calcolo della derivata prima if ( pvtDer1 != nullptr) { *pvtDer1 = V_NULL ; for ( int i = 0 ; i <= m_nDeg - 1 ; ++ i) { *pvtDer1 += vBern[i] * ( m_vPtCtrl[i+1] - m_vPtCtrl[i]) ; } *pvtDer1 *= m_nDeg ; } // aumento il grado IncreaseAllBernsteinOneDegree( dU, m_nDeg, vBern) ; // calcolo del punto ptPos = ORIG ; for ( int i = 0 ; i <= m_nDeg ; ++ i) ptPos += vBern[i] * m_vPtCtrl[i] ; } // altrimenti forma razionale else { // porto i punti in forma omogenea moltiplicandoli per i pesi PNTVECTOR vPtWCtrl( m_nDeg + 1) ; for ( int i = 0 ; i <= m_nDeg ; ++ i) vPtWCtrl[i] = m_vWeCtrl[i] * m_vPtCtrl[i] ; // calcolo dei polinomi di Bernstein di grado opportuno DBLVECTOR vBern( m_nDeg + 1) ; GetAllBernstein( dU, m_nDeg - 2, vBern) ; // se richiesto, calcolo della derivata seconda double dW2 = 0 ; if ( pvtDer2 != nullptr && pvtDer1 != nullptr) { *pvtDer2 = V_NULL ; for ( int i = 0 ; i <= m_nDeg - 2 ; ++ i) { *pvtDer2 += vBern[i] * ( vPtWCtrl[i+2] + vPtWCtrl[i] - 2 * vPtWCtrl[i+1]) ; dW2 += vBern[i] * ( m_vWeCtrl[i+2] + m_vWeCtrl[i] - 2 * m_vWeCtrl[i+1]) ; } *pvtDer2 *= m_nDeg * ( m_nDeg - 1) ; dW2 *= m_nDeg * ( m_nDeg - 1) ; } // aumento il grado IncreaseAllBernsteinOneDegree( dU, m_nDeg - 1, vBern) ; // se richiesto, calcolo della derivata prima double dW1 = 0 ; if ( pvtDer1 != nullptr) { *pvtDer1 = V_NULL ; for ( int i = 0 ; i <= m_nDeg - 1 ; ++ i) { *pvtDer1 += vBern[i] * ( vPtWCtrl[i+1] - vPtWCtrl[i]) ; dW1 += vBern[i] * ( m_vWeCtrl[i+1] - m_vWeCtrl[i]) ; } *pvtDer1 *= m_nDeg ; dW1 *= m_nDeg ; } // aumento il grado IncreaseAllBernsteinOneDegree( dU, m_nDeg, vBern) ; // calcolo del punto double dW = 0 ; ptPos = ORIG ; for ( int i = 0 ; i <= m_nDeg ; ++ i) { ptPos += vBern[i] * vPtWCtrl[i] ; dW += vBern[i] * m_vWeCtrl[i] ; } // ritrasformo da forma omogenea a forma standard double dInvW = 1 / ( ( dW > EPS_ZERO) ? dW : EPS_ZERO) ; ptPos *= dInvW ; if ( pvtDer1 != nullptr) { Vector3d vtPos( ptPos.x, ptPos.y, ptPos.z) ; *pvtDer1 = ( *pvtDer1 - dW1 * vtPos) * dInvW ; if ( pvtDer2 != nullptr) *pvtDer2 = ( *pvtDer2 - 2 * dW1 * *pvtDer1 - dW2 * vtPos) * dInvW ; } } return true ; } //---------------------------------------------------------------------------- int CurveBezier::GetSingularParam( double& dPar) const { // se da calcolare, lo faccio if ( m_dParSing < -1.5) CalcSingularParam() ; // se esiste singolarità if ( m_dParSing > 0 && m_dParSing < 1) { dPar = m_dParSing ; return 1 ; } // altrimenti, non esiste else return 0 ; } //---------------------------------------------------------------------------- bool CurveBezier::CalcSingularParam( void) const { // non si considerano singolarità agli estremi ( non creano problemi) // le curve di grado 1 non possono avere singolarità if ( m_nDeg <= 1) { m_dParSing = -1 ; return true ; } // le curve di grado 2 possono avere singolarità solo se i punti sono allineati con ordine P0 P2 P1 if ( m_nDeg == 2) { Vector3d vtV1 = m_vPtCtrl[1] - m_vPtCtrl[0] ; Vector3d vtV2 = m_vPtCtrl[2] - m_vPtCtrl[1] ; if ( ( vtV1 * vtV2) > - EPS_ZERO) { m_dParSing = -1 ; return true ; } } // calcolo della derivata o del suo numeratore se curva razionale PolynomialPoint3d pol3P ; // se polinomiale if ( ! m_bRat) { // derivata come curva di Bezier delle differenze tra i punti di controllo CurveBezier cbDeriv ; cbDeriv.Init( m_nDeg - 1, false) ; for ( int i = 0 ; i < m_nDeg ; ++ i) cbDeriv.SetControlPoint( i, m_nDeg * ( m_vPtCtrl[i+1] + ( - m_vPtCtrl[i]))) ; // trasformo nella forma monomia cbDeriv.ToPowerBase( pol3P) ; } // altrimenti razionale else { // numeratore e denominatore in forma monomia PolynomialPoint3d pol3Num ; Polynomial polDen ; ToPowerBase( pol3Num, polDen) ; // derivata del numeratore PolynomialPoint3d pol3NumD ; pol3NumD.Derive( pol3Num) ; // derivata del denominatore Polynomial polDenD ; polDenD.Derive( polDen) ; // numeratore della derivata complessiva ( NumD * Den - Num * DenD) pol3P = pol3NumD * polDen - pol3Num * polDenD ; pol3P.AdjustDegree() ; } // calcolo gli zeri della componente più importante della derivata DBLVECTOR vdRoot ; int nZ = pol3P.FindMainComponentRoots( vdRoot) ; // scarto gli zeri fuori dall'intervallo aperto 0-1 e quelli ripetuti nZ = FilterMultipleAndOutOfRangeRoots( vdRoot, 0 + EPS_PARAM, 1 - EPS_PARAM, EPS_PARAM) ; // verifico se in corrispondenza di qualche zero si annulla la derivata for ( int i = 0 ; i < nZ ; ++ i) { Point3d ptPos ; Vector3d vtDer1 ; GetPointD1D2( vdRoot[i], ptPos, &vtDer1) ; if ( vtDer1.IsZero()) { m_dParSing = vdRoot[i] ; if ( GetEGkDebugLev() >= 5) LOG_DBG_INFO( GetEGkLogger(), "INFO : Found Singularity in CurveBezier") return true ; } } // non ci sono singolarità m_dParSing = -1 ; return true ; } //---------------------------------------------------------------------------- // i coefficienti sono ordinati dalla potenza più bassa alla più alta // se razionale, ritorna la sola forma monomiale del numeratore //---------------------------------------------------------------------------- bool CurveBezier::ToPowerBase( PolynomialPoint3d& pol3P) const { // algoritmo di Sederberg (BYU) CAGD cap. 3 // copio i punti di controllo PNTVECTOR vPow( m_vPtCtrl.begin(), m_vPtCtrl.end()) ; // se razionale, li moltiplico per i relativi pesi if ( m_bRat) { for ( int i = 0 ; i <= m_nDeg ; ++ i) vPow[i] *= m_vWeCtrl[i] ; } // applico differenze successive, lasciando sul posto il risultato for ( int i = 1 ; i <= m_nDeg ; ++ i) { for ( int j = m_nDeg ; j >= i ; -- j) vPow[j] += - vPow[j-1] ; } // calcolo i coefficienti binomiali DBLVECTOR dBinom( m_nDeg + 1, 0) ; dBinom[0] = 1 ; for ( int i = 1 ; i <= m_nDeg ; ++ i) { for ( int j = i ; j > 0 ; -- j) dBinom[j] += dBinom[j-1] ; } // ottengo i coefficienti delle potenze for ( int i = 1 ; i <= m_nDeg ; ++ i) vPow[i] *= dBinom[i] ; // assegno il risultato al parametro di ritorno pol3P.Set( m_nDeg, vPow) ; return true ; } //---------------------------------------------------------------------------- // i coefficienti sono ordinati dalla potenza più bassa alla più alta // se polinomiale, la forma monomiale del denominatore è un 1 //---------------------------------------------------------------------------- bool CurveBezier::ToPowerBase( PolynomialPoint3d& pol3Num, Polynomial& polDen) const { // algoritmo di Sederberg (BYU) CAGD cap. 3 // copio i punti di controllo PNTVECTOR vPow( m_vPtCtrl.begin(), m_vPtCtrl.end()) ; // sistemo i pesi DBLVECTOR dPow( m_nDeg + 1, 1) ; if ( m_bRat) { // moltiplico i punti per i relativi pesi for ( int i = 0 ; i <= m_nDeg ; ++ i) vPow[i] *= m_vWeCtrl[i] ; // copio i pesi dPow = m_vWeCtrl ; } // applico differenze successive, lasciando sul posto il risultato for ( int i = 1 ; i <= m_nDeg ; ++ i) { for ( int j = m_nDeg ; j >= i ; -- j) { vPow[j] += - vPow[j-1] ; if ( m_bRat) dPow[j] += - dPow[j-1] ; } } // calcolo i coefficienti binomiali DBLVECTOR dBinom( m_nDeg + 1, 0) ; dBinom[0] = 1 ; for ( int i = 1 ; i <= m_nDeg ; ++ i) { for ( int j = i ; j > 0 ; -- j) dBinom[j] += dBinom[j-1] ; } // ottengo i coefficienti delle potenze for ( int i = 1 ; i <= m_nDeg ; ++ i) { vPow[i] *= dBinom[i] ; if ( m_bRat) dPow[i] *= dBinom[i] ; } // assegno i risultati ai parametri di ritorno pol3Num.Set( m_nDeg, vPow) ; if ( m_bRat) polDen.Set( m_nDeg, dPow) ; else polDen.SetToConstant( 1) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::GetControlPolygonLength( double& dLen) const { // la curva deve essere valida if ( m_nStatus != OK) return false ; // ciclo sui punti di controllo dLen = 0 ; for ( int i = 1 ; i <= m_nDeg ; ++ i) dLen += Dist( m_vPtCtrl[i], m_vPtCtrl[i-1]) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::GetApproxLength( double& dLen) const { // la curva deve essere valida if ( m_nStatus != OK) return false ; // se ridotta ad un punto if ( IsAPoint()) { dLen = 0 ; return true ; } // se grado 1 if ( m_nDeg == 1) { dLen = Dist( m_vPtCtrl[0], m_vPtCtrl[m_nDeg]) ; return true ; } // la divido in due metà CurveBezier crvBez1 = *this ; crvBez1.TrimEndAtParam( 0.5) ; double dCordLen1 = Dist( crvBez1.m_vPtCtrl[0], crvBez1.m_vPtCtrl[m_nDeg]) ; double dCtrlLen1 ; crvBez1.GetControlPolygonLength( dCtrlLen1) ; CurveBezier crvBez2 = *this ; crvBez2.TrimStartAtParam( 0.5) ; double dCordLen2 = Dist( crvBez2.m_vPtCtrl[0], crvBez2.m_vPtCtrl[m_nDeg]) ; double dCtrlLen2 ; crvBez2.GetControlPolygonLength( dCtrlLen2) ; // applico la formula di Gravesen (Graphics Gems V) dLen = ( 2 * ( dCordLen1 + dCordLen2) + ( m_nDeg - 1) * ( dCtrlLen1 + dCtrlLen2)) / ( m_nDeg + 1) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::GetLength( double& dLen) const { return ( GetLengthAtParam( 1, dLen) && dLen > EPS_SMALL) ; } //---------------------------------------------------------------------------- double CurveBezier::GetSegmentLength( int nLev, double dU0, double dU1, double dU2, const Point3d& ptP0, const Point3d& ptP1, const Point3d& ptP2) const { // algoritmo dell'approssimazione con arco per tre punti di S. Vincent e D. Forsey const int MAX_LEV = 10 ; const double MAX_ARC = 1.05 ; const double LEN_RATIO = 1.2 ; // verifica superamento del massimo livello di recursione per debug if ( nLev >= MAX_LEV && GetEGkDebugLev() >= 5) LOG_DBG_ERR( GetEGkLogger(), "ERROR : Exceeded recursions") // calcolo delle distanze double dD1 = Dist( ptP0, ptP2) ; double dDa = Dist( ptP0, ptP1) ; double dDb = Dist( ptP1, ptP2) ; double dD2 = dDa + dDb ; // distanza piccola o massimo livello di recursione, approx arco ok if ( dD2 < EPS_SMALL || nLev >= MAX_LEV) return ( dD2 + ( dD2 - dD1) / 3) ; // se ci sono anomalie si suddivide if ( dD1 < EPS_SMALL || dD2 / dD1 > MAX_ARC || dDa < 0.1 * EPS_SMALL || dDb / dDa > LEN_RATIO || dDb < 0.1 * EPS_SMALL || dDa / dDb > LEN_RATIO) { double dUMid ; Point3d ptMid ; // prima metà dUMid = 0.5 * ( dU0 + dU1) ; GetPointD1D2( dUMid, ptMid) ; double dD3 = GetSegmentLength( nLev +1, dU0, dUMid, dU1, ptP0, ptMid, ptP1) ; // seconda metà dUMid = 0.5 * ( dU1 + dU2) ; GetPointD1D2( dUMid, ptMid) ; double dD4 = GetSegmentLength( nLev + 1, dU1, dUMid, dU2, ptP1, ptMid, ptP2) ; // la lunghezza è la somma delle due return ( dD3 + dD4) ; } // caso normale return ( dD2 + ( dD2 - dD1) / 3) ; } //---------------------------------------------------------------------------- bool CurveBezier::GetLengthAtParam( double dU, double& dLen) const { // la curva deve essere validata if ( m_nStatus != OK) return false ; // fuori dominio del parametro -> errore if ( dU < 0 - EPS_PARAM) return false ; if ( dU > ( 1 + EPS_PARAM)) return false ; // inizio if ( dU < 0 + EPS_PARAM) { dLen = 0 ; return true ; } // parto con un numero fisso di punti e poi affino dove serve dLen = 0 ; double dUIni = 0 ; Point3d ptIni ; GetPointD1D2( 0, ptIni) ; double dUMid ; Point3d ptMid ; const int NUM_SEG = 16 ; for ( int i = 1 ; i <= NUM_SEG ; ++ i) { // ricavo il punto double dUFin = i / (double) NUM_SEG * dU ; Point3d ptFin ; GetPointD1D2( dUFin, ptFin) ; // se punto dispari if ( ( i % 2) == 1) { // assegno nuovo medio dUMid = dUFin ; ptMid = ptFin ; } // se punto pari, calcolo la lunghezza else { dLen += GetSegmentLength( 0, dUIni, dUMid, dUFin, ptIni, ptMid, ptFin) ; // nuovo iniziale prende i valori del finale dUIni = dUFin ; ptIni = ptFin ; } } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::GetSegmentParam( double dLen, double& dCurrLen, double& dSegLen, double& dUIni, double& dUFin) const { double dUStart = dUIni ; double dUEnd = dUFin ; Point3d ptIni ; GetPointD1D2( dUIni, ptIni) ; double dUMid ; Point3d ptMid ; const int NUM_SEG = 16 ; for ( int i = 1 ; i <= NUM_SEG ; ++ i) { // ricavo il punto dUFin = dUStart + i / (double) NUM_SEG * ( dUEnd - dUStart) ; Point3d ptFin ; GetPointD1D2( dUFin, ptFin) ; // se punto dispari if ( ( i % 2) == 1) { // assegno nuovo medio dUMid = dUFin ; ptMid = ptFin ; } // se punto pari, calcolo la lunghezza else { dSegLen = GetSegmentLength( 0, dUIni, dUMid, dUFin, ptIni, ptMid, ptFin) ; if ( ( dCurrLen + dSegLen) >= dLen) { return true ; } else dCurrLen += dSegLen ; // nuovo iniziale prende i valori del finale dUIni = dUFin ; ptIni = ptFin ; } } return false ; } //---------------------------------------------------------------------------- bool CurveBezier::GetParamAtLength( double dLen, double& dU) const { // la curva deve essere validata if ( m_nStatus != OK) return false ; // se prima di inizio, errore if ( dLen < - EPS_SMALL) return false ; // inizio if ( dLen < EPS_SMALL) { dU = 0 ; return true ; } // approfondisco nell'intervallo di interesse bool bOk ; double dSegLen ; double dCurrLen = 0 ; double dUIni = 0 ; double dUFin = 1 ; const double MIN_SEG_LEN = 0.2 ; while ( ( bOk = GetSegmentParam( dLen, dCurrLen, dSegLen, dUIni, dUFin)) && dSegLen > MIN_SEG_LEN) { // allungo di un poco l'intervallo di ricerca per compensare l'approssimazione della lunghezza dUFin = min( dUFin + ( dUFin - dUIni) * 0.05, 1.) ; } // aggiustamento finale parametro if ( bOk) dU = dUIni + ( dLen - dCurrLen) / dSegLen * ( dUFin - dUIni) ; return bOk ; } //---------------------------------------------------------------------------- bool CurveBezier::IsPointOn( const Point3d& ptP, double dTol) const { // verifico la distanza return ( DistPointCrvBezier( ptP, *this).IsEpsilon( dTol)) ; } //---------------------------------------------------------------------------- bool CurveBezier::GetParamAtPoint( const Point3d& ptP, double& dPar, double dTol) const { DistPointCrvBezier DPB( ptP, *this) ; if ( ! DPB.IsEpsilon( dTol)) return false ; int nFlag ; return DPB.GetParamAtMinDistPoint( 0, dPar, nFlag) ; } //---------------------------------------------------------------------------- bool CurveBezier::GetLengthAtPoint( const Point3d& ptP, double& dLen, double dTol) const { double dU ; if ( ! GetParamAtPoint( ptP, dU, dTol)) return false ; return GetLengthAtParam( dU, dLen) ; } //---------------------------------------------------------------------------- bool CurveBezier::ApproxWithLines( int nStep, PolyLine& PL) const { // pulisco la polilinea PL.Clear() ; // la curva deve essere validata if ( m_nStatus != OK) return false ; // calcolo i punti richiesti double dStep = 1.0 / nStep ; for ( int i = 0 ; i <= nStep ; ++ i) { double dU = i * dStep ; Point3d ptPos ; if ( GetPointD1D2( dU, ptPos)) PL.AddUPoint( dU, ptPos) ; else return false ; } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::ApproxWithLines( double dLinTol, double dAngTolDeg, int nType, PolyLine& PL) const { // pulisco la polilinea PL.Clear() ; // la curva deve essere validata if ( m_nStatus != OK) return false ; // se di primo grado, basta inserire gli estremi if ( m_nDeg == 1) { return ( PL.AddUPoint( 0, m_vPtCtrl[0]) && PL.AddUPoint( 1, m_vPtCtrl[m_nDeg])) ; } // limiti minimi su tolleranza e deviazione angolare dLinTol = max( dLinTol, LIN_TOL_MIN) ; dAngTolDeg = max( dAngTolDeg, ANG_TOL_MIN_DEG) ; // se lineare con lato obbligato, gestione speciale if ( nType == APL_LEFT || nType == APL_LEFT_CONVEX || nType == APL_RIGHT || nType == APL_RIGHT_CONVEX) { // prima approssimazione lineare a 10 * Epsilon if ( ! ApproxWithLines( 10 * EPS_SMALL, dAngTolDeg, APL_STD, PL)) return false ; // eliminazione dei punti in tolleranza andando solo dalla parte ammessa Vector3d vtExtr = ( m_VtExtr.IsSmall() ? Z_AX : m_VtExtr) ; if ( ! PL.ApproxOnSide( vtExtr, ( nType == APL_LEFT), dLinTol)) return false ; // se necessario, sistemo per convessità dalla parte ammessa if ( nType == APL_RIGHT_CONVEX || nType == APL_LEFT_CONVEX) { if ( ! PL.MakeConvex( vtExtr, ( nType == APL_LEFT_CONVEX))) return false ; } return true ; } // inserisco il punto iniziale if ( ! PL.AddUPoint( 0, m_vPtCtrl[0])) return false ; // verifico se va divisa if ( ! FlatOrSplit( 0, *this, 0, 1, dLinTol, dAngTolDeg, PL)) return false ; // se è stato inserito un solo punto, aggiungo il finale if ( PL.GetPointNbr() == 1) return PL.AddUPoint( 1, m_vPtCtrl[m_nDeg]) ; // altrimenti, se l'ultimo punto non coincide con il finale lo sostituisco (distano al max di dLinTol) else { Point3d ptLast ; PL.GetLastPoint( ptLast) ; if ( ! AreSamePointApprox( ptLast, m_vPtCtrl[m_nDeg])) { PL.EraseLastUPoint() ; return PL.AddUPoint( 1, m_vPtCtrl[m_nDeg]) ; } } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::FlatOrSplit( int nLev, const CurveBezier& crvBez, double dParStart, double dParEnd, double dLinTol, double dAngTolDeg, PolyLine& PL) const { // se raggiunto il massimo livello di recursione ... const int MAX_LEV = 10 ; if ( nLev >= MAX_LEV) { // segnalo situazione per debug if ( GetEGkDebugLev() >= 5) LOG_DBG_ERR( GetEGkLogger(), "ERROR : Exceeded recursions") // considero la curva piatta (inserisco il punto se abbastanza lontano dal precedente) ed esco Point3d ptLast ; PL.GetLastPoint( ptLast) ; if ( SqDist( ptLast, crvBez.m_vPtCtrl[m_nDeg]) > ( dLinTol * dLinTol)) PL.AddUPoint( dParEnd, crvBez.m_vPtCtrl[m_nDeg]) ; return true ; } // massima distanza al quadrato dei punti di controllo intermedi dalla linea tra primo e ultimo double dMaxSqDist = 0 ; double dLen ; Vector3d vtDir ; DirDist( crvBez.m_vPtCtrl[0], crvBez.m_vPtCtrl[m_nDeg], vtDir, dLen) ; for ( int i = 1 ; i < m_nDeg ; i ++) { DistPointLine dstPL( crvBez.m_vPtCtrl[i], crvBez.m_vPtCtrl[0], vtDir, dLen) ; double dSqDist ; if ( dstPL.GetSqDist( dSqDist) && dSqDist > dMaxSqDist) dMaxSqDist = dSqDist ; } // se distanza entro tolleranza if ( dMaxSqDist <= ( dLinTol * dLinTol)) { // deviazione angolare tra primo e ultimo tratto del poligono di controllo (grado >= 1) Vector3d vtDirI = crvBez.m_vPtCtrl[1] - crvBez.m_vPtCtrl[0] ; Vector3d vtDirF = crvBez.m_vPtCtrl[m_nDeg] - crvBez.m_vPtCtrl[m_nDeg-1] ; double dAngDeg ; vtDirI.GetAngle( vtDirF, dAngDeg) ; // se deviazione angolare entro tolleranza oppure lunghezza poligono controllo entro tolleranza double dPolLen ; if ( abs( dAngDeg) <= dAngTolDeg || ( crvBez.GetControlPolygonLength( dPolLen) && dPolLen <= dLinTol)) { // considero la curva piatta (inserisco il punto se abbastanza lontano dal precedente) ed esco Point3d ptLast ; PL.GetLastPoint( ptLast) ; if ( SqDist( ptLast, crvBez.m_vPtCtrl[m_nDeg]) > ( dLinTol * dLinTol)) PL.AddUPoint( dParEnd, crvBez.m_vPtCtrl[m_nDeg]) ; return true ; } } // curva da dividere { double dParDiv ; double dParMid ; CurveBezier crvBez1 ; // se prima suddivisione e c'è singolarità, divido su questa if ( nLev == 0 && GetSingularParam( dParDiv) > 0) ; // altrimenti divido a metà else dParDiv = 0.5 ; dParMid = dParDiv * ( dParStart + dParEnd) ; // prima metà crvBez1 = crvBez ; crvBez1.TrimEndAtParam( dParDiv) ; if ( ! FlatOrSplit( nLev + 1, crvBez1, dParStart, dParMid, dLinTol, dAngTolDeg, PL)) return false ; // seconda metà crvBez1 = crvBez ; crvBez1.TrimStartAtParam( dParDiv) ; if ( ! FlatOrSplit( nLev + 1, crvBez1, dParMid, dParEnd, dLinTol, dAngTolDeg, PL)) return false ; } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::ApproxWithArcs( double dLinTol, double dAngTolDeg, PolyArc& PA) const { // se estrusione definita e diversa da Z+ if ( ! m_VtExtr.IsSmall() && ! m_VtExtr.IsZplus()) { // devo portarmi in un piano perpendicolare all'estrusione per costruirvi gli archi Frame3d frExtr ; if ( ! frExtr.Set( ORIG, m_VtExtr)) return false ; // copio la curva e la porto nel riferimento di estrusione CurveBezier crvAux ; if ( ! crvAux.CopyFrom( *this)) return false ; crvAux.ToLoc( frExtr) ; // approssimo bool bOk = crvAux.ApproxWithArcsXY( dLinTol, dAngTolDeg, PA) ; // porto il risultato nel riferimento originale PA.ToGlob( frExtr) ; return bOk ; } // l'estrusione è secondo Z+, pertanto sono già nel piano XY else return ApproxWithArcsXY( dLinTol, dAngTolDeg, PA) ; } //---------------------------------------------------------------------------- bool CurveBezier::ApproxWithArcsXY( double dLinTol, double dAngTolDeg, PolyArc& PA) const { // si creano gli archi nel piano XY // pulisco il poliarco PA.Clear() ; // costruisco una approssimazione lineare PolyLine PL ; if ( ! ApproxWithLines( dLinTol, dAngTolDeg, APL_STD, PL)) return false ; // approssimo la curva per approssimazioni successive mediante bisezione return BiArcOrSplit( 0, PL, dLinTol, dAngTolDeg, PA) ; } //---------------------------------------------------------------------------- bool CurveBezier::BiArcOrSplit( int nLev, PolyLine& PL, double dLinTol, double dAngTolDeg, PolyArc& PA) const { const int MAX_LEV = 10 ; // se non chiusa if ( ! PL.IsClosed()) { PtrOwner pCrv ; double dMaxDist ; // se la polilinea ha più di 2 punti if ( PL.GetPointNbr() > 2) { // calcolo punti e direzioni agli estremi della polilinea usando la curva di Bezier double dU ; Point3d ptP0, ptP1 ; Vector3d vtDir ; double dDir0Deg, dDir1Deg ; if ( ! PL.GetFirstU( dU) || ! GetPointTang( dU, ICurve::FROM_PLUS, ptP0, vtDir)) return false ; vtDir.ToSpherical( nullptr, nullptr, &dDir0Deg) ; if ( ! PL.GetLastU( dU) || ! GetPointTang( dU, ICurve::FROM_MINUS, ptP1, vtDir)) return false ; vtDir.ToSpherical( nullptr, nullptr, &dDir1Deg) ; // costruisco un biarco sulla polilinea (secondo metodo di Z. Sir) pCrv.Set( GetBiArc( ptP0, dDir0Deg, ptP1, dDir1Deg, PL, dMaxDist)) ; if ( IsNull( pCrv)) return false ; } // se la polilinea è formata da 2 punti else if ( PL.GetPointNbr() == 2) { // se molto vicini, esco double dLen ; if ( PL.GetLength( dLen) && dLen < EPS_SMALL) return true ; // costruisco la retta che li unisce PtrOwner pCC( CreateBasicCurveComposite()) ; if ( IsNull( pCC)) return false ; if ( ! pCC->FromPolyLine( PL)) return false ; pCrv.Set( pCC) ; dMaxDist = 0 ; } // se la polilinea ha un solo punto, esco else return true ; // se raggiunto il massimo livello di recursione, forzo l'accettazione del biarco if ( nLev >= MAX_LEV) { dMaxDist = 0 ; // segnalo situazione per debug if ( GetEGkDebugLev() >= 5) LOG_DBG_ERR( GetEGkLogger(), "ERROR : Exceeded recursions") } // se lunghezza abbastanza picccola, forzo l'accettazione della curva double dLen ; if ( PL.GetApproxLength( dLen) && dLen < 10 * EPS_SMALL) dMaxDist = 0 ; // se in tolleranza if ( dMaxDist <= dLinTol) { // creo un nuovo poliarco PolyArc PA2 ; if ( ! pCrv->ApproxWithArcs( dLinTol, dAngTolDeg, PA2)) return false ; // aggiusto la parametrizzazione double dUStart, dUEnd ; PL.GetFirstU( dUStart) ; PL.GetLastU( dUEnd) ; PA2.ParamLinearTransform( dUStart, dUEnd) ; // accodo all'esistente if ( ! PA.Join( PA2)) return false ; // esco return true ; } } // se raggiunto il massimo livello di recursione, errore if ( nLev >= MAX_LEV) { // segnalo situazione per debug LOG_ERROR( GetEGkLogger(), "ERROR : Exceeded recursions") return false ; } // spezzo l'intervallo in due parti double dParMid ; // se prima suddivisione e c'è singolarità, divido su questa if ( nLev == 0 && GetSingularParam( dParMid) > 0) ; // altrimenti divido a metà else { double dParStart, dParEnd ; if ( ! PL.GetFirstU( dParStart) || ! PL.GetLastU( dParEnd)) return false ; dParMid = 0.5 * ( dParStart + dParEnd) ; } PolyLine PL2 ; if ( ! PL.Split( dParMid, PL2)) return false ; // prima metà if ( ! BiArcOrSplit( nLev + 1, PL, dLinTol, dAngTolDeg, PA)) return false ; // seconda metà return BiArcOrSplit( nLev + 1, PL2, dLinTol, dAngTolDeg, PA) ; } //---------------------------------------------------------------------------- ICurve* CurveBezier::CopyParamRange( double dUStart, double dUEnd) const { // i parametri start ed end devono essere compresi nel dominio parametrico della curva if ( dUStart < - EPS_PARAM || dUStart > 1 + EPS_PARAM || dUEnd < - EPS_PARAM || dUEnd > 1 + EPS_PARAM) return nullptr ; // se il parametro start supera quello di end if ( dUStart > dUEnd - EPS_PARAM) { // se curva aperta, il trim la cancella completamente quindi non resta alcunché if ( ! IsClosed()) return nullptr ; // se curva chiusa, il trim si avvolge attorno al punto di giunzione else { // eseguo la copia sulla curva composita equivalente CurveComposite cCompo ; cCompo.CopyFrom( this) ; return cCompo.CopyParamRange( dUStart, dUEnd) ; } } // creo la curva di Bezier copia PtrOwner pCopy( Clone()) ; if ( IsNull( pCopy)) return nullptr ; // eseguo il trim if ( ! pCopy->TrimStartEndAtParam( dUStart, dUEnd)) return nullptr ; return ( ::Release( pCopy)) ; } //---------------------------------------------------------------------------- bool CurveBezier::Invert( void) { // la curva deve essere valida if ( m_nStatus != OK) return false ; // inverto i punti di controllo int nMid = ( m_nDeg + 1) / 2 ; for ( int i = 0 ; i < nMid ; ++ i) swap( m_vPtCtrl[i], m_vPtCtrl[m_nDeg-i]) ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::ModifyStart( const Point3d& ptNewStart) { // la curva deve essere valida if ( m_nStatus != OK) return false ; // modifico il primo punto di controllo m_vPtCtrl[0] = ptNewStart ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::ModifyEnd( const Point3d& ptNewEnd) { // la curva deve essere valida if ( m_nStatus != OK) return false ; // modifico l'ultimo punto di controllo m_vPtCtrl[m_nDeg] = ptNewEnd ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::TrimStartAtParam( double dUTrim) { // la curva deve essere valida if ( m_nStatus != OK) return false ; // eseguo il trim (algoritmo di de Casteljau) if ( ! m_bRat) { if ( ! deCastTrimStart( dUTrim, m_nDeg, m_vPtCtrl)) return false ; } else { if ( ! deCastRatTrimStart( dUTrim, m_nDeg, m_vPtCtrl, m_vWeCtrl)) return false ; } // imposto ricalcolo Voronoi ResetVoronoiObject() ; // con i controlli sopra fatti rimane validata, ma la grafica va ricalcolata m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::TrimEndAtParam( double dUTrim) { // la curva deve essere valida if ( m_nStatus != OK) return false ; // eseguo il trim (algoritmo di de Casteljau) if ( ! m_bRat) { if ( ! deCastTrimEnd( dUTrim, m_nDeg, m_vPtCtrl)) return false ; } else { if ( ! deCastRatTrimEnd( dUTrim, m_nDeg, m_vPtCtrl, m_vWeCtrl)) return false ; } // imposto ricalcolo Voronoi ResetVoronoiObject() ; // con i controlli sopra fatti rimane validata, ma la grafica va ricalcolata m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::TrimStartEndAtParam( double dUStartTrim, double dUEndTrim) { // i parametri start ed end devono essere compresi nel dominio parametrico della curva if ( dUStartTrim < - EPS_PARAM || dUStartTrim > 1 + EPS_PARAM || dUEndTrim < - EPS_PARAM || dUEndTrim > 1 + EPS_PARAM) return false ; // verifico che i trim non cancellino interamente la curva if ( dUStartTrim > dUEndTrim - EPS_PARAM) return false ; // trim finale if ( ! TrimEndAtParam( dUEndTrim)) return false ; // trim iniziale con il parametro opportunamente ricalcolato double dNewUStartTrim = dUStartTrim / dUEndTrim ; return TrimStartAtParam( dNewUStartTrim) ; } //---------------------------------------------------------------------------- bool CurveBezier::TrimStartAtLen( double dLenTrim) { // lunghezze negative vengono considerate nulle dLenTrim = max( dLenTrim, 0.) ; // converto le lunghezze in valori parametrici double dUTrim ; if ( ! GetParamAtLength( dLenTrim, dUTrim)) return false ; // utilizzo il trim sui parametri return TrimStartAtParam( dUTrim) ; } //---------------------------------------------------------------------------- bool CurveBezier::TrimEndAtLen( double dLenTrim) { // lunghezze negative vengono considerate nulle dLenTrim = max( dLenTrim, 0.) ; // converto le lunghezze in valori parametrici double dUTrim ; if ( ! GetParamAtLength( dLenTrim, dUTrim)) return false ; // utilizzo il trim sui parametri return TrimEndAtParam( dUTrim) ; } //---------------------------------------------------------------------------- bool CurveBezier::ExtendStartByLen( double dLenExt) { // la lunghezza deve essere positiva if ( dLenExt < - EPS_ZERO) return false ; // determino la lunghezza originale della curva double dLen ; if ( ! GetLengthAtParam( 1, dLen)) return false ; // stimo il valore del parametro al nuovo punto iniziale double dUTrim = - dLenExt / dLen ; // estrapolo sul parametro con de Casteljau // se polinomiale, va sicuramente bene if ( ! m_bRat) { for ( int k = 1 ; k <= m_nDeg ; k ++) { for ( int i = 0 ; i <= m_nDeg - k ; i ++) m_vPtCtrl[i] = ( 1 - dUTrim) * m_vPtCtrl[i] + dUTrim * m_vPtCtrl[i+1] ; } } // se razionale, devo verificare che i pesi non diventino negativi else { Point3d vPtCtrl[MAXDEG+1] ; double vWeCtrl[MAXDEG+1] ; for ( int i = 0 ; i <= m_nDeg ; i ++) { vPtCtrl[i] = m_vPtCtrl[i] ; vWeCtrl[i] = m_vWeCtrl[i] ; } for ( int k = 1 ; k <= m_nDeg ; k ++) { for ( int i = 0 ; i <= m_nDeg - k ; i ++) { vPtCtrl[i] = ( 1 - dUTrim) * vWeCtrl[i] * vPtCtrl[i] + dUTrim * vWeCtrl[i+1] * vPtCtrl[i+1] ; vWeCtrl[i] = ( 1 - dUTrim) * vWeCtrl[i] + dUTrim * vWeCtrl[i+1] ; if ( vWeCtrl[i] < MIN_EXT_WEIGHT) return false ; vPtCtrl[i] = vPtCtrl[i] * ( 1 / vWeCtrl[i]) ; } } for ( int i = 0 ; i <= m_nDeg ; i ++) { m_vPtCtrl[i] = vPtCtrl[i] ; m_vWeCtrl[i] = vWeCtrl[i] ; } } // imposto ricalcolo Voronoi ResetVoronoiObject() ; // con i controlli sopra fatti rimane validata, ma la grafica va ricalcolata m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::ExtendEndByLen( double dLenExt) { // la lunghezza deve essere positiva if ( dLenExt < - EPS_ZERO) return false ; // determino la lunghezza originale della curva double dLen ; if ( ! GetLengthAtParam( 1, dLen)) return false ; // stimo il valore del parametro al nuovo punto finale double dUTrim = 1 + dLenExt / dLen ; // estrapolo sul parametro con de Casteljau // se polinomiale, va sicuramente bene if ( ! m_bRat) { for ( int k = 1 ; k <= m_nDeg ; k ++) { for ( int i = m_nDeg ; i >= k ; i --) m_vPtCtrl[i] = ( 1 - dUTrim) * m_vPtCtrl[i-1] + dUTrim * m_vPtCtrl[ i] ; } } // se razionale, devo verificare che i pesi non diventino negativi else { Point3d vPtCtrl[MAXDEG+1] ; double vWeCtrl[MAXDEG+1] ; for ( int i = 0 ; i <= m_nDeg ; i ++) { vPtCtrl[i] = m_vPtCtrl[i] ; vWeCtrl[i] = m_vWeCtrl[i] ; } for ( int k = 1 ; k <= m_nDeg ; k ++) { for ( int i = m_nDeg ; i >= k ; i --) { vPtCtrl[i] = ( 1 - dUTrim) * vWeCtrl[i-1] * vPtCtrl[i-1] + dUTrim * vWeCtrl[ i] * vPtCtrl[ i] ; vWeCtrl[i] = ( 1 - dUTrim) * vWeCtrl[i-1] + dUTrim * vWeCtrl[ i] ; if ( vWeCtrl[i] < MIN_EXT_WEIGHT) return false ; vPtCtrl[i] = vPtCtrl[i] * ( 1 / vWeCtrl[i]) ; } } for ( int i = 0 ; i <= m_nDeg ; i ++) { m_vPtCtrl[i] = vPtCtrl[i] ; m_vWeCtrl[i] = vWeCtrl[i] ; } } // imposto ricalcolo Voronoi ResetVoronoiObject() ; // con i controlli sopra fatti rimane validata, ma la grafica va ricalcolata m_OGrMgr.Reset() ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::Translate( const Vector3d& vtMove) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // traslo Voronoi if ( m_pVoronoiObj != nullptr) m_pVoronoiObj->Translate( vtMove) ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // traslo i punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i].Translate( vtMove) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::Rotate( const Point3d& ptAx, const Vector3d& vtAx, double dCosAng, double dSinAng) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // verifico validità dell'asse di rotazione if ( vtAx.IsSmall()) return false ; // ruoto Voronoi if ( m_pVoronoiObj != nullptr) m_pVoronoiObj->Rotate( ptAx, vtAx, dCosAng, dSinAng) ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // ruoto i punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i].Rotate( ptAx, vtAx, dCosAng, dSinAng) ; // ruoto il vettore estrusione m_VtExtr.Rotate( vtAx, dCosAng, dSinAng) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::Scale( const Frame3d& frRef, double dCoeffX, double dCoeffY, double dCoeffZ) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // verifico non sia nulla if ( abs( dCoeffX) < EPS_ZERO && abs( dCoeffY) < EPS_ZERO && abs( dCoeffZ) < EPS_ZERO) return false ; // verifico che la scalatura dei punti non li porti tutti a coincidere PNTVECTOR vPnt ; vPnt.reserve( m_nDeg + 1) ; for ( int i = 0 ; i <= m_nDeg ; ++ i) { Point3d ptTmp = m_vPtCtrl[i] ; ptTmp.Scale( frRef, dCoeffX, dCoeffY, dCoeffZ) ; vPnt.push_back( ptTmp) ; } bool bOk = false ; for ( int i = 1 ; i <= m_nDeg ; ++ i) { if ( ! AreSamePointApprox( vPnt[0], vPnt[i])) { bOk = true ; break ; } } if ( ! bOk) return false ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // scalo i punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i] = vPnt[i] ; // scalo vettore estrusione, lo normalizzo e aggiusto spessore m_VtExtr.Scale( frRef, dCoeffX, dCoeffY, dCoeffZ) ; double dLen = m_VtExtr.Len() ; if ( dLen > EPS_ZERO) { m_VtExtr /= dLen ; m_dThick *= dLen ; } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::Mirror( const Point3d& ptOn, const Vector3d& vtNorm) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // verifico validità del piano di specchiatura if ( vtNorm.IsSmall()) return false ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // specchio i punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i].Mirror( ptOn, vtNorm) ; // specchio il vettore estrusione m_VtExtr.Mirror( vtNorm) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::Shear( const Point3d& ptOn, const Vector3d& vtNorm, const Vector3d& vtDir, double dCoeff) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // verifico validità dei parametri if ( vtNorm.IsSmall() || vtDir.IsSmall()) return false ; // imposto ricalcolo Voronoi ResetVoronoiObject() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // eseguo scorrimento dei punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i].Shear( ptOn, vtNorm, vtDir, dCoeff) ; // eseguo scorrimento del vettore estrusione, lo normalizzo e aggiusto spessore m_VtExtr.Shear( vtNorm, vtDir, dCoeff) ; double dLen = m_VtExtr.Len() ; if ( dLen > EPS_ZERO) { m_VtExtr /= dLen ; m_dThick *= dLen ; } return true ; } //---------------------------------------------------------------------------- bool CurveBezier::ToGlob( const Frame3d& frRef) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // verifico validità del frame if ( frRef.GetType() == Frame3d::ERR) return false ; // se frame identità, non devo fare alcunché if ( IsGlobFrame( frRef)) return true ; // trasformo Voronoi if ( m_pVoronoiObj != nullptr) m_pVoronoiObj->ToGlob( frRef) ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // trasformo i punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i].ToGlob( frRef) ; // trasformo il vettore estrusione m_VtExtr.ToGlob( frRef) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::ToLoc( const Frame3d& frRef) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // verifico validità del frame if ( frRef.GetType() == Frame3d::ERR) return false ; // se frame identità, non devo fare alcunché if ( IsGlobFrame( frRef)) return true ; // trasformo Voronoi if ( m_pVoronoiObj != nullptr) m_pVoronoiObj->ToLoc( frRef) ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // trasformo i punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i].ToLoc( frRef) ; // trasformo il vettore estrusione m_VtExtr.ToLoc( frRef) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::LocToLoc( const Frame3d& frOri, const Frame3d& frDest) { // la curva deve essere validata if ( m_nStatus != OK) return false ; // verifico validità dei frame if ( frOri.GetType() == Frame3d::ERR || frDest.GetType() == Frame3d::ERR) return false ; // se i due riferimenti coincidono, non devo fare alcunché if ( AreSameFrame( frOri, frDest)) return true ; // trasformo Voronoi if ( m_pVoronoiObj != nullptr) m_pVoronoiObj->LocToLoc( frOri, frDest) ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; // trasformo i punti di controllo for ( int i = 0 ; i <= m_nDeg ; ++ i) m_vPtCtrl[i].LocToLoc( frOri, frDest) ; // trasformo il vettore estrusione m_VtExtr.LocToLoc( frOri, frDest) ; return true ; } //---------------------------------------------------------------------------- bool CurveBezier::CalcVoronoiObject() const { if ( m_nStatus != OK) return false ; // creo oggetto vroni con la curva m_pVoronoiObj = new( std::nothrow) Voronoi( this, false) ; if ( m_pVoronoiObj == nullptr) return false ; return true ; } //---------------------------------------------------------------------------- Voronoi* CurveBezier::GetVoronoiObject() const { if ( m_nStatus != OK) return nullptr ; // se non è stato calcolato, lo calcolo if ( m_pVoronoiObj == nullptr) CalcVoronoiObject() ; // restituisco Voronoi return m_pVoronoiObj ; } //---------------------------------------------------------------------------- void CurveBezier::ResetVoronoiObject() const { if ( m_pVoronoiObj != nullptr) delete m_pVoronoiObj ; m_pVoronoiObj = nullptr ; }