12862a6c76
- correzione nella triangolazione (da RiccardoE).
1318 lines
48 KiB
C++
1318 lines
48 KiB
C++
//----------------------------------------------------------------------------
|
||
// EgalTech 2014-2021
|
||
//----------------------------------------------------------------------------
|
||
// File : Triangulate.cpp Data : 18.12.21 Versione : 2.3l
|
||
// Contenuto : Implementazione della classe Triangulate.
|
||
//
|
||
//
|
||
//
|
||
// Modifiche : 08.04.14 DS Creazione modulo.
|
||
// 23.06.14 DS Corretto errore con contorni CW.
|
||
//
|
||
//----------------------------------------------------------------------------
|
||
|
||
//--------------------------- Include ----------------------------------------
|
||
#include "stdafx.h"
|
||
#include "DllMain.h"
|
||
#include "Triangulate.h"
|
||
#include "ProjPlane.h"
|
||
#include "earcut.hpp"
|
||
#include "/EgtDev/Include/EGkPolyLine.h"
|
||
#include "/EgtDev/Include/EGkPlane3d.h"
|
||
#include "/EgtDev/Include/EGkStringUtils3d.h"
|
||
#include "/EgtDev/Include/EgtNumUtils.h"
|
||
#include "CurveComposite.h"
|
||
#include "IntersCrvCompoCrvCompo.h"
|
||
#include <algorithm>
|
||
|
||
using namespace std ;
|
||
|
||
//----------------------------------------------------------------------------
|
||
enum EarStatus{ EAS_NULL = -1, EAS_NO = 0, EAS_OK = 1} ;
|
||
|
||
//----------------------------------------------------------------------------
|
||
static bool ChangeStartPntVector( int nNewStart, PNTVECTOR& vPi) ;
|
||
|
||
//----------------------------------------------------------------------------
|
||
static bool FORCE_EARCUT_HPP = false ;
|
||
|
||
//----------------------------------------------------------------------------
|
||
// In : PolyLine
|
||
// Out : PNTVECTOR (Point3d Vector) : points of the polyline
|
||
// INTVECTOR (int Vector) : 3*T indices of above points for T triangles
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::Make( const PolyLine& PL, PNTVECTOR& vPt, INTVECTOR& vTr)
|
||
{
|
||
// pulisco i vettori di ritorno
|
||
vPt.clear() ;
|
||
vTr.clear() ;
|
||
// verifico che la polilinea contenga almeno 4 punti (primo e ultimo coincidenti)
|
||
if ( PL.GetPointNbr() < 4)
|
||
return false ;
|
||
// verifico che la polilinea sia chiusa e piana e calcolo il piano medio del poligono
|
||
double dArea ;
|
||
Plane3d plPlane ;
|
||
if ( ! PL.IsClosedAndFlat( plPlane, dArea, 50 * EPS_SMALL))
|
||
return false ;
|
||
// determino il piano ottimale di proiezione e il relativo senso di rotazione
|
||
bool bCCW ;
|
||
if ( ! CalcProjPlane( plPlane.GetVersN(), m_nPlane, bCCW))
|
||
return false ;
|
||
|
||
// forzo esecuzione triangolazione con earcut.hpp
|
||
if ( FORCE_EARCUT_HPP) {
|
||
return MakeByEC_HPP( PL, bCCW, vPt, vTr) ;
|
||
}
|
||
|
||
// riempio il vettore con i vertici del poligono da triangolare
|
||
vPt.reserve( PL.GetPointNbr() - 1) ;
|
||
// salto il primo punto (coincide con l'ultimo)
|
||
Point3d ptP ;
|
||
if ( ! PL.GetFirstPoint( ptP))
|
||
return false ;
|
||
// inserisco i punti
|
||
while ( PL.GetNextPoint( ptP))
|
||
vPt.push_back( ptP) ;
|
||
// se non CCW inverto il vettore dei punti
|
||
if ( ! bCCW)
|
||
reverse( vPt.begin(), vPt.end()) ;
|
||
|
||
// eseguo la triangolazione
|
||
double dMinMinAng2, dMinMinAng3 ;
|
||
if ( ! MakeByEC2( vPt, vTr, dMinMinAng2)) {
|
||
if ( ! MakeByEC3( vPt, vTr, dMinMinAng3)) {
|
||
LOG_INFO( GetEGkLogger(), "Info : problems in MakeByEC23(1)")
|
||
return MakeByEC_HPP( PL, bCCW, vPt, vTr) ;
|
||
}
|
||
}
|
||
else if ( dMinMinAng2 < 5) {
|
||
INTVECTOR vMyTr ;
|
||
if ( MakeByEC3( vPt, vMyTr, dMinMinAng3) && dMinMinAng3 > dMinMinAng2)
|
||
vTr = vMyTr ;
|
||
}
|
||
|
||
// se era CW, devo invertire il senso dei triangoli
|
||
if ( ! bCCW)
|
||
reverse( vTr.begin(), vTr.end()) ;
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// In : POLYLINEVECTOR : vector of polylines, the first outer, the others inner
|
||
// Out : PNTVECTOR (Point3d Vector) : points of the polyline
|
||
// INTVECTOR (int Vector) : 3*T indices of above points for T triangles
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::Make( const POLYLINEVECTOR& vPL, PNTVECTOR& vPt, INTVECTOR& vTr)
|
||
{
|
||
// pulisco i vettori di ritorno
|
||
vPt.clear() ;
|
||
vTr.clear() ;
|
||
// ci devono essere delle polilinee nel vettore
|
||
if ( &vPL == nullptr || vPL.empty())
|
||
return false ;
|
||
// se una sola polilinea mi riconduco al caso precedente
|
||
if ( vPL.size() == 1)
|
||
return Make( vPL[0], vPt, vTr) ;
|
||
// verifico che la polilinea esterna sia chiusa e piana e calcolo il piano medio del poligono
|
||
double dArea ;
|
||
Plane3d plExtPlane ;
|
||
if ( ! vPL[0].IsClosedAndFlat( plExtPlane, dArea, 50 * EPS_SMALL))
|
||
return false ;
|
||
// determino il piano ottimale di proiezione e il relativo senso di rotazione
|
||
bool bCCW ;
|
||
if ( ! CalcProjPlane( plExtPlane.GetVersN(), m_nPlane, bCCW))
|
||
return false ;
|
||
// verifico le altre polilinee
|
||
for ( int i = 1 ; i < int( vPL.size()) ; ++i) {
|
||
// deve essere chiusa, giacere nello stesso piano ed essere orientata al contrario della esterna
|
||
double dArea2 ;
|
||
Plane3d plPlane ;
|
||
if ( ! vPL[i].IsClosedAndFlat( plPlane, dArea2, 50 * EPS_SMALL) ||
|
||
! AreOppositeVectorApprox( plExtPlane.GetVersN(), plPlane.GetVersN()))
|
||
return false ;
|
||
}
|
||
|
||
// forzo esecuzione triangolazione con earcut.hpp
|
||
if ( FORCE_EARCUT_HPP) {
|
||
return MakeByEC_HPP( vPL, bCCW, vPt, vTr) ;
|
||
}
|
||
|
||
// se non CCW inverto tutte le polilinee
|
||
if ( ! bCCW) {
|
||
for ( int i = 0 ; i < int( vPL.size()) ; ++i)
|
||
const_cast<PolyLine&>(vPL[i]).Invert( true) ;
|
||
}
|
||
// calcolo ordine decrescente su Xmax o Ymax o Zmax secondo m_nPlane per le curve interne
|
||
INTVECTOR vOrd ;
|
||
bool bOk = SortInternalLoops( vPL, vOrd) ;
|
||
// riempio il vettore con i punti dei poligoni da triangolare
|
||
// calcolo spazio totale e spazio massimo per un percorso interno
|
||
int nTot = vPL[0].GetPointNbr() - 1 ;
|
||
int nMax = 0 ;
|
||
for ( int i = 1 ; i < int( vPL.size()) ; ++i) {
|
||
nTot += vPL[i].GetPointNbr() + 1 ;
|
||
if ( vPL[i].GetPointNbr() > nMax)
|
||
nMax = vPL[i].GetPointNbr() ;
|
||
}
|
||
// riservo spazio pari al totale dei punti (anche quelli ripetuti di chiusura che servono per i link)
|
||
vPt.reserve( nTot) ;
|
||
// inserisco i punti del contorno esterno (tranne il primo che coincide con l'ultimo)
|
||
bOk = bOk && GetPntVectorFromPolyline( vPL[0], false, vPt) ;
|
||
// ciclo sui percorsi interni ordinati secondo Xmax decrescente
|
||
PNTVECTOR vPi ;
|
||
vPi.reserve( nMax) ;
|
||
for ( int i = 1 ; bOk && i < int( vPL.size()) ; ++ i) {
|
||
// riordino il percorso per avere punto iniziale con Xmax
|
||
if ( ! GetPntVectorFromPolyline( vPL[vOrd[i]], true, vPi)) {
|
||
bOk = false ;
|
||
break ;
|
||
}
|
||
// cerco un punto del percorso esterno visibile dal punto iniziale del precedente interno
|
||
int nI ;
|
||
if ( ! GetOuterPntToJoin( vPt, vPi[0], nI)) {
|
||
bOk = false ;
|
||
break ;
|
||
}
|
||
// riordino percorso esterno per avere questo punto all'inizio
|
||
if ( ! ChangeStartPntVector( nI, vPt)) {
|
||
bOk = false ;
|
||
break ;
|
||
}
|
||
// ripeto punto iniziale
|
||
vPt.push_back( vPt[0]) ;
|
||
// accodo percorso interno
|
||
for ( int j = 0 ; j < int( vPi.size()) ; ++j)
|
||
vPt.push_back( vPi[j]) ;
|
||
// ripeto punto iniziale di percorso interno
|
||
vPt.push_back( vPi[0]) ;
|
||
// cancello percorso interno
|
||
vPi.clear() ;
|
||
}
|
||
// ripristino l'orientamento delle polilinee originali per il caso non CCW
|
||
if ( ! bCCW) {
|
||
for ( int i = 0 ; i < int( vPL.size()) ; ++i)
|
||
const_cast<PolyLine&>(vPL[i]).Invert( true) ;
|
||
}
|
||
if ( ! bOk)
|
||
return MakeByEC_HPP( vPL, bCCW, vPt, vTr) ;
|
||
|
||
// eseguo la triangolazione
|
||
double dMinMinAng2, dMinMinAng3 ;
|
||
if ( ! MakeByEC2( vPt, vTr, dMinMinAng2)) {
|
||
if ( ! MakeByEC3( vPt, vTr, dMinMinAng3)) {
|
||
LOG_INFO( GetEGkLogger(), "Info : problems in MakeByEC23(N)")
|
||
return MakeByEC_HPP( vPL, bCCW, vPt, vTr) ;
|
||
}
|
||
}
|
||
else if ( dMinMinAng2 < 5) {
|
||
INTVECTOR vMyTr ;
|
||
if ( MakeByEC3( vPt, vMyTr, dMinMinAng3) && dMinMinAng3 > dMinMinAng2)
|
||
vTr = vMyTr ;
|
||
}
|
||
|
||
// se era CW, devo invertire il senso dei triangoli
|
||
if ( ! bCCW)
|
||
reverse( vTr.begin(), vTr.end()) ;
|
||
|
||
return true ;
|
||
}
|
||
|
||
//---------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::MakeAdvanced( const POLYLINEVECTOR& vPLORIG, PNTVECTOR& vPt, INTVECTOR& vTr)
|
||
{
|
||
vPt.clear() ;
|
||
vTr.clear() ;
|
||
// se non ho PolyLine, allora non faccio nulla
|
||
if ( int( vPLORIG.size()) == 0)
|
||
return true ;
|
||
|
||
// copio il vettore di PolyLine
|
||
POLYLINEVECTOR vPL ;
|
||
for ( int i = 0 ; i < int( vPLORIG.size()) ; ++ i)
|
||
vPL.push_back( vPLORIG[i]) ;
|
||
|
||
typedef std::pair<int,double> INDAREA ;
|
||
std::vector<INDAREA> m_vArea ;
|
||
// calcolo piano medio e area delle curve
|
||
m_vArea.reserve( vPL.size()) ;
|
||
Vector3d vtN0 ;
|
||
for ( int i = 0 ; i < int( vPL.size()) ; ++ i) {
|
||
// calcolo piano medio e area
|
||
Plane3d plPlane ;
|
||
double dArea ;
|
||
if ( ! vPL[i].IsClosedAndFlat( plPlane, dArea))
|
||
return false ;
|
||
// imposto la normale del primo contorno come riferimento
|
||
if ( i == 0)
|
||
vtN0 = plPlane.GetVersN() ;
|
||
// verifico che le normali siano molto vicine
|
||
if ( ! AreSameOrOppositeVectorApprox( plPlane.GetVersN(), vtN0))
|
||
return false ;
|
||
// assegno il segno all'area secondo il verso della normale
|
||
if ( ( plPlane.GetVersN() * vtN0) > 0)
|
||
m_vArea.emplace_back( i, dArea) ;
|
||
else
|
||
m_vArea.emplace_back( i, - dArea) ;
|
||
}
|
||
// ordino in senso decrescente sull'area
|
||
sort( m_vArea.begin(), m_vArea.end(),
|
||
[]( const INDAREA& a, const INDAREA& b) { return ( abs( a.second) > abs( b.second)) ; }) ;
|
||
|
||
// dalle PolyLine passo alle curve nel piano XY ( prendo la prima come riferimento, trascuro le Z delle successive)
|
||
Frame3d frRef ; frRef.Set( ORIG, vtN0) ;
|
||
if ( ! frRef.IsValid())
|
||
return false ;
|
||
ICRVCOMPOPOVECTOR vCrvCompo( int( vPL.size())) ;
|
||
for ( int i = 0 ; i < int( vPL.size()) ; ++ i) {
|
||
vCrvCompo[i].Set( CreateCurveComposite()) ;
|
||
vCrvCompo[i]->FromPolyLine( vPL[i]) ;
|
||
vCrvCompo[i]->ToLoc( frRef) ;
|
||
}
|
||
|
||
// creo una matrice di interi ; ogni riga corrisponde ad un chunk, dove in posizione 0 c'è il loop esterno e nelle
|
||
// successive i loop interni
|
||
INTMATRIX vnPLIndMat ;
|
||
|
||
// aggiungo le diverse curve
|
||
bool bFirstCrv ;
|
||
Plane3d plExtLoop ;
|
||
double dAreaExtLoop = 0. ;
|
||
do {
|
||
bFirstCrv = true ;
|
||
for ( int i = 0 ; i < int( m_vArea.size()) ; ++ i) {
|
||
// recupero indice di percorso e verifico sia valido
|
||
int j = m_vArea[i].first ;
|
||
if ( j < 0)
|
||
continue ;
|
||
// lo inserisco come esterno...
|
||
if ( bFirstCrv) {
|
||
vnPLIndMat.push_back({ j}) ;
|
||
m_vArea[i].first = -1 ;
|
||
dAreaExtLoop = m_vArea[i].second ;
|
||
// inverto se necessario
|
||
if ( m_vArea[i].second < EPS_SMALL) {
|
||
vPL[j].Invert() ;
|
||
vCrvCompo[j]->Invert() ;
|
||
dAreaExtLoop *= -1 ;
|
||
}
|
||
bFirstCrv = false ;
|
||
}
|
||
// ... altrimenti verifico se il loop è interno o no
|
||
else {
|
||
// il loop è interno se è sia interno al loop esterno della riga di vnPLIndMat e allo stesso tempo
|
||
// esterno a tutti i loop già inseriti nella riga attuale.
|
||
// verifica rispetto loop esterno
|
||
IntersCurveCurve ccInt( *vCrvCompo[vnPLIndMat.back().front()], *vCrvCompo[j]) ;
|
||
CRVCVECTOR ccClass ;
|
||
if ( ccInt.GetCrossOrOverlapIntersCount() > 0 ||
|
||
! ccInt.GetCurveClassification( 1, EPS_SMALL, ccClass) ||
|
||
ccClass.empty() || ccClass[0].nClass != CRVC_IN)
|
||
continue ;
|
||
// verifica rispetto ai loop interni
|
||
bool bOk = true ;
|
||
for ( int k = 1 ; k < int( vnPLIndMat.back().size()) ; ++ k) {
|
||
IntersCurveCurve ccInt2( *vCrvCompo[vnPLIndMat.back()[k]], *vCrvCompo[j]) ;
|
||
CRVCVECTOR ccClass2 ;
|
||
if ( ccInt2.GetCrossOrOverlapIntersCount() > 0 ||
|
||
! ccInt2.GetCurveClassification( 1, EPS_SMALL, ccClass2) ||
|
||
ccClass2.empty() || ccClass2[0].nClass != CRVC_IN) {
|
||
bOk = false ;
|
||
break ;
|
||
}
|
||
}
|
||
if ( bOk) {
|
||
// inserisco nella matrice
|
||
vnPLIndMat.back().push_back( j) ;
|
||
m_vArea[i].first = -1 ;
|
||
// inverto se necessario
|
||
if ( m_vArea[i].second * dAreaExtLoop > 0.) {
|
||
vPL[j].Invert() ;
|
||
vCrvCompo[j]->Invert() ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} while ( ! bFirstCrv) ;
|
||
|
||
// chiamo la Triangolazione per ogni riga della matrice ( quindi su ogni "Chunk")
|
||
for ( int i = 0 ; i < int( vnPLIndMat.size()) ; ++ i) {
|
||
PNTVECTOR vPt_tmp ; INTVECTOR vTr_tmp ;
|
||
POLYLINEVECTOR vPL_tmp ;
|
||
for ( int j = 0 ; j < int( vnPLIndMat[i].size()) ; ++ j)
|
||
vPL_tmp.push_back( vPL[vnPLIndMat[i][j]]) ;
|
||
if ( ! Make( vPL_tmp, vPt_tmp, vTr_tmp))
|
||
return false ;
|
||
int nSize = int( vPt.size()) ;
|
||
for ( int p = 0 ; p < int( vPt_tmp.size()) ; ++ p)
|
||
vPt.push_back( vPt_tmp[p]) ;
|
||
for ( int t = 0 ; t < int( vTr_tmp.size()) ; ++ t)
|
||
vTr.push_back( nSize + vTr_tmp[t]) ;
|
||
}
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Triangulate the CCW n-gon specified by the vertices vPt (Pt[n] != Pt[0])
|
||
// Ear Clipping algorithm from mapbox
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::MakeByEC_HPP( const PolyLine& PL, bool bCCW, PNTVECTOR& vPt, INTVECTOR& vTr)
|
||
{
|
||
// pulisco i vettori di ritorno
|
||
vPt.clear() ;
|
||
vTr.clear() ;
|
||
// riempio il vettore con i vertici del poligono da triangolare
|
||
vPt.reserve( PL.GetPointNbr() - 1) ;
|
||
// salto il primo punto (coincide con l'ultimo)
|
||
Point3d ptP ;
|
||
if ( ( bCCW && ! PL.GetFirstPoint( ptP)) ||
|
||
( ! bCCW && ! PL.GetLastPoint( ptP)))
|
||
return false ;
|
||
// inserisco i punti
|
||
while ( ( bCCW && PL.GetNextPoint( ptP)) ||
|
||
( ! bCCW && PL.GetPrevPoint( ptP)))
|
||
vPt.push_back( ptP) ;
|
||
// Create array
|
||
using Point = array<double, 2> ;
|
||
vector<vector<Point>> polygon ;
|
||
// Fill polygon structure with actual data. Any winding order works.
|
||
// The first polyline defines the main polygon.
|
||
polygon.resize( 1) ;
|
||
polygon[0].reserve( vPt.size()) ;
|
||
for ( int i = 0 ; i < int( vPt.size()) ; ++i) {
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
polygon[0].push_back( { vPt[i].x, vPt[i].y}) ;
|
||
break ;
|
||
case PL_YZ :
|
||
polygon[0].push_back( { vPt[i].y, vPt[i].z}) ;
|
||
break ;
|
||
case PL_ZX :
|
||
polygon[0].push_back( { vPt[i].z, vPt[i].x}) ;
|
||
break ;
|
||
}
|
||
}
|
||
// Run tessellation
|
||
// Returns array of indices that refer to the vertices of the input polygon.
|
||
// Three subsequent indices form a triangle. Output triangles are counterclockwise.
|
||
vTr = mapbox::earcut<int32_t>( polygon) ;
|
||
if ( vTr.empty()) {
|
||
LOG_INFO( GetEGkLogger(), "Info : problems in MakeByEC_HPP(1)")
|
||
return false ;
|
||
}
|
||
if ( ! bCCW)
|
||
reverse( vTr.begin(), vTr.end()) ;
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::MakeByEC_HPP( const POLYLINEVECTOR& vPL, bool bCCW, PNTVECTOR& vPt, INTVECTOR& vTr)
|
||
{
|
||
// pulisco i vettori di ritorno
|
||
vPt.clear() ;
|
||
vTr.clear() ;
|
||
// Creo il vettore con i punti dei poligoni da triangolare
|
||
// calcolo spazio totale e spazio massimo per un percorso interno
|
||
int nTot = vPL[0].GetPointNbr() - 1 ;
|
||
for ( int i = 1 ; i < int( vPL.size()) ; ++i)
|
||
nTot += vPL[i].GetPointNbr() - 1 ;
|
||
// riservo spazio pari al totale dei punti (esclusi quelli ripetuti)
|
||
vPt.reserve( nTot) ;
|
||
// Create array
|
||
using Point = array<double, 2> ;
|
||
vector<vector<Point>> polygon ;
|
||
polygon.resize( vPL.size()) ;
|
||
// Eseguo riempimento di vettore e array
|
||
for ( int i = 0 ; i < int( vPL.size()) ; ++i) {
|
||
polygon[i].reserve( vPL[i].GetPointNbr() - 1) ;
|
||
// salto il primo punto (coincide con l'ultimo)
|
||
Point3d ptP ;
|
||
if ( ( bCCW && ! vPL[i].GetFirstPoint( ptP)) ||
|
||
( ! bCCW && ! vPL[i].GetLastPoint( ptP)))
|
||
return false ;
|
||
// inserisco i punti
|
||
while ( ( bCCW && vPL[i].GetNextPoint( ptP)) ||
|
||
( ! bCCW && vPL[i].GetPrevPoint( ptP))) {
|
||
vPt.push_back( ptP) ;
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
polygon[i].push_back( { ptP.x, ptP.y}) ;
|
||
break ;
|
||
case PL_YZ :
|
||
polygon[i].push_back( { ptP.y, ptP.z}) ;
|
||
break ;
|
||
case PL_ZX :
|
||
polygon[i].push_back( { ptP.z, ptP.x}) ;
|
||
break ;
|
||
}
|
||
}
|
||
}
|
||
// Run tessellation
|
||
// Returns array of indices that refer to the vertices of the input polygon.
|
||
// Three subsequent indices form a triangle. Output triangles are counterclockwise.
|
||
vTr = mapbox::earcut<int32_t>( polygon) ;
|
||
if ( vTr.empty()) {
|
||
LOG_INFO( GetEGkLogger(), "Info : problems in MakeByEC_HPP(N)")
|
||
return false ;
|
||
}
|
||
if ( ! bCCW)
|
||
reverse( vTr.begin(), vTr.end()) ;
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Triangulate the CCW n-gon specified by the vertices vPt (Pt[n] != Pt[0])
|
||
// Ear Clipping algorithm
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::MakeByEC( const PNTVECTOR& vPt, INTVECTOR& vTr)
|
||
{
|
||
// Clear triangle vector
|
||
vTr.clear() ;
|
||
|
||
// At least 3 points
|
||
int n = int( vPt.size()) ;
|
||
if ( n < 3)
|
||
return false ;
|
||
|
||
// Creo il vettore degli indici del Poligono
|
||
INTVECTOR vPol ;
|
||
vPol.reserve( n) ;
|
||
for ( int i = 0 ; i < n ; ++ i)
|
||
vPol.push_back( i) ;
|
||
|
||
// Preallocate triangle vector ( #triangles = n - 2)
|
||
vTr.reserve( 3 * ( n - 2)) ;
|
||
|
||
// Set up previous and next links to effectively form a double-linked vertex list
|
||
INTVECTOR vPrev( n) ;
|
||
INTVECTOR vNext( n) ;
|
||
for ( int j = 0 ; j < n ; ++ j) {
|
||
vPrev[j] = j - 1 ;
|
||
vNext[j] = j + 1 ;
|
||
}
|
||
vPrev[0] = n - 1 ;
|
||
vNext[n-1] = 0 ;
|
||
|
||
// Start at vertex 0
|
||
int i = 0 ;
|
||
int nCount = n ;
|
||
// Keep removing vertices until just a triangle left
|
||
while ( n >= 3) {
|
||
// To avoid infinite loop
|
||
if ( nCount <= 0)
|
||
return false ;
|
||
-- nCount ;
|
||
// Test if current vertex, v[i], is an ear
|
||
bool bIsEar = TestTriangle( vPt, vPol, vPrev, vNext, i) ;
|
||
bool bSave = bIsEar ;
|
||
// Verify if triangle is null (accept to discard)
|
||
if ( ! bIsEar) {
|
||
if ( Collinear( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]))
|
||
bIsEar = true ;
|
||
}
|
||
// If current vertex v[i] is an ear, delete it and visit the previous vertex
|
||
if ( bIsEar) {
|
||
// Triangle (v[prev[i]], v[i], v[next[i]]) is an ear
|
||
if ( bSave) {
|
||
vTr.push_back( vPol[vPrev[i]]) ;
|
||
vTr.push_back( vPol[i]) ;
|
||
vTr.push_back( vPol[vNext[i]]) ;
|
||
}
|
||
// ‘Delete’ vertex v[i] by redirecting next and previous links
|
||
// of neighboring verts past it. Decrement vertex count
|
||
vNext[vPrev[i]] = vNext[i] ;
|
||
vPrev[vNext[i]] = vPrev[i] ;
|
||
n--;
|
||
// Visit the previous vertex next
|
||
i = vPrev[i] ;
|
||
// Reset Count
|
||
nCount = n ;
|
||
}
|
||
else {
|
||
// Current vertex is not an ear; visit the next vertex
|
||
i = vNext[i] ;
|
||
}
|
||
}
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Triangulate the CCW n-gon specified by the vertices vPt (Pt[n] != Pt[0])
|
||
// Ear Clipping algorithm enhanced, choose smaller diagonal
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::MakeByEC2( const PNTVECTOR& vPt, INTVECTOR& vTr, double& dMinMinAng)
|
||
{
|
||
// Clear triangle vector
|
||
vTr.clear() ;
|
||
dMinMinAng = ANG_STRAIGHT ;
|
||
|
||
// At least 3 points
|
||
int n = int( vPt.size()) ;
|
||
if ( n < 3)
|
||
return false ;
|
||
|
||
// Creo il vettore degli indici del Poligono
|
||
INTVECTOR vPol ;
|
||
vPol.reserve( n) ;
|
||
for ( int i = 0 ; i < n ; ++ i)
|
||
vPol.push_back( i) ;
|
||
|
||
// Preallocate triangle vector ( #triangles = n - 2)
|
||
vTr.reserve( 3 * ( n - 2)) ;
|
||
|
||
// Set up previous and next links to effectively form a double-linked vertex list
|
||
INTVECTOR vPrev( n) ;
|
||
INTVECTOR vNext( n) ;
|
||
for ( int j = 0 ; j < n ; ++ j) {
|
||
vPrev[j] = j - 1 ;
|
||
vNext[j] = j + 1 ;
|
||
}
|
||
vPrev[0] = n - 1 ;
|
||
vNext[n-1] = 0 ;
|
||
|
||
// Prepare Ear status vector
|
||
INTVECTOR vEar( n, EAS_NULL) ;
|
||
|
||
// Prepare PointGrid
|
||
if ( ! PrepareGrid( vPt, vPol, vPrev, vNext))
|
||
return false ;
|
||
|
||
// Start at vertex 0
|
||
int i = 0 ;
|
||
int nCount = n ;
|
||
// Keep removing vertices until just a triangle left
|
||
while ( n >= 3) {
|
||
// To avoid infinite loop
|
||
if ( nCount <= 0)
|
||
return false ;
|
||
-- nCount ;
|
||
// Test if current vertex, v[i], is an ear
|
||
if ( vEar[i] == EAS_NULL)
|
||
vEar[i] = ( TestTriangle( vPt, vPol, vPrev, vNext, i) ? EAS_OK : EAS_NO) ;
|
||
bool bIsEar = ( vEar[i] == EAS_OK) ;
|
||
bool bSave = bIsEar ;
|
||
if ( bIsEar) {
|
||
// Save square distance of diagonal
|
||
double dSqDist = SqDist(vPt[vPol[vPrev[i]]], vPt[vPol[vNext[i]]]) ;
|
||
// Start point for trials
|
||
int j = i ;
|
||
int k = i ;
|
||
// Try with 19 next
|
||
int nLimN = min( 19, n / 2) ;
|
||
for ( int h = 0 ; h < nLimN ; ++ h) {
|
||
j = vNext[j] ;
|
||
double dMySqDist = SqDist( vPt[vPol[vPrev[j]]], vPt[vPol[vNext[j]]]) ;
|
||
if ( dMySqDist < 0.9 * dSqDist) {
|
||
if ( vEar[j] == EAS_NULL)
|
||
vEar[j] = ( TestTriangle( vPt, vPol, vPrev, vNext, j) ? EAS_OK : EAS_NO) ;
|
||
if ( vEar[j] == EAS_OK) {
|
||
dSqDist = dMySqDist ;
|
||
i = j ;
|
||
}
|
||
}
|
||
}
|
||
// Try with 19 prev
|
||
int nLimP = min( 19, n / 2) ;
|
||
for ( int h = 0 ; h < nLimP ; ++ h) {
|
||
k = vPrev[k] ;
|
||
double dMySqDist = SqDist( vPt[vPol[vPrev[k]]], vPt[vPol[vNext[k]]]) ;
|
||
if ( dMySqDist < 0.9 * dSqDist) {
|
||
if ( vEar[k] == EAS_NULL)
|
||
vEar[k] = ( TestTriangle( vPt, vPol, vPrev, vNext, k) ? EAS_OK : EAS_NO) ;
|
||
if ( vEar[k] == EAS_OK) {
|
||
dSqDist = dMySqDist ;
|
||
i = k ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// Verify if triangle is null (accept to discard)
|
||
else {
|
||
if ( Collinear( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]) &&
|
||
! SameDirection( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]))
|
||
bIsEar = true ;
|
||
}
|
||
// If current vertex v[i] is an ear, delete it and visit the previous vertex
|
||
if ( bIsEar) {
|
||
// Triangle (v[prev[i]], v[i], v[next[i]]) is an ear
|
||
if ( bSave) {
|
||
vTr.push_back( vPol[vPrev[i]]) ;
|
||
vTr.push_back( vPol[i]) ;
|
||
vTr.push_back( vPol[vNext[i]]) ;
|
||
double dMinAng = CalcTriangleMinAngle( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]) ;
|
||
dMinMinAng = min( dMinMinAng, dMinAng) ;
|
||
}
|
||
// Reset earity of diagonal endpoints
|
||
vEar[vPrev[i]] = EAS_NULL ;
|
||
vEar[vNext[i]] = EAS_NULL ;
|
||
// ‘Delete’ vertex v[i] by redirecting next and previous links
|
||
// of neighboring verts past it. Decrement vertex count
|
||
vNext[vPrev[i]] = vNext[i] ;
|
||
vPrev[vNext[i]] = vPrev[i] ;
|
||
-- n ;
|
||
// Visit the previous vertex next
|
||
i = vPrev[i] ;
|
||
// Reset Count
|
||
nCount = n ;
|
||
}
|
||
else {
|
||
// Current vertex is not an ear; visit the next vertex
|
||
i = vNext[i] ;
|
||
}
|
||
}
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Triangulate the CCW n-gon specified by the vertices vPt (Pt[n] != Pt[0])
|
||
// Ear Clipping algorithm enhanced, choose max MinAng
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::MakeByEC3( const PNTVECTOR& vPt, INTVECTOR& vTr, double& dMinMinAng)
|
||
{
|
||
// Clear triangle vector
|
||
vTr.clear() ;
|
||
dMinMinAng = ANG_STRAIGHT ;
|
||
|
||
// At least 3 points
|
||
int n = int( vPt.size()) ;
|
||
if ( n < 3)
|
||
return false ;
|
||
|
||
// Creo il vettore degli indici del Poligono
|
||
INTVECTOR vPol ;
|
||
vPol.reserve( n) ;
|
||
for ( int i = 0 ; i < n ; ++ i)
|
||
vPol.push_back( i) ;
|
||
|
||
// Preallocate triangle vector ( #triangles = n - 2)
|
||
vTr.reserve( 3 * ( n - 2)) ;
|
||
|
||
// Set up previous and next links to effectively form a double-linked vertex list
|
||
INTVECTOR vPrev( n) ;
|
||
INTVECTOR vNext( n) ;
|
||
for ( int j = 0 ; j < n ; ++ j) {
|
||
vPrev[j] = j - 1 ;
|
||
vNext[j] = j + 1 ;
|
||
}
|
||
vPrev[0] = n - 1 ;
|
||
vNext[n-1] = 0 ;
|
||
|
||
// Prepare Ear status vector
|
||
INTVECTOR vEar( n, EAS_NULL) ;
|
||
|
||
// Prepare PointGrid
|
||
if ( ! PrepareGrid( vPt, vPol, vPrev, vNext))
|
||
return false ;
|
||
|
||
// Start at vertex 0
|
||
int i = 0 ;
|
||
int nCount = n ;
|
||
// Keep removing vertices until just a triangle left
|
||
while ( n >= 3) {
|
||
// To avoid infinite loop
|
||
if ( nCount <= 0)
|
||
return false ;
|
||
-- nCount ;
|
||
// Test if current vertex, v[i], is an ear
|
||
if ( vEar[i] == EAS_NULL)
|
||
vEar[i] = ( TestTriangle( vPt, vPol, vPrev, vNext, i) ? EAS_OK : EAS_NO) ;
|
||
bool bIsEar = ( vEar[i] == EAS_OK) ;
|
||
bool bSave = bIsEar ;
|
||
double dMinAng = -1 ;
|
||
if ( bIsEar) {
|
||
// Square Length & MinAngle
|
||
double dSqLen = SqDist(vPt[vPol[vPrev[i]]], vPt[vPol[vNext[i]]]) ;
|
||
dMinAng = CalcTriangleMinAngle( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]) ;
|
||
// Start point for trials
|
||
int j = i ;
|
||
int k = i ;
|
||
// Try with 29 next
|
||
int nLimN = min( 29, n / 2) ;
|
||
for ( int h = 0 ; h < nLimN ; ++ h) {
|
||
j = vNext[j] ;
|
||
double dMySqLen = SqDist( vPt[vPol[vPrev[j]]], vPt[vPol[vNext[j]]]) ;
|
||
if ( dMySqLen < 2 * dSqLen) {
|
||
double dMyMinAng = CalcTriangleMinAngle( vPt[vPol[vPrev[j]]], vPt[vPol[j]], vPt[vPol[vNext[j]]]) ;
|
||
if ( dMyMinAng > 1.01 * dMinAng) {
|
||
if ( vEar[j] == EAS_NULL)
|
||
vEar[j] = ( TestTriangle( vPt, vPol, vPrev, vNext, j) ? EAS_OK : EAS_NO) ;
|
||
if ( vEar[j] == EAS_OK) {
|
||
dSqLen = dMySqLen ;
|
||
dMinAng = dMyMinAng ;
|
||
i = j ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// Try with 29 prev
|
||
int nLimP = min( 29, n / 2) ;
|
||
for ( int h = 0 ; h < nLimP ; ++ h) {
|
||
k = vPrev[k] ;
|
||
double dMySqLen = SqDist( vPt[vPol[vPrev[k]]], vPt[vPol[vNext[k]]]) ;
|
||
if ( dMySqLen < 2 * dSqLen) {
|
||
double dMyMinAng = CalcTriangleMinAngle( vPt[vPol[vPrev[k]]], vPt[vPol[k]], vPt[vPol[vNext[k]]]) ;
|
||
if ( dMyMinAng > 1.01 * dMinAng) {
|
||
if ( vEar[k] == EAS_NULL)
|
||
vEar[k] = ( TestTriangle( vPt, vPol, vPrev, vNext, k) ? EAS_OK : EAS_NO) ;
|
||
if ( vEar[k] == EAS_OK) {
|
||
dSqLen = dMySqLen ;
|
||
dMinAng = dMyMinAng ;
|
||
i = k ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// Verify if triangle is null (accept to discard)
|
||
else {
|
||
if ( Collinear( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]) &&
|
||
! SameDirection( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]))
|
||
bIsEar = true ;
|
||
}
|
||
// If current vertex v[i] is an ear, delete it and visit the previous vertex
|
||
if ( bIsEar) {
|
||
// Triangle (v[prev[i]], v[i], v[next[i]]) is an ear to save
|
||
if ( bSave) {
|
||
vTr.push_back( vPol[vPrev[i]]) ;
|
||
vTr.push_back( vPol[i]) ;
|
||
vTr.push_back( vPol[vNext[i]]) ;
|
||
dMinMinAng = min( dMinMinAng, dMinAng) ;
|
||
}
|
||
// Reset earity of diagonal endpoints
|
||
vEar[vPrev[i]] = EAS_NULL ;
|
||
vEar[vNext[i]] = EAS_NULL ;
|
||
// ‘Delete’ vertex v[i] by redirecting next and previous links
|
||
// of neighboring verts past it. Decrement vertex count
|
||
vNext[vPrev[i]] = vNext[i] ;
|
||
vPrev[vNext[i]] = vPrev[i] ;
|
||
-- n ;
|
||
// Visit the previous vertex next
|
||
i = vPrev[i] ;
|
||
// Reset Count
|
||
nCount = n ;
|
||
}
|
||
else {
|
||
// Current vertex is not an ear; visit the next vertex
|
||
i = vNext[i] ;
|
||
}
|
||
}
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::PrepareGrid( const PNTVECTOR& vPt, const INTVECTOR& vPol,
|
||
const INTVECTOR& vPrev, const INTVECTOR& vNext)
|
||
{
|
||
// points number
|
||
int n = int( vPol.size()) ;
|
||
// overall box
|
||
BBox3d b3All ;
|
||
for ( int j = 0 ; j < n ; ++ j)
|
||
b3All.Add( vPt[vPol[j]]) ;
|
||
// grid cell dimension
|
||
double dCellDim ;
|
||
b3All.GetRadius( dCellDim) ;
|
||
dCellDim *= 2 / sqrt( n) ;
|
||
// grid
|
||
if ( ! m_VertGrid.Init( 2 * n, dCellDim))
|
||
return false ;
|
||
for ( int j = 0 ; j < n ; ++ j) {
|
||
// insert only reflex vertex
|
||
if ( ! TriangleIsCCW( vPt[vPol[vPrev[j]]], vPt[vPol[j]], vPt[vPol[vNext[j]]])) {
|
||
if ( ! m_VertGrid.InsertPoint( vPt[vPol[j]], j))
|
||
return false ;
|
||
}
|
||
}
|
||
m_vVert.reserve( n / 5) ;
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::TestTriangle( const PNTVECTOR& vPt, const INTVECTOR& vPol,
|
||
const INTVECTOR& vPrev, INTVECTOR& vNext, int i)
|
||
{
|
||
// Test if current vertex, v[i], is an ear
|
||
bool bIsEar = true ;
|
||
// An ear must be convex (here counterclockwise)
|
||
if ( TriangleIsCCW( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]]) &&
|
||
! Collinear( vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]])) {
|
||
// Loop over all vertices not part of the tentative ear
|
||
BBox3d b3Tria ;
|
||
b3Tria.Add( vPt[vPol[vPrev[i]]]) ;
|
||
b3Tria.Add( vPt[vPol[i]]) ;
|
||
b3Tria.Add( vPt[vPol[vNext[i]]]) ;
|
||
// espando per compensare piccole approssimazioni
|
||
b3Tria.Expand( 10 * EPS_SMALL) ;
|
||
m_VertGrid.Find( b3Tria, m_vVert) ;
|
||
for ( int j = 0 ; j < int( m_vVert.size()) ; ++ j) {
|
||
int k = m_vVert[j] ;
|
||
if ( k == i || k == vPrev[i] || k == vNext[i] )
|
||
continue ;
|
||
// If vertex k is on a triangle vertex
|
||
if ( AreSamePoint( vPt[vPol[k]], vPt[vPol[vPrev[i]]]) ||
|
||
AreSamePoint( vPt[vPol[k]], vPt[vPol[i]]) ||
|
||
AreSamePoint( vPt[vPol[k]], vPt[vPol[vNext[i]]])) {
|
||
// Test previous segment with triangle diagonal
|
||
if ( TestIntersection( vPt[vPol[vPrev[k]]], vPt[vPol[k]], vPt[vPol[vPrev[i]]], vPt[vPol[vNext[i]]])) {
|
||
bIsEar = false ;
|
||
break ;
|
||
}
|
||
// Test next segment with triangle diagonal
|
||
if ( TestIntersection( vPt[vPol[k]], vPt[vPol[vNext[k]]], vPt[vPol[vPrev[i]]], vPt[vPol[vNext[i]]])) {
|
||
bIsEar = false ;
|
||
break ;
|
||
}
|
||
}
|
||
// If vertex k is inside the ear triangle, then this is not an ear
|
||
else if ( TestPointInTriangle( vPt[vPol[k]], vPt[vPol[vPrev[i]]], vPt[vPol[i]], vPt[vPol[vNext[i]]])) {
|
||
bIsEar = false ;
|
||
break ;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
// The ‘ear’ triangle is clockwise so v[i] is not an ear
|
||
bIsEar = false ;
|
||
}
|
||
|
||
return bIsEar ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Ratio between Circumcircle Radius and Incircle Diameter (minimum 1 for equilateral)
|
||
double
|
||
Triangulate::CalcTriangleAspectRatio( const Point3d& ptPa, const Point3d& ptPb, const Point3d& ptPc)
|
||
{
|
||
double dSqDistA = SquareDist( ptPa, ptPb) ;
|
||
double dSqDistB = SquareDist( ptPb, ptPc) ;
|
||
double dSqDistC = SquareDist( ptPc, ptPa) ;
|
||
double dTwoArea = abs( TwoArea( ptPa, ptPb, ptPc)) ;
|
||
if ( dTwoArea < SQ_EPS_SMALL)
|
||
return INFINITO ;
|
||
else
|
||
return ( max( { dSqDistA, dSqDistB, dSqDistC}) / dTwoArea) ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
double
|
||
Triangulate::CalcTriangleMinAngle( const Point3d& ptPa, const Point3d& ptPb, const Point3d& ptPc)
|
||
{
|
||
double dSqLenA = SquareDist( ptPa, ptPb) ;
|
||
double dSqLenB = SquareDist( ptPb, ptPc) ;
|
||
double dSqLenC = SquareDist( ptPc, ptPa) ;
|
||
if ( dSqLenA > dSqLenB)
|
||
swap( dSqLenA, dSqLenB) ;
|
||
if ( dSqLenA > dSqLenC)
|
||
swap( dSqLenA, dSqLenC) ;
|
||
double dLenB = sqrt( dSqLenB) ;
|
||
double dLenC = sqrt( dSqLenC) ;
|
||
double dCosA = ( dSqLenB + dSqLenC - dSqLenA) / ( 2 * dLenB * dLenC) ;
|
||
dCosA = Clamp( dCosA, -1., +1.) ;
|
||
double dAngA = acos( dCosA) * RADTODEG ;
|
||
return dAngA ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
double
|
||
Triangulate::SquareDist( const Point3d& ptA, const Point3d& ptB)
|
||
{
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
return (( ptB.x - ptA.x) * ( ptB.x - ptA.x) + ( ptB.y - ptA.y) * ( ptB.y - ptA.y)) ;
|
||
case PL_YZ :
|
||
return (( ptB.y - ptA.y) * ( ptB.y - ptA.y) + ( ptB.z - ptA.z) * ( ptB.z - ptA.z)) ;
|
||
case PL_ZX :
|
||
return (( ptB.z - ptA.z) * ( ptB.z - ptA.z) + ( ptB.x - ptA.x) * ( ptB.x - ptA.x)) ;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Vector product is double of the area (positive if CCW)
|
||
double
|
||
Triangulate::TwoArea( const Point3d& ptA, const Point3d& ptB, const Point3d& ptC)
|
||
{
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
return ( ptB.x - ptA.x) * ( ptC.y - ptB.y) - ( ptB.y - ptA.y) * ( ptC.x - ptB.x) ;
|
||
case PL_YZ :
|
||
return ( ptB.y - ptA.y) * ( ptC.z - ptB.z) - ( ptB.z - ptA.z) * ( ptC.y - ptB.y) ;
|
||
case PL_ZX :
|
||
return ( ptB.z - ptA.z) * ( ptC.x - ptB.x) - ( ptB.x - ptA.x) * ( ptC.z - ptB.z) ;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Distance less than tolerance
|
||
bool
|
||
Triangulate::AreSamePoint( const Point3d& ptA, const Point3d& ptB, double dToler)
|
||
{
|
||
return ( SquareDist( ptA, ptB) < dToler * dToler) ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Same Direction <--> Scalar Product Positive
|
||
bool
|
||
Triangulate::SameDirection( const Point3d& ptA, const Point3d& ptB, const Point3d& ptC)
|
||
{
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
return (( ptB.x - ptA.x) * ( ptC.x - ptB.x) + ( ptB.y - ptA.y) * ( ptC.y - ptB.y)) > 0 ;
|
||
case PL_YZ :
|
||
return (( ptB.y - ptA.y) * ( ptC.y - ptB.y) + ( ptB.z - ptA.z) * ( ptC.z - ptB.z)) > 0 ;
|
||
case PL_ZX :
|
||
return (( ptB.z - ptA.z) * ( ptC.z - ptB.z) + ( ptB.x - ptA.x) * ( ptC.x - ptB.x)) > 0 ;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Collinear <--> Null Area
|
||
bool
|
||
Triangulate::Collinear( const Point3d& ptA, const Point3d& ptB, const Point3d& ptC, double dToler)
|
||
{
|
||
double dSqDistA = SquareDist( ptA, ptB) ;
|
||
double dSqDistB = SquareDist( ptB, ptC) ;
|
||
double dSqDistC = SquareDist( ptC, ptA) ;
|
||
double dTwoArea = abs( TwoArea( ptA, ptB, ptC)) ;
|
||
return ( dTwoArea * dTwoArea < dToler * dToler * max( { dSqDistA, dSqDistB, dSqDistC})) ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Positive Area means A -> B -> C counterclockwise
|
||
bool
|
||
Triangulate::TriangleIsCCW( const Point3d& ptA, const Point3d& ptB, const Point3d& ptC, double dToler)
|
||
{
|
||
return ( TwoArea( ptA, ptB, ptC) > dToler * dToler) ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Proper intersection between line segments
|
||
bool
|
||
Triangulate::TestIntersection( const Point3d& ptA1, const Point3d& ptA2,
|
||
const Point3d& ptB1, const Point3d& ptB2)
|
||
{
|
||
// Collinearity is not considered intersection
|
||
if ( Collinear( ptA1, ptA2, ptB1) ||
|
||
Collinear( ptA1, ptA2, ptB2) ||
|
||
Collinear( ptB1, ptB2, ptA1) ||
|
||
Collinear( ptB1, ptB2, ptA2))
|
||
return false ;
|
||
// To intersect, the endpoints of a line segment must be on opposite side of the other line segment
|
||
return ( TriangleIsCCW( ptA1, ptA2, ptB1) != TriangleIsCCW( ptA1, ptA2, ptB2) &&
|
||
TriangleIsCCW( ptB1, ptB2, ptA1) != TriangleIsCCW( ptB1, ptB2, ptA2)) ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// Test if point p is contained in triangle (a, b, c)
|
||
bool
|
||
Triangulate::TestPointInTriangle( const Point3d& ptP, const Point3d& ptA, const Point3d& ptB, const Point3d& ptC)
|
||
{
|
||
// If P is on a vertex is considered outside
|
||
if ( AreSamePoint( ptP, ptA))
|
||
return false ;
|
||
if ( AreSamePoint( ptP, ptB))
|
||
return false ;
|
||
if ( AreSamePoint( ptP, ptC))
|
||
return false ;
|
||
// If P is on the right of at least one edge is outside
|
||
if ( TriangleIsCCW( ptA, ptP, ptB))
|
||
return false ;
|
||
if ( TriangleIsCCW( ptB, ptP, ptC))
|
||
return false ;
|
||
if ( TriangleIsCCW( ptC, ptP, ptA))
|
||
return false ;
|
||
// P is in triangle
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::SortInternalLoops( const POLYLINEVECTOR& vPL, INTVECTOR& vOrd)
|
||
{
|
||
// riempio vettore con indice e massimo specifico per ogni loop interno
|
||
typedef pair<int,double> INDMAX ; // coppia indice, massimo
|
||
typedef vector<INDMAX> INDMAXVECTOR ; // vettore di coppie indice, massimo
|
||
INDMAXVECTOR vMax ;
|
||
vMax.reserve( vPL.size()) ;
|
||
vMax.emplace_back( 0, 0.0) ;
|
||
for ( int i = 1 ; i < int( vPL.size()) ; ++ i) {
|
||
BBox3d b3Loc ;
|
||
vPL[i].GetLocalBBox( b3Loc) ;
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
vMax.emplace_back( i, b3Loc.GetMax().x) ;
|
||
break ;
|
||
case PL_YZ :
|
||
vMax.emplace_back( i, b3Loc.GetMax().y) ;
|
||
break ;
|
||
case PL_ZX :
|
||
vMax.emplace_back( i, b3Loc.GetMax().z) ;
|
||
break ;
|
||
}
|
||
}
|
||
// ordino vettore in senso decrescente rispetto al massimo
|
||
sort( vMax.begin() + 1, vMax.end(),
|
||
[]( const INDMAX& a, const INDMAX& b) { return ( a.second > b.second) ; }) ;
|
||
// copio indice nel vettore di ordine
|
||
vOrd.reserve( vPL.size()) ;
|
||
for ( int i = 0 ; i < int( vPL.size()) ; ++ i)
|
||
vOrd.push_back( vMax[i].first) ;
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::GetPntVectorFromPolyline( const PolyLine& PL, bool bXmaxStart, PNTVECTOR& vPi)
|
||
{
|
||
// copio i punti nel vettore (tranne il primo che coincide con l'ultimo)
|
||
Point3d ptP ;
|
||
if ( ! PL.GetFirstPoint( ptP))
|
||
return false ;
|
||
while ( PL.GetNextPoint( ptP)) {
|
||
vPi.push_back( ptP) ;
|
||
}
|
||
// se non richiesto riordino per Xmax o Ymax o Zmax, ho finito
|
||
if ( ! bXmaxStart)
|
||
return true ;
|
||
// determino l'indice del punto con Xmax
|
||
int nI = - 1 ;
|
||
double dXmax = - INFINITO ;
|
||
for ( int i = 0 ; i < int( vPi.size()) ; ++ i) {
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
if ( vPi[i].x > dXmax) {
|
||
dXmax = vPi[i].x ;
|
||
nI = i ;
|
||
}
|
||
break ;
|
||
case PL_YZ :
|
||
if ( vPi[i].y > dXmax) {
|
||
dXmax = vPi[i].y ;
|
||
nI = i ;
|
||
}
|
||
break ;
|
||
case PL_ZX :
|
||
if ( vPi[i].z > dXmax) {
|
||
dXmax = vPi[i].z ;
|
||
nI = i ;
|
||
}
|
||
break ;
|
||
}
|
||
}
|
||
if ( nI == - 1)
|
||
return false ;
|
||
// riordino il vettore, per avere il punto con Xmax in prima posizione
|
||
return ChangeStartPntVector( nI, vPi) ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::GetOuterPntToJoin( const PNTVECTOR& vPt, const Point3d& ptP, int& nI)
|
||
{
|
||
// cerco prima intersezione del raggio dal punto con direzione X+ al contorno esterno
|
||
nI = - 1 ;
|
||
double dMinDist = INFINITO ;
|
||
Point3d ptInt ;
|
||
int nNumPt = int( vPt.size()) ;
|
||
for ( int i = 0 ; i < nNumPt ; ++ i) {
|
||
// indice punto precedente
|
||
int h = ( i - 1 >= 0 ) ? i - 1 : nNumPt - 1 ;
|
||
// indice punto successivo
|
||
int j = ( i + 1 < nNumPt) ? i + 1 : 0 ;
|
||
// mi metto nel piano principale
|
||
switch ( m_nPlane) {
|
||
default : // PL_XY
|
||
// se i punti coincidono
|
||
if ( abs( vPt[i].x - ptP.x) < EPS_SMALL && abs( vPt[i].y - ptP.y) < EPS_SMALL) {
|
||
dMinDist = 0 ;
|
||
nI = i ;
|
||
ptInt = vPt[i] ;
|
||
}
|
||
// se punto esattamente sul raggio e raggio interno al settore
|
||
else if ( vPt[i].x > ptP.x && abs( vPt[i].y - ptP.y) < EPS_SMALL &&
|
||
PointInSector( ptP, vPt[h], vPt[i], vPt[j])) {
|
||
// se distanza minore della minima, nuovo minimo
|
||
if ( ( vPt[i].x - ptP.x) < dMinDist) {
|
||
dMinDist = vPt[i].x - ptP.x ;
|
||
nI = i ;
|
||
ptInt = vPt[i] ;
|
||
}
|
||
}
|
||
// se segmento al punto successivo che attraversa il raggio e raggio interno ovvero a sinistra ( segmento crescente in Y)
|
||
else if ( vPt[i].y < ptP.y && vPt[j].y > ptP.y) {
|
||
// calcolo l'ascissa di intersezione
|
||
double dCoeff = ( ptP.y - vPt[i].y) / ( vPt[j].y - vPt[i].y) ;
|
||
double dX = vPt[i].x + ( vPt[j].x - vPt[i].x) * dCoeff ;
|
||
// se sta sul raggio e distanza minore della minima
|
||
if ( dX > ptP.x - EPS_SMALL && ( dX - ptP.x) < dMinDist) {
|
||
dMinDist = dX - ptP.x ;
|
||
nI = ( vPt[i].x >= vPt[j].x) ? i : j ;
|
||
double dZ = vPt[i].z + ( vPt[j].z - vPt[i].z) * dCoeff ;
|
||
ptInt.Set( dX, ptP.y, dZ) ;
|
||
}
|
||
}
|
||
break ;
|
||
case PL_YZ :
|
||
// se i punti coincidono
|
||
if ( abs( vPt[i].y - ptP.y) < EPS_SMALL && abs( vPt[i].z - ptP.z) < EPS_SMALL) {
|
||
dMinDist = 0 ;
|
||
nI = i ;
|
||
ptInt = vPt[i] ;
|
||
}
|
||
// se punto esattamente sul raggio e raggio interno al settore
|
||
else if ( vPt[i].y > ptP.y && abs( vPt[i].z - ptP.z) < EPS_SMALL &&
|
||
PointInSector( ptP, vPt[h], vPt[i], vPt[j])) {
|
||
// se distanza minore della minima, nuovo minimo
|
||
if ( ( vPt[i].y - ptP.y) < dMinDist) {
|
||
dMinDist = vPt[i].y - ptP.y ;
|
||
nI = i ;
|
||
ptInt = vPt[i] ;
|
||
}
|
||
}
|
||
// se segmento al punto successivo che attraversa il raggio e raggio interno ovvero a sinistra ( segmento crescente in Y)
|
||
else if ( vPt[i].z < ptP.z && vPt[j].z > ptP.z) {
|
||
// calcolo l'ascissa di intersezione
|
||
double dCoeff = ( ptP.z - vPt[i].z) / ( vPt[j].z - vPt[i].z) ;
|
||
double dY = vPt[i].y + ( vPt[j].y - vPt[i].y) * dCoeff ;
|
||
// se sta sul raggio e distanza minore della minima
|
||
if ( dY > ptP.y - EPS_SMALL && ( dY - ptP.y) < dMinDist) {
|
||
dMinDist = dY - ptP.y ;
|
||
nI = ( vPt[i].y >= vPt[j].y) ? i : j ;
|
||
double dX = vPt[i].x + ( vPt[j].x - vPt[i].x) * dCoeff ;
|
||
ptInt.Set( dX, dY, ptP.z) ;
|
||
}
|
||
}
|
||
break ;
|
||
case PL_ZX :
|
||
// se i punti coincidono
|
||
if ( abs( vPt[i].z - ptP.z) < EPS_SMALL && abs( vPt[i].x - ptP.x) < EPS_SMALL) {
|
||
dMinDist = 0 ;
|
||
nI = i ;
|
||
ptInt = vPt[i] ;
|
||
}
|
||
// se punto esattamente sul raggio e raggio interno al settore
|
||
else if ( vPt[i].z > ptP.z && abs( vPt[i].x - ptP.x) < EPS_SMALL &&
|
||
PointInSector( ptP, vPt[h], vPt[i], vPt[j])) {
|
||
// se distanza minore della minima, nuovo minimo
|
||
if ( ( vPt[i].z - ptP.z) < dMinDist) {
|
||
dMinDist = vPt[i].z - ptP.z ;
|
||
nI = i ;
|
||
ptInt = vPt[i] ;
|
||
}
|
||
}
|
||
// se segmento al punto successivo che attraversa il raggio e raggio interno ovvero a sinistra ( segmento crescente in Y)
|
||
else if ( vPt[i].x < ptP.x && vPt[j].x > ptP.x) {
|
||
// calcolo l'ascissa di intersezione
|
||
double dCoeff = ( ptP.x - vPt[i].x) / ( vPt[j].x - vPt[i].x) ;
|
||
double dZ = vPt[i].z + ( vPt[j].z - vPt[i].z) * dCoeff ;
|
||
// se sta sul raggio e distanza minore della minima
|
||
if ( dZ > ptP.z - EPS_SMALL && ( dZ - ptP.z) < dMinDist) {
|
||
dMinDist = dZ - ptP.z ;
|
||
nI = ( vPt[i].z >= vPt[j].z) ? i : j ;
|
||
double dY = vPt[i].y + ( vPt[j].y - vPt[i].y) * dCoeff ;
|
||
ptInt.Set( ptP.x, dY, dZ) ;
|
||
}
|
||
}
|
||
break ;
|
||
}
|
||
}
|
||
// non ho trovato alcunché, errore
|
||
if ( nI == - 1)
|
||
return false ;
|
||
// se ho trovato un punto esatto del contorno, non devo fare altri controlli
|
||
if ( AreSamePointApprox( ptInt, vPt[nI]))
|
||
return true ;
|
||
// devo ora verificare che il segmento che unisce i punti non intersechi altri lati del contorno esterno
|
||
// altrimenti tengo il punto con raggio più vicino a X_AX o Y_AX o Z_AX secondo m_nPlane
|
||
int nJ = nI ;
|
||
Point3d ptPa = ptP ;
|
||
Point3d ptPb = vPt[nI] ;
|
||
Point3d ptPc = ptInt ;
|
||
bool bSwap = false ;
|
||
switch ( m_nPlane) {
|
||
default : /* PL_XY */ bSwap = ( ptPb.y > ptPc.y) ; break ;
|
||
case PL_YZ : bSwap = ( ptPb.z > ptPc.z) ; break ;
|
||
case PL_ZX : bSwap = ( ptPb.x > ptPc.x) ; break ;
|
||
}
|
||
if ( bSwap)
|
||
swap( ptPb, ptPc) ;
|
||
double dMinTan = INFINITO ;
|
||
double dMinSqDist = SQ_INFINITO ;
|
||
for ( int i = 0 ; i < nNumPt ; ++ i) {
|
||
// salto il punto già trovato
|
||
if ( i == nJ)
|
||
continue ;
|
||
// verifico se sta nel triangolo
|
||
if ( TestPointInTriangle( vPt[i], ptPa, ptPb, ptPc)) {
|
||
double dTan = INFINITO ;
|
||
switch ( m_nPlane) {
|
||
default : /* PL_XY */ dTan = abs(vPt[i].y - ptP.y) / (vPt[i].x - ptP.x) ; break ;
|
||
case PL_YZ : dTan = abs(vPt[i].z - ptP.z) / (vPt[i].y - ptP.y) ; break ;
|
||
case PL_ZX : dTan = abs(vPt[i].x - ptP.x) / (vPt[i].z - ptP.z) ; break ;
|
||
}
|
||
if ( dTan < dMinTan + EPS_ZERO) {
|
||
// indice punto precedente
|
||
int h = ( i - 1 >= 0) ? i - 1 : nNumPt - 1 ;
|
||
// indice punto successivo
|
||
int j = ( i + 1 < nNumPt) ? i + 1 : 0 ;
|
||
// verifico che il raggio che unisce i punti stia nel settore
|
||
if ( PointInSector( ptP, vPt[h], vPt[i], vPt[j])) {
|
||
double dSqDist = SqDist( vPt[i], ptP) ;
|
||
if ( dTan < dMinTan - EPS_ZERO || dSqDist < dMinSqDist) {
|
||
dMinTan = dTan ;
|
||
dMinSqDist = dSqDist ;
|
||
nI = i ;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return true ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
Triangulate::PointInSector( const Point3d& ptTest, const Point3d& ptPrev, const Point3d& ptCorn, const Point3d& ptNext)
|
||
{
|
||
// la parte valida del settore è a sinistra dei segmenti ptPrev --> ptCorn --> ptNext
|
||
// se corner convesso
|
||
if ( TriangleIsCCW( ptPrev, ptCorn, ptNext, 0))
|
||
return ( TriangleIsCCW( ptPrev, ptCorn, ptTest) &&
|
||
TriangleIsCCW( ptTest, ptCorn, ptNext)) ;
|
||
// altrimenti corner concavo ( reflex)
|
||
else
|
||
return ! ( TriangleIsCCW( ptTest, ptCorn, ptPrev, 0) &&
|
||
TriangleIsCCW( ptNext, ptCorn, ptTest, 0)) ;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool
|
||
ChangeStartPntVector( int nNewStart, PNTVECTOR& vPi)
|
||
{
|
||
// se il nuovo inizio coincide col vecchio, non devo fare alcunché
|
||
if ( nNewStart == 0)
|
||
return true ;
|
||
// se il nuovo indice è oltre la dimensione del vettore, errore
|
||
if ( nNewStart >= int( vPi.size()))
|
||
return false ;
|
||
// ciclo di aggiustamento
|
||
rotate( vPi.begin(), vPi.begin() + nNewStart, vPi.end()) ;
|
||
return true ;
|
||
}
|