//---------------------------------------------------------------------------- // EgalTech 2016-2020 //---------------------------------------------------------------------------- // File : OffsetCurveOnX.cpp Data : 13.02.20 Versione : 2.2b2 // Contenuto : Classe per offset di Curve lungo la sola X. // // // // Modifiche : 02.04.16 DS Creazione modulo. // // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include "CurveComposite.h" #include "CurveLine.h" #include "CurveArc.h" #include "GeoConst.h" #include "/EgtDev/Include/EGkOffsetCurveOnX.h" #include "/EgtDev/Include/EGkIntersCurves.h" #include "/EgtDev/Include/EGkDistPointCurve.h" #include "/EgtDev/Include/EgtPointerOwner.h" #include using namespace std ; //---------------------------------------------------------------------------- static bool SplitLeftRightArcs( CurveComposite& cCompo) ; static bool SimpleOffsetOnX( ICurve* pCrv, double dDist) ; static bool VerifyAndAdjustSamePoint( ICurve* pCrv1, ICurve* pCrv2) ; static bool VerifyAndAdjustInternalAngle( ICurve* pCrv1, ICurve* pCrv2) ; static bool SafeDist( double dDist, double dCoeff, ICurve* pCrv1, ICurve* pCrv2) ; //---------------------------------------------------------------------------- OffsetCurveOnX::~OffsetCurveOnX( void) { for ( auto& pCrv : m_CrvLst) { if ( pCrv != nullptr) { delete pCrv ; pCrv = nullptr ; } } m_CrvLst.clear() ; } //---------------------------------------------------------------------------- bool OffsetCurveOnX::Make( const ICurve* pCrv, double dDist) { // verifico che la curva esista e sia piana Plane3d plPlane ; if ( pCrv == nullptr || ! pCrv->IsFlat( plPlane)) return false ; // se esiste estrusione, verifico sia perpendicolare al piano della curva Vector3d vtExtr ; if ( pCrv->GetExtrusion( vtExtr) && ! vtExtr.IsSmall()) { if ( ! AreSameOrOppositeVectorApprox( plPlane.GetVersN(), vtExtr)) return false ; } else vtExtr = plPlane.GetVersN() ; // determino se necessario cambiare riferimento ( dal vettore estrusione) bool bNeedRef = ( ! vtExtr.IsZplus()) ; // creo una copia della curva CurveComposite ccCopy ; if ( ! ccCopy.CopyFrom( pCrv)) return false ; ccCopy.SetExtrusion( vtExtr) ; // se necessario cambio il riferimento Frame3d frExtr ; if ( bNeedRef) { // calcolo il riferimento OCS con VtExtr come asse Z if ( ! frExtr.Set( ORIG, vtExtr)) return false ; // esprimo la curva in questo riferimento ccCopy.ToLoc( frExtr) ; } // verifico che la curva sia fatta solo da rette e archi che giacciono nel piano XY (VtExtr è ora Z+) if ( ! ccCopy.ArcsBezierCurvesToArcsPerpExtr( 10 * EPS_SMALL, ANG_TOL_STD_DEG)) return false ; // spezzo eventuali archi che cambiano pendenza if ( ! SplitLeftRightArcs( ccCopy)) return false ; // se offset qusi nullo, copio la curva nel risultato ed esco if ( abs( dDist) < 10 * EPS_SMALL) { PtrOwner pCopy( CreateBasicCurveComposite()) ; if ( IsNull( pCopy) || ! pCopy->RelocateFrom( ccCopy)) return false ; m_CrvLst.push_back( Release( pCopy)) ; return true ; } // verifico se curva chiusa bool bClosed = ccCopy.IsClosed() ; // Primo passo : estraggo entità dalla copia ed eseguo opportuno offset orizzontale 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 PtrOwner pCrv1( ccCopy2.RemoveFirstOrLastCurve( false)) ; while ( ! IsNull( pCrv1)) { // eseguo semplice offset orizzontale pCrv1->SetTempProp( nInd1) ; if ( SimpleOffsetOnX( pCrv1, dDist)) // metto in lista m_CrvLst.push_back( Release( pCrv1)) ; // passo alla curva successiva pCrv1.Set( ccCopy2.RemoveFirstOrLastCurve( false)) ; ++ nInd1 ; } if ( m_CrvLst.empty()) return false ; // Secondo passo : taglio angoli tipo 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) { if ( ! VerifyAndAdjustSamePoint( pPrev, pCrv)) VerifyAndAdjustInternalAngle( pPrev, pCrv) ; } // passo al successivo pPrev = pCrv ; ++ iIter ; } // Terzo 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 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() ; // unisco tutte le curve possibili pCrvCompo->MergeCurves( 10 * EPS_SMALL, ANG_TOL_STD_DEG) ; // se non rimasto nulla, esco double dLen ; if ( ! pCrvCompo->IsValid() || ! pCrvCompo->GetLength( dLen) || dLen < 10 * EPS_SMALL) return true ; // inserisco la curva nella lista m_CrvLst.push_back( Release( pCrvCompo)) ; CurveComposite* pCompo1 = GetBasicCurveComposite( m_CrvLst.front()) ; // Quarto 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 if ( ! vU.empty() && ( dLen - vLen.back()) < 2 * EPS_SMALL) { vU.pop_back() ; vLen.pop_back() ; } // eseguo la divisione int nCount = 1 ; 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)) ; ++ nCount ; // 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, trimmo l'ultima parte if ( dUPrev > EPS_PARAM) pCompo1->TrimStartAtParam( dUPrev) ; // Quinto passo : elimino le parti che sono troppo vicine al percorso originale 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 ( ! SafeDist( dDist, 0.53, pCrv, &ccCopy) || ! SafeDist( dDist, 0.13, pCrv, &ccCopy) || ! SafeDist( dDist, 0.89, pCrv, &ccCopy)) { delete pCrv ; iIter = m_CrvLst.erase( iIter) ; continue ; } // passo alla successiva ++ iIter ; } // Sesto 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, 10 * EPS_SMALL)) { pCrvCo->AddCurve( pCrvCo2, true, 10 * EPS_SMALL) ; m_CrvLst.erase( iIter2) ; ptEnd = ptEnd2 ; iIter2 = next( iIter) ; } else if ( AreSamePointEpsilon( ptEnd2, ptStart, 10 * EPS_SMALL)) { pCrvCo->AddCurve( pCrvCo2, false, 10 * EPS_SMALL) ; m_CrvLst.erase( iIter2) ; ptStart = ptStart2 ; iIter2 = next( iIter) ; } else ++ iIter2 ; } ++ iIter ; } // riporto il risultato nel riferimento originale if ( bNeedRef) { for ( auto pCrv : m_CrvLst) pCrv->ToGlob( frExtr) ; } return true ; } //---------------------------------------------------------------------------- ICurve* OffsetCurveOnX::GetCurve( void) { if ( m_CrvLst.empty()) return nullptr ; ICurve* pCrv = m_CrvLst.front() ; m_CrvLst.pop_front() ; return pCrv ; } //---------------------------------------------------------------------------- ICurve* OffsetCurveOnX::GetLongerCurve( void) { if ( m_CrvLst.empty()) return nullptr ; // cerco la curva più lunga double dMaxLen = 0 ; auto iMaxIter = m_CrvLst.end() ; for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ; ++ iIter) { double dLen ; if ( (*iIter)->GetLength( dLen) && dLen > dMaxLen) { dMaxLen = dLen ; iMaxIter = iIter ; } } // se trovata if ( iMaxIter != m_CrvLst.end()) { ICurve* pCrv = (*iMaxIter) ; m_CrvLst.erase( iMaxIter) ; return pCrv ; } return nullptr ; } //---------------------------------------------------------------------------- bool SplitLeftRightArcs( CurveComposite& cCompo) { int i = 0 ; const ICurve* pCrv = cCompo.GetCurve( i) ; while ( pCrv != nullptr) { if ( pCrv->GetType() == CRV_ARC) { const CurveArc* pArc = GetBasicCurveArc( pCrv) ; // determino la rotazione angolare dall'inizio alle direzioni su e giù double dAng1, dAng2 ; bool bDet ; pArc->GetStartVersor().GetRotation( Y_AX, Z_AX, dAng1, bDet) ; pArc->GetStartVersor().GetRotation( - Y_AX, Z_AX, dAng2, bDet) ; // oriento queste rotazioni come l'angolo al centro dell'arco if ( pArc->GetAngCenter() > 0) { if ( dAng1 < 0) dAng1 += ANG_FULL ; if ( dAng2 < 0) dAng2 += ANG_FULL ; } else { if ( dAng1 > 0) dAng1 -= ANG_FULL ; if ( dAng2 > 0) dAng2 -= ANG_FULL ; } // le ordino in senso crescente di ampiezza assoluta if ( abs( dAng1) > abs( dAng2)) swap( dAng1, dAng2) ; // verifico se la prima di queste rotazioni è compresa nell'arco if ( abs( dAng1) > EPS_ANG_SMALL && abs( dAng1) < abs( pArc->GetAngCenter()) - EPS_ANG_SMALL) { cCompo.AddJoint( i + dAng1 / pArc->GetAngCenter()) ; } // verifico la seconda else if ( abs( dAng2) > EPS_ANG_SMALL && abs( dAng2) < abs( pArc->GetAngCenter()) - EPS_ANG_SMALL) { cCompo.AddJoint( i + dAng2 / pArc->GetAngCenter()) ; } } // passo alla successiva pCrv = cCompo.GetCurve( ++i) ; } return true ; } //---------------------------------------------------------------------------- bool SimpleOffsetOnX( ICurve* pCrv, double dDist) { Point3d ptStart, ptEnd ; pCrv->GetStartPoint( ptStart) ; pCrv->GetEndPoint( ptEnd) ; if ( abs( ptStart.y - ptEnd.y) < EPS_SMALL) { return ( abs( ptStart.x - ptEnd.x) > dDist) ; } else if ( ptStart.y < ptEnd.y) pCrv->Translate( Vector3d( dDist, 0, 0)) ; else pCrv->Translate( Vector3d( - dDist, 0, 0)) ; return true ; } //---------------------------------------------------------------------------- bool VerifyAndAdjustSamePoint( ICurve* pCrv1, ICurve* pCrv2) { // 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)) return false ; // se coincidono esattamente, va bene così if ( AreSamePointExact( ptP1, ptP2)) return true ; // sono in tolleranza, ma devo ricongiungere gli estremi Point3d ptMid = 0.5 * ( ptP1 + ptP2) ; return ( pCrv1->ModifyEnd( ptMid) && pCrv2->ModifyStart( ptMid)) ; } //---------------------------------------------------------------------------- bool VerifyAndAdjustInternalAngle( ICurve* pCrv1, ICurve* pCrv2) { // 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 ptNew = 0.5 * ( ptNew1 + ptNew2) ; return ( pCrv1->ModifyEnd( ptNew) && pCrv2->ModifyStart( ptNew)) ; } //---------------------------------------------------------------------------- bool SafeDist( double dDist, double dCoeff, ICurve* pCrv1, ICurve* pCrv2) { // calcolo il punto e due vicini double dUStart, dUEnd ; pCrv1->GetDomain( dUStart, dUEnd) ; double dU = ( 1 - dCoeff) * dUStart + dCoeff * dUEnd ; Point3d ptP ; pCrv1->GetPointD1D2( dU, ICurve::FROM_MINUS, ptP) ; Point3d ptPrev, ptNext ; Vector3d vtPrev, vtNext ; pCrv1->GetPointTang( dU - 100 * EPS_PARAM, ICurve::FROM_PLUS, ptPrev, vtPrev) ; pCrv1->GetPointTang( dU + 100 * EPS_PARAM, ICurve::FROM_MINUS, ptNext, vtNext) ; // non si considerano i tratti orizzontali (tolleranza fino a 1deg) const double SEN_ANG_LIM = 0.0175 ; if ( abs( vtPrev.y) < SEN_ANG_LIM || abs( vtNext.y) < SEN_ANG_LIM) return true ; // segmento orizzontale di prova centrato sul punto e lungo dDist + dDist Vector3d vtDelta( abs(dDist) - 5 * EPS_SMALL, 0, 0) ; CurveLine Line ; Line.Set( ptP - vtDelta, ptP + vtDelta) ; // interseco il segmento di test con la curva originale IntersCurveCurve IntCC( Line, *pCrv2) ; return ( IntCC.GetIntersCount() == 0) ; }