Files
Riccardo Elitropi ce05ce577c EgtGeomKernel 3.1d4 :
- In Voronoi migliorati i controlli sulla chiusura delle curve (copyright Sara)
- in CalcPocketing corrette le funzioni del calcolo delle Feed e migliorati i controlli sugli ingressi.
2026-04-20 15:05:03 +02:00

1255 lines
40 KiB
C++

//----------------------------------------------------------------------------
// EgalTech 2015-2023
//----------------------------------------------------------------------------
// File : Voronoi.cpp Data : 23.11.23 Versione : 2.5k5
// Contenuto : Classe per Voronoi con libreria VRONI.
//
//
//
// Modifiche : 23.11.23 SP Creazione modulo.
//--------------------------- Include ----------------------------------------
#include "stdafx.h"
#include "GeoConst.h"
#include "CurveComposite.h"
#include "CurveArc.h"
#include "CurveLine.h"
#include "CurveBezier.h"
#include "SurfFlatRegion.h"
#include "OffsetAux.h"
#include "RemoveCurveDefects.h"
#include "Voronoi.h"
#include "/EgtDev/Include/EGkDistPointCurve.h"
#include "/EgtDev/Include/EGkChainCurves.h"
#include "/EgtDev/Extern/vroni/Include/vroni_object.h"
using namespace std ;
//----------------------------------------------------------------------------
Voronoi::Voronoi( const ICurve* pCrv, bool bAllowAdd)
: m_vroni( nullptr), m_nBound( VORONOI_STD_BOUND), m_bVDComputed( false), m_bAllowAdd( true)
{
// tento di aggiungere la curva
if ( ! AddCurve( pCrv))
Clear() ;
m_bAllowAdd = bAllowAdd ;
}
//----------------------------------------------------------------------------
Voronoi::Voronoi( const ISurfFlatRegion* pSfr, bool bAllowAdd)
: m_vroni( nullptr), m_nBound( VORONOI_STD_BOUND), m_bVDComputed( false), m_bAllowAdd( true)
{
// tento di aggiungere la superficie
if ( ! AddSurfFlatRegion( pSfr))
Clear() ;
m_bAllowAdd = bAllowAdd ;
}
//----------------------------------------------------------------------------
Voronoi::~Voronoi( void)
{
Clear() ;
}
//----------------------------------------------------------------------------
bool
Voronoi::Clear( void)
{
// pulizia oggetto vroni
if ( m_vroni != nullptr)
delete m_vroni ;
m_vroni = nullptr ;
// ciclo di pulizia delle curve associate
for ( auto pCrv : m_vpCrvs)
delete pCrv ;
m_vpCrvs.clear() ;
m_nBound = VORONOI_STD_BOUND ;
m_bVDComputed = false ;
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::AddCurve( const ICurve* pCrv)
{
if ( ! m_bAllowAdd)
return false ;
if ( pCrv == nullptr)
return false ;
// verifico se è una linea
int nType = pCrv->GetType() ;
bool bIsLine = ( nType == CRV_LINE) ;
if ( nType == CRV_COMPO) {
const CurveComposite* pCompo = GetBasicCurveComposite( pCrv) ;
Point3d ptStart, ptEnd ;
if ( pCompo != nullptr && pCompo->IsALine( 10 * EPS_SMALL, ptStart, ptEnd))
bIsLine = true ;
}
// verifico sia piana e trovo piano su cui giace
Plane3d plPlane ;
if ( bIsLine) {
// linea è sicuramente piana. Scelgo piano definito dall'estrusione ( se definita)
Point3d ptS ; pCrv->GetStartPoint( ptS) ;
Vector3d vtExtr ; pCrv->GetExtrusion( vtExtr) ;
if ( ! vtExtr.IsSmall())
plPlane.Set( ptS, vtExtr) ;
else
plPlane.Set( ptS, Z_AX) ;
}
else {
if ( ! pCrv->IsFlat( plPlane, false, 10 * EPS_SMALL))
return false ;
}
if ( m_vpCrvs.empty()) {
// se prima curva considerata assegno il frame al Voronoi
m_Frame.Set( plPlane.GetPoint(), plPlane.GetVersN()) ;
}
else {
// altrimenti verifico sia complanare ad eventuali curve già presenti
if ( ! AreSameOrOppositeVectorApprox( m_Frame.VersZ(), plPlane.GetVersN()) || ! PointInPlaneApprox( m_Frame.Orig(), plPlane))
return false ;
}
// creo una copia della curva e la porto in locale
PtrOwner<ICurve> pCrvLoc( pCrv->Clone()) ;
if ( IsNull( pCrvLoc))
return false ;
pCrvLoc->ToLoc( m_Frame) ;
try {
// verifico se oggetto vroni è stato inizializzato
if ( m_vroni == nullptr) {
m_vroni = new( nothrow) vroniObject() ;
if ( m_vroni == nullptr)
return false ;
m_vroni->apiInitializeProgram() ;
}
// aggiungo la curva in locale all'oggetto vroni
if ( ! AddCurveToVroni( pCrvLoc))
return false ;
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage()) ;
return false ;
}
// aggiorno il box complessivo
BBox3d bBox ;
pCrvLoc->GetLocalBBox( bBox) ;
m_bBox.Add( bBox) ;
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::AddSurfFlatRegion( const ISurfFlatRegion* pSfr)
{
if ( ! m_bAllowAdd)
return false ;
if ( pSfr == nullptr)
return false ;
// recupero il piano
Point3d ptCen ; pSfr->GetCentroid( ptCen) ;
Vector3d vtN = pSfr->GetNormVersor() ;
if ( m_vpCrvs.empty()) {
// assegno il frame al Voronoi
m_Frame.Set( ptCen, vtN) ;
}
else {
// verifico sia complanare ad eventuali curve già presenti
Plane3d plPlane ;
plPlane.Set( ptCen, pSfr->GetNormVersor()) ;
if ( ! AreSameOrOppositeVectorApprox( m_Frame.VersZ(), pSfr->GetNormVersor()) || ! PointInPlaneApprox( m_Frame.Orig(), plPlane))
return false ;
}
try {
// verifico se oggetto vroni è stato inizializzato
if ( m_vroni == nullptr) {
m_vroni = new( nothrow) vroniObject() ;
if ( m_vroni == nullptr)
return false ;
m_vroni->apiInitializeProgram() ;
}
// aggiungo le curve di loop
for ( int i = 0 ; i < pSfr->GetChunkCount() ; i ++) {
for ( int j = 0 ; j < pSfr->GetLoopCount( i) ; j ++) {
PtrOwner<ICurve> pCrvLoc( pSfr->GetLoop( i, j)) ;
if ( IsNull( pCrvLoc))
return false ;
pCrvLoc->ToLoc( m_Frame) ;
if ( ! AddCurveToVroni( pCrvLoc))
return false ;
}
}
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage())
return false ;
}
// aggiorno il box complessivo
BBox3d bBox ;
Frame3d frSrf = m_Frame ;
frSrf.Invert() ;
pSfr->GetBBox( frSrf, bBox) ;
m_bBox.Add( bBox) ;
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::AddCurveToVroni( const ICurve* pCrv)
{
int nLoopId = m_vpCrvs.size() ;
// aggiungo il punto iniziale
Point3d ptStart ;
if ( ! pCrv->GetStartPoint( ptStart))
return false ;
int nCrv = m_vroni->HandlePnt( ptStart.x, ptStart.y, {nLoopId, 0}) ;
// aggiungo la parte rimanente della curva
switch ( pCrv->GetType()) {
case CRV_LINE :
m_vpCrvs.emplace_back( pCrv->Clone()) ;
return AddLineToVroni( GetCurveLine( pCrv), nCrv, nLoopId) ;
case CRV_ARC :
m_vpCrvs.emplace_back( pCrv->Clone()) ;
return AddArcToVroni( GetCurveArc( pCrv), nCrv, nLoopId) ;
case CRV_COMPO :
return AddCompoToVroni( GetCurveComposite( pCrv), nCrv, nLoopId) ;
case CRV_BEZIER :
return AddBezierToVroni( GetCurveBezier( pCrv), nCrv, nLoopId) ;
default :
return false ;
}
return false ;
}
//----------------------------------------------------------------------------
bool
Voronoi::AddLineToVroni( const ICurveLine* pLine, int& nVroniCrv, int nLoopId, int nCrvId, Point3d ptEnd)
{
if ( pLine == nullptr)
return false ;
// verifico se il punto finale viene forzato oppure deve essere ricavato dalla pLine
if ( ! ptEnd.IsValid()) {
if ( ! pLine->GetEndPoint( ptEnd))
return false ;
}
m_vroni->AddSeg( &nVroniCrv, ptEnd.x, ptEnd.y, {nLoopId, nCrvId}) ;
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::AddArcToVroni( const ICurveArc* pArc, int& nVroniCrv, int nLoopId, int nCrvId, Point3d ptEnd)
{
if ( pArc == nullptr)
return false ;
Point3d ptCen = pArc->GetCenter() ;
double dAngCen = pArc->GetAngCenter() ;
int nArcSiteType = ( dAngCen > 0 ? ARC : -ARC ) ;
Vector3d vtN = pArc->GetNormVersor() ;
if ( AreOppositeVectorApprox( vtN, Z_AX))
nArcSiteType *= -1 ;
if ( pArc->IsACircle()) {
// spezzo arco in due parti
Point3d ptM ;
if ( ! pArc->GetMidPoint( ptM))
return false ;
m_vroni->AddArc( &nVroniCrv, ptM.x, ptM.y, ptCen.x, ptCen.y, nArcSiteType, {nLoopId, nCrvId}) ;
// impogno che punto finale coincida con lo start ( per tolleranze vroni)
Point3d ptStart ; pArc->GetStartPoint( ptStart) ;
m_vroni->AddArc( &nVroniCrv, ptStart.x, ptStart.y, ptCen.x, ptCen.y, nArcSiteType, {nLoopId, nCrvId}) ;
}
else {
// verifico se il punto finale viene forzato oppure deve essere ricavato dal pArc
if ( ! ptEnd.IsValid()) {
if ( ! pArc->GetEndPoint( ptEnd))
return false ;
}
m_vroni->AddArc( &nVroniCrv, ptEnd.x, ptEnd.y, ptCen.x, ptCen.y, nArcSiteType, {nLoopId, nCrvId}) ;
}
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::AddCompoToVroni( const ICurveComposite* pCompo, int& nVroniCrv, int nLoopId)
{
if ( pCompo == nullptr)
return false ;
PtrOwner<CurveComposite> pCopy( CloneBasicCurveComposite( pCompo)) ;
if ( IsNull( pCopy))
return false ;
// verifico che la curva sia fatta solo da rette e archi che giacciono nel piano XY ( estrusione deve essere Z+)
Vector3d vtExtr ; pCopy->GetExtrusion( vtExtr) ;
pCopy->SetExtrusion( Z_AX) ;
if ( ! pCopy->ArcsBezierCurvesToArcsPerpExtr( 10 * EPS_SMALL, ANG_TOL_STD_DEG))
return false ;
pCopy->SetExtrusion( vtExtr) ;
// aggiungo tutte le sottocurve
bool bClosed = pCopy->IsClosed() ;
for ( int i = 0 ; i < pCopy->GetCurveCount() ; i++) {
Point3d ptForcedEnd = P_INVALID ;
// se curva è chiusa, forzo l'end point a coincidere con lo start ( per le tolleranze di vroni)
if ( i == pCopy->GetCurveCount() - 1 && bClosed)
pCompo->GetStartPoint( ptForcedEnd) ;
// aggiungo
const ICurve* pCrv = pCopy->GetCurve( i) ;
int nType = pCrv->GetType() ;
if ( nType == CRV_LINE)
AddLineToVroni( GetCurveLine( pCrv), nVroniCrv, nLoopId, i, ptForcedEnd) ;
else if ( nType == CRV_ARC)
AddArcToVroni( GetCurveArc( pCrv), nVroniCrv, nLoopId, i, ptForcedEnd) ;
}
// aggiungo al vettore di curve
m_vpCrvs.emplace_back( Release( pCopy)) ;
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::AddBezierToVroni( const ICurveBezier* pBezier, int& nVroniCrv, int nLoopId)
{
if ( pBezier == nullptr)
return false ;
// riconduco al caso di curva composita
PtrOwner<CurveComposite> pCompo( CreateBasicCurveComposite()) ;
if ( IsNull( pCompo))
return false ;
PolyArc PA ;
if ( ! pBezier->ApproxWithArcsEx( LIN_TOL_STD, ANG_TOL_STD_DEG, LIN_FEA_LEN_STD, PA) || ! pCompo->FromPolyArc( PA))
return false ;
Vector3d vtExtr ; pBezier->GetExtrusion( vtExtr) ;
pCompo->SetExtrusion( vtExtr) ;
return AddCompoToVroni( pCompo, nVroniCrv, nLoopId) ;
}
//----------------------------------------------------------------------------
ICurve*
Voronoi::GetCurve( int nId) const
{
// verifico validità indice
if ( nId < 0 || nId > ( int)m_vpCrvs.size() - 1)
return nullptr ;
// ne faccio una copia
ICurve* pCrv = m_vpCrvs[nId]->Clone() ;
if ( pCrv == nullptr)
return nullptr ;
// la porto nel riferimento globale
pCrv->ToGlob( m_Frame) ;
return pCrv ;
}
//----------------------------------------------------------------------------
bool
Voronoi::GetVroniPlane( Plane3d& plPlane) const
{
if ( ! IsValid())
return false ;
plPlane.Set( m_Frame.Orig(), m_Frame.VersZ()) ;
return plPlane.IsValid() ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcVoronoi( int nBound)
{
// se già stato calcolato con lo stesso bound non devo fare nulla
if ( m_bVDComputed && nBound == m_nBound)
return true ;
// se già stato calcolato reset dei dati
if ( m_bVDComputed)
m_vroni->ResetVoronoiDiagram() ;
// come valore minimo per il bound considero quello standard di vroni
m_nBound = max( nBound, VORONOI_STD_BOUND) ;
// calcolo
m_bVDComputed = true ;
string sTmp = "" ;
m_vroni->apiComputeVD( false, true, false, m_nBound, 0, 0, &sTmp[0], false, false, false, &sTmp[0], true) ;
return true ;
}
//----------------------------------------------------------------------------
ICurve*
Voronoi::GetBisectorCurve( int i)
{
if ( i >= m_vroni->GetNumberOfEdges())
return nullptr ;
// identifico il tipo di bisettore
int nType = m_vroni->GetBisectorType( i) ;
// linea
if ( nType == BisectorType::LINE) {
// recupero i dati del bisettore da vroni
Point3d ptS, ptE ;
double dParS, dParE ;
m_vroni->GetLinearBisectorData( i, ptS.v, ptE.v, dParS, dParE) ;
// creo la linea
CurveLine* pLine = CreateBasicCurveLine() ;
if ( pLine == nullptr)
return nullptr ;
// costruisco il bisettore orientato dal parametro minore al maggiore
if ( dParS > dParE) {
swap( ptS, ptE) ;
swap( dParS, dParE) ;
}
pLine->Set( ptS, ptE) ;
pLine->SetTempParam( dParS, 0) ;
pLine->SetTempParam( dParE, 1) ;
pLine->ToGlob( m_Frame) ;
return pLine ;
}
// degenerate hyperellipse ( arco)
else if ( nType == BisectorType::DEGENERATE_HYPERELL) {
// recupero i dati del bisettore da vroni
Point3d ptS, ptE, ptC ;
double dParS, dParE ;
m_vroni->GetDegenerateHyperEllipticBisectorData( i, ptS.v, ptE.v, ptC.v, dParS, dParE) ;
// se estremi coincidenti ignoro ( da vroni non possono arrivare circonferenze)
if ( AreSamePointApprox( ptS, ptE))
return nullptr ;
// creo arco
CurveArc* pArc = CreateBasicCurveArc() ;
if ( pArc == nullptr)
return nullptr ;
pArc->SetC2P( ptC, ptS, ptE) ;
pArc->SetTempParam( dParS, 0) ;
pArc->SetTempParam( dParS, 1) ; // dParE = dParS
pArc->ToGlob( m_Frame) ;
return pArc ;
}
// bisettore generico
else if ( nType != BisectorType::NONE) {
// approssimo linearmente il bisettore
int nPoints = m_vroni->GetApproxedBisectorPointsNbr( i) ;
if ( nPoints < 2)
return nullptr ;
CurveComposite* pCompo = CreateBasicCurveComposite() ;
if ( pCompo == nullptr)
return nullptr ;
// verifico se devo leggere i punti del bisettore al contrario per averlo orientato dal parametro minore al maggiore
bool bInvert = false ;
double dPar1, dPar2 ;
m_vroni->GetApproxedBisectorParams( i, dPar1, dPar2) ;
if ( dPar1 > dPar2 + EPS_SMALL)
bInvert = true ;
// punto iniziale
Point3d pt ;
double dParS ;
m_vroni->GetApproxedBisectorPoint( i, bInvert ? nPoints - 1 : 0, pt.v, dParS) ;
pCompo->AddPoint( pt) ;
int nCrvCount = 0 ;
double dParPrev = dParS ;
int j = bInvert ? nPoints - 2 : 1 ;
while ( ( bInvert && j >= 0) || ( ! bInvert && j < nPoints)) {
double dPar ;
m_vroni->GetApproxedBisectorPoint( i, j, pt.v, dPar) ;
if ( pCompo->AddLine( pt)) {
// setto i parametri sulla sottocurva
pCompo->SetCurveTempParam( nCrvCount, dParPrev, 0) ;
pCompo->SetCurveTempParam( nCrvCount, dPar, 1) ;
// aggiorno parametro precedente
dParPrev = dPar ;
nCrvCount ++ ;
}
// aggiorno per punto successivo
if ( bInvert)
j -- ;
else
j ++ ;
}
// setto parametri sulla curva
pCompo->SetTempParam( dParS, 0) ;
pCompo->SetTempParam( dParPrev, 1) ;
pCompo->ToGlob( m_Frame) ;
return pCompo ;
}
return nullptr ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcVoronoiDiagram( ICURVEPOVECTOR& vCrvs, int nBound)
{
vCrvs.clear() ;
if ( ! IsValid())
return false ;
try {
// verifico se necessario calcolo Voronoi
if ( ! m_bVDComputed || nBound != m_nBound)
CalcVoronoi( nBound) ;
for ( int i = 4 ; i < m_vroni->GetNumberOfEdges() ; i ++) {
// recupero la curva del bisettore
PtrOwner<ICurve> pCrv( GetBisectorCurve( i)) ;
if ( ! IsNull( pCrv) && pCrv->IsValid())
vCrvs.emplace_back( Release( pCrv)) ;
}
// libero la memoria di vroni utilizzata per calcolare bisettore
m_vroni->apiFreeBisectorBuffer() ;
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage()) ;
return false ;
}
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcMedialAxis( ICURVEPOVECTOR& vCrvs, int nSide)
{
vCrvs.clear() ;
if ( ! IsValid())
return false ;
// lato per il medial axis
bool bLeft = true ;
bool bRight = true ;
if ( nSide == WMAT_LEFT)
bRight = false ;
else if ( nSide == WMAT_RIGHT)
bLeft = false ;
try {
if ( ! m_bVDComputed)
CalcVoronoi() ;
// calcolo medial axis
m_vroni->apiComputeWMAT( false, 0.0, 0.0, false, bLeft, bRight) ;
for ( int i = 4 ; i < m_vroni->GetNumberOfEdges() ; i ++) {
// verifico se il lato appartiene al medial axis
if ( m_vroni->IsWMATEdge( i)) {
PtrOwner<ICurve> pCrv( GetBisectorCurve( i)) ;
if ( ! IsNull( pCrv) && pCrv->IsValid())
vCrvs.emplace_back( Release( pCrv)) ;
}
}
// libero la memoria di vroni utilizzata per calcolare bisettore
m_vroni->apiFreeBisectorBuffer() ;
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage()) ;
return false ;
}
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcOffset( ICURVEPOVECTOR& vOffs, double dOffs, int nType)
{
vOffs.clear() ;
if ( ! IsValid())
return false ;
// se offset nullo restituisco direttamente le curve
if ( abs( dOffs) < EPS_SMALL) {
for ( auto pCrv : m_vpCrvs)
vOffs.emplace_back( pCrv->Clone()) ;
}
// calcolo offset
ICRVCOMPOPLIST OffsList ;
if ( ! CalcVroniOffset( OffsList, abs( dOffs)))
return false ;
// sistemo le curve di offset calcolate con vroni
for ( auto pCrv : OffsList) {
// seleziono le porzioni dell'offset che si trovano dal lato richiesto
ICRVCOMPOPOVECTOR vResult = AdjustOffsetCurves( pCrv, dOffs) ;
for ( int i = 0 ; i < int( vResult.size()) ; ++ i) {
// eventuale inversione
if ( dOffs > EPS_SMALL)
vResult[i]->Invert() ;
// sistemo il punto di inizio
if ( vResult[i]->IsClosed())
AdjustOffsetStart( vResult[i]) ;
// identifico i raccordi
if ( ( nType & ICurve::OFF_CHAMFER) != 0 || ( nType & ICurve::OFF_EXTEND) != 0)
IdentifyFillets( vResult[i], dOffs) ;
// unisco le parti allineate
vResult[i]->MergeCurves( LIN_TOL_MIN, ANG_TOL_STD_DEG, true, true) ;
// aggiungo al vettore finale
vOffs.emplace_back( Release( vResult[i])) ;
}
// dealloco curva
delete( pCrv) ;
}
// aggiusto i raccordi
if ( ( nType & ICurve::OFF_CHAMFER) != 0 || ( nType & ICurve::OFF_EXTEND) != 0)
if ( ! AdjustCurveFillets( vOffs, dOffs, nType)) {
vOffs.clear() ;
return false ;
}
// porto nel frame globale
for ( int i = 0 ; i < int( vOffs.size()) ; i++)
vOffs[i]->ToGlob( m_Frame) ;
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcSingleCurvesOffset( ICURVEPOVECTOR& vOffs, double dOffs)
{
// calcola, se possibile, le curve di offset del valore richiesto come curve singole, andando a recuperare i tratti del
// medial axis corrispondenti. L'offset invece restituirebbe curve chiuse formate da tratti praticamente sovrapposti.
vOffs.clear() ;
if ( abs( dOffs) < EPS_SMALL)
return true ;
if ( ! IsValid())
return false ;
try {
if ( ! m_bVDComputed)
CalcVoronoi() ;
// individuo il lato richiesto
bool bLeft = ( dOffs < 0) ;
bool bRight = ! bLeft ;
for ( int i = 0 ; i < int( m_vpCrvs.size()) ; i++) {
if ( ! m_vpCrvs[i]->IsClosed()) {
// se è presente una curva aperta il medial axis deve essere fatto sia a destra sia a sinistra
bLeft = true ;
bRight = true ;
break ;
}
}
int nSideRef = ( dOffs < 0 ? MDS_LEFT : MDS_RIGHT) ;
// seleziono le curve del medial axis aventi parametro costante pari all'offset richiesto
m_vroni->apiComputeWMAT( false, 0.0, 0.0, false, bLeft, bRight) ;
ICURVEPOVECTOR vCrvs ;
for ( int i = 4 ; i < m_vroni->GetNumberOfEdges() ; i ++) {
// verifico se bisettore di medial axis
if ( m_vroni->IsWMATEdge( i)) {
// verifico i parametri
double dParS, dParE ;
m_vroni->GetBisectorParams( i, dParS, dParE) ;
if ( abs( dParS - abs( dOffs)) < EPS_SMALL && abs( dParE - abs( dOffs)) < EPS_SMALL) {
PtrOwner<ICurve> pCrv( GetBisectorCurve( i)) ;
if ( ! IsNull( pCrv) && pCrv->IsValid()) {
// se necessario verifico se dal lato corretto rispetto ai siti di riferimento
if ( bLeft && bRight) {
// recupero i siti di riferimento
int nOrigCrv1, nOrigSubCrv1, nOrigCrv2, nOrigSubCrv2 ;
m_vroni->GetBisectorSites( i, nOrigCrv1, nOrigSubCrv1, nOrigCrv2, nOrigSubCrv2) ;
if ( nOrigCrv1 != -1) {
// verifico il lato rispetto al primo sito
pCrv->SetTempProp( nOrigSubCrv1 + 1, 0) ;
pCrv->SetTempProp( nOrigCrv1, 1) ;
int nSide = GetOffsetCurveSide( pCrv) ;
if ( nSide != nSideRef)
continue ;
}
if ( nOrigCrv2 != -1) {
// verifico il lato rispetto al secondo sito
pCrv->SetTempProp( nOrigSubCrv2 + 1, 0) ;
pCrv->SetTempProp( nOrigCrv2, 1) ;
int nSide = GetOffsetCurveSide( pCrv) ;
if ( nSide != nSideRef)
continue ;
}
}
vCrvs.emplace_back( Release( pCrv)) ;
}
}
}
}
// concateno le curve ottenute
if ( vCrvs.size() == 1)
vOffs.emplace_back( Release( vCrvs[0])) ;
else if ( ! vCrvs.empty()) {
ChainCurves chainC ;
chainC.Init( true, 10 * EPS_SMALL, int( vCrvs.size())) ;
for ( int i = 0 ; i < int( vCrvs.size()) ; ++ i) {
Point3d ptS, ptE ;
Vector3d vtS, vtE ;
if ( ! vCrvs[i]->GetStartPoint( ptS) || ! vCrvs[i]->GetStartDir( vtS) ||
! vCrvs[i]->GetEndPoint( ptE) || ! vCrvs[i]->GetEndDir( vtE))
return false ;
if ( ! chainC.AddCurve( i + 1, ptS, vtS, ptE, vtE))
return false ;
}
INTVECTOR vIds ;
while ( chainC.GetChainFromNear( ORIG, false, vIds)) {
// creo una curva composita
PtrOwner<ICurveComposite> pCrvCompo( CreateCurveComposite()) ;
if ( IsNull( pCrvCompo))
return false ;
for ( int i = 0 ; i < int( vIds.size()) ; ++ i) {
// recupero l'indice della curva
int nInd = abs( vIds[i]) - 1 ;
// verifico se necessaria inversione
if ( vIds[i] < 0)
vCrvs[nInd]->Invert() ;
// aggiungo alla composita
if ( ! pCrvCompo->AddCurve( Release( vCrvs[nInd])))
return false ;
}
if ( pCrvCompo->IsValid()) {
pCrvCompo->MergeCurves( LIN_TOL_MIN, ANG_TOL_STD_DEG) ;
vOffs.emplace_back( Release( pCrvCompo)) ;
}
}
}
// libero la memoria di vroni utilizzata per calcolare bisettore
m_vroni->apiFreeBisectorBuffer() ;
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage()) ;
return false ;
}
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcSpecialPointOffset( PNTVECTVECTOR& vResult, double dOffs)
{
// calcola i punti e le tangenti sui bisettori del medial axis in corrispondenza del valore di offset richiesto
vResult.clear() ;
if ( abs( dOffs) < EPS_SMALL)
return true ;
if ( ! IsValid())
return false ;
try {
// verifico se necessario ricalcolo Voronoi
UpdateVoronoi( dOffs) ;
// indivudio lato medial axis per curve chiuse ( suppongo di chiamare la funzione dalla singola curva, quindi vale controllare
// la chiusura solo sulla prima curva. Eventualmente da estendere)
bool bLeft = true, bRight = true ;
if ( m_vpCrvs[0]->IsClosed()) {
bLeft = dOffs < 0 ;
bRight = ! bLeft ;
}
// calcolo medial axis
m_vroni->apiComputeWMAT( false, 0.0, 0.0, false, bLeft, bRight) ;
for ( int i = 4 ; i < m_vroni->GetNumberOfEdges() ; i ++) {
// verifico se il lato appartiene al medial axis
if ( m_vroni->IsWMATEdge( i)) {
// verifico se coinvolto dall'offset
double dParS, dParE ;
m_vroni->GetBisectorParams( i, dParS, dParE) ;
if ( dParS > dParE)
swap( dParS, dParE) ;
if ( abs( dOffs) < dParS || abs( dOffs) > dParE)
continue ;
// calcolo il punto sul bisettore in corrispondenza dell'offset
Point3d pt ;
m_vroni->GetBisectorPointAtParam( i, abs( dOffs), pt.v) ;
pt.ToGlob( m_Frame) ;
// calcolo il vettore tangente
PtrOwner<ICurve> pCrv( GetBisectorCurve( i)) ;
if ( IsNull( pCrv))
return false ;
double dPar ;
Point3d ptTemp ;
Vector3d vtDir ;
if ( ! pCrv->GetParamAtPoint( pt, dPar, 100 * EPS_SMALL) || ! pCrv->GetPointD1D2( dPar, ICurve::FROM_MINUS, ptTemp, &vtDir))
return false ;
vtDir.Normalize() ;
vResult.emplace_back( pt, vtDir) ;
}
}
// libero la memoria di vroni utilizzata per calcolare bisettori
m_vroni->apiFreeBisectorBuffer() ;
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage()) ;
return false ;
}
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcFatCurve( ICURVEPOVECTOR& vCrvs, double dOffs, bool bSquareEnds, bool bSquareMids,
bool bMergeOnlySameProps)
{
vCrvs.clear() ;
if ( ! IsValid())
return false ;
// se offset nullo errore
if ( abs( dOffs) < EPS_SMALL)
return false ;
ICRVCOMPOPLIST OffsList ;
if ( ! CalcVroniOffset( OffsList, abs( dOffs)))
return false ;
// sistemo le curve di offset calcolate con vroni
bool bClosed = m_vpCrvs[0]->IsClosed() ;
for ( auto pCrvOffs : OffsList) {
// eventuale inversione
if ( dOffs > EPS_SMALL)
pCrvOffs->Invert() ;
// identifico i raccordi da modificare
if ( bClosed && bSquareMids) {
// se curva è chiusa tutti i raccordi rispondono a bSquareMids
IdentifyFillets( pCrvOffs, dOffs) ;
}
else if ( ! bClosed && ( bSquareMids || bSquareEnds)) {
// se curva è aperta devo distinguere i raccordi interni da quelli relativi agli estremi e
// modificare solo quelli richiesti
for ( int j = 0 ; j < pCrvOffs->GetCurveCount() ; j ++) {
int nOrigCrv ; pCrvOffs->GetCurveTempProp( j, nOrigCrv) ;
double dTmpParam ; pCrvOffs->GetCurveTempParam( j, dTmpParam) ;
if ( nOrigCrv == 0 && ( ( bSquareMids && dTmpParam < EPS_SMALL) || ( bSquareEnds && dTmpParam > EPS_SMALL)))
pCrvOffs->SetCurveTempParam( j, 1) ;
else
pCrvOffs->SetCurveTempParam( j, 0) ;
}
}
// unisco le parti allineate
pCrvOffs->MergeCurves( LIN_TOL_MIN, ANG_TOL_STD_DEG, true, bMergeOnlySameProps) ;
// aggiungo al vettore finale
vCrvs.emplace_back( pCrvOffs) ;
}
// sistemo i raccordi
if ( bSquareMids || bSquareEnds)
if ( ! AdjustCurveFillets( vCrvs, dOffs, ICurve::OFF_EXTEND)) {
vCrvs.clear() ;
return false ;
}
// porto nel frame globale
for ( int i = 0 ; i < int( vCrvs.size()) ; i++)
vCrvs[i]->ToGlob( m_Frame) ;
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::CalcVroniOffset( ICRVCOMPOPLIST& OffsList, double dOffs)
{
OffsList.clear() ;
if ( ! IsValid())
return false ;
try {
// reset di eventuali offset precedenti
m_vroni->apiResetOffsetData() ;
// verifico necessario calcolo o ricalcolo di Voronoi
UpdateVoronoi( dOffs) ;
string sTmp = "" ;
// viene sempre calcolato sia right_offset sia left_offset anche nel caso di curve chiuse per evitare problemi di identificazione
// di vroni nel caso di piccole autointersezioni
m_vroni->apiComputeOff( false, &sTmp[0], false, false, dOffs, 0.0, false, true, true) ;
// recupero le curve di offset da vroni
int nOffsCnt = m_vroni->GetOffsetCount() ;
for ( int i = 0 ; i < nOffsCnt ; i++) {
Point3d ptSChain = P_INVALID ;
PtrOwner<CurveComposite> pCrvOffs ( CreateBasicCurveComposite()) ;
int nCrvCnt = m_vroni->GetOffsetCurveCount( i) ; // numero di sottocurve
for ( int j = 0 ; j < nCrvCnt ; j ++) {
// recupero la sottocurva da vroni
Point3d ptS, ptE, ptC ;
int nType ;
int nOrigCrv, nOrigLoop, nOrigPnt ; // sito
m_vroni->GetOffsetCurve( i, j, nType, ptS.v, ptE.v, ptC.v, nOrigLoop, nOrigCrv, nOrigPnt) ;
if ( j == 0)
pCrvOffs->AddPoint( ptS) ;
// se estremi coincidenti la curva va ignorata ( da vroni non possono arrivare circonferenze)
// ma controllo se appartiene ad una catena di tratti infinitesimi
if ( AreSamePointApprox( ptS, ptE)) {
if ( ptSChain.IsValid()) {
if ( ! AreSamePointApprox( ptSChain, ptE)) {
if ( ! pCrvOffs->AddLine( ptE))
return false ;
ptSChain = P_INVALID ;
}
}
else {
// assegno come inizio di possibile catena
ptSChain = ptS ;
}
continue ;
}
else
ptSChain = P_INVALID ;
if ( nType == t_site::SEG) {
if ( ! pCrvOffs->AddLine( ptE))
return false ;
}
else {
PtrOwner<CurveArc> pArc( CreateBasicCurveArc()) ;
if ( ! pArc->SetC2P( ptC, ptS, ptE)) {
// se raggio minore di EPS_SMALL approssimo con linea
if ( AreSamePointApprox( ptC, ptS)) {
if ( ! pCrvOffs->AddLine( ptE))
return false ;
}
else
return false ;
}
else {
// verifico orientamento
double dAng = pArc->GetAngCenter() ;
if ( ( nType == CCW && dAng < - EPS_ANG_SMALL) || ( nType == CW && dAng > EPS_ANG_SMALL))
pArc->ToExplementary() ;
// aggiungo alla composita
if ( ! pCrvOffs->AddCurve( Release( pArc)))
return false ;
}
}
// setto come info la sottocurva da cui si è generata
int nCurrCrvId = pCrvOffs->GetCurveCount() - 1 ;
pCrvOffs->SetCurveTempProp( nCurrCrvId, nOrigCrv + 1, 0) ;
pCrvOffs->SetCurveTempProp( nCurrCrvId, nOrigLoop, 1) ;
// verifico se è una giunzione, ovvero un raccordo relativo agli estremi di una curva ( con distinzione fra curva
// aperta e chiusa)
if ( nOrigCrv == -1) {
int nOrigCrvCnt = 1 ;
if ( m_vpCrvs[nOrigLoop]->GetType() == CRV_COMPO)
nOrigCrvCnt = GetBasicCurveComposite( m_vpCrvs[nOrigLoop])->GetCurveCount() ;
if ( nOrigPnt == 0 || nOrigPnt == nOrigCrvCnt) {
double dParam = m_vpCrvs[nOrigLoop]->IsClosed() ? VRONI_JUNCTION_CLOSED : VRONI_JUNCTION_OPEN ;
pCrvOffs->SetCurveTempParam( nCurrCrvId, dParam, 0) ;
}
}
}
// rimuovo tratti di lunghezza inferiore a 5 * EPS_SMALL
RemoveCurveSmallParts( pCrvOffs, 5 * EPS_SMALL) ;
// aggiungo la curva alla lista degli offset
if ( ! IsNull( pCrvOffs) && pCrvOffs->IsValid() && pCrvOffs->GetCurveCount() > 0) {
// forzo chiusura
if ( ! pCrvOffs->IsClosed()) {
Point3d ptS ; pCrvOffs->GetStartPoint( ptS) ;
Point3d ptE ; pCrvOffs->GetEndPoint( ptE) ;
if ( SqDist( ptS, ptE) > 100. * SQ_EPS_SMALL)
return false ;
pCrvOffs->Close() ;
}
OffsList.push_back( Release( pCrvOffs)) ;
}
}
// libero la memoria di vroni dedicata agli offset
m_vroni->apiFreeOffsetData() ;
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage()) ;
return false ;
}
return true ;
}
//----------------------------------------------------------------------------
bool
Voronoi::UpdateVoronoi( double dOffs)
{
// calcolo il bound necessario per l'offset desiderato
double dNeededBound = abs( dOffs) / 0.49 / sqrt( m_bBox.GetDimX() * m_bBox.GetDimX() + m_bBox.GetDimY() * m_bBox.GetDimY()) ;
if ( ! m_bVDComputed || dNeededBound > m_nBound) {
// aggiorno il valore del bound
int nBound = ( int)( ceil( dNeededBound) + 0.5) ;
// calcolo il nuovo diagramma
CalcVoronoi( nBound) ;
}
return true ;
}
//----------------------------------------------------------------------------
ICRVCOMPOPOVECTOR
Voronoi::AdjustOffsetCurves( const ICurveComposite* pCompo, double dOffs) const
{
ICRVCOMPOPOVECTOR vResult ;
int nSideRef = dOffs < EPS_SMALL ? MDS_LEFT : MDS_RIGHT ;
// verifico se presenti giunzioni
bool bJunctions = false ;
for ( int i = 0 ; i < pCompo->GetCurveCount() ; i ++) {
double dParam ; pCompo->GetCurveTempParam( i, dParam) ;
if ( abs( dParam - VRONI_JUNCTION_OPEN) < EPS_SMALL) {
bJunctions = true ;
break ;
}
}
if ( ! bJunctions) {
// controllo la curva complessiva
int nSide = GetOffsetCurveSide( pCompo->GetCurve( 0)) ;
if ( nSide == nSideRef)
vResult.emplace_back( pCompo->Clone()) ;
}
else {
// scorro la curva eliminando le giunzioni relative a curve aperte e le sottocurve che si trovano dal lato sbagliato
PtrOwner<CurveComposite> pCompoCurr( CreateBasicCurveComposite()) ;
for ( int i = 0 ; i < pCompo->GetCurveCount() ; i ++) {
bool bKeep = true ;
double dParTmp ; pCompo->GetCurveTempParam( i, dParTmp) ;
if ( abs( dParTmp - VRONI_JUNCTION_OPEN) < EPS_SMALL)
bKeep = false ;
else {
int nSide = GetOffsetCurveSide( pCompo->GetCurve( i)) ;
bKeep = ( nSide == nSideRef) ;
}
if ( bKeep)
pCompoCurr->AddCurve( pCompo->GetCurve( i)->Clone()) ;
else {
// salvo la curva ottenuta fino ad ora e resetto per i prossimi tratti validi
if ( pCompoCurr->IsValid()) {
vResult.emplace_back( Release( pCompoCurr)) ;
pCompoCurr.Set( CreateBasicCurveComposite()) ;
}
}
}
// salvo eventuale ultima curva
if ( pCompoCurr->IsValid())
vResult.emplace_back( Release( pCompoCurr)) ;
// verifico se posso concatenare prima e ultima curva
if ( vResult.size() > 1) {
Point3d ptStart ; vResult.front()->GetStartPoint( ptStart) ;
Point3d ptEnd ; vResult.back()->GetEndPoint( ptEnd) ;
if ( AreSamePointApprox( ptStart, ptEnd) && vResult.front()->AddCurve( vResult.back()->Clone(), false))
vResult.pop_back() ;
}
}
return vResult ;
}
//---------------------------------------------------------------------------
int
Voronoi::GetOffsetCurveSide( const ICurve* pCrv) const
{
if ( pCrv == nullptr)
return -1 ;
Point3d ptM ; pCrv->GetMidPoint( ptM) ;
// recupero curva e sottocurva di riferimento dalle temp prop
int nOrigSubCrv = pCrv->GetTempProp( 0) ;
int nOrigCrv = pCrv->GetTempProp( 1) ;
const ICurve* pCrvRef = m_vpCrvs[nOrigCrv] ;
if ( nOrigSubCrv != 0 && m_vpCrvs[nOrigCrv]->GetType() == CRV_COMPO) {
const CurveComposite* pCompoOrig = GetBasicCurveComposite( m_vpCrvs[nOrigCrv]) ;
if ( pCompoOrig != nullptr)
pCrvRef = pCompoOrig->GetCurve( nOrigSubCrv - 1) ;
}
DistPointCurve distPC( ptM, *pCrvRef) ;
int nSide = MDS_ON ;
distPC.GetSideAtMinDistPoint( 0, Z_AX, nSide) ;
return nSide ;
}
//---------------------------------------------------------------------------
bool
Voronoi::AdjustOffsetStart( ICurveComposite* pCrv) const
{
for ( int i = 0 ; i < pCrv->GetCurveCount() ; i++) {
// cerco il tratto associato alla prima sottocurva originale
int nOrigCrv ; pCrv->GetCurveTempProp( i, nOrigCrv) ;
if ( nOrigCrv == 1) {
pCrv->ChangeStartPoint( i) ;
break ;
}
// oppure associato ad una giunzione
double dParam ; pCrv->GetCurveTempParam( i, dParam) ;
if ( abs( dParam - VRONI_JUNCTION_CLOSED) < EPS_SMALL) {
pCrv->ChangeStartPoint( i + 1) ;
break ;
}
}
return true ;
}
//---------------------------------------------------------------------------
bool
Voronoi::Translate( const Vector3d& vtMove)
{
if ( ! IsValid())
return false ;
return m_Frame.Translate( vtMove) ;
}
//---------------------------------------------------------------------------
bool
Voronoi::Rotate( const Point3d& ptAx, const Vector3d& vtAx, double dAngDeg)
{
if ( ! IsValid())
return false ;
return m_Frame.Rotate( ptAx, vtAx, dAngDeg) ;
}
//---------------------------------------------------------------------------
bool
Voronoi::Rotate( const Point3d& ptAx, const Vector3d& vtAx, double dCosAng, double dSinAng)
{
if ( ! IsValid())
return false ;
return m_Frame.Rotate( ptAx, vtAx, dCosAng, dSinAng) ;
}
//---------------------------------------------------------------------------
bool
Voronoi::ToGlob( const Frame3d& frRef)
{
if ( ! IsValid())
return false ;
return m_Frame.ToGlob( frRef) ;
}
//---------------------------------------------------------------------------
bool
Voronoi::ToLoc( const Frame3d& frRef)
{
if ( ! IsValid())
return false ;
return m_Frame.ToLoc( frRef) ;
}
//---------------------------------------------------------------------------
bool
Voronoi::LocToLoc( const Frame3d& frOri, const Frame3d& frDest)
{
if ( ! IsValid())
return false ;
return m_Frame.LocToLoc( frOri, frDest) ;
}
//---------------------------------------------------------------------------
bool
Voronoi::CalcLimitOffset( int nCrv, bool bLeft, double& dOffs)
{
if ( nCrv < 0 || nCrv > int( m_vpCrvs.size()) - 1)
return false ;
// se curva aperta errore
if ( ! m_vpCrvs[nCrv]->IsClosed())
return false ;
dOffs = - INFINITO ;
try {
// verifico se necessario calcolo Voronoi
if ( ! m_bVDComputed)
CalcVoronoi() ;
for ( int i = 4 ; i < m_vroni->GetNumberOfEdges() ; i ++) {
// verifico se è un bisettore relativo alla curva richiesta e dal lato opportuno
if ( m_vroni->IsRelatedEdge( i, nCrv, bLeft)) {
// calcolo i parametri del bisettore
double dParS, dParE ;
m_vroni->GetBisectorParams( i, dParS, dParE) ;
dOffs = max( { dParS, dParE, dOffs}) ;
}
}
// libero la memoria di vroni dedicata agli offset
m_vroni->apiFreeOffsetData() ;
}
catch (...) {
LOG_ERROR( GetEGkLogger(), m_vroni->GetExceptionMessage()) ;
return false ;
}
return true ;
}