647 lines
16 KiB
C++
647 lines
16 KiB
C++
//----------------------------------------------------------------------------
|
|
// EgalTech 2013-2013
|
|
//----------------------------------------------------------------------------
|
|
// File : CurveBezier.cpp Data : 22.11.13 Versione : 1.3a1
|
|
// Contenuto : Implementazione della classe Curva di Bezier.
|
|
//
|
|
//
|
|
//
|
|
// Modifiche : 28.12.12 DS Creazione modulo.
|
|
//
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
//--------------------------- Include ----------------------------------------
|
|
#include "stdafx.h"
|
|
#include <new>
|
|
#include "\EgtDev\Include\EGnStringUtils.h"
|
|
#include "CurveBezier.h"
|
|
#include "GeoObjFactory.h"
|
|
|
|
using namespace std ;
|
|
|
|
//----------------------------------------------------------------------------
|
|
GEOOBJ_REGISTER( CRV_BEZ, "C_BEZ", CurveBezier) ;
|
|
|
|
//----------------------------------------------------------------------------
|
|
CurveBezier::CurveBezier( void)
|
|
{
|
|
m_nDeg = 0 ;
|
|
m_bRat = false ;
|
|
m_nDimArr = 0 ;
|
|
m_aPtCtrl = nullptr ;
|
|
m_aWeCtrl = nullptr ;
|
|
m_nStatus = TO_VERIFY ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
CurveBezier::~CurveBezier( void)
|
|
{
|
|
if ( m_nDimArr > 0) {
|
|
delete [] m_aPtCtrl ;
|
|
delete [] m_aWeCtrl ;
|
|
}
|
|
m_nDimArr = 0 ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Init( int nDeg, bool bIsRational)
|
|
{
|
|
const int MINDIM = 4 ;
|
|
|
|
|
|
// verifico validità grado
|
|
if ( nDeg < 1 || nDeg > MAXDEG)
|
|
return false ;
|
|
|
|
// se spazio dinamico sufficiente
|
|
if ( ( nDeg + 1) <= m_nDimArr)
|
|
;
|
|
// se altrimenti spazio statico sufficiente
|
|
else if ( ( nDeg + 1) <= ST_PTC) {
|
|
m_aPtCtrl = m_aStPtCtrl ;
|
|
if ( bIsRational)
|
|
m_aWeCtrl = m_aStWeCtrl ;
|
|
}
|
|
// altrimenti
|
|
else {
|
|
// se necessaria, pulizia
|
|
if ( m_nDimArr > 0) {
|
|
delete [] m_aPtCtrl ;
|
|
delete [] m_aWeCtrl ;
|
|
}
|
|
// alloco punti
|
|
m_aPtCtrl = new Point3d [ nDeg + 1] ;
|
|
if ( m_aPtCtrl == nullptr)
|
|
return false ;
|
|
// se razionale, alloco pesi
|
|
if ( bIsRational) {
|
|
m_aWeCtrl = new double [ nDeg + 1] ;
|
|
if ( m_aWeCtrl == nullptr) {
|
|
delete [] m_aPtCtrl ;
|
|
return false ;
|
|
}
|
|
}
|
|
// salvo dimensione array allocati
|
|
m_nDimArr = nDeg + 1 ;
|
|
}
|
|
|
|
// salvo il grado
|
|
m_nDeg = nDeg ;
|
|
|
|
// salvo flag di razionale
|
|
m_bRat = bIsRational ;
|
|
|
|
// pulisco, assegnando 0 ai punti e 1 ai pesi
|
|
memset( m_aPtCtrl, 0, sizeof( Point3d) * ( m_nDeg + 1)) ;
|
|
if ( m_bRat) {
|
|
for ( int i = 0 ; i <= m_nDeg ; ++ i)
|
|
m_aWeCtrl[i] = 1 ;
|
|
}
|
|
m_nStatus = TO_VERIFY ;
|
|
|
|
return Validate() ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::SetControlPoint( int nInd, const Point3d& ptCtrl)
|
|
{
|
|
// verifico validità indice
|
|
if ( m_nStatus != OK || nInd < 0 || nInd > m_nDeg)
|
|
return false ;
|
|
|
|
// assegno il valore
|
|
m_aPtCtrl[nInd] = ptCtrl ;
|
|
|
|
// se razionale, metto il peso a 1
|
|
if ( m_bRat)
|
|
m_aWeCtrl[nInd] = 1 ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::SetControlPoint( int nInd, const Point3d& ptCtrl, double dW)
|
|
{
|
|
// verifico validità, razionalità e indice
|
|
if ( m_nStatus != OK || ! m_bRat || nInd < 0 || nInd > m_nDeg)
|
|
return false ;
|
|
|
|
// verifico che il peso non sia nullo o negativo
|
|
if ( dW < EPS_SMALL)
|
|
return false ;
|
|
|
|
// assegno il valore e il peso
|
|
m_aPtCtrl[nInd] = ptCtrl ;
|
|
m_aWeCtrl[nInd] = dW ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
const Point3d&
|
|
CurveBezier::GetControlPoint( int nInd, bool* pbOk) const
|
|
{
|
|
// verifico validità e indice
|
|
if ( m_nStatus != OK || nInd < 0 || nInd > m_nDeg) {
|
|
if ( pbOk != NULL)
|
|
*pbOk = false ;
|
|
return ORIG ;
|
|
}
|
|
// ritorno i dati
|
|
if ( pbOk != NULL)
|
|
*pbOk = true ;
|
|
return m_aPtCtrl[nInd] ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
double
|
|
CurveBezier::GetControlWeight( int nInd, bool* pbOk) const
|
|
{
|
|
// verifico validità, razionalità e indice
|
|
if ( m_nStatus != OK || ! m_bRat || nInd < 0 || nInd > m_nDeg) {
|
|
if ( pbOk != NULL)
|
|
*pbOk = false ;
|
|
return 0 ;
|
|
}
|
|
// ritorno i dati
|
|
if ( pbOk != NULL)
|
|
*pbOk = true ;
|
|
return m_aWeCtrl[nInd] ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
CurveBezier*
|
|
CurveBezier::Clone( void) const
|
|
{
|
|
CurveBezier* pCrv ;
|
|
|
|
|
|
// alloco oggetto
|
|
pCrv = new(nothrow) CurveBezier ;
|
|
if ( pCrv != nullptr)
|
|
*pCrv = *(const_cast<CurveBezier*>(this)) ;
|
|
|
|
return pCrv ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
const string&
|
|
CurveBezier::GetKey( void) const
|
|
{
|
|
return GEOOBJ_GETKEY( CurveBezier) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Save( ostream& osOut) const
|
|
{
|
|
int i ;
|
|
|
|
|
|
// flag razionale
|
|
osOut << m_bRat ;
|
|
// dati della curva di bezier
|
|
osOut << ";" << ToString( m_nDeg) ;
|
|
// ciclo sui punti di controllo ( con pesi se razionale)
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i) {
|
|
osOut << ";" << ToString( m_aPtCtrl[i]) ;
|
|
if ( m_bRat) {
|
|
osOut << "," << ToString( m_aWeCtrl[i]) ;
|
|
}
|
|
}
|
|
// terminazione
|
|
osOut << ";" << endl ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Load( CScan& TheScanner)
|
|
{
|
|
string sLine ;
|
|
STRVECTOR vsParams ;
|
|
bool bIsRat ;
|
|
int nDeg ;
|
|
Point3d ptP ;
|
|
double dW ;
|
|
int i ;
|
|
|
|
|
|
// leggo la prossima linea
|
|
if ( ! TheScanner.GetLine( sLine))
|
|
return false ;
|
|
// la divido in parametri
|
|
Tokenize( sLine, ";", vsParams) ;
|
|
// almeno 2 parametri : flag razionale e grado
|
|
if ( vsParams.size() < 2)
|
|
return false ;
|
|
// recupero il flag
|
|
if ( ! FromString( vsParams[0], bIsRat))
|
|
return false ;
|
|
// recupero il grado
|
|
if ( ! FromString( vsParams[1], nDeg))
|
|
return false ;
|
|
// ri-controllo il numero dei parametri
|
|
if ( vsParams.size() != ( 2 + ( nDeg + 1)))
|
|
return false ;
|
|
// inizializzo la curva di Bezier
|
|
if ( ! Init( nDeg, bIsRat))
|
|
return false ;
|
|
// se integrale
|
|
if ( ! bIsRat) {
|
|
// recupero e setto punti di controllo
|
|
for ( i = 0 ; i <= nDeg ; ++ i) {
|
|
if ( ! FromString( vsParams[i+2], ptP) ||
|
|
! SetControlPoint( i, ptP))
|
|
return false ;
|
|
}
|
|
}
|
|
// altrimenti razionale
|
|
else {
|
|
// recupero e setto punti di controllo
|
|
for ( i = 0 ; i <= nDeg ; ++ i) {
|
|
if ( ! FromString( vsParams[i+2], ptP, dW) ||
|
|
! SetControlPoint( i, ptP, dW))
|
|
return false ;
|
|
}
|
|
}
|
|
// eseguo validazione
|
|
return Validate() ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Validate( void)
|
|
{
|
|
if ( m_nStatus == TO_VERIFY)
|
|
m_nStatus = ( ( m_nDeg > 0 && m_aPtCtrl != nullptr) ? OK : ERR) ;
|
|
|
|
return ( m_nStatus == OK) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::GetStartPoint( Point3d& ptStart) const
|
|
{
|
|
// verifico lo stato
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// assegno il punto
|
|
ptStart = m_aPtCtrl[0] ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::GetEndPoint( Point3d& ptEnd) const
|
|
{
|
|
// verifico lo stato
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// assegno il punto
|
|
ptEnd = m_aPtCtrl[m_nDeg] ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::GetDomain( double& dStart, double& dEnd) const
|
|
{
|
|
// verifico lo stato
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// assegno gli estremi del dominio
|
|
dStart = 0 ;
|
|
dEnd = 1 ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Calcolo i polinomi di Bernstein e da questi il punto ( vedi Piegl-Tiller)
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::GetPointD1D2( double dU, Point3d& ptPos, Vector3d& vtDer1, Vector3d& vtDer2) const
|
|
{
|
|
int i ;
|
|
double dBern[MAXDEG] ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// il parametro U deve essere compreso tra 0 e 1
|
|
if ( dU < 0)
|
|
dU = 0 ;
|
|
else if ( dU > 1)
|
|
dU = 1 ;
|
|
|
|
// se forma polinomiale (o integrale)
|
|
if ( ! m_bRat) {
|
|
|
|
// calcolo dei polinomi di Bernstein di grado opportuno
|
|
dBern[0] = 1 ;
|
|
for ( i = 1 ; i <= m_nDeg - 2 ; ++ i)
|
|
IncreaseBernsteinOneDegree( dU, i, dBern) ;
|
|
|
|
// calcolo della derivata seconda
|
|
vtDer2.Set( 0, 0, 0) ;
|
|
for ( i = 0 ; i <= m_nDeg - 2 ; ++ i) {
|
|
vtDer2 = vtDer2 + dBern[i] * ( m_aPtCtrl[i+2] + m_aPtCtrl[i] - 2 * m_aPtCtrl[i+1]) ;
|
|
}
|
|
vtDer2 = m_nDeg * ( m_nDeg - 1) * vtDer2 ;
|
|
|
|
// aumento il grado
|
|
IncreaseBernsteinOneDegree( dU, m_nDeg - 1, dBern) ;
|
|
|
|
// calcolo della derivata prima
|
|
vtDer1.Set( 0, 0, 0) ;
|
|
for ( i = 0 ; i <= m_nDeg - 1 ; ++ i) {
|
|
vtDer1 = vtDer1 + dBern[i] * ( m_aPtCtrl[i+1] - m_aPtCtrl[i]) ;
|
|
}
|
|
vtDer1 = m_nDeg * vtDer1 ;
|
|
|
|
// aumento il grado
|
|
IncreaseBernsteinOneDegree( dU, m_nDeg, dBern) ;
|
|
|
|
// calcolo del punto
|
|
ptPos.Set( 0, 0, 0) ;
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
ptPos = ptPos + dBern[i] * m_aPtCtrl[i] ;
|
|
}
|
|
|
|
// altrimenti forma razionale
|
|
else {
|
|
double dW ;
|
|
double dW1 ;
|
|
double dW2 ;
|
|
double dInvW ;
|
|
Point3d aPtWCtrl[MAXDEG] ;
|
|
Vector3d vtPos ;
|
|
|
|
// porto i punti in forma omogenea moltiplicandoli per i pesi
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
aPtWCtrl[i] = m_aWeCtrl[i] * m_aPtCtrl[i] ;
|
|
|
|
// calcolo dei polinomi di Bernstein di grado opportuno
|
|
dBern[0] = 1 ;
|
|
for ( i = 1 ; i <= m_nDeg - 2 ; ++ i)
|
|
IncreaseBernsteinOneDegree( dU, i, dBern) ;
|
|
|
|
// calcolo della derivata seconda
|
|
vtDer2.Set( 0, 0, 0) ; dW2 = 0 ;
|
|
for ( i = 0 ; i <= m_nDeg - 2 ; ++ i) {
|
|
vtDer2 = vtDer2 + dBern[i] * ( aPtWCtrl[i+2] + aPtWCtrl[i] - 2 * aPtWCtrl[i+1]) ;
|
|
dW2 = dW2 + dBern[i] * ( m_aWeCtrl[i+2] + m_aWeCtrl[i] - 2 * m_aWeCtrl[i+1]) ;
|
|
}
|
|
vtDer2 = m_nDeg * ( m_nDeg - 1) * vtDer2 ;
|
|
dW2 = m_nDeg * ( m_nDeg - 1) * dW2 ;
|
|
|
|
// aumento il grado
|
|
IncreaseBernsteinOneDegree( dU, m_nDeg - 1, dBern) ;
|
|
|
|
// calcolo della derivata prima
|
|
vtDer1.Set( 0, 0, 0) ; dW1 = 0 ;
|
|
for ( i = 0 ; i <= m_nDeg - 1 ; ++ i) {
|
|
vtDer1 = vtDer1 + dBern[i] * ( aPtWCtrl[i+1] - aPtWCtrl[i]) ;
|
|
dW1 = dW1 + dBern[i] * ( m_aWeCtrl[i+1] - m_aWeCtrl[i]) ;
|
|
}
|
|
vtDer1 = m_nDeg * vtDer1 ;
|
|
dW1 = m_nDeg * dW1 ;
|
|
|
|
// aumento il grado
|
|
IncreaseBernsteinOneDegree( dU, m_nDeg, dBern) ;
|
|
|
|
// calcolo del punto
|
|
ptPos.Set( 0, 0, 0) ; dW = 0 ;
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i) {
|
|
ptPos = ptPos + dBern[i] * aPtWCtrl[i] ;
|
|
dW = dW + dBern[i] * m_aWeCtrl[i] ;
|
|
}
|
|
|
|
// ritrasformo da forma omogenea a forma standard
|
|
dInvW = 1 / ( ( dW > EPS_ZERO) ? dW : EPS_ZERO) ;
|
|
ptPos = ptPos * dInvW ;
|
|
vtPos.Set( ptPos.x, ptPos.y, ptPos.z) ;
|
|
vtDer1 = ( vtDer1 - dW1 * vtPos) * dInvW ;
|
|
vtDer2 = ( vtDer2 - 2 * dW1 * vtDer1 - dW2 * vtPos) * dInvW ;
|
|
}
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void
|
|
CurveBezier::IncreaseBernsteinOneDegree( double dU, int nDeg, double dBern[]) const
|
|
{
|
|
int j ;
|
|
double dU1 ;
|
|
double dTot ;
|
|
double dTmp ;
|
|
|
|
|
|
dU1 = 1 - dU ;
|
|
dTot = 0 ;
|
|
for ( j = 0 ; j < nDeg ; ++ j) {
|
|
dTmp = dBern[j] ;
|
|
dBern[j] = dTot + dU1 * dTmp ;
|
|
dTot = dU * dTmp ;
|
|
}
|
|
dBern[nDeg] = ( nDeg > 0 ? dTot : 1) ;
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::GetLength( double& dLen) const
|
|
{
|
|
const int NUM_SEG = 20 ;
|
|
int i ;
|
|
double dU ;
|
|
Point3d ptIni ;
|
|
Point3d ptFin ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// approssimo la curva con un numero fisso di segmenti !!! DA MIGLIORARE !!!
|
|
dLen = 0 ;
|
|
for ( i = 0 ; i <= NUM_SEG ; ++ i) {
|
|
// ricavo il punto
|
|
dU = i / (double) NUM_SEG ;
|
|
GetPoint( dU, ptFin) ;
|
|
// dal secondo posso calcolare la lunghezza
|
|
if ( i > 0)
|
|
dLen += Dist( ptIni, ptFin) ;
|
|
// nuovo iniziale prende i valori del finale
|
|
ptIni = ptFin ;
|
|
}
|
|
|
|
return ( dLen > EPS_SMALL) ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Reverse( void)
|
|
{
|
|
int i ;
|
|
int nMid ;
|
|
Point3d ptTemp ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// inverto i punti di controllo
|
|
nMid = ( m_nDeg + 1) / 2 ;
|
|
for ( i = 0 ; i < nMid ; ++ i) {
|
|
ptTemp = m_aPtCtrl[i] ;
|
|
m_aPtCtrl[i] = m_aPtCtrl[m_nDeg-i] ;
|
|
m_aPtCtrl[m_nDeg-i] = ptTemp ;
|
|
}
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Translate( const Vector3d& vtMove)
|
|
{
|
|
int i ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// traslo i punti di controllo
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
m_aPtCtrl[i].Translate( vtMove) ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Rotate( const Point3d& ptAx, const Vector3d& vtAx, double dCosAng, double dSinAng)
|
|
{
|
|
int i ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// verifico validità dell'asse di rotazione
|
|
if ( vtAx.IsSmall())
|
|
return false ;
|
|
|
|
// ruoto i punti di controllo
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
m_aPtCtrl[i].Rotate( ptAx, vtAx, dCosAng, dSinAng) ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Scale( const Point3d& ptCen, double dCoeffX, double dCoeffY, double dCoeffZ)
|
|
{
|
|
int i ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// verifico non sia nulla
|
|
if ( fabs( dCoeffX) < EPS_ZERO && fabs( dCoeffY) < EPS_ZERO && fabs( dCoeffZ) < EPS_ZERO)
|
|
return false ;
|
|
|
|
// scalo i punti di controllo
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
m_aPtCtrl[i].Scale( ptCen, dCoeffX, dCoeffY, dCoeffZ) ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::Mirror( const Point3d& ptOn, const Vector3d& vtNorm)
|
|
{
|
|
int i ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// verifico validità del piano di specchiatura
|
|
if ( vtNorm.IsSmall())
|
|
return false ;
|
|
|
|
// specchio i punti di controllo
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
m_aPtCtrl[i].Mirror( ptOn, vtNorm) ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::ToGlob( const Frame3d& frRef)
|
|
{
|
|
int i ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// verifico validità del frame
|
|
if ( frRef.GetType() == Frame3d::ERR)
|
|
return false ;
|
|
|
|
// trasformo i punti di controllo
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
m_aPtCtrl[i].ToGlob( frRef) ;
|
|
|
|
return true ;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
bool
|
|
CurveBezier::ToLoc( const Frame3d& frRef)
|
|
{
|
|
int i ;
|
|
|
|
|
|
// la curva deve essere validata
|
|
if ( m_nStatus != OK)
|
|
return false ;
|
|
|
|
// verifico validità del frame
|
|
if ( frRef.GetType() == Frame3d::ERR)
|
|
return false ;
|
|
|
|
// trasformo i punti di controllo
|
|
for ( i = 0 ; i <= m_nDeg ; ++ i)
|
|
m_aPtCtrl[i].ToLoc( frRef) ;
|
|
|
|
return true ;
|
|
}
|