3245f7fe75
- aggiunte funzioni di striping per trimming dei termoformati.
792 lines
31 KiB
C++
792 lines
31 KiB
C++
//----------------------------------------------------------------------------
|
|
// EgalTech 2025
|
|
//----------------------------------------------------------------------------
|
|
// File : SurfTriMeshOffset.cpp Data : 07.07.25 Versione : 2.7g1
|
|
// Contenuto : Implementazione funzione per Offset di Superfici TriMesh.
|
|
//
|
|
//
|
|
//
|
|
// Modifiche : 10.06.25 RE Creazione modulo.
|
|
// 10.06.25 RE Offset di superfici chiuse.
|
|
// 04.07.25 RE Thickening Offset di superfici generiche.
|
|
// 10.12.25 RE Creazione superfici Shell.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
//--------------------------- Include ----------------------------------------
|
|
#include "stdafx.h"
|
|
#include "VolZmap.h"
|
|
#include "SurfTriMesh.h"
|
|
#include "/EgtDev/Include/EGkDistPointSurfTm.h"
|
|
#include "/EgtDev/Include/EGkDistPointCurve.h"
|
|
#include "/EgtDev/Include/EGkDistPointTria.h"
|
|
#include "/EgtDev/Include/EGkSurfTriMeshAux.h"
|
|
#include "/EgtDev/Include/EGkStmFromCurves.h"
|
|
#include "/EgtDev/Include/EGkStmFromTriangleSoup.h"
|
|
#include <future>
|
|
|
|
#define DEBUG 0
|
|
#if DEBUG
|
|
#include "/EgtDev/Include/EGnStringUtils.h"
|
|
#include "/EgtDev/Include/EGkGeoObjSave.h"
|
|
#include "/EgtDev/Include/EGkGeoPoint3d.h"
|
|
#include "/EgtDev/Include/EGkGeoVector3d.h"
|
|
#include "/EgtDev/Include/EGkStmStandard.h"
|
|
#include "/EgtDev/Include/EgtPerfCounter.h"
|
|
std::vector<IGeoObj*> VT ;
|
|
std::vector<Color> VC ;
|
|
#endif
|
|
|
|
using namespace std ;
|
|
|
|
//----------------------------------------------------------------------------
|
|
static ISurfTriMesh*
|
|
SumStm( const CISURFTMPVECTOR& vStm)
|
|
{
|
|
// se vettore vuoto, non faccio nulla
|
|
if ( vStm.empty())
|
|
return nullptr ;
|
|
// definisco la superficie somma tra tutte ( la prima deve essere valida)
|
|
PtrOwner<ISurfTriMesh> pStmAdd( CreateSurfTriMesh()) ;
|
|
if ( IsNull( pStmAdd))
|
|
return nullptr ;
|
|
// scorro le superfici
|
|
for ( const ISurfTriMesh* pStm : vStm) {
|
|
if ( pStm == nullptr || ! pStm->IsValid() || pStm->GetTriangleCount() == 0)
|
|
continue ;
|
|
if ( ! pStmAdd->IsValid() || pStmAdd->GetTriangleCount() == 0) {
|
|
if ( ! pStmAdd->CopyFrom( pStm))
|
|
return nullptr ;
|
|
}
|
|
else
|
|
pStmAdd->Add( *pStm) ;
|
|
}
|
|
// restituisco la superficie ottenuta
|
|
return ( Release( pStmAdd)) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Funzioni per la distanza tra punto e superficie TriMesh in parallelo
|
|
//----------------------------------------------------------------------------
|
|
static bool
|
|
BoundingBoxDifference( const BBox3d& boxA, const BBox3d& boxB, BOXVECTOR& vBoxDiff)
|
|
{
|
|
// svuoto il risultato
|
|
vBoxDiff.clear() ;
|
|
// se box A vuoto, risultato vuoto
|
|
if ( boxA.IsEmpty())
|
|
return false ;
|
|
// se box B vuoto o i box non si intersecano, risultato è ancora A
|
|
BBox3d boxInt ;
|
|
if ( boxB.IsSmall() || ! boxA.FindIntersection( boxB, boxInt)) {
|
|
vBoxDiff.emplace_back( boxA) ;
|
|
return true ;
|
|
}
|
|
// recupero i punti estremi dei box A e Intersezione
|
|
Point3d ptMinA, ptMaxA ; boxA.GetMinMax( ptMinA, ptMaxA) ;
|
|
Point3d ptMinInt, ptMaxInt ; boxInt.GetMinMax( ptMinInt, ptMaxInt) ;
|
|
// sotto
|
|
if ( ptMinInt.z - ptMinA.z > EPS_SMALL) {
|
|
BBox3d boxD( ptMinA, Point3d( ptMaxA.x, ptMaxA.y, ptMinInt.z)) ;
|
|
vBoxDiff.emplace_back( boxD) ;
|
|
}
|
|
// sopra
|
|
if ( ptMaxA.z - ptMaxInt.z > EPS_SMALL) {
|
|
BBox3d boxD( Point3d( ptMinA.x, ptMinA.y, ptMaxInt.z), ptMaxA) ;
|
|
vBoxDiff.emplace_back( boxD) ;
|
|
}
|
|
// davanti
|
|
if ( ptMinInt.y - ptMinA.y > EPS_SMALL) {
|
|
BBox3d boxD( Point3d( ptMinA.x, ptMinA.y, ptMinInt.z), Point3d( ptMaxA.x, ptMinInt.y, ptMaxInt.z)) ;
|
|
vBoxDiff.emplace_back( boxD) ;
|
|
}
|
|
// dietro
|
|
if ( ptMaxA.y - ptMaxInt.y > EPS_SMALL) {
|
|
BBox3d boxD( Point3d( ptMinA.x, ptMaxInt.y, ptMinInt.z), Point3d( ptMaxA.x, ptMaxA.y, ptMaxInt.z)) ;
|
|
vBoxDiff.emplace_back( boxD) ;
|
|
}
|
|
// sinistra
|
|
if ( ptMinInt.x - ptMinA.x > EPS_SMALL) {
|
|
BBox3d boxD( Point3d( ptMinA.x, ptMinInt.y, ptMinInt.z), Point3d( ptMinInt.x, ptMaxInt.y, ptMaxInt.z)) ;
|
|
vBoxDiff.emplace_back( boxD) ;
|
|
}
|
|
// destra
|
|
if ( ptMaxA.y - ptMaxInt.y > EPS_SMALL) {
|
|
BBox3d boxD( Point3d( ptMaxInt.x, ptMinInt.y, ptMinInt.z), Point3d( ptMaxA.x, ptMaxInt.y, ptMaxInt.z)) ;
|
|
vBoxDiff.emplace_back( boxD) ;
|
|
}
|
|
// risultato
|
|
return ( ! vBoxDiff.empty()) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
static bool
|
|
DistPointSurfTmMultiThread( const Point3d& ptP, const SurfTriMesh& SurfTm, double& dDist,
|
|
bool& bIsInside, INTVECTOR& vIndClosestTria)
|
|
{
|
|
// verifico che la supercicie sia valida
|
|
if ( ! SurfTm.IsValid())
|
|
return false ;
|
|
|
|
// inizializzo distanza non calcolata
|
|
dDist = - 1. ;
|
|
// vettore di indici dei triangoli più vicini inizialmente vuoto
|
|
vIndClosestTria.clear() ;
|
|
// vettore dei flag temporanei inizialmente tutto a 0
|
|
INTVECTOR vIntFlags( SurfTm.GetTriangleCount(), 0) ;
|
|
|
|
// recupero e verifico il box locale della superficie
|
|
BBox3d b3Stm = SurfTm.GetAllTriaBox() ;
|
|
if ( b3Stm.IsEmpty())
|
|
return false ;
|
|
|
|
// cerco triangoli in box centrati sul punto dato di ampiezza crescente ed escludendo le parti già verificate.
|
|
// termino quando non trovo più triangoli che possano soddisfare la richiesta.
|
|
Point3d ptMin, ptMax ; b3Stm.GetMinMax( ptMin, ptMax) ;
|
|
double dDeltaLen = max( min( min( b3Stm.GetDimX(), b3Stm.GetDimY()), b3Stm.GetDimZ()) / 40., 20.) ;
|
|
double dBoxHalfLenX = max( max( ptMin.x - ptP.x, ptP.x - ptMax.x), 0.) + dDeltaLen ;
|
|
double dBoxHalfLenY = max( max( ptMin.y - ptP.y, ptP.y - ptMax.y), 0.) + dDeltaLen ;
|
|
double dBoxHalfLenZ = max( max( ptMin.z - ptP.z, ptP.z - ptMax.z), 0.) + dDeltaLen ;
|
|
// considero anche il box precedente per poter analizzare solo il volume differenza tra i due
|
|
BBox3d boxPPrev( ptP) ;
|
|
BBox3d boxP( ptP, dBoxHalfLenX, dBoxHalfLenY, dBoxHalfLenZ) ;
|
|
// variabili distanza minima, indice del triangolo di distanza minima, punto di distanza minima
|
|
double dMinDist = DBL_MAX ;
|
|
int nMinDistTriaIndex = SVT_NULL ;
|
|
Point3d ptMinDistPoint ;
|
|
// finché non si verifica la condizione di terminazione ingrandisco il box.
|
|
bool bContinue = true ;
|
|
|
|
// creazione del vettore dei triangoli più vicini a ptP
|
|
vector<pair<int, Triangle3d>> vTria ; // <indice triangolo, Triangolo>
|
|
while ( bContinue) {
|
|
// calcolo il box differenza con il precedente per non esplorare parti già considerate
|
|
BOXVECTOR vBox ;
|
|
BoundingBoxDifference( boxP, boxPPrev, vBox) ;
|
|
// Ciclo sui box differenza
|
|
bool bCollide = false ;
|
|
for ( const auto& b3Box : vBox) {
|
|
// interseco il box con quello della superficie e ne verifico la distanza minima dal punto
|
|
BBox3d b3Int ;
|
|
if ( ! b3Box.FindIntersection( b3Stm, b3Int) || b3Int.DistFromPoint( ptP) > dMinDist)
|
|
continue ;
|
|
// ricerca sui triangoli nel box
|
|
bCollide = true ;
|
|
INTVECTOR vnIds ;
|
|
if ( SurfTm.GetAllTriaOverlapBox( b3Int, vnIds)) {
|
|
// ciclo sui triangoli del sotto-box corrente
|
|
for ( auto nT : vnIds) {
|
|
Triangle3d trCurTria ;
|
|
if ( vIntFlags[nT] == 0 && SurfTm.GetTriangle( nT, trCurTria)) {
|
|
vIntFlags[nT] = 1 ;
|
|
DistPointTriangle distPT( ptP, trCurTria) ;
|
|
double dCurrDist ;
|
|
// se la distanza del triangolo è valida e minore di quella attuale aggiorno
|
|
if ( distPT.GetDist( dCurrDist)) {
|
|
// se distanze uguali...
|
|
if ( abs( dCurrDist - dMinDist) < EPS_SMALL)
|
|
// aggiungo il triangolo
|
|
vTria.emplace_back( make_pair( nT, trCurTria)) ;
|
|
// se minore...
|
|
else if ( dCurrDist < dMinDist) {
|
|
// pulisco il vettore
|
|
vTria.clear() ;
|
|
dMinDist = dCurrDist ;
|
|
nMinDistTriaIndex = nT ;
|
|
distPT.GetMinDistPoint( ptMinDistPoint) ;
|
|
// aggiungo il triangolo
|
|
vTria.emplace_back( make_pair( nT, trCurTria)) ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// se si verifica la condizione di terminazione arresto il ciclo altrimenti aggiorno i box
|
|
if ( ! bCollide || dMinDist < EPS_SMALL)
|
|
bContinue = false ;
|
|
else {
|
|
boxPPrev = boxP ;
|
|
boxP.Expand( dDeltaLen) ;
|
|
}
|
|
}
|
|
// se non ho trovato nessun triangolo, errore
|
|
if ( nMinDistTriaIndex == SVT_NULL)
|
|
return false ;
|
|
|
|
// riempio il vettore dei triangoli a minima distanza
|
|
for ( auto& Tria : vTria)
|
|
vIndClosestTria.emplace_back( Tria.first) ;
|
|
// salvo la distanza
|
|
dDist = dMinDist ;
|
|
|
|
// determino il Side
|
|
if ( dDist < EPS_SMALL) {
|
|
bIsInside = false ;
|
|
return true ;
|
|
}
|
|
// se ho solo un triangolo
|
|
else if ( int( vTria.size()) == 1) {
|
|
bIsInside = ( ( ptP - ptMinDistPoint) * vTria.back().second.GetN() < - EPS_SMALL) ;
|
|
return true ;
|
|
}
|
|
|
|
// controllo se tutti i triangoli a minima distanza forniscono la stessa informazione
|
|
// ( il punto potrebbe essere esterno a tutti, interno a tutti o indefinito )
|
|
bool bInside = false ;
|
|
bool bOutside = false ;
|
|
for ( int i = 0 ; i < int( vTria.size()) ; ++ i) {
|
|
if ( ( ptP - vTria[i].second.GetP( 0)) * vTria[i].second.GetN() < - EPS_SMALL)
|
|
bInside = true ;
|
|
else
|
|
bOutside = true ;
|
|
}
|
|
|
|
bIsInside = false ;
|
|
if ( bOutside == bInside) {
|
|
Point3d ptBar_tot ;
|
|
for ( const auto& Tria : vTria)
|
|
ptBar_tot += Tria.second.GetCentroid() ;
|
|
for ( const auto& Tria : vTria) {
|
|
Point3d ptInters1, ptInters2 ;
|
|
int nType = IntersLineTria( ptP, ptBar_tot, Tria.second, ptInters1, ptInters2) ;
|
|
if ( nType == ILTT_IN) {
|
|
DistPointTriangle( ptP, Tria.second).GetMinDistPoint( ptMinDistPoint) ;
|
|
bIsInside = ( ( ptP - ptMinDistPoint) * Tria.second.GetN() < - EPS_SMALL) ;
|
|
nMinDistTriaIndex = Tria.first ;
|
|
break ;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
bIsInside = bInside ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
static bool
|
|
ClassifyTrianglesMultiThread( const TRIA3DEXVECTOR& vTria, int nIndS, int nIndE,
|
|
const SurfTriMesh& SurfTm, double dOffs, double dPrec,
|
|
bool bSaveInside, BOOLVECTOR& vbSafe)
|
|
{
|
|
// verifico che la superficie sia valida
|
|
if ( ! SurfTm.IsValid())
|
|
return false ;
|
|
// verifico la validità degli indici
|
|
if ( nIndS < 0 || nIndE >= int( vTria.size()))
|
|
return false ;
|
|
// verifico la dimensione dei vettori
|
|
if ( vTria.size() != vbSafe.size())
|
|
return false ;
|
|
|
|
// scorro gli indici dei triangoli da classificare
|
|
for ( int k = nIndS ; k <= nIndE ; ++ k) {
|
|
// recupero il triangolo corrente
|
|
const Triangle3dEx& Tria = vTria[k] ;
|
|
// preparo gli elementi di classificazione
|
|
DBLVECTOR vDists ; vDists.resize( 3) ;
|
|
INTMATRIX matIndClosestTria ; matIndClosestTria.resize( 3) ;
|
|
// verifico che i suoi punti siano distanti almeno |dOffs| - dTol dalla superficie
|
|
vbSafe[k] = true ;
|
|
for ( int i = 0 ; vbSafe[k] && i < 3 ; ++ i) {
|
|
bool bIsInside = false ;
|
|
DistPointSurfTmMultiThread( Tria.GetP( i), SurfTm, vDists[i], bIsInside, matIndClosestTria[i]) ;
|
|
vbSafe[k] = ( ( vDists[i] > abs( dOffs) - 0.25 * dPrec) &&
|
|
( vDists[i] < abs( dOffs) + 0.25 * dPrec) &&
|
|
( bIsInside == bSaveInside)) ;
|
|
}
|
|
// se tutti sufficientemente distanti
|
|
if ( vbSafe[k]) {
|
|
// i triangoli a minima distanza devono avere normale simile
|
|
bool bPerp = true ;
|
|
for ( int i = 0 ; bPerp && i < 3 ; ++ i) {
|
|
for ( int j = 0 ; bPerp && j < int( matIndClosestTria[i].size()) ; ++ j) {
|
|
Triangle3d TriaCloser ;
|
|
SurfTm.GetTriangle( matIndClosestTria[i][j], TriaCloser) ;
|
|
bPerp = ( abs( Tria.GetN() * TriaCloser.GetN()) < cos( 30. * DEGTORAD)) ;
|
|
}
|
|
}
|
|
vbSafe[k] = ( ! bPerp) ;
|
|
}
|
|
}
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Funzione che crea l'Offset di una superficie TriMesh
|
|
//----------------------------------------------------------------------------
|
|
ISurfTriMesh*
|
|
CreateSurfTriMeshOffset( const ISurfTriMesh* pStm, double dOffs, double dPrec, int nType)
|
|
{
|
|
return ( CreateSurfTriMeshesOffset( { pStm}, dOffs, dPrec, nType)) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Funzione che crea il Fat Offset di una superficie TriMesh
|
|
//----------------------------------------------------------------------------
|
|
ISurfTriMesh*
|
|
CreateSurfTriMeshThickeningOffset( const ISurfTriMesh* pStm, double dOffs, double dPrec, int nType)
|
|
{
|
|
return ( CreateSurfTriMeshesThickeningOffset( { pStm}, dOffs, dPrec, nType)) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Funzione che crea l'Offset di un insieme di superfici
|
|
//----------------------------------------------------------------------------
|
|
ISurfTriMesh*
|
|
CreateSurfTriMeshesOffset( const CISURFTMPVECTOR& vStm, double dOffs, double dPrec, int nType)
|
|
{
|
|
// se vettore delle superfici vuoto, non faccio nulla
|
|
if ( vStm.empty())
|
|
return nullptr ;
|
|
// controllo sul valore di tolleranza lineare
|
|
double dMyPrec = max( dPrec, 100. * EPS_SMALL) ;
|
|
// --- NB. ( Il valore di Offset deve essere maggiore di 10 * EPS_SMALL in valore assoluto)
|
|
// Nel caso sia minore, restituisco semplicemente la somma delle superfici
|
|
// ( questo valore serve per rimanere coerente con l'Offset delle curve)
|
|
if ( abs( dOffs) < 10. * EPS_SMALL)
|
|
return SumStm( vStm) ;
|
|
|
|
// creo lo Zmap associato alle superfici TriMesh
|
|
VolZmap myVolZmap ;
|
|
if ( ! myVolZmap.CreateFromTriMeshOffset( vStm, dOffs, dMyPrec, nType))
|
|
return nullptr ;
|
|
if ( ! myVolZmap.IsValid())
|
|
return nullptr ;
|
|
|
|
// recupero le superfici aperte
|
|
CISURFTMPVECTOR vStmOpen ;
|
|
for ( const ISurfTriMesh* pStm : vStm) {
|
|
if ( pStm != nullptr && pStm->IsValid() && ! pStm->IsClosed())
|
|
vStmOpen.emplace_back( pStm) ;
|
|
}
|
|
|
|
// --- se non ho superfici aperte
|
|
if ( vStmOpen.empty()) {
|
|
// restituisco la superficie TriMesh di Offset
|
|
return ( myVolZmap.GetSurfTriMesh()) ;
|
|
}
|
|
|
|
// --- se ho delle superfici chiuse
|
|
TRIA3DEXVECTOR vAllTria, vTriaOffs ;
|
|
for ( int nB = 0 ; nB < myVolZmap.GetBlockCount() ; ++ nB) {
|
|
TRIA3DEXVECTOR vTria, vTriaSafe ;
|
|
myVolZmap.GetBlockTriangles( nB, vTria) ;
|
|
#if DEBUG
|
|
TRIA3DVECTOR vTriaUnsafe ;
|
|
#endif
|
|
for ( int nT = 0 ; nT < int( vTria.size()) ; ++ nT) {
|
|
Triangle3dEx& Tria = vTria[nT] ;
|
|
BBox3d BBoxTria ;
|
|
Tria.GetLocalBBox( BBoxTria) ;
|
|
// azzero flag di colore
|
|
vAllTria.push_back( Tria) ;
|
|
}
|
|
}
|
|
// classifico i triangoli
|
|
PtrOwner<const SurfTriMesh> pStmBasic( nullptr) ;
|
|
if ( int( vStmOpen.size() == 1))
|
|
pStmBasic.Set( GetBasicSurfTriMesh( CloneSurfTriMesh( vStmOpen[0]))) ;
|
|
else {
|
|
StmFromTriangleSoup AllOpenStmSoup ; AllOpenStmSoup.Start() ;
|
|
for ( const ISurfTriMesh* pStmOpen : vStmOpen) {
|
|
if ( pStmOpen != nullptr && pStmOpen->IsValid()) {
|
|
for ( int nT = 0 ; nT < pStmOpen->GetTriangleCount() ; ++ nT) {
|
|
Triangle3d Tria ;
|
|
if ( pStmOpen->GetTriangle( nT, Tria))
|
|
AllOpenStmSoup.AddTriangle( Tria) ;
|
|
}
|
|
}
|
|
}
|
|
AllOpenStmSoup.End() ;
|
|
pStmBasic.Set( GetBasicSurfTriMesh( AllOpenStmSoup.GetSurf())) ;
|
|
}
|
|
if ( pStmBasic == nullptr)
|
|
return nullptr ;
|
|
BBox3d b3Stm = pStmBasic->GetAllTriaBox() ;
|
|
if ( b3Stm.IsEmpty())
|
|
return nullptr ;
|
|
// numero di triangoli da analizzare
|
|
int nTriaCnt = int( vAllTria.size()) ;
|
|
// definisco un vettore di Flag per i triangoli già visitati
|
|
INTVECTOR vIntFlags( pStmBasic->GetTriangleCount()) ;
|
|
// numero massimo di thread concorrenti
|
|
int nThreadMax = thread::hardware_concurrency() ;
|
|
bool bOk = true ;
|
|
BOOLVECTOR vbSafeTria( vAllTria.size(), true) ;
|
|
if ( nThreadMax <= 1 || nTriaCnt < 50)
|
|
ClassifyTrianglesMultiThread( vAllTria, 0, nTriaCnt - 1, *pStmBasic, abs( dOffs), dPrec, ( dOffs < 0.), vbSafeTria) ;
|
|
else {
|
|
const int MAX_PARTS = 32 ;
|
|
INTINTVECTOR vFstLst( MAX_PARTS) ;
|
|
// calcolo le parti del vettore
|
|
int nPartCnt = min( nThreadMax, MAX_PARTS) ;
|
|
int nPartDim = nTriaCnt / nPartCnt + 1 ;
|
|
for ( int i = 0 ; i < nPartCnt ; ++ i) {
|
|
vFstLst[i].first = i * nPartDim ;
|
|
vFstLst[i].second = min( ( i + 1) * nPartDim, nTriaCnt) - 1 ;
|
|
}
|
|
// processo le parti
|
|
future<bool> vRes[MAX_PARTS] ;
|
|
for ( int i = 0 ; i < nPartCnt ; ++ i) {
|
|
vRes[i] = async( launch::async, &ClassifyTrianglesMultiThread, cref( vAllTria), vFstLst[i].first,
|
|
vFstLst[i].second, cref( *pStmBasic), abs( dOffs), dPrec, ( dOffs < 0.), ref( vbSafeTria)) ;
|
|
}
|
|
// attendo i risultati
|
|
int nFin = 0 ;
|
|
while ( nFin < nPartCnt) {
|
|
for ( int i = 0 ; i < nPartCnt ; ++ i) {
|
|
if ( vRes[i].valid() && vRes[i].wait_for( chrono::nanoseconds{ 1}) == future_status::ready) {
|
|
bOk = vRes[i].get() && bOk ;
|
|
++ nFin ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( ! bOk)
|
|
return nullptr ;
|
|
TRIA3DEXVECTOR vTriaSafe ; vTriaSafe.reserve( vAllTria.size()) ;
|
|
#if DEBUG
|
|
TRIA3DEXVECTOR vTriaUnSafe ; vTriaUnSafe.reserve( vAllTria.size()) ;
|
|
#endif
|
|
for ( int i = 0 ; i < int( vAllTria.size()) ; ++ i) {
|
|
if ( vbSafeTria[i])
|
|
vTriaSafe.emplace_back( vAllTria[i]) ;
|
|
#if DEBUG
|
|
if ( ! vbSafeTria[i])
|
|
vTriaUnSafe.emplace_back( vAllTria[i]) ;
|
|
#endif
|
|
}
|
|
|
|
// definisco la superficie con i soli triangoli validi
|
|
StmFromTriangleSoup TriaSoup ; TriaSoup.Start() ;
|
|
for ( const Triangle3d& SafeTria : vTriaSafe)
|
|
TriaSoup.AddTriangle( SafeTria) ;
|
|
TriaSoup.End() ;
|
|
PtrOwner<ISurfTriMesh> pStmOffs( TriaSoup.GetSurf()) ;
|
|
if ( IsNull( pStmOffs) || ! pStmOffs->IsValid() || pStmOffs->GetTriangleCount() == 0)
|
|
return nullptr ;
|
|
|
|
#if DEBUG
|
|
StmFromTriangleSoup _invalidSoup ; _invalidSoup.Start() ;
|
|
for ( const Triangle3d& _unsafeTria : vTriaUnSafe)
|
|
_invalidSoup.AddTriangle( _unsafeTria) ;
|
|
_invalidSoup.End() ;
|
|
VT.emplace_back( pStmOffs->Clone()) ;
|
|
VC.emplace_back( LIME) ;
|
|
VT.emplace_back( _invalidSoup.GetSurf()) ;
|
|
VC.emplace_back( RED) ;
|
|
SaveGeoObj( VT, VC, "C:\\Temp\\TriangleSelection.nge") ;
|
|
#endif
|
|
|
|
return ( Release( pStmOffs)) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//* Funzione che crea il Fat Offset di un insieme di superfici
|
|
//----------------------------------------------------------------------------
|
|
ISurfTriMesh*
|
|
CreateSurfTriMeshesThickeningOffset( const CISURFTMPVECTOR& vStm, double dOffs, double dPrec, int nType)
|
|
{
|
|
// se vettore delle superfici vuoto, non faccio nulla
|
|
if ( vStm.empty())
|
|
return nullptr ;
|
|
// controllo sul valore di tolleranza lineare
|
|
double dMyPrec = max( dPrec, 100 * EPS_SMALL) ;
|
|
// --- NB. ( Il valore di Offset deve essere maggiore di 10 * EPS_SMALL in valore assoluto)
|
|
// Nel caso sia minore, restituisco semplicemente la somma delle superfici
|
|
// ( questo valore serve per rimanere coerente con l'Offset delle curve)
|
|
if ( abs( dOffs) < 10 * EPS_SMALL)
|
|
return SumStm( vStm) ;
|
|
|
|
// creo lo Zmap associato alle superfici TriMesh
|
|
VolZmap OneVolZmap ;
|
|
if ( ! OneVolZmap.CreateFromTriMeshThickeningOffset( vStm, dOffs, dMyPrec, nType))
|
|
return nullptr ;
|
|
if ( ! OneVolZmap.IsValid())
|
|
return nullptr ;
|
|
|
|
// restituisco la superficie TriMesh
|
|
return ( OneVolZmap.GetSurfTriMesh()) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Funzione per creare la Superficie TriMesh Shell da una Trimesh aperta
|
|
//----------------------------------------------------------------------------
|
|
ISurfTriMesh*
|
|
CreateSurfTriMeshShell( const ISurfTriMesh* pStm, double dThick, double dPrec)
|
|
{
|
|
// verifico che la superficie sia valida ed aperta
|
|
if ( pStm == nullptr || ! pStm->IsValid() || pStm->IsClosed())
|
|
return nullptr ;
|
|
// lo spessore deve essere sempre positivo, il verso è sempre dato dalla normale dei triangoli
|
|
dThick = - max( 10. * EPS_SMALL, abs( dThick)) ;
|
|
|
|
// creo il suo Offset ( salvandomi lo Zmap per l'orientamento)
|
|
#if DEBUG
|
|
PerformanceCounter PC ; PC.Start() ;
|
|
#endif
|
|
VolZmap myVolZMap ;
|
|
if ( ! myVolZMap.CreateFromTriMeshOffset( { pStm}, dThick, dPrec))
|
|
return nullptr ;
|
|
if ( ! myVolZMap.IsValid())
|
|
return nullptr ;
|
|
#if DEBUG
|
|
LOG_INFO( GetEGkLogger(), ( string{ "Tria Time : "} + ToString( PC.Stop())).c_str()) ;
|
|
VT.clear() ; VC.clear() ;
|
|
VT.emplace_back( myVolZMap.Clone()) ;
|
|
VC.emplace_back( BLACK) ;
|
|
VT.emplace_back( pStm->Clone()) ;
|
|
VC.emplace_back( YELLOW) ;
|
|
SaveGeoObj( VT, VC, "C:\\Temp\\VolZMapOffs.nge") ;
|
|
#endif
|
|
|
|
#if DEBUG
|
|
VT.clear() ; VC.clear() ;
|
|
PC.Start() ;
|
|
#endif
|
|
|
|
// recupero i triangoli dallo ZMap creato
|
|
TRIA3DEXVECTOR vAllTria, vTriaOffs ;
|
|
for ( int nB = 0 ; nB < myVolZMap.GetBlockCount() ; ++ nB) {
|
|
TRIA3DEXVECTOR vTria, vTriaSafe ;
|
|
myVolZMap.GetBlockTriangles( nB, vTria) ;
|
|
#if DEBUG
|
|
TRIA3DVECTOR vTriaUnsafe ;
|
|
#endif
|
|
for ( int nT = 0 ; nT < int( vTria.size()) ; ++ nT) {
|
|
Triangle3dEx& Tria = vTria[nT] ;
|
|
BBox3d BBoxTria ;
|
|
Tria.GetLocalBBox( BBoxTria) ;
|
|
// azzero flag di colore
|
|
vAllTria.push_back( Tria) ;
|
|
}
|
|
}
|
|
// classifico i triangoli
|
|
const SurfTriMesh* pStmBasic = GetBasicSurfTriMesh( pStm) ;
|
|
if ( pStmBasic == nullptr)
|
|
return nullptr ;
|
|
BBox3d b3Stm = pStmBasic->GetAllTriaBox() ;
|
|
if ( b3Stm.IsEmpty())
|
|
return nullptr ;
|
|
// numero di triangoli da analizzare
|
|
int nTriaCnt = int( vAllTria.size()) ;
|
|
// definisco un vettore di Flag per i triangoli già visitati
|
|
INTVECTOR vIntFlags( pStmBasic->GetTriangleCount()) ;
|
|
// numero massimo di thread concorrenti
|
|
int nThreadMax = thread::hardware_concurrency() ;
|
|
bool bOk = true ;
|
|
BOOLVECTOR vbSafeTria( vAllTria.size(), true) ;
|
|
if ( nThreadMax <= 1 || nTriaCnt < 50)
|
|
ClassifyTrianglesMultiThread( vAllTria, 0, nTriaCnt - 1, *pStmBasic, dThick, dPrec, true, vbSafeTria) ;
|
|
else {
|
|
const int MAX_PARTS = 32 ;
|
|
INTINTVECTOR vFstLst( MAX_PARTS) ;
|
|
// calcolo le parti del vettore
|
|
int nPartCnt = min( nThreadMax, MAX_PARTS) ;
|
|
int nPartDim = nTriaCnt / nPartCnt + 1 ;
|
|
for ( int i = 0 ; i < nPartCnt ; ++ i) {
|
|
vFstLst[i].first = i * nPartDim ;
|
|
vFstLst[i].second = min( ( i + 1) * nPartDim, nTriaCnt) - 1 ;
|
|
}
|
|
// processo le parti
|
|
future<bool> vRes[MAX_PARTS] ;
|
|
for ( int i = 0 ; i < nPartCnt ; ++ i) {
|
|
vRes[i] = async( launch::async, &ClassifyTrianglesMultiThread, cref( vAllTria), vFstLst[i].first,
|
|
vFstLst[i].second, cref( *pStmBasic), dThick, dPrec, true, ref( vbSafeTria)) ;
|
|
}
|
|
// attendo i risultati
|
|
int nFin = 0 ;
|
|
while ( nFin < nPartCnt) {
|
|
for ( int i = 0 ; i < nPartCnt ; ++ i) {
|
|
if ( vRes[i].valid() && vRes[i].wait_for( chrono::nanoseconds{ 1}) == future_status::ready) {
|
|
bOk = vRes[i].get() && bOk ;
|
|
++ nFin ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( ! bOk)
|
|
return nullptr ;
|
|
TRIA3DEXVECTOR vTriaSafe ; vTriaSafe.reserve( vAllTria.size()) ;
|
|
#if DEBUG
|
|
TRIA3DEXVECTOR vTriaUnSafe ; vTriaUnSafe.reserve( vAllTria.size()) ;
|
|
#endif
|
|
for ( int i = 0 ; i < int( vAllTria.size()) ; ++ i) {
|
|
if ( vbSafeTria[i])
|
|
vTriaSafe.emplace_back( vAllTria[i]) ;
|
|
#if DEBUG
|
|
if ( ! vbSafeTria[i])
|
|
vTriaUnSafe.emplace_back( vAllTria[i]) ;
|
|
#endif
|
|
}
|
|
|
|
// definisco la superficie con i soli triangoli validi
|
|
StmFromTriangleSoup TriaSoup ; TriaSoup.Start() ;
|
|
for ( const Triangle3d& SafeTria : vTriaSafe)
|
|
TriaSoup.AddTriangle( SafeTria) ;
|
|
TriaSoup.End() ;
|
|
PtrOwner<ISurfTriMesh> pStmOffs( TriaSoup.GetSurf()) ;
|
|
if ( IsNull( pStmOffs) || ! pStmOffs->IsValid() || pStmOffs->GetTriangleCount() == 0)
|
|
return nullptr ;
|
|
|
|
#if DEBUG
|
|
LOG_INFO( GetEGkLogger(), ( string{ "Tria Time ( exceed Approx) : "} + ToString( PC.Stop())).c_str()) ;
|
|
StmFromTriangleSoup _invalidSoup ; _invalidSoup.Start() ;
|
|
for ( const Triangle3d& _unsafeTria : vTriaUnSafe)
|
|
_invalidSoup.AddTriangle( _unsafeTria) ;
|
|
_invalidSoup.End() ;
|
|
VT.emplace_back( pStmOffs->Clone()) ;
|
|
VC.emplace_back( LIME) ;
|
|
VT.emplace_back( _invalidSoup.GetSurf()) ;
|
|
VC.emplace_back( RED) ;
|
|
SaveGeoObj( VT, VC, "C:\\Temp\\TriangleSelection.nge") ;
|
|
VT.clear() ; VC.clear() ;
|
|
PC.Start() ;
|
|
#endif
|
|
|
|
// recupero i loops della superficie originaria e del suo Offset orientato ( non devono essere diminuiti)
|
|
POLYLINEVECTOR vPL, vPLOffs ;
|
|
if ( ! pStm->GetLoops( vPL) || ! pStmOffs->GetLoops( vPLOffs))
|
|
return nullptr ;
|
|
|
|
// trasformo ogni loop in curve composite ( devono essere chiuse)
|
|
ICRVCOMPOPOVECTOR vCompoLoops ; vCompoLoops.reserve( vPL.size()) ;
|
|
for ( const PolyLine& PL : vPL) {
|
|
if ( PL.IsClosed()) {
|
|
if ( ! vCompoLoops.emplace_back( CreateCurveComposite()) ||
|
|
! vCompoLoops.back()->FromPolyLine( PL) ||
|
|
! vCompoLoops.back()->IsValid())
|
|
return nullptr ;
|
|
}
|
|
}
|
|
ICRVCOMPOPOVECTOR vCompoOffsLoops ; vCompoOffsLoops.reserve( vPLOffs.size()) ;
|
|
for ( const PolyLine& PLOffs : vPLOffs) {
|
|
if ( PLOffs.IsClosed()) {
|
|
if ( ! vCompoOffsLoops.emplace_back( CreateCurveComposite()) ||
|
|
! vCompoOffsLoops.back()->FromPolyLine( PLOffs) ||
|
|
! vCompoOffsLoops.back()->IsValid())
|
|
return nullptr ;
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
VT.emplace_back( pStmOffs->Clone()) ;
|
|
VC.emplace_back( YELLOW) ;
|
|
for ( ICurveComposite* pCompo : vCompoLoops) {
|
|
VT.emplace_back( pCompo->Clone()) ;
|
|
VC.emplace_back( AQUA) ;
|
|
}
|
|
for ( ICurveComposite* pCompoOffs : vCompoOffsLoops) {
|
|
VT.emplace_back( pCompoOffs->Clone()) ;
|
|
VC.emplace_back( ORANGE) ;
|
|
}
|
|
SaveGeoObj( VT, VC, "C:\\Temp\\myCurve.nge") ;
|
|
VT.clear() ; VC.clear() ;
|
|
#endif
|
|
|
|
// per ogni curva della superficie originale cerco la sua associata
|
|
// NB. per la creazione della superficie ruled la prima curva è quellla che determina il verso
|
|
// dei triangoli associati. Per un corretto ed automatico orientamento della superficie
|
|
// la prima curva deve essere sempre definita dalla superficie originale ed invertita ( le
|
|
// curve nella rigata devono seguire lo stesso orientamento
|
|
ISURFTMPOVECTOR vStmRuled ; vStmRuled.reserve( vCompoLoops.size()) ;
|
|
BOOLVECTOR vIndMatched( vCompoOffsLoops.size(), false) ;
|
|
for ( ICurveComposite* pCompoLoop : vCompoLoops) {
|
|
// sposto il punto iniziale della curva nel tratto più lungo
|
|
double dMaxLen = - INFINITO ;
|
|
int nIndCrv = 0 ;
|
|
for ( int nCrv = 0 ; nCrv < pCompoLoop->GetCurveCount() ; ++ nCrv) {
|
|
const ICurve* pCurve = pCompoLoop->GetCurve( nCrv) ;
|
|
if ( pCurve != nullptr && pCurve->IsValid()) {
|
|
double dCurrLen = 0. ;
|
|
pCurve->GetLength( dCurrLen) ;
|
|
if ( dCurrLen > dMaxLen) {
|
|
dMaxLen = dCurrLen ;
|
|
nIndCrv = nCrv ;
|
|
}
|
|
}
|
|
}
|
|
pCompoLoop->ChangeStartPoint( nIndCrv + 0.5) ;
|
|
Point3d ptStart ; pCompoLoop->GetStartPoint( ptStart) ;
|
|
// dalle altre curve derivanti dalla superficie di Offset cerco quella più vicina al punto inziale
|
|
double dMinSqDist = INFINITO ;
|
|
int nIndOffsCrv = -1 ;
|
|
Point3d ptMinDist ;
|
|
for ( int nOffsCrv = 0 ; nOffsCrv < int( vCompoOffsLoops.size()) ; ++ nOffsCrv) {
|
|
if ( vIndMatched[nOffsCrv])
|
|
continue ;
|
|
// recupero la curva e calcolo la distanza
|
|
const ICurveComposite* pCompoOffsLoop = vCompoOffsLoops[nOffsCrv] ;
|
|
int nFlag = 0 ;
|
|
Point3d ptCurrMinDist ;
|
|
if ( DistPointCurve( ptStart, *pCompoOffsLoop).GetMinDistPoint( 0., ptCurrMinDist, nFlag)) {
|
|
double dCurrSqDist = SqDist( ptStart, ptCurrMinDist) ;
|
|
if ( dCurrSqDist < dMinSqDist) {
|
|
dMinSqDist = dCurrSqDist ;
|
|
nIndOffsCrv = nOffsCrv ;
|
|
ptMinDist = ptCurrMinDist ;
|
|
}
|
|
}
|
|
}
|
|
if ( nIndOffsCrv == -1)
|
|
return nullptr ;
|
|
vIndMatched[ nIndOffsCrv] = true ;
|
|
// associo le due curve
|
|
ICurveComposite* pCompoOffsLoop = vCompoOffsLoops[nIndOffsCrv] ;
|
|
double dParMinDist = 0. ;
|
|
pCompoOffsLoop->GetParamAtPoint( ptMinDist, dParMinDist, 10. * EPS_SMALL) ;
|
|
pCompoOffsLoop->ChangeStartPoint( dParMinDist) ;
|
|
|
|
#if DEBUG
|
|
Color _cCol = Color( double( rand()) / RAND_MAX, double( rand()) / RAND_MAX, double( rand()) / RAND_MAX, 1.) ;
|
|
VT.emplace_back( pCompoLoop->Clone()) ;
|
|
VC.emplace_back( _cCol) ;
|
|
VT.emplace_back( pCompoOffsLoop->Clone()) ;
|
|
VC.emplace_back( _cCol) ;
|
|
#endif
|
|
|
|
// creo la superficie tra queste due curve e la oriento in modo da definire un volume
|
|
pCompoLoop->Invert() ;
|
|
PtrOwner<ISurfTriMesh> pStmRuled( GetSurfTriMeshRuled( pCompoLoop, pCompoOffsLoop, ISurfTriMesh::RuledType::RLT_MINDIST)) ;
|
|
if ( IsNull( pStmRuled) || ! pStmRuled->IsValid() ||
|
|
! vStmRuled.emplace_back( Release( pStmRuled)))
|
|
return nullptr ;
|
|
|
|
#if DEBUG
|
|
LOG_INFO( GetEGkLogger(), ( string{ "Strip generation : "} + ToString( PC.Stop())).c_str()) ;
|
|
VT.emplace_back( vStmRuled.back()->Clone()) ;
|
|
VC.emplace_back( _cCol) ;
|
|
_cCol.SetAlpha( .5) ;
|
|
#endif
|
|
}
|
|
|
|
#if DEBUG
|
|
SaveGeoObj( VT, VC, "C:\\Temp\\Strips.nge") ;
|
|
PC.Start() ;
|
|
#endif
|
|
|
|
// compongo la superficie finale
|
|
PtrOwner<ISurfTriMesh> pStmOrig( CloneSurfTriMesh( pStm)) ;
|
|
if ( IsNull( pStmOrig) || ! pStmOrig->IsValid())
|
|
return nullptr ;
|
|
PtrOwner<ISurfTriMesh> pStmRef( Release( pStmOrig)) ;
|
|
if ( IsNull( pStmRef) || ! pStmRef->IsValid())
|
|
return nullptr ;
|
|
for ( int nStrip = 0 ; nStrip < int( vStmRuled.size()) ; ++ nStrip) {
|
|
if ( ! pStmRef->DoSewing( *vStmRuled[nStrip]))
|
|
return nullptr ;
|
|
}
|
|
if ( ! pStmRef->DoSewing( *pStmOffs))
|
|
return nullptr ;
|
|
pStmRef->Repair() ;
|
|
|
|
#if DEBUG
|
|
LOG_INFO( GetEGkLogger(), ( string{ "Sewing : "} + ToString( PC.Stop())).c_str()) ;
|
|
#endif
|
|
|
|
return ( ( ! IsNull( pStmRef) && pStmRef->IsValid()) ? Release( pStmRef) : nullptr) ;
|
|
}
|