EgtGeomKernel :
- corretta la funzione per il trim di sup. di Bezier con piani, in zone di salto per periodicità e in caso di tagli multipli.
This commit is contained in:
+159
-178
@@ -36,6 +36,7 @@
|
||||
#include "/EgtDev/Extern/Eigen/Dense"
|
||||
#include "/EgtDev/Include/EGkCurveComposite.h"
|
||||
#include "/EgtDev/Include/EGkGeoObjSave.h"
|
||||
#include <limits>
|
||||
|
||||
using namespace std ;
|
||||
|
||||
@@ -1647,6 +1648,22 @@ SurfBezier::AddCurveCompoToCuts( ICurveComposite* pCrvCompo, ICRVCOMPOPOVECTOR&
|
||||
return true ;
|
||||
}
|
||||
|
||||
typedef tuple<int,int,int> TRINT ;
|
||||
|
||||
//static bool operator==(TRINT& ta, TRINT& tb)
|
||||
//{
|
||||
// return get<0>(ta) == get<0>(tb) && get<1>(ta) == get<1>(tb) && get<2>(ta) == get<2>(tb) ;
|
||||
//}
|
||||
|
||||
template<>
|
||||
struct hash<TRINT> {
|
||||
std::size_t operator()(const TRINT& t) const
|
||||
{
|
||||
// Compute individual hash values for first, second and third and combine them using XOR and bit shifting:
|
||||
return ((hash<int>()(get<0>(t))) ^ (hash<int>()(get<1>(t)) << 1) >> 1) ^ (hash<int>()(get<2>(t)) << 1) ;
|
||||
}
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool
|
||||
SurfBezier::Cut( const Plane3d& plPlane, bool bSaveOnEq)
|
||||
@@ -1772,12 +1789,12 @@ SurfBezier::Cut( const Plane3d& plPlane, bool bSaveOnEq)
|
||||
}
|
||||
AddCurveCompoToCuts( pCrvCompo, vpCCOpen, vpCCClosed) ;
|
||||
}
|
||||
////debug
|
||||
//vector<IGeoObj*> vGeoObj ;
|
||||
//for ( int i = 0 ; i < int(vpCCOpen.size()); ++i )
|
||||
// vGeoObj.emplace_back(static_cast<IGeoObj*>( vpCCOpen[i]->Clone())) ;
|
||||
//SaveGeoObj( vGeoObj, "D:\\Temp\\inters\\sphere_openCuts_beforeJoint.nge") ;
|
||||
////debug
|
||||
//debug
|
||||
vector<IGeoObj*> vGeoObj ;
|
||||
for ( int i = 0 ; i < int(vpCCOpen.size()); ++i )
|
||||
vGeoObj.emplace_back(static_cast<IGeoObj*>( vpCCOpen[i]->Clone())) ;
|
||||
SaveGeoObj( vGeoObj, "D:\\Temp\\inters\\sphere_openCuts_beforeJoint.nge") ;
|
||||
//debug
|
||||
if ( int( vpCCOpen.size()) != 0) {
|
||||
// devo verificare se devo giuntare la prima curva aperta con l'ultima
|
||||
Point3d ptStartOpen, ptEndOpen ;
|
||||
@@ -1790,188 +1807,162 @@ SurfBezier::Cut( const Plane3d& plPlane, bool bSaveOnEq)
|
||||
}
|
||||
}
|
||||
|
||||
// creo il nuovo bordo usando i loop aperti
|
||||
//POLYLINEMATRIX vPolygons ;
|
||||
|
||||
//comincio a creare la superficie aggiungendo i tagli aperti ai bordi attualmente esistenti
|
||||
SurfFlatRegionByContours sfrContour ;
|
||||
if ( int(vpCCOpen.size()) != 0 ) {
|
||||
//Tree Tree(Point3d( 0, 0, 0), Point3d( m_nSpanU * SBZ_TREG_COEFF, m_nSpanV * SBZ_TREG_COEFF)) ;
|
||||
//POLYLINEVECTOR vPL ;
|
||||
//for ( int i = 0 ; i < int(vpCCOpen.size()); ++i ) {
|
||||
// PolyLine pl ;
|
||||
// vpCCOpen[i]->ApproxWithLines( LIN_TOL_FINE, ANG_TOL_STD_DEG, ICurveComposite::APL_STD, pl) ;
|
||||
// vPL.emplace_back( pl) ;
|
||||
//}
|
||||
//Tree.AddCutsToRoot( vPL) ;
|
||||
|
||||
//////debug
|
||||
////vector<IGeoObj*> vGeoObj ;
|
||||
////for ( int k = 0 ; k < int( vPL.size()) ; ++ k) {
|
||||
//// PtrOwner<ICurveComposite> pCrv( CreateCurveComposite()) ;
|
||||
//// pCrv->FromPolyLine( vPL[k]) ;
|
||||
//// vGeoObj.emplace_back( static_cast<IGeoObj*>(Release( pCrv))) ;
|
||||
////}
|
||||
////SaveGeoObj( vGeoObj, "D:\\Temp\\inters\\sphere_openCuts.nge") ;
|
||||
//////debug
|
||||
|
||||
//Tree.CreateCellContour( vPolygons) ;
|
||||
|
||||
|
||||
|
||||
// qui devo aggiungere tutto del codice nuovo per ricostruire in altro modo il nuovo bordo della superficie
|
||||
// recupero la regione attuale
|
||||
PtrOwner<ISurfFlatRegion> pNewTrim( GetTrimRegion()->Clone()) ;
|
||||
|
||||
unordered_map<TRINT,ICCIVECTOR> mInters ;
|
||||
// costruisco la mappa delle intersezioni
|
||||
PtrOwner<ISurfFlatRegion> pNewTrim( CreateBasicSurfFlatRegion()) ;
|
||||
if ( m_bTrimmed)
|
||||
pNewTrim.Set( GetTrimRegion()->Clone()) ;
|
||||
else
|
||||
pNewTrim.Set( GetSurfFlatRegionRectangle( SBZ_TREG_COEFF * m_nSpanU, SBZ_TREG_COEFF * m_nSpanV)) ;
|
||||
//debug
|
||||
PtrOwner<ISurfFlatRegion> pSrfCopy( pNewTrim->Clone()) ;
|
||||
SaveGeoObj( Release(pSrfCopy), "D:\\Temp\\inters\\sphere_actualTrimRegion.nge") ;
|
||||
//debug
|
||||
|
||||
////debug
|
||||
//PtrOwner<ICurve> pCrvCopy( pNewTrim->GetLoop(0,0)) ;
|
||||
//SaveGeoObj( Release(pCrvCopy), "D:\\Temp\\inters\\sphere_actualTrimRegionEdge.nge") ;
|
||||
////debug
|
||||
|
||||
for ( int c = 0 ; c < pNewTrim->GetChunkCount() ; ++c) {
|
||||
for ( int l = 0 ; l < pNewTrim->GetLoopCount( c) ; ++l) {
|
||||
for ( int t = 0 ; t < int( vpCCOpen.size()); ++t) {
|
||||
PtrOwner<ICurve> pLoop( pNewTrim->GetLoop( c, l)) ;
|
||||
// prima curva è il loop, seconda curva è il loop
|
||||
IntersCurveCurve icc( *pLoop, *vpCCOpen[t]) ;
|
||||
if ( icc.GetIntersCount() != 0) {
|
||||
ICCIVECTOR vICC ;
|
||||
for ( int i = 0 ; i < int( icc.GetIntersCount()); ++i) {
|
||||
IntCrvCrvInfo iccInfo ;
|
||||
icc.GetIntCrvCrvInfo( i, iccInfo) ;
|
||||
vICC.emplace_back( iccInfo) ;
|
||||
}
|
||||
mInters.insert( pair<TRINT,ICCIVECTOR>( TRINT(c,l,t), vICC)) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vettore di flag che mi indica quali tagli aperti sono stati aggiunti al nuovo bordo
|
||||
BOOLVECTOR vbAdded( vpCCOpen.size()) ;
|
||||
fill(vbAdded.begin(), vbAdded.end(), false) ;
|
||||
PtrOwner<ICurveComposite> pCCNewEdge( CreateCurveComposite()) ;
|
||||
PtrOwner<ICurveLine> pCL( CreateCurveLine()) ;
|
||||
bool bAddedAll = false ;
|
||||
int nFirstIntersChunck = -1 ;
|
||||
int nFirstIntersLoop = -1 ;
|
||||
//bool bAddedAll = false ;
|
||||
TRINT tiFirstInters ;
|
||||
double dParamFirstInters = -1 ;
|
||||
// parto aggiungendo il primo taglio
|
||||
int nNewToAdd = 0 ;
|
||||
//while ( ! bAddedAll) {
|
||||
bool bFirstCurveOfEdge = true ;
|
||||
while ( nNewToAdd != -1) {
|
||||
// aggiungo il taglio
|
||||
PtrOwner<ICurveComposite> pLastCC( vpCCOpen[nNewToAdd]->Clone()) ;
|
||||
//PtrOwner<ICurveComposite> pLastCC( vpCCOpen[nNewToAdd]->Clone()) ;
|
||||
pCCNewEdge->AddCurve( Release( vpCCOpen[nNewToAdd])) ;
|
||||
// aggiorno la lista degli aggiunti
|
||||
vbAdded[nNewToAdd] = true ;
|
||||
|
||||
|
||||
|
||||
// ora devo trovare la sua intersezione con un edge
|
||||
// visto che scorro tutto ogni volta posso almeno evitare il conto se incontro l'edge da cui avevo già trovato lo start
|
||||
int nChunkE = -1 ;
|
||||
int nLoopE = -1 ;
|
||||
int nChunkS = -1 ;
|
||||
int nLoopS = -1 ;
|
||||
// scorro tutti i chunk e tutti i loop per trovare l'intersezione
|
||||
int nInters = 0 ; // per ogni loop devo trovare due intersezioni, una di partenza e una di arrivo
|
||||
IntCrvCrvInfo iccInfo0 ;
|
||||
IntCrvCrvInfo iccInfo1 ;
|
||||
bool bStartInters0 = false ;
|
||||
for ( int c = 0 ; c < pNewTrim->GetChunkCount() ; ++c) {
|
||||
for ( int l = 0 ; l < pNewTrim->GetLoopCount( c) ; ++l) {
|
||||
if ( c == nChunkS && l == nLoopS)
|
||||
continue ;
|
||||
PtrOwner<ICurve> pLoop( pNewTrim->GetLoop( c, l)) ;
|
||||
IntersCurveCurve icc( *pLastCC, *pLoop) ;
|
||||
if ( icc.GetIntersCount() == 0)
|
||||
continue ;
|
||||
if ( icc.GetIntersCount() == 1){
|
||||
if ( nInters == 0) {
|
||||
icc.GetIntCrvCrvInfo( 0, iccInfo0) ;
|
||||
// do per scontato di avere solo intersezioni all'inizio o alla fine del taglio
|
||||
if ( iccInfo0.IciA->dU < EPS_SMALL ) {
|
||||
nChunkS = c ;
|
||||
nLoopS = l ;
|
||||
bStartInters0 = true ;
|
||||
}
|
||||
else {
|
||||
nChunkE = c ;
|
||||
nLoopE = l ;
|
||||
bStartInters0 = false ;
|
||||
}
|
||||
}
|
||||
else if ( nInters == 1) {
|
||||
icc.GetIntCrvCrvInfo( 1, iccInfo1) ;
|
||||
// do per scontato di avere solo intersezioni all'inizio o alla fine del taglio
|
||||
if ( iccInfo0.IciA->dU < EPS_SMALL ) {
|
||||
nChunkS = c ;
|
||||
nLoopS = l ;
|
||||
}
|
||||
else {
|
||||
nChunkE = c ;
|
||||
nLoopE = l ;
|
||||
// di questo taglio mi salvo il chunk e loop di start e end
|
||||
TRINT tiStart, tiEnd ;
|
||||
for (const auto& pair : mInters) {
|
||||
if ( get<2>(pair.first) == nNewToAdd ) {
|
||||
for (int p = 0 ; p < int(pair.second.size()) ; ++p) {
|
||||
if ( pair.second[p].IciB->dU < EPS_SMALL) {
|
||||
tiStart = pair.first ;
|
||||
if ( bFirstCurveOfEdge){
|
||||
// salvo l'inizio del taglio che è la prima curva di questa curva compo
|
||||
tiFirstInters = pair.first ;
|
||||
dParamFirstInters = pair.second[p].IciA->dU ; // parametro di intersezione sul loop
|
||||
bFirstCurveOfEdge = false ;
|
||||
}
|
||||
}
|
||||
else
|
||||
return false ;
|
||||
++nInters ;
|
||||
tiEnd = pair.first ;
|
||||
}
|
||||
else if (icc.GetIntersCount() == 2) {
|
||||
icc.GetIntCrvCrvInfo( 0, iccInfo0) ;
|
||||
// do per scontato di avere solo intersezioni all'inizio o alla fine del taglio
|
||||
if ( iccInfo0.IciA->dU < EPS_SMALL ) {
|
||||
nChunkS = c ;
|
||||
nLoopS = l ;
|
||||
bStartInters0 = true ;
|
||||
}
|
||||
else {
|
||||
nChunkE = c ;
|
||||
nLoopE = l ;
|
||||
bStartInters0 = false ;
|
||||
}
|
||||
icc.GetIntCrvCrvInfo( 1, iccInfo1) ;
|
||||
// do per scontato di avere solo intersezioni all'inizio o alla fine del taglio
|
||||
if ( iccInfo1.IciA->dU < EPS_SMALL ) {
|
||||
nChunkS = c ;
|
||||
nLoopS = l ;
|
||||
}
|
||||
else {
|
||||
nChunkE = c ;
|
||||
nLoopE = l ;
|
||||
}
|
||||
++nInters ;
|
||||
++nInters ;
|
||||
}
|
||||
// quando ho trovato due intersezioni esco
|
||||
if ( nInters == 2)
|
||||
break ;
|
||||
}
|
||||
// quando ho trovato due intersezioni esco
|
||||
if ( nInters == 2)
|
||||
break ;
|
||||
}
|
||||
// se ho appena aggiunto il primo taglio mi salvo il suo start che mi indicherà dove terminare la ricostruzione del bordo
|
||||
if ( nNewToAdd == 0 ) {
|
||||
nFirstIntersChunck = nChunkS ;
|
||||
nFirstIntersLoop = nLoopS ;
|
||||
dParamFirstInters = bStartInters0 ? iccInfo0.IciB->dU : iccInfo1.IciB->dU ;
|
||||
}
|
||||
|
||||
// devo trovare fino a che punto seguire il loop che ho trovato come prosecuzione del taglio corrente
|
||||
// devo quindi trovare la prossima intersezione con un taglio
|
||||
ICCIVECTOR vICCI ;
|
||||
PtrOwner<ICurve> pLoop( pNewTrim->GetLoop( nChunkE, nLoopE)) ;
|
||||
for ( int t = 0 ; t < int( vpCCOpen.size()); ++t) {
|
||||
IntersCurveCurve icc( *pLoop, *vpCCOpen[t]) ;
|
||||
if ( icc.GetIntersCount() == 0)
|
||||
continue ;
|
||||
else if ( icc.GetIntersCount() == 1) {
|
||||
vICCI.emplace_back() ;
|
||||
icc.GetIntCrvCrvInfo( 0, vICCI.back()) ;
|
||||
}
|
||||
|
||||
|
||||
int nInters = -1 ;
|
||||
double dNextCut = numeric_limits<double>::infinity() ;
|
||||
double dEndCurrentCut ;
|
||||
for ( int i = 0 ; i < int( mInters[tiEnd].size()); ++i) {
|
||||
// se ho trovato l'intersezione con la fine del taglio corrente, salvo il parametro sul loop
|
||||
if ( mInters[tiEnd][0].IciB->dU > EPS_SMALL)
|
||||
dEndCurrentCut = mInters[tiEnd][0].IciA->dU ;
|
||||
}
|
||||
|
||||
// se non trovo nessuna altra intersezione prima della fine del loop allora devo ripetere tutto cercando a partire dall'inizio del loop
|
||||
for ( const auto& pair : mInters) {
|
||||
if ( get<0>(pair.first) == get<0>(tiEnd) && get<1>(pair.first) == get<1>(tiEnd)) {
|
||||
for ( int i = 0 ; i < int(pair.second.size()); ++i ) {
|
||||
// se trovo una nuova intersezione che incontro prima di quella che mi ero salvato precedentemente allora
|
||||
// mi salvo questa nuova che ho trovato
|
||||
if ( pair.second[i].IciA->dU < dNextCut && pair.second[i].IciA->dU > dEndCurrentCut) {
|
||||
dNextCut = pair.second[i].IciA->dU ;
|
||||
nInters = get<2>(pair.first) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool bRestart = false ;
|
||||
if ( nInters == -1) {
|
||||
bRestart = true ;
|
||||
dNextCut = numeric_limits<double>::infinity() ;
|
||||
for ( const auto& pair : mInters) {
|
||||
if ( get<0>(pair.first) == get<0>(tiEnd) && get<1>(pair.first) == get<1>(tiEnd)) {
|
||||
for ( int i = 0 ; i < int(pair.second.size()); ++i ) {
|
||||
// se trovo una nuova intersezione che incontro prima di quella che mi ero salvato precedentemente allora
|
||||
// mi salvo questa nuova che ho trovato
|
||||
if ( pair.second[i].IciA->dU < dNextCut) {
|
||||
dNextCut = pair.second[i].IciA->dU ;
|
||||
nInters = get<2>(pair.first) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PtrOwner<ICurve> pLoopTrimmed( pNewTrim->GetLoop( get<0>(tiEnd), get<1>(tiEnd))) ;
|
||||
// trimmo il loop che ho trovato come prosecuzione del taglio corrente e la aggiungo al taglio
|
||||
pLoop->TrimStartAtParam(bStartInters0 ? iccInfo0.IciB->dU : iccInfo1.IciB->dU) ;
|
||||
pLoop->TrimEndAtParam() ;
|
||||
// se arrivo fino alla fine del loop devo ricominciare a seguire il loop fino ad un'intersezione?
|
||||
PtrOwner<ICurve> pLoop2( pNewTrim->GetLoop( nChunkE, nLoopE)) ;
|
||||
pLoop->TrimEndAtParam() ;// appiccico un pezzo dall'inizio
|
||||
|
||||
|
||||
|
||||
|
||||
// trovo il prossimo taglio ancora da aggiungere
|
||||
nNewToAdd = -1 ;
|
||||
for ( int b = 0 ; b < int(vbAdded.size()) ; ++b ) {
|
||||
if ( ! vbAdded[b]) {
|
||||
nNewToAdd = b ;
|
||||
//vbAdded[b] = true ;
|
||||
break ;
|
||||
pLoopTrimmed->TrimEndAtParam( dNextCut) ;
|
||||
// se non sono ripassato per lo start del loop mi basta aggiungere il pezzo di loop che ho trimmato
|
||||
if ( ! bRestart) {
|
||||
pLoopTrimmed->TrimStartAtParam( dEndCurrentCut) ;
|
||||
pCCNewEdge->AddCurve( Release( pLoopTrimmed)) ;
|
||||
}
|
||||
else {
|
||||
// prima di aggiungere il pezzo che ho identificato devo aggiungere quello che dal parametro corrente mi fa
|
||||
// andare al punto finale e quindi tornare al punto iniziale
|
||||
PtrOwner<ICurve> pLoopTrimmed0( pNewTrim->GetLoop( get<0>(tiEnd), get<1>(tiEnd))) ;
|
||||
pLoopTrimmed0->TrimStartAtParam( dEndCurrentCut) ;
|
||||
pCCNewEdge->AddCurve( Release(pLoopTrimmed0)) ;
|
||||
// e poi aggiungo il tratto che ho identificato per arrivare al prossimo taglio
|
||||
pCCNewEdge->AddCurve( Release(pLoopTrimmed)) ;
|
||||
}
|
||||
// se il prossimo taglio identificato è quello da cui sono partito allora aggiungo il bordo ricostruito fino a questo momento
|
||||
// alla flat region e comincio a costruire un altro bordo
|
||||
// altrimenti continuo ad aggiungere curve al bordo corrente
|
||||
if ( nInters == get<2>(tiFirstInters) ) {
|
||||
pCCNewEdge->Close() ;
|
||||
sfrContour.AddCurve( Release( pCCNewEdge)) ;
|
||||
pCCNewEdge.Set( CreateBasicCurveComposite()) ;
|
||||
bFirstCurveOfEdge = true ;
|
||||
// trovo il prossimo taglio ancora da aggiungere
|
||||
nNewToAdd = -1 ;
|
||||
for ( int b = 0 ; b < int(vbAdded.size()) ; ++b ) {
|
||||
if ( ! vbAdded[b]) {
|
||||
nNewToAdd = b ;
|
||||
//vbAdded[b] = true ;
|
||||
break ;
|
||||
}
|
||||
}
|
||||
}
|
||||
//// controllo se ho aggiunto tutti i tagli
|
||||
//bAddedAll = true ;
|
||||
//for ( int b = 0 ; b < int( vbAdded.size()) ; ++b ) {
|
||||
// bAddedAll = bAddedAll && vbAdded[b] ;
|
||||
//}
|
||||
else
|
||||
nNewToAdd = nInters;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1992,19 +1983,6 @@ SurfBezier::Cut( const Plane3d& plPlane, bool bSaveOnEq)
|
||||
pNewStm->GetLoops( vPLTria) ;
|
||||
}
|
||||
|
||||
SurfFlatRegionByContours sfrContour ;
|
||||
// aggiungo i tagli sul bordo
|
||||
for ( int i = 0 ; i < int( vPolygons.size()); ++i ) {
|
||||
for ( int j = 0 ; j < int( vPolygons[i].size()); ++j) {
|
||||
PtrOwner<ICurveComposite> pCC( CreateCurveComposite()) ;
|
||||
pCC->FromPolyLine( vPolygons[i][j]) ;
|
||||
////debug
|
||||
//PtrOwner<ICurveComposite> pCrvCopy( pCC->Clone()) ;
|
||||
//SaveGeoObj( Release(pCrvCopy), "D:\\Temp\\inters\\sphere_trim_curve2D.nge") ;
|
||||
////debug
|
||||
sfrContour.AddCurve( Release( pCC)) ;
|
||||
}
|
||||
}
|
||||
// aggiungo i loop chiusi
|
||||
for ( int i = 0 ; i < int( vpCCClosed.size()); ++i )
|
||||
sfrContour.AddCurve( Release( vpCCClosed[i])) ;
|
||||
@@ -2076,10 +2054,10 @@ SurfBezier::Cut( const Plane3d& plPlane, bool bSaveOnEq)
|
||||
// 1. se il punto è sopra il piano ed era dentro una curva CCW
|
||||
// 2. se il punto è sotto il piano ed era interno ad una curva CW
|
||||
|
||||
//debug
|
||||
PtrOwner<ISurfFlatRegion> pSrfFR_Copy( pSFR->Clone()) ;
|
||||
SaveGeoObj( Release(pSrfFR_Copy), "D:\\Temp\\inters\\failed_trim.nge", GDB_SV_BIN) ;
|
||||
//debug
|
||||
////debug
|
||||
//PtrOwner<ISurfFlatRegion> pSrfFR_Copy( pSFR->Clone()) ;
|
||||
//SaveGeoObj( Release(pSrfFR_Copy), "D:\\Temp\\inters\\failed_trim.nge", GDB_SV_BIN) ;
|
||||
////debug
|
||||
|
||||
// la SetTrimRegion controlla se avevo trim precedenti ed eventualmente fa l'intersezione con lo spazio esistente
|
||||
if ( ( dDist > 0 && bPos) || ( dDist < 0 && ! bPos)) {
|
||||
@@ -2211,9 +2189,12 @@ SurfBezier::UnprojectPointFromStm( int nT, const Point3d& ptI, Point3d& ptSP, in
|
||||
}
|
||||
}
|
||||
}
|
||||
// questa è ancora da sviluppare //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// devo anche tener conto della possibilità che i lati siano collassati in poli
|
||||
//if ( m_vbPole[0])
|
||||
// ;
|
||||
if ( m_vbPole[0] ) {
|
||||
;
|
||||
}
|
||||
|
||||
// se l'intersezione era su un vertice restituisco le coordinate parametriche del vertice
|
||||
if ( nIL == 3 ) {
|
||||
if ( AreSamePointApprox(ptI, vPT[0]))
|
||||
|
||||
Reference in New Issue
Block a user