Files
EgtGeomKernel/OffsetCurveOnX.cpp
Dario Sassi 49f957ced5 EgtGeomKernel 2.2b2 :
- corretta OffsetCurveOnX per tratti orizzontali più corti dell'offset.
2020-02-13 11:32:00 +00:00

465 lines
16 KiB
C++

//----------------------------------------------------------------------------
// 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 <algorithm>
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<CurveComposite> 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<ICurve> 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<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() ;
// 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<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
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<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) ;
// 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) ;
}