//---------------------------------------------------------------------------- // EgalTech 2015-2023 //---------------------------------------------------------------------------- // File : OffsetCurve.cpp Data : 03.08.23 Versione : 2.5h1 // Contenuto : Classe per offset avanzato di Curve. // // // // Modifiche : 23.09.15 DS Creazione modulo. // 24.06.19 DS Agg. GetShorterCurve. // 10.10.20 DS Migliorata gestione angoli interni. // 03.08.23 DS Migliorato riconoscimento angoli esterni di valore circa 180deg. // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include "GeoConst.h" #include "CurveLine.h" #include "CurveArc.h" #include "CurveComposite.h" #include "RemoveCurveDefects.h" #include "OffsetAux.h" #include "Voronoi.h" #include "/EgtDev/Include/EGkOffsetCurve.h" #include "/EgtDev/Include/EGkIntersCurves.h" #include "/EgtDev/Include/EGkDistPointCurve.h" #include "/EgtDev/Include/EGkIntervals.h" #include "/EgtDev/Include/EgtPointerOwner.h" #include using namespace std ; //---------------------------------------------------------------------------- static bool PreviousIsLine( ICURVEPLIST::const_iterator iIter, const ICURVEPLIST& CrvLst, bool bClosed) ; static bool PreviousIsLonger( int nInd1, const DBLVECTOR& vLens, bool bClosed) ; static bool NextIsLine( ICURVEPLIST::const_iterator iIter, const ICURVEPLIST& CrvLst, bool bClosed) ; static bool NextIsLonger( int nInd1, const DBLVECTOR& vLens, bool bClosed) ; static bool CalcAngle( const ICurve* pCrv1, const ICurve* pCrv2, double& dAngDeg) ; static bool VerifyAndAdjustSamePoint( ICurve* pCrv1, ICurve* pCrv2, int& nRes) ; static bool VerifyAndAdjustInternalAngle( ICurve* pCrv1, ICurve* pCrv2, int& nRes) ; static bool VerifyAndAdjustExternalAngle( ICurve* pCrv1, ICurve* pCrv2, double dAngDeg, double dDist, int nType, CurveComposite& ccAux) ; static double GetMinDist( double dCoeff, ICurve* pCrv1, ICurve* pCrv2) ; //---------------------------------------------------------------------------- OffsetCurve::~OffsetCurve( void) { Reset() ; } //---------------------------------------------------------------------------- bool OffsetCurve::Reset( void) { for ( auto& pCrv : m_CrvLst) { if ( pCrv != nullptr) { delete pCrv ; pCrv = nullptr ; } } m_CrvLst.clear() ; m_ptOffs = P_INVALID ; m_vtOut = V_INVALID ; return true ; } //---------------------------------------------------------------------------- bool OffsetCurve::Make( const ICurve* pCrv, double dDist, int nType) { // metodo di calcolo impostato da USE_VORONOI // pulisco tutto Reset() ; // verifico che la curva esista if ( pCrv == nullptr) return false ; // verifico se la curva è un segmento di retta bool bIsLine = false ; const CurveLine* pLine = GetBasicCurveLine( pCrv) ; if ( pLine != nullptr) bIsLine = true ; else { const CurveComposite* pCompo = GetBasicCurveComposite( pCrv) ; Point3d ptStart, ptEnd ; if ( pCompo != nullptr && pCompo->IsALine( m_dLinTol, ptStart, ptEnd)) bIsLine = true ; } // verifico che la curva sia piana (per le linee è comunque sempre vero) Plane3d plPlane ; if ( ! pCrv->IsFlat( plPlane, bIsLine, 10 * EPS_SMALL) && ! bIsLine) return false ; // recupero o assegno estrusione Vector3d vtExtr ; if ( ! pCrv->GetExtrusion( vtExtr) || vtExtr.IsSmall()) { vtExtr = plPlane.GetVersN() ; if ( vtExtr.IsZminus()) vtExtr.Invert() ; } // recupero normale al piano della curva Vector3d vtNorm = plPlane.GetVersN() ; if ( vtNorm * vtExtr < 0) vtNorm.Invert() ; // verifico che estrusione non sia troppo vicina al piano della curva (almeno 30 gradi di angolazione) double dExtrOnN = vtExtr * vtNorm ; if ( dExtrOnN < 0.5) return false ; // recupero spessore double dThick ; pCrv->GetThickness( dThick) ; // se offset nullo, copio la curva ed esco if ( abs( dDist) < 10 * EPS_SMALL) { PtrOwner< CurveComposite> pCopy( CreateBasicCurveComposite()) ; if ( IsNull( pCopy) || ! pCopy->CopyFrom( pCrv)) return false ; // assegno estrusione e spessore come curva originale pCopy->SetExtrusion( vtExtr) ; pCopy->SetThickness( dThick) ; // unisco parti allineate (tranne gli estremi) pCopy->MergeCurves( 10 * EPS_SMALL, ANG_TOL_STD_DEG, false) ; // sposto in lista m_CrvLst.push_back( Release( pCopy)) ; return true ; } // determino se necessario cambiare riferimento ( dal vettore estrusione) e mai con Voronoi bool bNeedRef = ( ! vtExtr.IsZplus() && ! USE_VORONOI) ; // determino se necessario effettuare scalatura bool bNeedScale = ( abs( dExtrOnN) < cos( 0.1 * DEGTORAD)) ; // creo una copia della curva CurveComposite ccCopy ; if ( ! ccCopy.CopyFrom( pCrv)) return false ; ccCopy.SetExtrusion( vtExtr) ; // se necessario cambio il riferimento Frame3d frCopy ; if ( bNeedScale) { // calcolo il riferimento con vtNorm come asse Z e componente di vtExtr perpendicolare a vtNorm come asse X if ( ! frCopy.Set( ORIG, vtNorm, vtExtr - dExtrOnN * vtNorm)) return false ; ccCopy.SetExtrusion( vtNorm) ; // esprimo la curva in questo riferimento ccCopy.ToLoc( frCopy) ; // scalo lungo l'asse X locale ccCopy.Scale( GLOB_FRM, dExtrOnN, 1, 1) ; } else if ( bNeedRef) { // calcolo il riferimento OCS con VtExtr come asse Z if ( ! frCopy.Set( ORIG, vtExtr)) return false ; // esprimo la curva in questo riferimento ccCopy.ToLoc( frCopy) ; } // recupero punto iniziale Point3d ptStart ; ccCopy.GetStartPoint( ptStart) ; // verifico se curva chiusa e non forzata aperta bool bClosed = ccCopy.IsClosed() && ( nType & ICurve::OFF_FORCE_OPEN) == 0 ; bool bChangeStart = true ; // -------------------- OFFSET STANDARD --------------------------------- if ( ! USE_VORONOI) { // verifico che la curva sia fatta solo da rette e archi che giacciono nel piano XY (VtExtr è ora Z+) if ( ! ccCopy.ArcsBezierCurvesToArcsPerpExtr( m_dLinTol, ANG_TOL_STD_DEG)) return false ; // elimino eventuali piccole Z if ( ! RemoveCurveSmallZs( &ccCopy, m_dLinTol)) return false ; // elimino tratti molto corti if ( ! RemoveCurveSmallParts( &ccCopy, m_dLinTol)) return false ; // converto archi diritti in segmenti di retta const double ANG_CEN_MAX = 0.5 ; if ( ! ccCopy.StraightArcsToLines( 2 * EPS_SMALL, ANG_CEN_MAX)) return false ; // unisco parti allineate if ( ! ccCopy.MergeCurves( m_dLinTol, ANG_TOL_STD_DEG, bClosed, true)) return false ; // verifico se il punto iniziale è stato modificato Point3d ptNewStart ; ccCopy.GetStartPoint( ptNewStart) ; bChangeStart = ( ! AreSamePointApprox( ptNewStart, ptStart)) ; // calcolo le lunghezze delle diverse entità DBLVECTOR vLens ; { const ICurve* pCrv1 = ccCopy.GetFirstCurve() ; while ( pCrv1 != nullptr) { double dLen ; if ( ! pCrv1->GetLength( dLen)) return false ; vLens.push_back( dLen) ; pCrv1 = ccCopy.GetNextCurve() ; } } // calcolo gli angoli tra le diverse entità DBLVECTOR vAngs ; { vAngs.push_back( 0) ; const ICurve* pCrv1 = ccCopy.GetFirstCurve() ; const ICurve* pCrv2 = ccCopy.GetNextCurve() ; while ( pCrv2 != nullptr) { double dAngDeg ; if ( ! CalcAngle( pCrv1, pCrv2, dAngDeg)) return false ; vAngs.push_back( dAngDeg) ; pCrv1 = pCrv2 ; pCrv2 = ccCopy.GetNextCurve() ; } // se chiusa, calcolo angolo da ultima a prima if ( bClosed) { pCrv2 = ccCopy.GetFirstCurve() ; double dAngDeg ; if ( ! CalcAngle( pCrv1, pCrv2, dAngDeg)) return false ; vAngs.push_back( dAngDeg) ; vAngs[0] = dAngDeg ; } else vAngs.push_back( 0) ; } // primo passo : estraggo entità dalla copia, loro offset elementare e aggiunta raccordi esterni (sempre fillet) CurveComposite ccCopy2 ; if ( ! ccCopy2.CopyFrom( &ccCopy)) return false ; // indice della prima curva (1-based, per poter assegnare un significato al segno) int nInd1 = 1 ; // recupero la prima curva e la offsetto PtrOwner pCrv1( ccCopy2.RemoveFirstOrLastCurve( false)) ; if ( IsNull( pCrv1)) return false ; pCrv1->SetTempProp( nInd1) ; if ( ! pCrv1->SimpleOffset( dDist)) { CurveArc* pArc = GetBasicCurveArc( pCrv1) ; if ( pArc == nullptr) return false ; if ( pArc->MyExtendedOffset( dDist, true)) pCrv1->SetTempProp( - nInd1) ; } // curve successive PtrOwner pCrv2( ccCopy2.RemoveFirstOrLastCurve( false)) ; while ( ! IsNull( pCrv2)) { // eseguo semplice offset pCrv2->SetTempProp( nInd1 + 1) ; if ( ! pCrv2->SimpleOffset( dDist)) { CurveArc* pArc = GetBasicCurveArc( pCrv2) ; if ( pArc == nullptr) return false ; if ( pArc->MyExtendedOffset( dDist, true)) pCrv2->SetTempProp( - ( nInd1 + 1)) ; } // verifico relazione con la curva precedente e aggiungo eventuali curve intermedie CurveComposite ccTemp ; VerifyAndAdjustExternalAngle( pCrv1, pCrv2, vAngs[nInd1], dDist, ICurve::OFF_FILLET, ccTemp) ; // metto in lista curva precedente m_CrvLst.push_back( Release( pCrv1)) ; // se aggiunto qualcosa, lo metto in lista if ( ccTemp.GetCurveCount() > 0) { PtrOwner pCrv3( ccTemp.RemoveFirstOrLastCurve( false)) ; while ( ! IsNull( pCrv3)) { m_CrvLst.push_back( Release( pCrv3)) ; pCrv3.Set( ccTemp.RemoveFirstOrLastCurve( false)) ; } } // aggiorno curva precedente ++ nInd1 ; pCrv1.Set( pCrv2) ; // passo alla curva successiva pCrv2.Set( ccCopy2.RemoveFirstOrLastCurve( false)) ; } // Metto in lista l'ultima curva rimasta if ( ! IsNull( pCrv1)) m_CrvLst.push_back( Release( pCrv1)) ; // se originale chiuso, devo confrontare anche ultima e prima curva if ( bClosed && m_CrvLst.size() >= 2) { // la curva precedente è l'ultima dell'offset ICurve* pCrv1 = m_CrvLst.back() ; // la curva successiva ora è la prima dell'offset ICurve* pCrv2 = m_CrvLst.front() ; // verifico relazione con la curva precedente e aggiungo eventuali curve intermedie CurveComposite ccTemp ; VerifyAndAdjustExternalAngle( pCrv1, pCrv2, vAngs[nInd1], dDist, ICurve::OFF_FILLET, ccTemp) ; // se aggiunto qualcosa, lo metto in lista if ( ccTemp.GetCurveCount() > 0) { PtrOwner pCrv3( ccTemp.RemoveFirstOrLastCurve( false)) ; while ( ! IsNull( pCrv3)) { m_CrvLst.push_back( Release( pCrv3)) ; pCrv3.Set( ccTemp.RemoveFirstOrLastCurve( false)) ; } } } // aggiorno lunghezza segmenti di retta (possono essere stati allungati) for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ; ++ iIter) { const ICurve* pCrv = *iIter ; if ( pCrv->GetType() == CRV_LINE) { double dLen ; pCrv->GetLength( dLen) ; int nInd = abs( pCrv->GetTempProp()) - 1 ; if ( nInd >= 0 && nInd < int( vLens.size())) vLens[nInd] = dLen ; } } // secondo passo : eliminazione archi invertiti e rette impossibili for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ;) { ICurve* pCrv = *iIter ; // controllo e rimuovo archi invertiti o nulli if ( pCrv->GetTempProp() < 0 || ( pCrv->GetType() == CRV_ARC && GetBasicCurveArc(pCrv)->GetRadius() < EPS_SMALL)) { delete pCrv ; pCrv = nullptr ; iIter = m_CrvLst.erase( iIter) ; continue ; } // controllo e rimuovo rette destinate all'eliminazione (da Voronoi con adiacenti) if ( pCrv->GetType() == CRV_LINE) { int nInd1 = abs( pCrv->GetTempProp()) ; // verifico se angoli precedente e successivo interni con rette di lunghezza non inferiore bool bPrevInt = PreviousIsLine( iIter, m_CrvLst, bClosed) && PreviousIsLonger( nInd1, vLens, bClosed) && ( ( dDist < 0 && vAngs[nInd1-1] > 0) || ( dDist > 0 && vAngs[nInd1-1] < 0)) ; bool bNextInt = NextIsLine( iIter, m_CrvLst, bClosed) && NextIsLonger( nInd1, vLens, bClosed) && ( ( dDist < 0 && vAngs[nInd1] > 0) || ( dDist > 0 && vAngs[nInd1] < 0)) ; // calcolo la massima estensione di offset (Voronoi con entità adiacenti) double dMaxDist = INFINITO ; if ( bPrevInt && bNextInt) { double dTgA = tan( 0.5 * ( ANG_STRAIGHT - abs( vAngs[nInd1-1])) * DEGTORAD) ; double dTgB = tan( 0.5 * ( ANG_STRAIGHT - abs( vAngs[nInd1])) * DEGTORAD) ; dMaxDist = dTgA * dTgB / ( dTgA + dTgB) * vLens[nInd1-1] ; } else if ( bPrevInt) { double dTgA = tan( 0.5 * ( ANG_STRAIGHT - abs( vAngs[nInd1-1])) * DEGTORAD) ; dMaxDist = dTgA * vLens[nInd1-1] ; } else if ( bNextInt) { double dTgB = tan( 0.5 * ( ANG_STRAIGHT - abs( vAngs[nInd1])) * DEGTORAD) ; dMaxDist = dTgB * vLens[nInd1-1] ; } if ( abs( dDist) > dMaxDist + 5 * EPS_SMALL) { delete pCrv ; iIter = m_CrvLst.erase( iIter) ; continue ; } } // passo al successivo ++ iIter ; } // terzo passo : taglio angoli interni ICurve* pPrev = nullptr ; if ( bClosed && ! m_CrvLst.empty()) pPrev = m_CrvLst.back() ; for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ;) { ICurve* pCrv = *iIter ; // gestione e verifica di estremi quasi coincidenti ed angoli interni if ( pPrev != nullptr) { int nRes ; if ( VerifyAndAdjustSamePoint( pPrev, pCrv, nRes)) { if ( nRes == 4) VerifyAndAdjustInternalAngle( pPrev, pCrv, nRes) ; // precedente da cancellare if ( ( nRes & 1) != 0) { auto iErase = prev( iIter != m_CrvLst.begin() ? iIter : m_CrvLst.end()) ; delete pPrev ; pPrev = nullptr ; m_CrvLst.erase( iErase) ; if ( m_CrvLst.empty()) break ; auto iPrev = prev( iIter != m_CrvLst.begin() ? iIter : m_CrvLst.end()) ; pPrev = *iPrev ; } // corrente da cancellare if ( ( nRes & 2) != 0) { delete pCrv ; pCrv = nullptr ; iIter = m_CrvLst.erase( iIter) ; } // se eseguita una cancellazione, non devo incrementare if ( ( nRes & 3) != 0) continue ; } } // passo al successivo pPrev = pCrv ; ++ iIter ; } // quarto passo : conversione in curva composita chiudendo gli eventuali buchi con rette PtrOwner pCrvCompo( CreateBasicCurveComposite()) ; if ( IsNull( pCrvCompo)) return false ; Point3d ptPrec ; if ( ! m_CrvLst.empty()) m_CrvLst.front()->GetStartPoint( ptPrec) ; for ( auto& pCrv : m_CrvLst) { // se curva troppo piccola la salto double dCrvLen ; if ( ! pCrv->GetLength( dCrvLen)) { delete( pCrv) ; pCrv = nullptr ; continue ; } // se punto iniziale diverso da precedente, inserisco segmento di collegamento Point3d ptStart ; pCrv->GetStartPoint( ptStart) ; if ( ! AreSamePointApprox( ptPrec, ptStart)) { CurveLine crvLine ; crvLine.Set( ptPrec, ptStart) ; pCrvCompo->AddCurve( crvLine) ; } // salvo punto finale come nuovo precedente pCrv->GetEndPoint( ptPrec) ; // aggiungo la curva alla composita pCrvCompo->AddCurve( pCrv) ; pCrv = nullptr ; } m_CrvLst.clear() ; // se curva chiusa, verifico anche gap tra inizio e fine if ( bClosed) pCrvCompo->Close() ; // se non rimasto nulla, esco double dLen ; if ( ! pCrvCompo->IsValid() || ! pCrvCompo->GetLength( dLen) || dLen < m_dLinTol) return true ; // inserisco la curva nella lista m_CrvLst.push_back( Release( pCrvCompo)) ; CurveComposite* pCompo1 = GetBasicCurveComposite( m_CrvLst.front()) ; // quinto passo : spezzatura della curva composita negli eventuali punti di auto-intersezione // calcolo le auto-intersezioni SelfIntersCurve sintC( *pCompo1) ; DBLVECTOR vU ; IntCrvCrvInfo iccInfo ; for ( int i = 0 ; sintC.GetIntCrvCrvInfo( i, iccInfo) ; ++ i) { if ( ! iccInfo.bOverlap) { vU.push_back( iccInfo.IciA[0].dU) ; vU.push_back( iccInfo.IciB[0].dU) ; } else { vU.push_back( iccInfo.IciA[0].dU) ; vU.push_back( iccInfo.IciA[1].dU) ; vU.push_back( iccInfo.IciB[0].dU) ; vU.push_back( iccInfo.IciB[1].dU) ; } } // ordino il vettore in senso crescente sort( vU.begin(), vU.end(), less()) ; // calcolo il vettore delle lunghezze di ogni parte di curva DBLVECTOR vLen ; for ( const auto& dU : vU) { double dULen ; pCompo1->GetLengthAtParam( dU, dULen) ; vLen.push_back( dULen) ; } // se ultima parte molto piccola, la elimino while ( ! vU.empty() && ( dLen - vLen.back()) < 2 * EPS_SMALL) { vU.pop_back() ; vLen.pop_back() ; } // eseguo la divisione double dUPrev = 0 ; double dLenPrev = 0 ; for ( int i = 0 ; i < int( vU.size()) ; ++ i) { // se lunghezza della curva risulta piccola, salto if ( ( vLen[i] - dLenPrev) < 2 * EPS_SMALL) continue ; // copio la curva PtrOwner pCopy( pCompo1->Clone()) ; if ( IsNull( pCopy)) return false ; m_CrvLst.push_back( Release( pCopy)) ; // trimmo l'originale pCompo1->TrimStartEndAtParam( dUPrev, vU[i]) ; // la copia diventa il nuovo corrente pCompo1 = GetBasicCurveComposite( m_CrvLst.back()) ; dUPrev = vU[i] ; dLenPrev = vLen[i] ; } // se fatta almeno una suddivisione bool bFirstLastSame = false ; if ( dUPrev > EPS_PARAM) { // trimmo l'ultima parte pCompo1->TrimStartAtParam( dUPrev) ; // rilevo le curve chiuse con due parti di una stessa curva all'inizio e alla fine if ( bClosed && vU[0] > EPS_PARAM && vLen[0] >= 2 * EPS_SMALL) bFirstLastSame = true ; } // sesto passo : se curva aperta, elimino i tratti che stanno nella circonferenza di offset dei punti estremi if ( ! bClosed) { // ciconferenza sull'estremità iniziale Point3d ptStart ; ccCopy.GetStartPoint( ptStart) ; PtrOwner pCircS( CreateBasicCurveArc()) ; if ( IsNull( pCircS) || ! pCircS->Set( ptStart, Z_AX, abs( dDist))) return false ; // elimino le parti di offset interne a questa circonferenza for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ;) { // recupero la curva e il suo dominio ICurve* pCrv = *iIter ; double dStart, dEnd ; if ( ! pCrv->GetDomain( dStart, dEnd)) return false ; // determino gli intervalli da conservare (inizializzo come tutta la curva per poi rimuovere la parte finale) Intervals inOk( EPS_PARAM) ; inOk.Set( dStart, dEnd) ; IntersCurveCurve ccInt( *pCrv, *pCircS) ; CRVCVECTOR ccPart ; if ( ! ccInt.GetCurveClassification( 0, EPS_SMALL, ccPart)) return false ; for ( auto& ccOne : ccPart) { switch ( ccOne.nClass) { case CRVC_IN : // intervallo standard if ( ccOne.dParS < ccOne.dParE - EPS_PARAM) inOk.Subtract( ccOne.dParS, ccOne.dParE) ; // intervallo che attraversa punto di chiusura della curva else { inOk.Subtract( ccOne.dParS, dEnd) ; inOk.Subtract( dStart, ccOne.dParE) ; } break ; case CRVC_NULL : return false ; } } // eseguo trim (solo della parte iniziale) double dMin, dMax ; if ( inOk.GetLast( dMin, dMax)) { if ( dMin > dStart + 100 * EPS_PARAM) pCrv->TrimStartAtParam( dMin) ; } else { delete pCrv ; iIter = m_CrvLst.erase( iIter) ; continue ; } // passo alla successiva ++ iIter ; } // circonferenza sull'estremità finale Point3d ptEnd ; ccCopy.GetEndPoint( ptEnd) ; PtrOwner pCircE( CreateBasicCurveArc()) ; if ( IsNull( pCircE) || ! pCircE->Set( ptEnd, Z_AX, abs( dDist))) return false ; // elimino le parti di offset interna a questa circonferenza for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ;) { // recupero la curva e il suo dominio ICurve* pCrv = *iIter ; double dStart, dEnd ; if ( ! pCrv->GetDomain( dStart, dEnd)) return false ; // determino gli intervalli da conservare (inizializzo come tutta la curva per poi poter rimuovere parti) Intervals inOk( EPS_PARAM) ; inOk.Set( dStart, dEnd) ; IntersCurveCurve ccInt( *pCrv, *pCircE) ; CRVCVECTOR ccPart ; if ( ! ccInt.GetCurveClassification( 0, EPS_SMALL, ccPart)) return false ; for ( auto& ccOne : ccPart) { switch ( ccOne.nClass) { case CRVC_IN : // intervallo standard if ( ccOne.dParS < ccOne.dParE - EPS_PARAM) inOk.Subtract( ccOne.dParS, ccOne.dParE) ; // intervallo che attraversa punto di chiusura della curva else { inOk.Subtract( ccOne.dParS, dEnd) ; inOk.Subtract( dStart, ccOne.dParE) ; } break ; case CRVC_NULL : return false ; } } // eseguo trim (solo della parte finale) double dMin, dMax ; if ( inOk.GetFirst( dMin, dMax)) { if ( dMax < dEnd - 100 * EPS_PARAM) pCrv->TrimEndAtParam( dMax) ; } else { delete pCrv ; iIter = m_CrvLst.erase( iIter) ; continue ; } // passo alla successiva ++ iIter ; } } // settimo passo : elimino le parti che sono troppo vicine al percorso originale bool bFirstLastDeleted = false ; for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ;) { ICurve* pCrv = *iIter ; // distanza minima di alcuni punti interni della curva dalla curva originale if ( GetMinDist( 0.5, pCrv, &ccCopy) < abs( dDist) - 5 * EPS_SMALL || GetMinDist( 0.25, pCrv, &ccCopy) < abs( dDist) - 5 * EPS_SMALL || GetMinDist( 0.75, pCrv, &ccCopy) < abs( dDist) - 5 * EPS_SMALL || GetMinDist( 0.125, pCrv, &ccCopy) < abs( dDist) - 5 * EPS_SMALL || GetMinDist( 0.875, pCrv, &ccCopy) < abs( dDist) - 5 * EPS_SMALL || GetMinDist( 0.0625, pCrv, &ccCopy) < abs( dDist) - 5 * EPS_SMALL || GetMinDist( 0.9375, pCrv, &ccCopy) < abs( dDist) - 5 * EPS_SMALL) { // se prima e ultima sono la stessa curva e non ancora cancellate e prima da cancellare if ( bFirstLastSame && ! bFirstLastDeleted && iIter == m_CrvLst.begin()) { bFirstLastDeleted = true ; // cancello ultima delete *prev( m_CrvLst.end()) ; m_CrvLst.pop_back() ; } // se prima e ultima sono la stessa curva e non ancora cancellate e ultima da cancellare if ( bFirstLastSame && ! bFirstLastDeleted && iIter == prev( m_CrvLst.end())) { bFirstLastDeleted = true ; // cancello prima delete *m_CrvLst.begin() ; m_CrvLst.pop_front() ; } // cancello la corrente delete pCrv ; iIter = m_CrvLst.erase( iIter) ; // evito incremento continue ; } // passo alla successiva ++ iIter ; } // ottavo passo : concateno i percorsi risultanti (senza cambiare verso) for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ;) { CurveComposite* pCrvCo = GetBasicCurveComposite( *iIter) ; // recupero punti iniziale e finale della curva Point3d ptStart, ptEnd ; pCrvCo->GetStartPoint( ptStart) ; pCrvCo->GetEndPoint( ptEnd) ; // ciclo sulle curve successive per verificare se possibile concatenamento for ( auto iIter2 = next( iIter) ; iIter2 != m_CrvLst.end() ;) { CurveComposite* pCrvCo2 = GetBasicCurveComposite( *iIter2) ; // recupero punti iniziale e finale della curva Point3d ptStart2, ptEnd2 ; pCrvCo2->GetStartPoint( ptStart2) ; pCrvCo2->GetEndPoint( ptEnd2) ; // verifiche di concatenamento if ( AreSamePointEpsilon( ptEnd, ptStart2, m_dLinTol)) { if ( pCrvCo->AddCurve( pCrvCo2, true, m_dLinTol)) ptEnd = ptEnd2 ; m_CrvLst.erase( iIter2) ; iIter2 = next( iIter) ; } else if ( AreSamePointEpsilon( ptEnd2, ptStart, m_dLinTol)) { if ( pCrvCo->AddCurve( pCrvCo2, false, m_dLinTol)) ptStart = ptStart2 ; m_CrvLst.erase( iIter2) ; iIter2 = next( iIter) ; } else ++ iIter2 ; } ++ iIter ; } // nono passo : se con smusso o estensione, sostituisco i fillet con questi // NB questa parte non è gestita in modo efficiente perchè dovrebbe essere sempre disabilitata. // Le funzioni sono state ottimizzate per lavorare con voronoi if ( ( nType & ICurve::OFF_CHAMFER) != 0 || ( nType & ICurve::OFF_EXTEND) != 0) { ICURVEPOVECTOR vCrvs ; vCrvs.reserve( m_CrvLst.size()) ; for ( auto pCrv : m_CrvLst) { IdentifyFillets( GetCurveComposite( pCrv), dDist) ; vCrvs.emplace_back( pCrv) ; } if ( ! AdjustCurveFillets( vCrvs, dDist, nType)) return false ; m_CrvLst.clear() ; for ( int j = 0 ; j < int( vCrvs.size()) ; j ++) m_CrvLst.emplace_back( Release( vCrvs[j])) ; } } // -------------------- OFFSET CON VORONOI --------------------------------- else { // recupero Voronoi associato alla curva Voronoi* voronoiObj ; if ( bNeedRef || bNeedScale) voronoiObj = ccCopy.GetVoronoiObject() ; else voronoiObj = GetCurveVoronoi( *pCrv) ; if ( voronoiObj == nullptr) return false ; // calcolo offset con Voronoi ICURVEPOVECTOR vOffs ; voronoiObj->CalcOffset( vOffs, dDist, nType) ; if ( vOffs.empty()) { // se non ho ottenuto offset e sono circa al valore limite ritento con valore leggermente diverso per le tolleranze di vroni double dMaxOffs ; if ( ! pCrv->IsClosed() || ( voronoiObj->CalcLimitOffset( 0, dDist < 0, dMaxOffs) && abs( dMaxOffs - abs( dDist)) < EPS_SMALL)) { double dCorr = ( dDist > 0 ? - VRONI_OFFS_TOL : VRONI_OFFS_TOL) ; voronoiObj->CalcOffset( vOffs, dDist + dCorr, nType) ; } // se ancora vuoto calcolo i punti speciali di offset ( punti e direzioni sui bisettore alla distanza richiesta) if ( vOffs.empty()) { PNTVECTVECTOR vPntOffs ; voronoiObj->CalcSpecialPointOffset( vPntOffs, dDist) ; // NB al momento vengono gestiti solo i casi in cui vi è un unico punto di offset. Se si ottengono più punti di offset // ( e.g. alcune curve aperte) non se ne restiusce nessuno. Da estendere, se necessario, individuando i punti dal lato // di offset richiesto if ( vPntOffs.size() == 1) { m_ptOffs = vPntOffs[0].first ; m_vtOut = vPntOffs[0].second ; } } } for ( int i = 0 ; i < ( int)vOffs.size() ; i ++) m_CrvLst.emplace_back( Release( vOffs[i])) ; // cambio lo start solo se di tipo fillet bChangeStart = ( nType & ICurve::OFF_CHAMFER) == 0 && ( nType & ICurve::OFF_EXTEND) == 0 ; } // eventuale ripristino del punto iniziale if ( bChangeStart) { for ( auto pCrv : m_CrvLst) { CurveComposite* pCrvCo = GetBasicCurveComposite( pCrv) ; if ( pCrvCo != nullptr && pCrvCo->IsClosed()) { DistPointCurve distPC( ptStart, *pCrvCo) ; double dPar ; int nFlag ; if ( distPC.GetParamAtMinDistPoint( 0.0, dPar, nFlag) && abs( dPar) > EPS_PARAM) pCrvCo->ChangeStartPoint(dPar) ; } } } // riporto il risultato nel riferimento originale if ( bNeedScale) { for ( auto pCrv : m_CrvLst) { pCrv->Scale( GLOB_FRM, 1 / dExtrOnN, 1, 1) ; pCrv->ToGlob( frCopy) ; } if ( m_ptOffs.IsValid()) { m_ptOffs.Scale( GLOB_FRM, 1 / dExtrOnN, 1, 1) ; m_ptOffs.ToGlob( frCopy) ; m_vtOut.Scale( GLOB_FRM, 1 / dExtrOnN, 1, 1) ; m_vtOut.ToGlob( frCopy) ; } } else if ( bNeedRef) { for ( auto pCrv : m_CrvLst) pCrv->ToGlob( frCopy) ; if ( m_ptOffs.IsValid()) { m_ptOffs.ToGlob( frCopy) ; m_vtOut.ToGlob( frCopy) ; } } // assegno estrusione e spessore come curva originale e unisco parti allineate for ( auto pCrv : m_CrvLst) { pCrv->SetExtrusion( vtExtr) ; pCrv->SetThickness( dThick) ; // unisco eventuali parti allineate (tranne gli estremi) if ( pCrv->GetType() == CRV_COMPO) GetBasicCurveComposite( pCrv)->MergeCurves( 10 * EPS_SMALL, ANG_TOL_STD_DEG, false) ; } // ordino le curve in ordine decrescente di lunghezza if ( m_CrvLst.size() > 1) { for ( auto pCrv : m_CrvLst) { double dLen ; if ( pCrv->GetLength( dLen)) pCrv->SetTempProp( int( 1000 * dLen)) ; else pCrv->SetTempProp( 0) ; } m_CrvLst.sort( []( const ICurve* pA, const ICurve* pB) { return ( pA->GetTempProp() > pB->GetTempProp()) ; }) ; } // elimino eventuali curve non uniche molto corte e aperte while ( ( m_CrvLst.size() > 1)) { ICurve* pCrv = m_CrvLst.back() ; if ( ! pCrv->IsClosed() && pCrv->GetTempProp() < 1000 * 50 * EPS_SMALL) { delete( pCrv) ; m_CrvLst.pop_back() ; } else break ; } // se originale era chiusa, verifico le risultanti e se necessario cerco di chiuderle if ( bClosed) { for ( auto pCrv : m_CrvLst) { CurveComposite* pCrvCo = GetBasicCurveComposite( pCrv) ; if ( pCrvCo != nullptr) pCrvCo->Close() ; } } return true ; } //---------------------------------------------------------------------------- ICurve* OffsetCurve::GetCurve( void) { return GetLongerCurve() ; } //---------------------------------------------------------------------------- ICurve* OffsetCurve::GetLongerCurve( void) { if ( m_CrvLst.empty()) return nullptr ; // le curve sono ordinate in senso decrescente di lunghezza ICurve* pCrv = m_CrvLst.front() ; m_CrvLst.pop_front() ; return pCrv ; } //---------------------------------------------------------------------------- ICurve* OffsetCurve::GetShorterCurve( void) { if ( m_CrvLst.empty()) return nullptr ; // le curve sono ordinate in senso decrescente di lunghezza ICurve* pCrv = m_CrvLst.back() ; m_CrvLst.pop_back() ; return pCrv ; } //---------------------------------------------------------------------------- bool OffsetCurve::GetPointOffset( Point3d& ptOffs, Vector3d& vtOut) { // verifico se valori validi da restituire if ( ! m_ptOffs.IsValid() || ! m_vtOut.IsValid()) return false ; ptOffs = m_ptOffs ; vtOut = m_vtOut ; return true ; } //---------------------------------------------------------------------------- bool PreviousIsLine( ICURVEPLIST::const_iterator iIter, const ICURVEPLIST& CrvLst, bool bClosed) { // verifico non sia il primo if ( iIter == CrvLst.begin()) return false ; // passo al precedente auto iPrev = prev( iIter) ; // se non esiste e curva chiusa prendo l'ultimo if ( iPrev == CrvLst.end() && bClosed) -- iPrev ; // se non esiste o non è una linea, test fallito if ( iPrev == CrvLst.end() || (*iPrev)->GetType() != CRV_LINE ) return false ; // test superato return true ; } //---------------------------------------------------------------------------- bool PreviousIsLonger( int nInd1, const DBLVECTOR& vLens, bool bClosed) { // massimo indice nel vettore int nMax = int( vLens.size()) - 1 ; // verifico validità indice (questo indice è incrementato di 1) if ( nInd1 < 1 || nInd1 > nMax + 1) return false ; // indice del precedente nel vettore di lunghezze int nPrec = nInd1 - 2 ; // se indice sotto minimo e chiuso, uso il finale if ( nPrec < 0 && bClosed) nPrec = nMax ; // verifica finale dell'indice if ( nPrec < 0) return false ; // eseguo confronto return ( vLens[nPrec] > vLens[nInd1-1] - EPS_SMALL) ; } //---------------------------------------------------------------------------- bool NextIsLine( ICURVEPLIST::const_iterator iIter, const ICURVEPLIST& CrvLst, bool bClosed) { // passo al successivo auto iNext = next( iIter) ; // se non esiste e curva chiusa prendo il primo if ( iNext == CrvLst.end() && bClosed) iNext = CrvLst.begin() ; // se non esiste o non è una linea, test fallito if ( iNext == CrvLst.end() || (*iNext)->GetType() != CRV_LINE ) return false ; // test superato return true ; } //---------------------------------------------------------------------------- bool NextIsLonger( int nInd1, const DBLVECTOR& vLens, bool bClosed) { // massimo indice nel vettore int nMax = int( vLens.size()) - 1 ; // verifico validità indice (questo indice è incrementato di 1) if ( nInd1 < 1 || nInd1 > nMax + 1) return false ; // indice del successivo nel vettore di lunghezze int nNext = nInd1 ; // se indice oltre il massimo e chiuso, uso l'iniziale if ( nNext > nMax && bClosed) nNext = 0 ; // verifica finale dell'indice if ( nNext > nMax) return false ; // eseguo confronto return ( vLens[nNext] > vLens[nInd1-1] - EPS_SMALL) ; } //---------------------------------------------------------------------------- bool CalcAngle( const ICurve* pCrv1, const ICurve* pCrv2, double& dAngDeg) { // calcolo direzioni tangenti sull'estremo in comune Vector3d vtDir1, vtDir2 ; if ( ! pCrv1->GetEndDir( vtDir1) || ! pCrv2->GetStartDir( vtDir2)) return false ; if ( ! vtDir1.Normalize() || ! vtDir2.Normalize()) return false ; // calcolo l'angolo di rotazione dalla prima direzione alla seconda if ( ! vtDir1.GetAngleXY( vtDir2, dAngDeg)) return false ; // se vicino all'angolo piatto, si devono ricalcolare spostandosi un poco const double MAX_ANG_DELTA = 2 ; double dAngDelta = abs( abs( dAngDeg) - ANG_STRAIGHT) ; if ( dAngDelta < MAX_ANG_DELTA) { // angolo al centro delle curve double dAngCen1 = 0 ; if ( pCrv1->GetType() == CRV_ARC) dAngCen1 = abs( GetBasicCurveArc( pCrv1)->GetAngCenter()) ; double dAngCen2 = 0 ; if ( pCrv2->GetType() == CRV_ARC) dAngCen2 = abs( GetBasicCurveArc( pCrv2)->GetAngCenter()) ; // determino posizioni spostate double dU1 = 1 ; if ( dAngCen1 > EPS_ANG_SMALL) { dU1 = 1 - min( 1.1 * dAngDelta / ( dAngCen1 + dAngCen2), 0.5) ; } double dU2 = 0 ; if ( dAngCen2 > EPS_ANG_SMALL) { dU2 = 0 + min( 1.1 * dAngDelta / ( dAngCen1 + dAngCen2), 0.5) ; } // eseguo calcolo spostato Point3d ptDummy ; Vector3d vtDir1b ; Vector3d vtDir2b ; if ( ! pCrv1->GetPointTang( dU1, ICurve::FROM_MINUS, ptDummy, vtDir1b) || ! pCrv2->GetPointTang( dU2, ICurve::FROM_PLUS, ptDummy, vtDir2b)) return false ; if ( ! vtDir1b.GetAngleXY( vtDir2b, dAngDeg)) return false ; } return true ; } //---------------------------------------------------------------------------- bool VerifyAndAdjustSamePoint( ICurve* pCrv1, ICurve* pCrv2, int& nRes) { // verifica dei puntatori if ( pCrv1 == nullptr || pCrv2 == nullptr) return false ; // calcolo dei punti estremi (finale per prima curva, iniziale per seconda) Point3d ptP1, ptP2 ; if ( ! pCrv1->GetEndPoint( ptP1) || ! pCrv2->GetStartPoint( ptP2)) return false ; // verifica distanza tra estremi if ( ! AreSamePointXYEpsilon( ptP1, ptP2, 10 * EPS_SMALL)) { nRes = 4 ; return true ; } // se coincidono esattamente, va bene così if ( AreSamePointExact( ptP1, ptP2)) { nRes = 0 ; return true ; } // sono in tolleranza, ma devo ricongiungere gli estremi Point3d ptS1, ptE2 ; if ( ! pCrv1->GetStartPoint( ptS1) || ! pCrv2->GetEndPoint( ptE2)) return false ; nRes = 0 ; Point3d ptMid = 0.5 * ( ptP1 + ptP2) ; if ( AreSamePointApprox( ptS1, ptMid) || ! pCrv1->ModifyEnd( ptMid)) nRes += 1 ; if ( AreSamePointApprox( ptMid, ptE2) || ! pCrv2->ModifyStart( ptMid)) nRes += 2 ; return true ; } //---------------------------------------------------------------------------- bool VerifyAndAdjustInternalAngle( ICurve* pCrv1, ICurve* pCrv2, int& nRes) { // verifica dei puntatori if ( pCrv1 == nullptr || pCrv2 == nullptr) return false ; // calcolo l'intersezione tra le due curve IntersCurveCurve intCC( *pCrv1, *pCrv2) ; if ( intCC.GetIntersCount() == 0) return false ; // prendo l'intersezione più vicina al punto medio tra gli estremi delle curve Point3d ptP1, ptP2 ; if ( ! pCrv1->GetEndPoint( ptP1) || ! pCrv2->GetStartPoint( ptP2)) return false ; Point3d ptMid = 0.5 * ( ptP1 + ptP2) ; Point3d ptNew1, ptNew2 ; if ( ! intCC.GetIntersPointNearTo( 0, ptMid, ptNew1) || ! intCC.GetIntersPointNearTo( 1, ptMid, ptNew2)) return false ; // modifico le due curve sul punto medio di intersezione Point3d ptS1, ptE2 ; if ( ! pCrv1->GetStartPoint( ptS1) || ! pCrv2->GetEndPoint( ptE2)) return false ; nRes = 0 ; Point3d ptNew = 0.5 * ( ptNew1 + ptNew2) ; if ( AreSamePointApprox( ptS1, ptNew) || ! pCrv1->ModifyEnd( ptNew)) nRes += 1 ; if ( AreSamePointApprox( ptNew, ptE2) || ! pCrv2->ModifyStart( ptNew)) nRes += 2 ; return true ; } //---------------------------------------------------------------------------- bool VerifyAndAdjustExternalAngle( ICurve* pCrv1, ICurve* pCrv2, double dAngDeg, double dDist, int nType, CurveComposite& ccAux) { // verifica dei puntatori if ( pCrv1 == nullptr || pCrv2 == nullptr || &ccAux == nullptr) return false ; // pulisco la curva ausiliaria ccAux.Clear() ; // elimino dal tipo le parti estranee all'angolo esterno nType &= ( ICurve::OFF_FILLET | ICurve::OFF_CHAMFER | ICurve::OFF_EXTEND) ; // calcolo direzioni tangenti sull'estremo in comune Vector3d vtDir1, vtDir2 ; if ( ! pCrv1->GetEndDir( vtDir1) || ! pCrv2->GetStartDir( vtDir2)) return false ; if ( ! vtDir1.Normalize( EPS_ZERO) || ! vtDir2.Normalize( EPS_ZERO)) return false ; if ( pCrv1->GetTempProp() < 0) { vtDir1.Invert() ; vtDir2.Invert() ; } // verifico sia angolo esterno (accetto se entità quasi esattamente sovrapposte) if ( abs( dAngDeg) < ( ANG_STRAIGHT - 10 * EPS_ANG_ZERO) && ( ( dDist < 0 && dAngDeg > 0) || ( dDist > 0 && dAngDeg < 0))) return false ; // se l'angolo esterno supera il retto, offset extend diventa offset chamfer if ( nType == ICurve::OFF_EXTEND && abs( dAngDeg) > ANG_RIGHT + EPS_ANG_SMALL) nType = ICurve::OFF_CHAMFER ; // se angolo esterno molto piccolo, semplifico tutto const double SMALL_EXT_ANG = 0.1 ; bool bAngSmall = ( abs( dAngDeg) < SMALL_EXT_ANG) ; if ( bAngSmall) nType = ICurve::OFF_EXTEND ; // congiungo le due curve switch ( nType) { case ICurve::OFF_FILLET : { Point3d ptP1, ptP2 ; if ( ! pCrv1->GetEndPoint( ptP1) || ! pCrv2->GetStartPoint( ptP2)) return false ; double dAngStart ; vtDir1.ToSpherical( nullptr, nullptr, &dAngStart) ; PtrOwner pCrv( CreateBasicCurveArc()) ; if ( IsNull( pCrv) || ! pCrv->Set2PD( ptP1, ptP2, dAngStart)) return false ; // restituisco la curva return ccAux.AddCurve( Release( pCrv)) ; } break ; case ICurve::OFF_CHAMFER : { // lunghezza aggiuntiva in tangenza double dLen = abs( dDist) * tan( abs( dAngDeg) / 4 * DEGTORAD) ; // punti di costruzione smusso Point3d ptP1, ptP1a, ptP2a, ptP2 ; if ( ! pCrv1->GetEndPoint( ptP1) || ! pCrv2->GetStartPoint( ptP2)) return false ; ptP1a = ptP1 + vtDir1 * dLen ; ptP2a = ptP2 - vtDir2 * dLen ; // se prima c'è linea posso allungarla if ( pCrv1->GetType() == CRV_LINE) pCrv1->ModifyEnd( ptP1a) ; // altrimenti, devo aggiungere una nuova linea else { PtrOwner pCrv( CreateBasicCurveLine()) ; if ( IsNull( pCrv) || ! pCrv->Set( ptP1, ptP1a)) return false ; if ( ! ccAux.AddCurve( Release( pCrv))) return false ; } // tratto intermedio { PtrOwner pCrv( CreateBasicCurveLine()) ; if ( IsNull( pCrv) || ! pCrv->Set( ptP1a, ptP2a)) return false ; if ( ! ccAux.AddCurve( Release( pCrv))) return false ; } // se dopo c'è linea posso allungarla if ( pCrv2->GetType() == CRV_LINE) pCrv2->ModifyStart( ptP2a) ; // altrimenti, devo aggiungere una nuova linea else { PtrOwner pCrv( CreateBasicCurveLine()) ; if ( IsNull( pCrv) || ! pCrv->Set( ptP2a, ptP2)) return false ; if ( ! ccAux.AddCurve( Release( pCrv))) return false ; } return true ; } break ; case ICurve::OFF_EXTEND : { // lunghezza aggiuntiva in tangenza double dLen = abs( dDist) * tan( abs( dAngDeg) / 2 * DEGTORAD) ; // punti di costruzione estensione Point3d ptP1, ptPc, ptP2 ; if ( ! pCrv1->GetEndPoint( ptP1) || ! pCrv2->GetStartPoint( ptP2)) return false ; ptPc = ptP1 + vtDir1 * dLen ; // se prima c'è linea o angolo molto piccolo posso allungarla if ( ( pCrv1->GetType() == CRV_LINE) || bAngSmall) pCrv1->ModifyEnd( ptPc) ; // altrimenti, devo aggiungere una nuova linea else { PtrOwner pCrv( CreateBasicCurveLine()) ; if ( IsNull( pCrv) || ! pCrv->Set( ptP1, ptPc)) return false ; if ( ! ccAux.AddCurve( Release( pCrv))) return false ; } // se dopo c'è linea o angolo molto piccolo posso allungarla if ( ( pCrv2->GetType() == CRV_LINE) || bAngSmall) pCrv2->ModifyStart( ptPc) ; // altrimenti, devo aggiungere una nuova linea else { PtrOwner pCrv( CreateBasicCurveLine()) ; if ( IsNull( pCrv) || ! pCrv->Set( ptPc, ptP2)) return false ; if ( ! ccAux.AddCurve( Release( pCrv))) return false ; } return true ; } } return false ; } //---------------------------------------------------------------------------- double GetMinDist( double dCoeff, ICurve* pCrv1, ICurve* pCrv2) { double dUStart, dUEnd ; pCrv1->GetDomain( dUStart, dUEnd) ; Point3d ptP ; pCrv1->GetPointD1D2( ( dCoeff * dUStart + ( 1 - dCoeff) * dUEnd), ICurve::FROM_MINUS, ptP) ; double dMinDist ; DistPointCurve dstPC( ptP, *pCrv2) ; if ( ! dstPC.GetDist( dMinDist)) return 0 ; return dMinDist ; }