diff --git a/EgtGeomKernel.vcxproj b/EgtGeomKernel.vcxproj
index d1aee7b..b8aaaef 100644
--- a/EgtGeomKernel.vcxproj
+++ b/EgtGeomKernel.vcxproj
@@ -324,6 +324,7 @@ copy $(TargetPath) \EgtProg\Dll64
+
diff --git a/EgtGeomKernel.vcxproj.filters b/EgtGeomKernel.vcxproj.filters
index 03239a9..420d7be 100644
--- a/EgtGeomKernel.vcxproj.filters
+++ b/EgtGeomKernel.vcxproj.filters
@@ -576,6 +576,9 @@
File di origine\GeoStriping
+
+ File di origine\GeoOffset
+
diff --git a/OffsetCurve3d.cpp b/OffsetCurve3d.cpp
new file mode 100644
index 0000000..eb277c4
--- /dev/null
+++ b/OffsetCurve3d.cpp
@@ -0,0 +1,464 @@
+//----------------------------------------------------------------------------
+// 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 ;
+}
\ No newline at end of file