diff --git a/CurveArc.cpp b/CurveArc.cpp index 0eec806..4e07f74 100644 --- a/CurveArc.cpp +++ b/CurveArc.cpp @@ -1718,12 +1718,12 @@ CurveArc::CalcPointParamPosiz( const Point3d& ptP, double& dU, int& nPos) const // verifica posizione punto su arco nPos = PP_NULL ; // fuori if ( fabs( DiffAngle( dAngDeg, 0) * DEGTORAD * m_dRad) < EPS_SMALL) { - nPos = PP_START ; // vicino a inizio + nPos = ( IsACircle() ? PP_MID : PP_START) ; // se cerchio interno, altrimenti vicino a inizio dU = AngleNearAngle( dAngDeg, 0) / m_dAngCenDeg ; dU = max( dU, 0.) ; } else if ( fabs( DiffAngle( dAngDeg, m_dAngCenDeg) * DEGTORAD * m_dRad) < EPS_SMALL) { - nPos = PP_END ; // vicino a fine + nPos = ( IsACircle() ? PP_MID : PP_END) ; // se cerchio interno, altrimenti vicino a fine dU = AngleNearAngle( dAngDeg, m_dAngCenDeg) / m_dAngCenDeg ; dU = min( dU, 1.) ; } diff --git a/CurveComposite.cpp b/CurveComposite.cpp index 7fbff3c..bd1847e 100644 --- a/CurveComposite.cpp +++ b/CurveComposite.cpp @@ -36,7 +36,7 @@ GEOOBJ_REGISTER( CRV_COMPO, NGE_C_CMP, CurveComposite) ; //---------------------------------------------------------------------------- CurveComposite::CurveComposite( void) - : m_nStatus( TO_VERIFY), m_VtExtr(), m_dThick(), m_nTempProp() + : m_nStatus( TO_VERIFY), m_VtExtr(), m_dThick(), m_nTempProp(), m_Iter( m_CrvSmplS.end()) { } @@ -51,16 +51,15 @@ bool CurveComposite::Clear( void) { // ciclo di pulizia - PCSD_ITER Iter ; - for ( Iter = m_CrvSmplS.begin() ; Iter != m_CrvSmplS.end() ; ++Iter) + for ( auto Iter = m_CrvSmplS.begin() ; Iter != m_CrvSmplS.end() ; ++Iter) delete (*Iter) ; - m_CrvSmplS.clear() ; m_nStatus = TO_VERIFY ; m_VtExtr = V_NULL ; m_dThick = 0 ; m_nTempProp = 0 ; + m_Iter = m_CrvSmplS.end() ; // imposto ricalcolo della grafica m_OGrMgr.Reset() ; @@ -75,7 +74,7 @@ CurveComposite::AddCurve( const ICurve& cCrv, bool bEndOrStart, double dLinTol) // se curva semplice if ( cCrv.IsSimple()) { // creo una copia della curva - ICurve* pSmplCrv = ::GetCurve( cCrv.Clone()) ; + ICurve* pSmplCrv = cCrv.Clone() ; if ( pSmplCrv == nullptr) return false ; // inserisco la curva @@ -94,7 +93,7 @@ CurveComposite::AddCurve( const ICurve& cCrv, bool bEndOrStart, double dLinTol) pCrvOri = ( bEndOrStart ? pCrvCompo->GetFirstCurve() : pCrvCompo->GetLastCurve()) ; while ( pCrvOri != nullptr) { // creo una copia della curva - pSmplCrv = ::GetCurve( pCrvOri->Clone()) ; + pSmplCrv = pCrvOri->Clone() ; if ( pSmplCrv == nullptr) return false ; // inserisco la curva @@ -285,7 +284,7 @@ CurveComposite::FromSplit( const ICurve& cCrv, int nParts) for ( int i = 1 ; i <= nParts ; ++ i) { dStartLen = dEndLen ; dEndLen = dTotLen * i / (double) nParts ; - PtrOwner pSmplCrv( ::GetCurve( cCrv.Clone())) ; + PtrOwner pSmplCrv( cCrv.Clone()) ; if ( IsNull( pSmplCrv)) return false ; if ( ! pSmplCrv->TrimEndAtLen( dEndLen) || @@ -535,9 +534,8 @@ CurveComposite::CopyFrom( const CurveComposite& ccSrc) m_VtExtr = ccSrc.m_VtExtr ; m_dThick = ccSrc.m_dThick ; m_nTempProp = ccSrc.m_nTempProp ; - PCSD_CONST_ITER Iter ; - for ( Iter = ccSrc.m_CrvSmplS.begin() ; Iter != ccSrc.m_CrvSmplS.end() ; ++Iter) { - if ( ! AddCurve( **Iter)) + for ( auto& pCrv : ccSrc.m_CrvSmplS) { + if ( ! AddCurve( *pCrv)) return false ; } return true ; @@ -620,7 +618,7 @@ CurveComposite::Save( NgeWriter& ngeOut) const int i = 0 ; const ICurve* pCrvSmpl = GetFirstCurve() ; while ( pCrvSmpl != nullptr) { - // recupero il gestore di lettura/scrittura delle curve + // recupero il gestore di lettura/scrittura della curva const IGeoObjRW* pCSmplRW = dynamic_cast( pCrvSmpl) ; if ( pCSmplRW == nullptr) return false ; @@ -2110,7 +2108,7 @@ CurveComposite::GetPrevCurve( void) const return nullptr ; // vado al precedente - if ( m_Iter == m_CrvSmplS.begin()) + if ( m_Iter == m_CrvSmplS.begin() || m_Iter == m_CrvSmplS.end()) return nullptr ; -- m_Iter ; diff --git a/CurveComposite.h b/CurveComposite.h index 26f9090..870af0c 100644 --- a/CurveComposite.h +++ b/CurveComposite.h @@ -18,7 +18,6 @@ #include "DllMain.h" #include "GeoObjRW.h" #include "/EgtDev/Include/EGkCurveComposite.h" -#include "/EgtDev/Include/EgtLogger.h" #include //---------------------------------------------------------------------------- diff --git a/EgtGeomKernel.rc b/EgtGeomKernel.rc index 0545ff1..3f251a9 100644 Binary files a/EgtGeomKernel.rc and b/EgtGeomKernel.rc differ diff --git a/EgtGeomKernel.vcxproj b/EgtGeomKernel.vcxproj index b7547d9..5094aea 100644 --- a/EgtGeomKernel.vcxproj +++ b/EgtGeomKernel.vcxproj @@ -321,6 +321,7 @@ copy $(TargetPath) \EgtProg\Dll64 + @@ -465,6 +466,7 @@ copy $(TargetPath) \EgtProg\Dll64 + diff --git a/EgtGeomKernel.vcxproj.filters b/EgtGeomKernel.vcxproj.filters index 68d9fdd..b4ecf4f 100644 --- a/EgtGeomKernel.vcxproj.filters +++ b/EgtGeomKernel.vcxproj.filters @@ -300,6 +300,9 @@ File di origine\Base + + File di origine\Geo + @@ -689,6 +692,9 @@ File di intestazione\Include + + File di intestazione + diff --git a/GeoConst.h b/GeoConst.h index 9ef5425..41272d3 100644 --- a/GeoConst.h +++ b/GeoConst.h @@ -32,6 +32,8 @@ const double ANG_TOL_APPROX_DEG = 45 ; const double LIN_TOL_STD = 0.1 ; // deviazione angolare standard (in gradi) const double ANG_TOL_STD_DEG = 15 ; +// tolleranza lineare per visualizzazione di superfici regioni piane +const double LIN_TOL_SFR = 0.01 ; // lunghezza minima feature lineare const double LIN_FEA_LEN_STD = 20 ; diff --git a/Intervals.cpp b/Intervals.cpp index 52a75ca..16a8d93 100644 --- a/Intervals.cpp +++ b/Intervals.cpp @@ -80,7 +80,7 @@ Intervals::Add( double dMin, double dMax) break ; } // se il nuovo intervallo inizia dopo - if ( dMin > iIter->second + EPS_SMALL) + else if ( dMin > iIter->second + EPS_SMALL) // passo al successivo ++ iIter ; } diff --git a/NgeConst.h b/NgeConst.h index 29bcd3b..193e29c 100644 --- a/NgeConst.h +++ b/NgeConst.h @@ -1,7 +1,7 @@ //---------------------------------------------------------------------------- // EgalTech 2014-2015 //---------------------------------------------------------------------------- -// File : NgeConst.h Data : 22.05.15 Versione : 1.6e3 +// File : NgeConst.h Data : 05.08.15 Versione : 1.6h2 // Contenuto : Costanti per file Nge. // // @@ -12,6 +12,7 @@ // 21.01.15 DS 1009 -> Aggiunto ZMAP, cambiato Id di TEXT. // 11.03.15 DS 1010 -> Aggiunto flag orientata e elenco facce a SurfTm. // 22.05.15 DS 1011 -> Aggiunta possibilità estensione oggetti con IUserObj. +// 05.08.15 DS 1012 -> Aggiunta FlatRegion. // //---------------------------------------------------------------------------- @@ -35,7 +36,9 @@ const int NGE_VER_1010 = 1010 ; // 1010 : aggiunto flag orientata e elenco facce a SurfTm const int NGE_VER_1011 = 1011 ; // 1011 : aggiunta possibilità estensione oggetti con IUserObj (Keyword U) -const int NGE_VER_LAST = NGE_VER_1011 ; +const int NGE_VER_1012 = 1012 ; +// 1012 : aggiunta superficie FlatRegion +const int NGE_VER_LAST = NGE_VER_1012 ; // Indici KeyWord const int NGE_START = 0 ; const int NGE_END = 1 ; @@ -55,6 +58,7 @@ const int NGE_C_ARC = 14 ; const int NGE_C_BEZ = 15 ; const int NGE_C_CMP = 16 ; const int NGE_S_TRM = 17 ; -const int NGE_V_ZMP = 18 ; -const int NGE_E_TXT = 19 ; -const int NGE_LAST_ID = 19 ; // ultimo valore +const int NGE_S_FRG = 18 ; +const int NGE_V_ZMP = 19 ; +const int NGE_E_TXT = 20 ; +const int NGE_LAST_ID = 20 ; // ultimo valore diff --git a/NgeKeyW.h b/NgeKeyW.h index 6df61fe..6f0d5f4 100644 --- a/NgeKeyW.h +++ b/NgeKeyW.h @@ -38,6 +38,7 @@ const std::string NgeAscKeyW[] = { "START", // NGE_START "C_BEZ", // NGE_C_BEZ "C_CMP", // NGE_C_CMP "S_TRM", // NGE_S_TRM + "S_FRG", // NGE_S_FRG "V_ZMP", // NGE_V_ZMP "E_TXT"} ; // NGE_E_TXT @@ -67,5 +68,6 @@ const int NgeBinKeyW[] = { NGEB_GEN_BASE + 0x0F0F, // NGE_START NGEB_GEO_BASE + CRV_BEZ, // NGE_C_BEZ NGEB_GEO_BASE + CRV_COMPO, // NGE_C_CMP NGEB_GEO_BASE + SRF_TRIMESH, // NGE_S_TRM + NGEB_GEO_BASE + SRF_FLATRGN, // NGE_S_FRG NGEB_GEO_BASE + VOL_ZMAP, // NGE_V_ZMP NGEB_GEO_BASE + EXT_TEXT} ; // NGE_E_TXT \ No newline at end of file diff --git a/SelfIntersCurve.cpp b/SelfIntersCurve.cpp index a27fbeb..92b00bd 100644 --- a/SelfIntersCurve.cpp +++ b/SelfIntersCurve.cpp @@ -31,6 +31,11 @@ SelfIntersCurve::SelfIntersCurve( const ICurve& Curve) m_bOverlaps = false ; m_nNumInters = 0 ; m_pOriCrv = &Curve ; + m_pCurve = nullptr ; + + // verifico che la curva sia definita + if ( &Curve == nullptr || ! Curve.IsValid()) + return ; // se curva è arco da approssimare oppure è curva di Bezier if ( ( m_pOriCrv->GetType() == CRV_ARC && IsArcToApprox( *m_pOriCrv)) || @@ -126,7 +131,7 @@ SelfIntersCurve::GetIntCrvCrvInfo( int nInd, IntCrvCrvInfo& aInfo) return false ; aInfo = m_Info[nInd] ; // se curva originale approssimata, devo ricalcolare i parametri dei punti di intersezione - if ( m_pCurve != m_pOriCrv) { + if ( m_pCurve != m_pOriCrv) { if ( ! m_pOriCrv->GetParamAtPoint( aInfo.IciA[0].ptI, aInfo.IciA[0].dU, 10 * EPS_SMALL)) return false ; if ( aInfo.bOverlap && ! m_pOriCrv->GetParamAtPoint( aInfo.IciA[1].ptI, aInfo.IciA[1].dU, 10 * EPS_SMALL)) diff --git a/SurfFlatRegion.cpp b/SurfFlatRegion.cpp new file mode 100644 index 0000000..dc42fcd --- /dev/null +++ b/SurfFlatRegion.cpp @@ -0,0 +1,834 @@ +//---------------------------------------------------------------------------- +// EgalTech 2015-2015 +//---------------------------------------------------------------------------- +// File : SurfFlatRegion.cpp Data : 05.08.15 Versione : 1.6h2 +// Contenuto : Implementazione della classe Surface FlatRegion. +// +// +// +// Modifiche : 05.08.15 DS Creazione modulo. +// +// +//---------------------------------------------------------------------------- + +//--------------------------- Include ---------------------------------------- +#include "stdafx.h" +#include "SurfFlatRegion.h" +#include "GeoObjFactory.h" +#include "NgeWriter.h" +#include "NgeReader.h" +#include "CurveAux.h" +#include "GeoConst.h" +#include "/EgtDev/Include/EGkStringUtils3d.h" +#include "/EgtDev/Include/EGkUiUnits.h" +#include "/EgtDev/Include/EGkIntervals.h" +#include "/EgtDev/Include/EgtPointerOwner.h" + +using namespace std ; + +//---------------------------------------------------------------------------- +GEOOBJ_REGISTER( SRF_FLATRGN, NGE_S_FRG, SurfFlatRegion) ; + +//---------------------------------------------------------------------------- +SurfFlatRegion::SurfFlatRegion( void) + : m_pSTM( nullptr), m_nStatus( TO_VERIFY), m_pExtLoop( nullptr), m_nTempProp(), m_Iter( m_vpIntLoop.end()) +{ +} + +//---------------------------------------------------------------------------- +SurfFlatRegion::~SurfFlatRegion( void) +{ + Clear() ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Clear( void) +{ + if ( m_pSTM != nullptr) + delete( m_pSTM) ; + m_pSTM = nullptr ; + m_nStatus = TO_VERIFY ; + m_frF.Reset() ; + + // ciclo di pulizia + if ( m_pExtLoop != nullptr) + delete( m_pExtLoop) ; + m_pExtLoop = nullptr ; + for ( auto Iter = m_vpIntLoop.begin() ; Iter != m_vpIntLoop.end() ; ++Iter) + delete (*Iter) ; + m_vpIntLoop.clear() ; + + m_nTempProp = 0 ; + m_Iter = m_vpIntLoop.end() ; + + // imposto ricalcolo della grafica + m_OGrMgr.Reset() ; + + return true ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::SetExtLoop( const ICurve& cCrv) +{ + // verifico validità curva + if ( &cCrv == nullptr) + return false ; + // creo una copia della curva + ICurve* pCrv( cCrv.Clone()) ; + if ( pCrv == nullptr) + return false ; + // procedo + return SetExtLoop( pCrv) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::SetExtLoop( ICurve* pCrv) +{ + // resetto tutto + Clear() ; + // acquisisco la curva + PtrOwner pMyCrv( pCrv) ; + if ( IsNull( pMyCrv)) + return false ; + // verifico sia chiusa + if ( ! pMyCrv->IsClosed()) + return false ; + // verifico sia piana + Plane3d plPlane ; + if ( ! pMyCrv->IsFlat( plPlane)) + return false ; + // assegno il riferimento intrinseco + if ( ! m_frF.Set( ORIG + plPlane.vtN * plPlane.dDist, plPlane.vtN)) + return false ; + // porto la curva nel riferimento intrinseco + if ( ! pMyCrv->ToLoc( m_frF)) + return false ; + // verifico non abbia auto-intersezioni + SelfIntersCurve sInt( *pMyCrv) ; + if ( sInt.GetNumInters() > 0) + return false ; + // sistemo il senso di rotazione (deve essere CCW -> area > 0) + double dArea ; + if ( ! pMyCrv->GetAreaXY( dArea) || fabs( dArea) < 100 * EPS_SMALL * EPS_SMALL) + return false ; + if ( dArea < 0) + pMyCrv->Invert() ; + // assegno la curva al loop esterno + m_pExtLoop = Release( pMyCrv) ; + m_nStatus = OK ; + return true ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::AddIntLoop( const ICurve& cCrv) +{ + // verifico validità curva + if ( &cCrv == nullptr) + return false ; + // creo una copia della curva + ICurve* pCrv( cCrv.Clone()) ; + if ( pCrv == nullptr) + return false ; + // procedo + return AddIntLoop( pCrv) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::AddIntLoop( ICurve* pCrv) +{ + // acquisisco la curva + PtrOwner pMyCrv( pCrv) ; + if ( IsNull( pMyCrv)) + return false ; + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // verifico sia chiusa + if ( ! pMyCrv->IsClosed()) + return false ; + // porto la curva nel riferimento intrinseco + if ( ! pMyCrv->ToLoc( m_frF)) + return false ; + // verifico sia piana e il piano coincida con quello XY intrinseco + Plane3d plPlane ; + if ( ! pMyCrv->IsFlat( plPlane)) + return false ; + if ( ! plPlane.vtN.IsZplus() && ! plPlane.vtN.IsZminus() && fabs( plPlane.dDist) > EPS_SMALL) + return false ; + // verifico non abbia auto-intersezioni + SelfIntersCurve sInt( *pMyCrv) ; + if ( sInt.GetNumInters() > 0) + return false ; + // verifico non abbia intersezioni e sia interna al loop esterno + IntersCurveCurve ccInt( *pMyCrv, *m_pExtLoop) ; + CRVCVECTOR ccClass ; + if ( ccInt.GetNumInters() > 0 || + ! ccInt.GetCurveClassification( 0, ccClass) || + ccClass.empty() || ccClass[0].nClass != CRVC_IN) + return false ; + // verifico non abbia intersezioni con eventuali altri loop interni + for ( auto& pIntLoop : m_vpIntLoop) { + IntersCurveCurve ccInt( *pMyCrv, *pIntLoop) ; + if ( ccInt.GetNumInters() > 0) + return false ; + } + // sistemo il senso di rotazione (deve essere CW -> area < 0) + double dArea ; + if ( ! pMyCrv->GetAreaXY( dArea) || fabs( dArea) < 100 * EPS_SMALL * EPS_SMALL) + return false ; + if ( dArea > 0) + pMyCrv->Invert() ; + // aggiungo la curva all'elenco dei loop interni + m_vpIntLoop.push_back( Release( pMyCrv)) ; + return true ; +} + +//---------------------------------------------------------------------------- +SurfFlatRegion* +SurfFlatRegion::Clone( void) const +{ + // alloco oggetto + SurfFlatRegion* pSfr = new(nothrow) SurfFlatRegion ; + if ( pSfr != nullptr) { + if ( ! pSfr->CopyFrom( *this)) { + delete pSfr ; + return nullptr ; + } + } + + return pSfr ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::CopyFrom( const IGeoObj* pGObjSrc) +{ + const SurfFlatRegion* pSfr = dynamic_cast( pGObjSrc) ; + if ( pSfr == nullptr) + return false ; + return CopyFrom( *pSfr) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::CopyFrom( const SurfFlatRegion& sfrSrc) +{ + if ( &sfrSrc == this) + return true ; + Clear() ; + if ( sfrSrc.m_pSTM != nullptr) + m_pSTM = sfrSrc.m_pSTM->Clone() ; + m_frF = sfrSrc.m_frF ; + if ( sfrSrc.m_pExtLoop != nullptr) + m_pExtLoop = sfrSrc.m_pExtLoop->Clone() ; + for ( auto& pIntLoop : sfrSrc.m_vpIntLoop) { + ICurve* pCrv = pIntLoop->Clone() ; + if ( pCrv == nullptr) + return false ; + m_vpIntLoop.push_back( pCrv) ; + } + m_nTempProp = sfrSrc.m_nTempProp ; + m_nStatus = sfrSrc.m_nStatus ; + return ( m_nStatus == OK && m_pExtLoop != nullptr) ; +} + +//---------------------------------------------------------------------------- +GeoObjType +SurfFlatRegion::GetType( void) const +{ + return static_cast( GEOOBJ_GETTYPE( SurfFlatRegion)) ; +} + +//---------------------------------------------------------------------------- +const string& +SurfFlatRegion::GetTitle( void) const +{ + static const string sTitle = "FlatRegion" ; + return sTitle ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Dump( string& sOut, bool bMM, const char* szNewLine) const +{ + // verifico validità regione + if ( m_nStatus != OK) + sOut += string( "Status=Invalid") + szNewLine ; + // area + double dArea ; + if ( GetArea( dArea)) + sOut += "Area=" + ToString( GetAreaInUiUnits( dArea, bMM),1) + szNewLine ; + // riferimento intrinseco : origine, VersoreX, VersoreY, VersoreZ + sOut += "O(" + ToString( GetInUiUnits( m_frF.Orig(), bMM)) + ")" + szNewLine ; + sOut += "VX(" + ToString( m_frF.VersX()) + ")" + szNewLine ; + sOut += "VY(" + ToString( m_frF.VersY()) + ")" + szNewLine ; + sOut += "VZ(" + ToString( m_frF.VersZ()) + ")" + szNewLine ; + // loop esterno + if ( m_pExtLoop != nullptr) { + sOut += string( "External Loop :") + szNewLine ; + m_pExtLoop->Dump( sOut, bMM, szNewLine) ; + } + else + sOut += string( "External Loop Invalid") + szNewLine ; + // loop interni + sOut += "Internal Loops=" + ToString( GetIntLoopNumber()) + szNewLine ; + int i = 0 ; + for ( auto& pIntLoop : m_vpIntLoop) { + sOut += "##" + ToString( ++i) + szNewLine ; + pIntLoop->Dump( sOut, bMM, szNewLine) ; + } + + return true ; +} + +//---------------------------------------------------------------------------- +int +SurfFlatRegion::GetNgeId( void) const +{ + return GEOOBJ_GETNGEID( SurfFlatRegion) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Save( NgeWriter& ngeOut) const +{ + // frame + if ( ! ngeOut.WriteFrame( m_frF, ";", true)) + return false ; + // numero di loops (esterno + interni) + if ( ! ngeOut.WriteInt( 1 + int( m_vpIntLoop.size()), nullptr, true)) + return false ; + // ciclo sui loop + int i = 0 ; + const ICurve* pCrv = GetMyLoop( i) ; + while ( pCrv != nullptr) { + // recupero il gestore di lettura/scrittura della curva + const IGeoObjRW* pCSmplRW = dynamic_cast( pCrv) ; + if ( pCSmplRW == nullptr) + return false ; + // emetto il tipo della curva + if ( ! ngeOut.WriteKey( pCSmplRW->GetNgeId())) + return false ; + // assegno ed emetto il nome della curva + string sCrvName = "##" + ToString( i) ; + if ( ! ngeOut.WriteString( sCrvName, nullptr, true)) + return false ; + // salvo la curva + if ( ! pCSmplRW->Save( ngeOut)) + return false ; + // passo alla successiva + ++ i ; + pCrv = GetMyLoop( i) ; + } + return true ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Load( NgeReader& ngeIn) +{ + Clear() ; + // leggo i dati del riferimento + if ( ! ngeIn.ReadFrame( m_frF, ";", true)) + return false ; + // leggo la prossima linea ( 1 parametro) + // recupero il numero di curve componenti + int nCounter ; + if ( ! ngeIn.ReadInt( nCounter, nullptr, true)) + return false ; + // leggo le curve componenti + for ( int i = 0 ; i < nCounter ; ++ i) { + // recupero la prossima linea (con il tipo di oggetto) + int nNgeId ; + if ( ! ngeIn.ReadKey( nNgeId)) + return false ; + // creo l'oggetto + int nType = GEOOBJ_NGEIDTOTYPE( nNgeId) ; + IGeoObj* pGeoO = GEOOBJ_CREATE( nType) ; + if ( pGeoO == nullptr) + return false ; + // recupero la linea con il nome + string sName ; + int j ; + bool bOk = ngeIn.ReadString( sName, nullptr, true) && + FromString( sName.substr(2), j) && i == j ; + // ne leggo i dati + IGeoObjRW* pGObjRW = dynamic_cast( pGeoO) ; + bOk = bOk && ( pGObjRW != nullptr && pGObjRW->Load( ngeIn)) ; + // verifico sia una curva + ICurve* pCrv = ::GetCurve( pGeoO) ; + bOk = bOk && ( pCrv != nullptr) ; + // aggiungo questa curva + if ( bOk) { + if ( i == 0) + m_pExtLoop = pCrv ; + else + m_vpIntLoop.push_back( pCrv) ; + } + // se errore + if ( ! bOk) + return false ; + } + // aggiorno stato + m_nStatus = OK ; + return true ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::GetLocalBBox( BBox3d& b3Loc, int nFlag) const +{ + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // è il bounding box del loop esterno + return m_pExtLoop->GetBBox( m_frF, b3Loc, nFlag) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::GetBBox( const Frame3d& frRef, BBox3d& b3Ref, int nFlag) const +{ + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // frame composito + Frame3d frCompo = m_frF * frRef ; + // è il bounding box del loop esterno + return m_pExtLoop->GetBBox( frCompo, b3Ref, nFlag) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Translate( const Vector3d& vtMove) +{ + // verifico lo stato + if ( m_nStatus != OK) + return false ; + // imposto ricalcolo della grafica + ResetAuxSurf() ; + m_OGrMgr.Reset() ; + // traslo il riferimento + m_frF.Translate( vtMove) ; + return true ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Rotate( const Point3d& ptAx, const Vector3d& vtAx, double dCosAng, double dSinAng) +{ + // verifico lo stato + if ( m_nStatus != OK) + return false ; + // imposto ricalcolo della grafica + ResetAuxSurf() ; + m_OGrMgr.Reset() ; + // ruoto il riferimento + return m_frF.Rotate( ptAx, vtAx, dCosAng, dSinAng) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Scale( const Frame3d& frRef, double dCoeffX, double dCoeffY, double dCoeffZ) +{ + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // imposto ricalcolo della grafica + ResetAuxSurf() ; + m_OGrMgr.Reset() ; + + // se scalatura non uniforme, sostituisco eventuali archi con curve di Bezier + if ( fabs( dCoeffX - dCoeffY) > EPS_SMALL || fabs( dCoeffX - dCoeffZ) > EPS_SMALL) { + if ( ! ConvertArcsToBezierCurves()) + return false ; + } + + // parziale scalatura del riferimento (non può essere completa) + Frame3d frTrasf = m_frF ; + m_frF.PseudoScale( frRef, dCoeffX, dCoeffY, dCoeffZ) ; + + // porto i loop nel nuovo riferimento, senza modificarne la geometria in globale + frTrasf.ToLoc( m_frF) ; + m_pExtLoop->ToGlob( frTrasf) ; + for ( auto& pIntLoop : m_vpIntLoop) + pIntLoop->ToGlob( frTrasf) ; + + // porto il riferimento di scalatura nel nuovo riferimento del gruppo + Frame3d frRefLoc = frRef ; + frRefLoc.ToLoc( m_frF) ; + + // ciclo sui loop + bool bOk = m_pExtLoop->Scale( frRefLoc, dCoeffX, dCoeffY, dCoeffZ) ; + for ( auto& pIntLoop : m_vpIntLoop) { + if ( ! pIntLoop->Scale( frRefLoc, dCoeffX, dCoeffY, dCoeffZ)) + bOk = false ; + } + + return bOk ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Mirror( const Point3d& ptOn, const Vector3d& vtNorm) +{ + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // imposto ricalcolo della grafica + ResetAuxSurf() ; + m_OGrMgr.Reset() ; + + // parziale mirror del riferimento (non può essere completa) + Frame3d frTrasf = m_frF ; + m_frF.PseudoMirror( ptOn, vtNorm) ; + + // porto i loop nel nuovo riferimento, senza modificarne la geometria in globale + frTrasf.ToLoc( m_frF) ; + m_pExtLoop->ToGlob( frTrasf) ; + for ( auto& pIntLoop : m_vpIntLoop) + pIntLoop->ToGlob( frTrasf) ; + + // porto i dati del piano di mirror nel riferimento del gruppo + Point3d ptOnLoc = ptOn ; + ptOnLoc.ToLoc( m_frF) ; + Vector3d vtNormLoc = vtNorm ; + vtNormLoc.ToLoc( m_frF) ; + + // ciclo sui loop (mirror + invert) + bool bOk = m_pExtLoop->Mirror( ptOnLoc, vtNormLoc) && m_pExtLoop->Invert() ; + for ( auto& pIntLoop : m_vpIntLoop) { + if ( ! pIntLoop->Mirror( ptOnLoc, vtNormLoc) || ! pIntLoop->Invert()) + bOk = false ; + } + + return bOk ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Invert( void) +{ + // E' un mirror rispetto al piano XY del riferimento intrinseco + return Mirror( m_frF.Orig(), m_frF.VersZ()) ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::Shear( const Point3d& ptOn, const Vector3d& vtNorm, const Vector3d& vtDir, double dCoeff) +{ + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // imposto ricalcolo della grafica + ResetAuxSurf() ; + m_OGrMgr.Reset() ; + + // se il piano di scorrimento non coincide con XY intrinseco, converto eventuali archi in curve di Bezier + if ( ! AreSameOrOppositeVectorExact( m_frF.VersZ(), vtNorm)) { + if ( ! ConvertArcsToBezierCurves()) + return false ; + } + + // parziale scorrimento del riferimento (non può essere completa) + Frame3d frTrasf = m_frF ; + m_frF.PseudoShear( ptOn, vtNorm, vtDir, dCoeff) ; + + // porto i loop nel nuovo riferimento, senza modificarne la geometria in globale + frTrasf.ToLoc( m_frF) ; + m_pExtLoop->ToGlob( frTrasf) ; + for ( auto& pIntLoop : m_vpIntLoop) + pIntLoop->ToGlob( frTrasf) ; + + // porto i dati di scorrimento nel riferimento del gruppo + Point3d ptOnLoc = ptOn ; + ptOnLoc.ToLoc( m_frF) ; + Vector3d vtNormLoc = vtNorm ; + vtNormLoc.ToLoc( m_frF) ; + Vector3d vtDirLoc = vtDir ; + vtDirLoc.ToLoc( m_frF) ; + + // ciclo sugli oggetti + bool bOk = m_pExtLoop->Shear( ptOnLoc, vtNormLoc, vtDirLoc, dCoeff) ; + for ( auto& pIntLoop : m_vpIntLoop) { + if ( ! pIntLoop->Shear( ptOnLoc, vtNormLoc, vtDirLoc, dCoeff)) + bOk = false ; + } + + return bOk ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::ConvertArcsToBezierCurves( void) +{ + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // loop esterno + if ( m_pExtLoop->GetType() == CRV_ARC) { + ICurve* pCrvNew = ArcToBezierCurve( m_pExtLoop) ; + if ( pCrvNew == nullptr) + return false ; + delete m_pExtLoop ; + m_pExtLoop = pCrvNew ; + } + // eventuali loop interni + for ( auto& pIntLoop : m_vpIntLoop) { + if ( pIntLoop->GetType() == CRV_ARC) { + ICurve* pCrvNew = ArcToBezierCurve( pIntLoop) ; + if ( pCrvNew == nullptr) + return false ; + delete pIntLoop ; + pIntLoop = pCrvNew ; + } + } + return true ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::ToGlob( const Frame3d& frRef) +{ + // verifico lo stato + if ( m_nStatus != OK) + return false ; + // trasformo il riferimento + return m_frF.ToGlob( frRef) ; ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::ToLoc( const Frame3d& frRef) +{ + // verifico lo stato + if ( m_nStatus != OK) + return false ; + // trasformo il riferimento + return m_frF.ToLoc( frRef) ; ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::LocToLoc( const Frame3d& frOri, const Frame3d& frDest) +{ + // verifico lo stato + if ( m_nStatus != OK) + return false ; + // trasformo il riferimento + return m_frF.LocToLoc( frOri, frDest) ; ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::GetArea( double& dArea) const +{ + // controllo parametro di ritorno + if ( &dArea == nullptr) + return false ; + // inizio con area nulla + dArea = 0 ; + // la regione deve essere validata + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // sommo l'area di tutti i loop (gli interni hanno area negativa) + bool bOk = m_pExtLoop->GetAreaXY( dArea) ; + for ( auto& pIntLoop : m_vpIntLoop) { + double dTemp ; + if ( pIntLoop->GetAreaXY( dTemp)) + dArea += dTemp ; + else + bOk = false ; + } + + return bOk ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::GetCentroid( Point3d& ptCen) const +{ + // controllo parametro di ritorno + if ( &ptCen == nullptr) + return false ; + // la regione deve essere validata + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // determino il centroide del solo loop esterno + return m_pExtLoop->GetCentroid( ptCen) ; +} + +//---------------------------------------------------------------------------- +ICurve* +SurfFlatRegion::GetMyLoop( int nLoop) const +{ + // la regione deve essere validata + if ( m_nStatus != OK) + return nullptr ; + // se esterno + if ( nLoop == 0) + return m_pExtLoop ; + // altrimenti interno, verifico che l'indice sia nei limiti + if ( nLoop < 1 || nLoop > int( m_vpIntLoop.size())) + return nullptr ; + else + return m_vpIntLoop[nLoop-1] ; +} + +//---------------------------------------------------------------------------- +ICurve* +SurfFlatRegion::GetLoop( int nLoop) const +{ + // recupero il loop nel riferimento intrinseco + const ICurve* pMyCrv = GetMyLoop( nLoop) ; + if ( pMyCrv == nullptr) + return nullptr ; + // ne faccio una copia + ICurve* pCrv = pMyCrv->Clone() ; + if ( pCrv == nullptr) + return nullptr ; + // la porto nel riferimento in cui è inserito l'oggetto + pCrv->ToGlob( m_frF) ; + return pCrv ; +} + +//---------------------------------------------------------------------------- +const ISurfTriMesh* +SurfFlatRegion::GetAuxSurf( void) const +{ + // la superficie deve essere validata + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // se già calcolata, la restituisco + if ( m_pSTM != nullptr) + return m_pSTM ; + // la calcolo + // calcolo le polilinee che approssimano i loop + POLYLINEVECTOR vPL ; + vPL.resize( 1 + int( m_vpIntLoop.size())) ; + int i = - 1 ; + if ( ! m_pExtLoop->ApproxWithLines( LIN_TOL_SFR, ANG_TOL_STD_DEG, ICurve::APL_STD, vPL[++i])) + return nullptr ; + for ( auto& pIntLoop : m_vpIntLoop) { + if ( ! pIntLoop->ApproxWithLines( LIN_TOL_SFR, ANG_TOL_STD_DEG, ICurve::APL_STD, vPL[++i])) + return nullptr ; + } + // porto le polilinee in globale al riferimento intrinseco + for ( auto& PL : vPL) + PL.ToGlob( m_frF) ; + // creo, setto la superficie trimesh ed elimino punti ripetuti + PtrOwner pSTM( CreateBasicSurfTriMesh()) ; + if ( IsNull( pSTM) || ! pSTM->CreateByRegion( vPL) || ! pSTM->DoCompacting()) + return nullptr ; + // la salvo + m_pSTM = Release( pSTM) ; + return m_pSTM ; +} + +//---------------------------------------------------------------------------- +void +SurfFlatRegion::ResetAuxSurf( void) const +{ + if ( m_pSTM != nullptr) + delete( m_pSTM) ; + m_pSTM = nullptr ; +} + +//---------------------------------------------------------------------------- +bool +SurfFlatRegion::GetCurveClassification( const ICurve& Crv, CRVCVECTOR& ccClass) const +{ + // verifico lo stato + if ( m_nStatus != OK || m_pExtLoop == nullptr) + return false ; + // verifico la curva + if ( &Crv == nullptr || ! Crv.IsValid()) + return false ; + // curva in locale nel riferimento intrinseco + const ICurve* pCrvLoc = nullptr ; + PtrOwner pCopyCrv ; + if ( AreSameFrame( m_frF, GLOB_FRM)) + pCrvLoc = &Crv ; + else { + pCopyCrv.Set( Crv.Clone()) ; + if ( IsNull( pCopyCrv)) + return false ; + pCopyCrv->ToLoc( m_frF) ; + pCrvLoc = Get( pCopyCrv) ; + } + // intervalli delle diverse classi + Intervals inIn, inOut, inOnP, inOnM ; + // classifico la curva con tutti i loop presenti + int nLoop = 0 ; + const ICurve* pLoop = GetMyLoop( nLoop) ; + while ( pLoop != nullptr) { + // intersezione + IntersCurveCurve ccInt( *pCrvLoc, *pLoop) ; + // classificazione + CRVCVECTOR ccPart ; + if ( ! ccInt.GetCurveClassification( 0, ccPart)) + return false ; + for ( auto& ccOne : ccPart) { + switch ( ccOne.nClass) { + case CRVC_IN : inIn.Add( ccOne.dParS, ccOne.dParE) ; break ; + case CRVC_OUT : inOut.Add( ccOne.dParS, ccOne.dParE) ; break ; + case CRVC_ON_P : inOnP.Add( ccOne.dParS, ccOne.dParE) ; break ; + case CRVC_ON_M : inOnM.Add( ccOne.dParS, ccOne.dParE) ; break ; + default : return false ; + } + } + // passo al loop successivo + ++ nLoop ; + pLoop = GetMyLoop( nLoop) ; + } + // sistemo gli intervalli di classificazione + // Out prevale su tutto + double dMin, dMax ; + bool bFound = inOut.GetFirst( dMin, dMax) ; + while ( bFound) { + inIn.Remove( dMin, dMax) ; + inOnP.Remove( dMin, dMax) ; + inOnM.Remove( dMin, dMax) ; + bFound = inOut.GetNext( dMin, dMax) ; + } + // OnP e OnM prevalgono su In e sono sicuramente disgiunti tra loro + bFound = inOnP.GetFirst( dMin, dMax) ; + while ( bFound) { + inIn.Remove( dMin, dMax) ; + bFound = inOnP.GetNext( dMin, dMax) ; + } + bFound = inOnM.GetFirst( dMin, dMax) ; + while ( bFound) { + inIn.Remove( dMin, dMax) ; + bFound = inOnM.GetNext( dMin, dMax) ; + } + // ricostruisco la classificazione completa e la ordino + bFound = inIn.GetFirst( dMin, dMax) ; + while ( bFound) { + ccClass.emplace_back( dMin, dMax, CRVC_IN) ; + bFound = inIn.GetNext( dMin, dMax) ; + } + bFound = inOut.GetFirst( dMin, dMax) ; + while ( bFound) { + ccClass.emplace_back( dMin, dMax, CRVC_OUT) ; + bFound = inOut.GetNext( dMin, dMax) ; + } + bFound = inOnP.GetFirst( dMin, dMax) ; + while ( bFound) { + ccClass.emplace_back( dMin, dMax, CRVC_ON_P) ; + bFound = inOnP.GetNext( dMin, dMax) ; + } + bFound = inOnM.GetFirst( dMin, dMax) ; + while ( bFound) { + ccClass.emplace_back( dMin, dMax, CRVC_ON_M) ; + bFound = inOnM.GetNext( dMin, dMax) ; + } + sort( ccClass.begin(), ccClass.end(), + []( const CrvClass& a, const CrvClass& b) { return a.dParS < b.dParS ; }) ; + return true ; +} \ No newline at end of file diff --git a/SurfFlatRegion.h b/SurfFlatRegion.h new file mode 100644 index 0000000..3ad3958 --- /dev/null +++ b/SurfFlatRegion.h @@ -0,0 +1,133 @@ +//---------------------------------------------------------------------------- +// EgalTech 2015-2015 +//---------------------------------------------------------------------------- +// File : SurfFlatRegion.h Data : 05.08.15 Versione : 1.6h2 +// Contenuto : Dichiarazione della classe Surface FlatRegion. +// +// +// +// Modifiche : 05.08.15 DS Creazione modulo. +// +// +//---------------------------------------------------------------------------- + +#pragma once + +#include "ObjGraphicsMgr.h" +#include "DllMain.h" +#include "GeoObjRW.h" +#include "SurfTriMesh.h" +#include "/EgtDev/Include/EGkCurve.h" +#include "/EgtDev/Include/EGkSurfFlatRegion.h" +#include + +//---------------------------------------------------------------------------- +class SurfFlatRegion : public ISurfFlatRegion, public IGeoObjRW +{ + public : // IGeoObj + virtual ~SurfFlatRegion( void) ; + virtual SurfFlatRegion* Clone( void) const ; + virtual GeoObjType GetType( void) const ; + virtual bool IsValid( void) const + { return ( m_nStatus == OK) ; } + virtual const std::string& GetTitle( void) const ; + virtual bool Dump( std::string& sOut, bool bMM = true, const char* szNewLine = "\n") const ; + virtual bool GetLocalBBox( BBox3d& b3Loc, int nFlag = BBF_STANDARD) const ; + virtual bool GetBBox( const Frame3d& frRef, BBox3d& b3Ref, int nFlag = BBF_STANDARD) const ; + virtual bool Translate( const Vector3d& vtMove) ; + virtual bool Rotate( const Point3d& ptAx, const Vector3d& vtAx, double dAngDeg) + { double dAngRad = dAngDeg * DEGTORAD ; + return Rotate( ptAx, vtAx, cos( dAngRad), sin( dAngRad)) ; } + virtual bool Rotate( const Point3d& ptAx, const Vector3d& vtAx, double dCosAng, double dSinAng) ; + virtual bool Scale( const Frame3d& frRef, double dCoeffX, double dCoeffY, double dCoeffZ) ; + virtual bool Mirror( const Point3d& ptOn, const Vector3d& vtNorm) ; + virtual bool Shear( const Point3d& ptOn, const Vector3d& vtNorm, const Vector3d& vtDir, double dCoeff) ; + virtual bool ToGlob( const Frame3d& frRef) ; + virtual bool ToLoc( const Frame3d& frRef) ; + virtual bool LocToLoc( const Frame3d& frOri, const Frame3d& frDest) ; + virtual void SetObjGraphics( IObjGraphics* pOGr) + { m_OGrMgr.SetObjGraphics( pOGr) ; } + virtual IObjGraphics* GetObjGraphics( void) + { return m_OGrMgr.GetObjGraphics() ; } + virtual const IObjGraphics* GetObjGraphics( void) const + { return m_OGrMgr.GetObjGraphics() ; } + virtual void SetTempProp( int nProp) + { m_nTempProp = nProp ; } + virtual int GetTempProp( void) + { return m_nTempProp ; } + + public : // ISurf + virtual bool IsSimple( void) const + { return true ; } + virtual bool IsClosed( void) const + { return false ; } + virtual bool GetArea( double& dArea) const ; + virtual bool GetVolume( double& dVolume) const + { dVolume = 0 ; + return ( m_nStatus == OK) ; } + virtual bool GetCentroid( Point3d& ptCen) const ; + virtual bool Invert( void) ; + + public : // ISurfFlatRegion + virtual bool CopyFrom( const IGeoObj* pGObjSrc) ; + virtual bool Clear( void) ; + virtual bool SetExtLoop( const ICurve& cCrv) ; + virtual bool SetExtLoop( ICurve* pCrv) ; + virtual bool AddIntLoop( const ICurve& cCrv) ; + virtual bool AddIntLoop( ICurve* pCrv) ; + virtual ICurve* GetLoop( int nLoop) const ; // nLoop=0 esterno, successivi interni + virtual int GetIntLoopNumber( void) const + { return int( m_vpIntLoop.size()) ; } + virtual const ISurfTriMesh* GetAuxSurf( void) const ; + virtual bool GetCurveClassification( const ICurve& Crv, CRVCVECTOR& ccClass) const ; + + public : // IGeoObjRW + virtual int GetNgeId( void) const ; + virtual bool Save( NgeWriter& ngeOut) const ; + virtual bool Load( NgeReader& ngeIn) ; + + public : + SurfFlatRegion( void) ; + SurfFlatRegion( const SurfFlatRegion& stSrc) + { if ( ! CopyFrom( stSrc)) + LOG_ERROR( GetEGkLogger(), "SurfFlatRegion : copy constructor error") } + SurfFlatRegion& operator =( const SurfFlatRegion& stSrc) + { if ( ! CopyFrom( stSrc)) + LOG_ERROR( GetEGkLogger(), "SurfFlatRegion : copy error") + return *this ; } + private : + bool CopyFrom( const SurfFlatRegion& clSrc) ; + virtual ICurve* GetMyLoop( int nLoop) const ; // nLoop=0 esterno, successivi interni + void ResetAuxSurf( void) const ; + bool ConvertArcsToBezierCurves( void) ; + + private : + enum Status { ERR = 0, OK = 1, TO_VERIFY = 2} ; + + private : + typedef std::deque PCRV_DEQUE ; + typedef PCRV_DEQUE::iterator PCRD_ITER ; + typedef PCRV_DEQUE::const_iterator PCRD_CONST_ITER ; + + private : + ObjGraphicsMgr m_OGrMgr ; // gestore grafica dell'oggetto + mutable SurfTriMesh* m_pSTM ; // superficie trimesh ausiliaria + Status m_nStatus ; // stato + Frame3d m_frF ; // riferimento intrinseco + ICurve* m_pExtLoop ; // curva che forma il loop esterno + PCRV_DEQUE m_vpIntLoop ; // deque delle curve che formano i loop interni + int m_nTempProp ; // proprietà temporanea + mutable PCRD_CONST_ITER m_Iter ; // iteratore sui loop interni +} ; + +//----------------------------------------------------------------------------- +inline SurfFlatRegion* CreateBasicSurfFlatRegion( void) + { return (static_cast( CreateGeoObj( SRF_FLATRGN))) ; } +inline SurfFlatRegion* CloneBasicSurfFlatRegion( const IGeoObj* pGObj) + { if ( pGObj == nullptr || pGObj->GetType() != SRF_FLATRGN) + return nullptr ; + return (static_cast(pGObj->Clone())) ; } +inline const SurfFlatRegion* GetBasicSurfFlatRegion( const IGeoObj* pGObj) + { return (dynamic_cast( pGObj)) ; } +inline SurfFlatRegion* GetBasicSurfFlatRegion( IGeoObj* pGObj) + { return (dynamic_cast( pGObj)) ; }