Files
EgtGeomKernel/OffsetCurve.cpp
T
DarioS 4b1bf75911 EgtGeomKernel 2.3f3 :
- corretto crash in offset di curva generica con estrusione coincidente con linea.
2021-06-22 20:17:13 +02:00

1091 lines
40 KiB
C++

//----------------------------------------------------------------------------
// EgalTech 2015-2020
//----------------------------------------------------------------------------
// File : OffsetCurve.cpp Data : 10.10.20 Versione : 2.2j2
// 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.
//
//----------------------------------------------------------------------------
//--------------------------- Include ----------------------------------------
#include "stdafx.h"
#include "CurveComposite.h"
#include "CurveLine.h"
#include "CurveArc.h"
#include "GeoConst.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 <algorithm>
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) ;
static bool IsFillet( ICurve* pCrv, double dDist) ;
static bool ModifyFillet( ICurve* pCrv, double dDist, int nType, CurveComposite& ccAux) ;
//----------------------------------------------------------------------------
OffsetCurve::~OffsetCurve( void)
{
Reset() ;
}
//----------------------------------------------------------------------------
bool
OffsetCurve::Reset( void)
{
for ( auto& pCrv : m_CrvLst) {
if ( pCrv != nullptr) {
delete pCrv ;
pCrv = nullptr ;
}
}
m_CrvLst.clear() ;
return true ;
}
//----------------------------------------------------------------------------
bool
OffsetCurve::Make( const ICurve* pCrv, double dDist, int nType)
{
// pulisco tutto
Reset() ;
// 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() ;
// 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)
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 ;
// verifico se curva chiusa e non forzata aperta
bool bClosed = ccCopy.IsClosed() && ( nType & ICurve::OFF_FORCE_OPEN) == 0 ;
// unisco parti allineate (tranne gli estremi)
ccCopy.MergeCurves( 10 * EPS_SMALL, ANG_TOL_STD_DEG, false) ;
// 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<ICurve> pCrv1( ccCopy2.RemoveFirstOrLastCurve( false)) ;
if ( IsNull( pCrv1))
return false ;
pCrv1->SetTempProp( nInd1) ;
if ( ! pCrv1->SimpleOffset( dDist, ICurve::OFF_FILLET)) {
CurveArc* pArc = GetBasicCurveArc( pCrv1) ;
if ( pArc == nullptr)
return false ;
if ( pArc->MyExtendedOffset( dDist, true, ICurve::OFF_FILLET))
pCrv1->SetTempProp( - nInd1) ;
}
// curve successive
PtrOwner<ICurve> pCrv2( ccCopy2.RemoveFirstOrLastCurve( false)) ;
while ( ! IsNull( pCrv2)) {
// eseguo semplice offset
pCrv2->SetTempProp( nInd1 + 1) ;
if ( ! pCrv2->SimpleOffset( dDist, ICurve::OFF_FILLET)) {
CurveArc* pArc = GetBasicCurveArc( pCrv2) ;
if ( pArc == nullptr)
return false ;
if ( pArc->MyExtendedOffset( dDist, true, ICurve::OFF_FILLET))
pCrv2->SetTempProp( - ( nInd1 + 1)) ;
}
// verifico relazione con la curva precedente e aggiungo eventuali curve intermedie
CurveComposite ccTemp ;
bool bOk = 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<ICurve> pCrv3( ccTemp.RemoveFirstOrLastCurve( false)) ;
while ( ! IsNull( pCrv3)) {
m_CrvLst.push_back( Release( pCrv3)) ;
pCrv3.Set( ccTemp.RemoveFirstOrLastCurve( false)) ;
}
}
// aggiorno curva precedente
++ nInd1 ;
pCrv1.Set( Release( 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 ;
bool bOk = VerifyAndAdjustExternalAngle( pCrv1, pCrv2, vAngs[nInd1], dDist, ICurve::OFF_FILLET, ccTemp) ;
// se aggiunto qualcosa, lo metto in lista
if ( ccTemp.GetCurveCount() > 0) {
PtrOwner<ICurve> pCrv3( ccTemp.RemoveFirstOrLastCurve( false)) ;
while ( ! IsNull( pCrv3)) {
m_CrvLst.push_back( Release( pCrv3)) ;
pCrv3.Set( ccTemp.RemoveFirstOrLastCurve( false)) ;
}
}
}
// 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 ;
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 ;
m_CrvLst.erase( iErase) ;
auto iPrev = prev( iIter != m_CrvLst.begin() ? iIter : m_CrvLst.end()) ;
pPrev = *iPrev ;
}
// corrente da cancellare
if ( ( nRes & 2) != 0) {
delete pCrv ;
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<CurveComposite> 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() ;
// 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()) ;
// 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<double>()) ;
// 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
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<CurveComposite> 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) ;
// 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<CurveArc> 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, 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<CurveArc> 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, 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
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) {
delete pCrv ;
iIter = m_CrvLst.erase( iIter) ;
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, 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 ;
}
// nono passo : se con smusso o estensione, sostituisco i fillet con questi
if ( ( nType & ICurve::OFF_CHAMFER) != 0 || ( nType & ICurve::OFF_EXTEND) != 0) {
for ( auto iIter = m_CrvLst.begin() ; iIter != m_CrvLst.end() ; ++ iIter) {
// processo le curve semplici della composita
CurveComposite* pCrvCo = GetBasicCurveComposite( *iIter) ;
ICURVEPLIST CrvLst ;
PtrOwner<ICurve> pCrv( pCrvCo->RemoveFirstOrLastCurve( false)) ;
while ( ! IsNull( pCrv)) {
// se fillet lo trasformo in smusso o estensione
if ( IsFillet( pCrv, dDist)) {
CurveComposite ccTemp ;
bool bOk = ModifyFillet( pCrv, dDist, nType, ccTemp) ;
// metto in lista le curve risultanti
if ( ccTemp.GetCurveCount() > 0) {
PtrOwner<ICurve> pCrv2( ccTemp.RemoveFirstOrLastCurve( false)) ;
while ( ! IsNull( pCrv2)) {
CrvLst.push_back( Release( pCrv2)) ;
pCrv2.Set( ccTemp.RemoveFirstOrLastCurve( false)) ;
}
}
}
// altrimenti salvo in lista
else
CrvLst.push_back( Release( pCrv)) ;
// passo alla curva successiva
pCrv.Set( pCrvCo->RemoveFirstOrLastCurve( false)) ;
}
// rimetto le curve nella composita
for ( auto pCrv : CrvLst) {
pCrvCo->AddCurve( pCrv) ;
}
// unisco tratti allineati
pCrvCo->MergeCurves( LIN_TOL_MIN, ANG_TOL_STD_DEG) ;
}
}
// riporto il risultato nel riferimento originale
if ( bNeedRef) {
for ( auto pCrv : m_CrvLst)
pCrv->ToGlob( frExtr) ;
}
// assegno estrusione e spessore come curva originale
for ( auto pCrv : m_CrvLst) {
pCrv->SetExtrusion( vtExtr) ;
pCrv->SetThickness( dThick) ;
}
// 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()) ; }) ;
}
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
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
if ( abs( dAngDeg) > ( ANG_STRAIGHT - EPS_ANG_SMALL)) {
// eseguo calcolo spostato
double dU1 = 1 ;
double dU2 = 0 ;
Point3d ptDummy ;
Vector3d vtDir1b ;
Vector3d vtDir2b ;
if ( ! MoveParamToAvoidTg( dU1, ICurve::FROM_MINUS, *pCrv1) ||
! pCrv1->GetPointTang( dU1, ICurve::FROM_MINUS, ptDummy, vtDir1b) ||
! MoveParamToAvoidTg( dU2, ICurve::FROM_PLUS, *pCrv2) ||
! 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
nRes = 0 ;
Point3d ptMid = 0.5 * ( ptP1 + ptP2) ;
if ( ! pCrv1->ModifyEnd( ptMid))
nRes += 1 ;
if ( ! 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
nRes = 0 ;
Point3d ptNew = 0.5 * ( ptNew1 + ptNew2) ;
if ( ! pCrv1->ModifyEnd( ptNew))
nRes += 1 ;
if ( ! 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 = 1.0 ;
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<CurveArc> 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<CurveLine> pCrv( CreateBasicCurveLine()) ;
if ( IsNull( pCrv) || ! pCrv->Set( ptP1, ptP1a))
return false ;
if ( ! ccAux.AddCurve( Release( pCrv)))
return false ;
}
// tratto intermedio
{
PtrOwner<CurveLine> 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<CurveLine> 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<CurveLine> 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<CurveLine> 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 ;
}
//----------------------------------------------------------------------------
bool
IsFillet( ICurve* pCrv, double dDist)
{
// deve essere un arco
if ( pCrv->GetType() != CRV_ARC)
return false ;
CurveArc* pArc = GetBasicCurveArc( pCrv) ;
// deve avere raggio uguale alla distanza di offset
if ( abs( pArc->GetRadius() - abs( dDist)) > EPS_SMALL)
return false ;
// deve essere CCW se offset a destra e CW se offset a sinistra
return ( pArc->GetAngCenter() * dDist > 0) ;
}
//----------------------------------------------------------------------------
bool
ModifyFillet( ICurve* pCrv, double dDist, int nType, CurveComposite& ccAux)
{
// la curva deve essere un arco
CurveArc* pArc = GetBasicCurveArc( pCrv) ;
if ( pArc == nullptr)
return false ;
// angolo al centro dell'arco
double dAngDeg = pArc->GetAngCenter() ;
// elimino dal tipo le parti estranee all'angolo esterno
nType &= ( ICurve::OFF_FILLET | ICurve::OFF_CHAMFER | ICurve::OFF_EXTEND) ;
// 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 = 1.0 ;
bool bAngSmall = ( abs( dAngDeg) < SMALL_EXT_ANG) ;
if ( bAngSmall)
nType = ICurve::OFF_EXTEND ;
switch ( nType) {
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 ( ! pArc->GetStartPoint( ptP1) || ! pArc->GetEndPoint( ptP2))
return false ;
Vector3d vtDir1, vtDir2 ;
if ( ! pArc->GetStartDir( vtDir1) || ! pArc->GetEndDir( vtDir2))
return false ;
ptP1a = ptP1 + vtDir1 * dLen ;
ptP2a = ptP2 - vtDir2 * dLen ;
// aggiungo una nuova linea
PtrOwner<CurveLine> pLine1( CreateBasicCurveLine()) ;
if ( IsNull( pLine1) || ! pLine1->Set( ptP1, ptP1a))
return false ;
if ( ! ccAux.AddCurve( Release( pLine1)))
return false ;
// tratto intermedio
PtrOwner<CurveLine> pLine2( CreateBasicCurveLine()) ;
if ( IsNull( pLine2) || ! pLine2->Set( ptP1a, ptP2a))
return false ;
if ( ! ccAux.AddCurve( Release( pLine2)))
return false ;
// aggiungo una nuova linea
PtrOwner<CurveLine> pLine3( CreateBasicCurveLine()) ;
if ( IsNull( pLine3) || ! pLine3->Set( ptP2a, ptP2))
return false ;
if ( ! ccAux.AddCurve( Release( pLine3)))
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 ( ! pArc->GetStartPoint( ptP1) || ! pArc->GetEndPoint( ptP2))
return false ;
Vector3d vtDir1, vtDir2 ;
if ( ! pArc->GetStartDir( vtDir1) || ! pArc->GetEndDir( vtDir2))
return false ;
ptPc = ptP1 + vtDir1 * dLen ;
// aggiungo una nuova linea
PtrOwner<CurveLine> pLine1( CreateBasicCurveLine()) ;
if ( IsNull( pLine1) || ! pLine1->Set( ptP1, ptPc))
return false ;
if ( ! ccAux.AddCurve( Release( pLine1)))
return false ;
// aggiungo una nuova linea
PtrOwner<CurveLine> pLine2( CreateBasicCurveLine()) ;
if ( IsNull( pLine2) || ! pLine2->Set( ptPc, ptP2))
return false ;
if ( ! ccAux.AddCurve( Release( pLine2)))
return false ;
return true ;
}
}
return false ;
}