//---------------------------------------------------------------------------- // 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 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 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 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 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 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 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 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 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 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 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 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) 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 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 ; }