Files
EgtGeomKernel/OffsetCurve3d.cpp
T
Daniele Bariletti 1cfd283f26 EgtGeomKernel :
- prima versione dell'offset 3d (da correggere).
2026-06-11 18:05:15 +02:00

464 lines
17 KiB
C++

//----------------------------------------------------------------------------
// 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 <algorithm>
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<IGeoObj*> vGeo ;
vector<Color> 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<CurveComposite> 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<CurveComposite> 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<PtrOwner<ICurve>> 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<ICurveComposite> 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<Cyl> 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<ICurve> pCrvL( pCL) ;
vOffsetCrvs.insert( vOffsetCrvs.begin() + i + 1, std::move(pCrvL)) ;
++i ;
}
}
return true ;
}