//---------------------------------------------------------------------------- // EgalTech 2026 //---------------------------------------------------------------------------- // File : OffsetCurve3d.cpp Data : 10.06.26 Versione : 3.1f1 // Contenuto : Classe per offset di Curve 3d. // // // // Modifiche : 10.06.26 DB Creazione modulo. // //---------------------------------------------------------------------------- //--------------------------- Include ---------------------------------------- #include "stdafx.h" #include "GeoConst.h" #include "CurveLine.h" #include "CurveComposite.h" #include "RemoveCurveDefects.h" #include "IntersLineCyl.h" #include "/EgtDev/Include/EGkPoint3d.h" #include "/EgtDev/Include/EGkPolyLine.h" #include "/EgtDev/Include/EGkOffsetCurve3d.h" #include "/EgtDev/Include/EgtPointerOwner.h" #include using namespace std ; #define SAVECVRORIG 1 #define SAVEOFFDIR 1 #define SAVECYL 1 #if SAVECVRORIG || SAVEOFFDIR || SAVECYL #include "/EgtDev/Include/EGkColor.h" #include "/EgtDev/Include/EGkGeoVector3d.h" vector vGeo ; vector vCol ; #include "/EgtDev/Include/EGkGeoObjSave.h" #endif //---------------------------------------------------------------------------- bool AdjustConcavePartsInPath( const ICurveComposite* pCrv, ICURVEPOVECTOR& vOffsetCrvs, const INTVECTOR& vFlag, double dRad) ; //---------------------------------------------------------------------------- OffsetCurve3d::~OffsetCurve3d( void) { Reset() ; } //---------------------------------------------------------------------------- bool OffsetCurve3d::Reset( void) { for ( auto& pCrv : m_CrvLst) { if ( pCrv != nullptr) { delete pCrv ; pCrv = nullptr ; } } m_CrvLst.clear() ; return true ; } //---------------------------------------------------------------------------- bool OffsetCurve3d::Make( const PolyLine& PL, const VCT3DVECTOR& vOffDir, double dOffDist, int nType) { // pulisco tutto Reset() ; PtrOwner pCrv( CreateBasicCurveComposite()) ; if ( ! pCrv->FromPolyLine( PL)) return false ; #if SAVECVRORIG vGeo.push_back( pCrv->Clone()) ; vCol.push_back( AQUA) ; Point3d ptBase ; PL.GetFirstPoint( ptBase) ; for ( int i = 0 ; i < ssize( vOffDir) ; ++i) { IGeoVector3d* pVec = CreateGeoVector3d() ; pVec->Set( vOffDir[i] * dOffDist, ptBase) ; PL.GetNextPoint( ptBase) ; vGeo.push_back( pVec) ; vCol.push_back( BLUE) ; } #endif // verifico se la curva è un segmento di retta bool bIsLine = PL.GetPointNbr() == 2 ; if ( bIsLine) { // faccio l'offset di una linea return true ; } // se offset nullo, copio la curva ed esco if ( abs( dOffDist) < 10 * EPS_SMALL) { PtrOwner pCopy( CreateBasicCurveComposite()) ; if ( IsNull( pCopy) || ! pCopy->CopyFrom( pCrv)) return false ; // 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 ; } // elimino tratti molto corti if ( ! RemoveCurveSmallParts( pCrv, m_dLinTol)) return false ; bool bClosed = pCrv->IsClosed() ; INTVECTOR vFlag ; vFlag.push_back( OffsetCurve3d::AngType::ANG_STR) ; const ICurve* pSubCrv = pCrv->GetFirstCurve() ; for ( int i = 1 ; i < pCrv->GetCurveCount() ; ++i) { pSubCrv = pCrv->GetNextCurve() ; Vector3d vtDirCurr ; pSubCrv->GetStartDir( vtDirCurr) ; int nFlag = vtDirCurr * vOffDir[i] > 0 ? OffsetCurve3d::AngType::ANG_SMOOTH_CONC : OffsetCurve3d::AngType::ANG_STR ; vFlag.push_back( nFlag) ; } double dRadCorr ; Point3d ptPrev ; pCrv->GetStartPoint( ptPrev) ; ptPrev += vOffDir[0] * dOffDist ; Vector3d vtNormPrev ; Vector3d vtCorrPrev ; Vector3d vtTangPrev ; Vector3d vtDirPrev ; pCrv->GetStartDir( vtDirPrev) ; const ICurve* pCrvPrev = pCrv->GetFirstCurve() ; const ICurve* pCrvCurr ; vector> vOffsetCrvs ; for ( int i = 1 ; i < pCrv->GetCurveCount() ; ++i) { pCrvCurr = pCrv->GetNextCurve() ; Vector3d vtOffDir = vOffDir[i] ; Vector3d vtDirCurr ; pCrvCurr->GetStartDir( vtDirCurr) ; pCrvPrev->GetStartDir( vtDirPrev) ; Vector3d vtTang = Media( vtDirCurr, vtDirPrev) ; vtTang.Normalize() ; Vector3d vtNorm = vtOffDir ; vtNorm.Rotate( vtTang, -90) ; //Vector3d vtCorr = vtTang ^ vtNorm ; vtCorr.Normalize() ; // devo invertire vtCorr??? se sì, quando?///////////////////////////////////////// Vector3d vtCorr = vtOffDir ; double dCorrK = 1 ; if ( vFlag[i] == OffsetCurve3d::AngType::ANG_CONC) { double dHalfAlfa = acos( vtTang * vtTangPrev) ; dCorrK = 1 / sin( 90 - dHalfAlfa) ; } Point3d ptP ; pCrvCurr->GetStartPoint( ptP) ; dRadCorr = dOffDist ; ptP = ptP + dRadCorr * dCorrK * vtCorr ; // se secondo punto di angolo esterno di fianco, inserisco movimenti intermedi if ( vFlag[i] == OffsetCurve3d::AngType::ANG_CVEX && vFlag[i-1] == OffsetCurve3d::AngType::ANG_CVEX) { double dAlfa = acos( vtTang * vtTangPrev) ; double dDelta = dOffDist * tan( dAlfa / 4) ; Point3d ptAdd1 = ptPrev + dDelta * vtTangPrev ; ICurveLine* pCL1 = CreateBasicCurveLine() ; pCL1->Set( ptPrev, ptAdd1) ; vOffsetCrvs.emplace_back( pCL1) ; Point3d ptAdd2 = ptP - dDelta * vtTang ; ICurveLine* pCL2 = CreateBasicCurveLine() ; pCL2->Set( ptAdd1, ptAdd2) ; vOffsetCrvs.emplace_back( pCL2) ; ptPrev = ptAdd2 ; } //// se punto di angolo interno di fianco, elimino eventuali movimenti precedenti invertiti //if ( vFlag[i] == OffsetCurve3d::AngType::ANG_CVEX == 3) { // local nLastId = EgtGetLastInGroup( nClPathId) // while nLastId do // local vtMlast = ptP - EgtEP( nLastId, GDB_ID.ROOT) ; vtMlast:normalize() // if vtMlast * vtGpre < 0.5 then // ptPpre = EgtSP( nLastId, GDB_ID.ROOT) // EgtErase( nLastId) // else // break // end // nLastId = EgtGetLastInGroup( nClPathId) // end //} //// se appena dopo angolo interno di fianco, verifico se da aggiungere //bool bToAdd = true //if ( nFlpre == 3 and abs( dSideAng) > GEO.EPS_ANG_SMALL) { // local vtMove = ptP - ptPpre ; vtMove:normalize() // if vtMove * vtTang < 0.5 then // bToAdd = false // end //} // aggiungo tratto ICurveLine* pCL = CreateBasicCurveLine() ; pCL->Set( ptPrev, ptP) ; vOffsetCrvs.emplace_back( pCL) ; // aggiorno punto precedente ptPrev = ptP ; vtNormPrev = vtNorm ; vtCorrPrev = vtCorr ; vtTangPrev = vtTang ; vtDirPrev = vtDirCurr ; } //// qui faccio la correzione per gli angoli interni //AdjustConcavePartsInPath( pCrv, vOffsetCrvs, vFlag, dOffDist) ; #if SAVECVRORIG || SAVEOFFDIR || SAVECYL SaveGeoObj( vGeo, vCol, "C:\\Temp\\curve offset 3d\\crvoffset.nge") ; #endif PtrOwner pCrvOffset( CreateBasicCurveComposite()) ; for ( int i = 0 ; i < ssize( vOffsetCrvs) ; ++i) { if ( ! pCrvOffset->AddCurve( Release( vOffsetCrvs[i]))) return false ; } m_CrvLst.push_back( Release( pCrvOffset)) ; return true ; // raccordi // angoli interni // angoli esterni // auto intersezioni //// sesto passo : se curva aperta, elimino i tratti che stanno nella circonferenza di offset dei punti estremi //// ottavo passo : concateno i percorsi risultanti (senza cambiare verso) //// nono passo : se con smusso o estensione, sostituisco i fillet con questi //// 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()) ; }) ; //} //// 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* OffsetCurve3d::GetCurve( void) { return GetLongerCurve() ; } //---------------------------------------------------------------------------- ICurve* OffsetCurve3d::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* OffsetCurve3d::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 ; } struct Cyl { Cyl( void): frCyl( GLOB_FRM), dH( 0.), dRad( 0.) {;} ; Cyl( const Frame3d& _frCyl, double _dH, double _dRad, double _dLinTol) : frCyl( _frCyl), dH( _dH), dRad( _dRad) { ;} Cyl( const Point3d& _ptBase, const Vector3d& vtZ, double _dH, double _dRad, double _dLinTol) : dH( _dH), dRad( _dRad){ frCyl.Set( _ptBase, vtZ); } public : Frame3d frCyl ; public: double dH ; double dRad ; }; typedef vector CYLVECT ; //---------------------------------------------------------------------------- bool IsPointInsideCylinder( const Point3d& ptTest, const Cyl& offCyl) { Point3d ptTestLoc = ptTest ; ptTestLoc.ToLoc( offCyl.frCyl) ; if ( ptTestLoc.z > offCyl.dH || ptTestLoc.z < 0) return false ; double dDist = ptTestLoc.x * ptTestLoc.x + ptTestLoc.y * ptTestLoc.y ; double dRadSq = offCyl.dRad * offCyl.dRad ; if ( dDist > dRadSq) return false ; return true ; } bool AdjustConcavePartsInPath( const ICurveComposite* pCrv, ICURVEPOVECTOR& vOffsetCrvs, const INTVECTOR& vFlag, double dRad) { const double dLinTol = 5 * EPS_SMALL ; INTVECTOR vErase ; for ( int i = 0 ; i < ssize( vOffsetCrvs) ; ++i) { int nFlag = vFlag[i] ; if ( nFlag == OffsetCurve3d::AngType::ANG_SMOOTH_CONC) { // scorro i prossimi finchè trovo la fine della zona concava INTVECTOR vLines ; while ( nFlag == OffsetCurve3d::AngType::ANG_SMOOTH_CONC) { vLines.push_back( i) ; ++i ; nFlag = vFlag[i] ; } CYLVECT vCyl ; // creo un cilindro della dimensione del raggio for ( int j = 0 ; j < ssize( vLines) ; ++j) { const ICurve* pSubCrv = pCrv->GetCurve( vLines[j]) ; Point3d ptStart, ptEnd ; pSubCrv->GetStartPoint( ptStart) ; pSubCrv->GetEndPoint( ptEnd) ; Vector3d vtHeight = ptEnd - ptStart ; vtHeight.Normalize() ; double dHeight = vtHeight.Len() ; vCyl.emplace_back( ptStart, vtHeight, dHeight, dRad, dLinTol) ; } // controllo l'end di ogni linea per verificare se sta nel cilindro definito da uno degli altri tratti // controllo tutto i punti bool bErasedSomePart = false ; bool bErasedPrev = false ; INTVECTOR vInters ; for ( int j = 0 ; j < ssize( vLines) ; ++j) { Point3d ptStart, ptEnd ; const ICurve* pSubCrv = pCrv->GetCurve( vLines[j]) ; if ( pSubCrv == nullptr) return false ; pSubCrv->GetEndPoint( ptEnd) ; pSubCrv->GetStartPoint( ptStart) ; // se stanno in uno dei cilindri degli altri tratti della zona concava for ( int k = 0 ; k < ssize( vLines) ; ++k) { if ( j == k) continue ; bool bToErase = IsPointInsideCylinder( ptEnd, vCyl[k]) ; if ( bErasedPrev && ! bToErase) bToErase = bToErase || IsPointInsideCylinder( ptStart, vCyl[k]) ; if ( bToErase) { bErasedSomePart = true ; bErasedPrev = true ; vInters.push_back( vLines[j]) ; if ( j < ssize( vLines) - 1) vInters.push_back( vLines[j+1]) ; ++j ; break ; } else bErasedPrev = false ; } } if ( bErasedSomePart) { // calcolo le intersezioni effettive del primo e ultimo tratto cancellati con i cilindri che li hanno cancellati // controllo che effettivamente tutti i tratti cancellati siano consecutivi for ( int j = 1 ; j < ssize( vInters) ; ++j) { if ( vInters[j] != vInters[j-1] + 1) return false ; } for ( int j = 0 ; j < ssize( vInters) ; ++j) { // cancello i tratti intermedi if ( j > 0 && j < ssize( vInters) - 1) { vErase.push_back( vInters[j]) ; continue ; } // per il primo e ultimo controllo le intersezioni con tutti i cilindri ICurve* pCL = vOffsetCrvs[vInters[j]] ; Point3d ptStart ; pCL->GetStartPoint( ptStart) ; Vector3d vtStart ; pCL->GetStartDir( vtStart) ; double dLen ; pCL->GetLength( dLen) ; double dUTrim = ( j == 0 ? INFINITO : 0) ; Point3d ptTrim = P_INVALID ; for ( int k = 0 ; k < ssize( vCyl) ; ++k) { if ( vInters[j] == k) continue ; Point3d ptInt1 = P_INVALID, ptInt2 = P_INVALID ; double dU1, dU2 ; Vector3d vtN1, vtN2 ; if ( IntersLineCyl( ptStart, vtStart * dLen, vCyl[k].frCyl, vCyl[k].dH, vCyl[k].dRad, false, false, dU1, ptInt1, vtN1, dU2, ptInt2, vtN2, true)) { bool bUpdate = ( j == 0 ? dU1 < dUTrim : dU1 > dUTrim) ; bUpdate = bUpdate && ptInt1.IsValid() && dU1 > 0 && dU1 < 1 ; bUpdate = bUpdate && vtN1 * vtStart < 0 ; if ( bUpdate) { dUTrim = dU1 ; ptTrim = ptInt1 ; } bUpdate = ( j == 0 ? dU2 < dUTrim : dU2 > dUTrim) ; bUpdate = bUpdate && ptInt2.IsValid() && dU2 > 0 && dU2 < 1 ; bUpdate = bUpdate && vtN2 * vtStart > 0 ; if ( bUpdate) { dUTrim = dU2 ; ptTrim = ptInt2 ; } } } if ( ptTrim.IsValid()) { if ( j == 0) { pCL->ModifyEnd( ptTrim) ; double dNewLen ; pCL->GetLength( dNewLen) ; if ( dNewLen < 0.1 && vInters[0] != 0) { // se fosse il primo allora potrei modificare il successivo int nPrev = vInters[0] - 1 ; vErase.push_back( vInters[0]) ; vInters[0] = nPrev ; ICurve* pCLPrev = vOffsetCrvs[nPrev] ; pCLPrev->ModifyEnd( ptTrim) ; } } else { pCL->ModifyStart( ptTrim) ; double dNewLen ; pCL->GetLength( dNewLen) ; if ( dNewLen < 0.1 && vInters[j] != ssize( vOffsetCrvs) - 1) { // se fosse l'ultima curva allora potrei modificare la precedente int nNext = vInters[j] + 1 ; vErase.push_back( vInters[j]) ; vInters[j] = nNext ; ICurve* pCLNext = vOffsetCrvs[nNext] ; pCLNext->ModifyStart( ptTrim) ; } } } } } i = vLines.back() ; } } // scorro tutto il vettore delle linee di offset e unisco aggiungendo una linea dove ne ho cancellate for ( int i = 0 ; i < ssize( vOffsetCrvs) - 1 ; ++i) { Point3d ptEndCurr, ptStartNext ; vOffsetCrvs[i]->GetEndPoint( ptEndCurr) ; vOffsetCrvs[i+1]->GetStartPoint( ptStartNext) ; if ( ! AreSamePointApprox( ptEndCurr, ptStartNext)) { ICurveLine* pCL = CreateBasicCurveLine() ; pCL->Set( ptEndCurr, ptStartNext) ; PtrOwner pCrvL( pCL) ; vOffsetCrvs.insert( vOffsetCrvs.begin() + i + 1, std::move(pCrvL)) ; ++i ; } } return true ; }