exetended form BasicGrid, changed type of t in class Link (from Iterator to Pointer to the object)

This commit is contained in:
Nico Pietroni 2005-08-02 11:18:36 +00:00
parent 7bc4ef59fd
commit 66921c752b
1 changed files with 374 additions and 383 deletions

View File

@ -21,9 +21,12 @@
* * * *
****************************************************************************/ ****************************************************************************/
/**************************************************************************** /****************************************************************************
History History
$Log: not supported by cvs2svn $ $Log: not supported by cvs2svn $
Revision 1.13 2005/04/14 17:23:08 ponchio
*** empty log message ***
Revision 1.12 2005/03/15 11:43:18 cignoni Revision 1.12 2005/03/15 11:43:18 cignoni
Removed BestDim function from the grid_static_ptr class and moved to a indipendent file (grid_util.h) for sake of generality. Removed BestDim function from the grid_static_ptr class and moved to a indipendent file (grid_util.h) for sake of generality.
@ -75,400 +78,388 @@ Initial commit
#include <vcg/space/box3.h> #include <vcg/space/box3.h>
#include <vcg/space/line3.h> #include <vcg/space/line3.h>
#include <vcg/space/index/grid_util.h> #include <vcg/space/index/grid_util.h>
namespace vcg { namespace vcg {
/** Static Uniform Grid
A spatial search structure for a accessing a container of objects.
It is based on a uniform grid overlayed over a protion of space.
The grid partion the space into cells. Cells contains just pointers
to the object that are stored elsewhere.
The set of objects is meant to be static and pointer stable.
Useful for situation were many space related query are issued over /** Static Uniform Grid
the same dataset (ray tracing, measuring distances between meshes, A spatial search structure for a accessing a container of objects.
re-detailing ecc.). It is based on a uniform grid overlayed over a protion of space.
Works well for distribution that ar reasonably uniform. The grid partion the space into cells. Cells contains just pointers
How to use it: to the object that are stored elsewhere.
ContainerType must have a 'value_type' typedef inside. The set of objects is meant to be static and pointer stable.
(stl containers already have it)
Objects pointed by cells (of kind 'value_type') must have Useful for situation were many space related query are issued over
a 'ScalarType' typedef (float or double usually) the same dataset (ray tracing, measuring distances between meshes,
and 2 member functions: re-detailing ecc.).
Works well for distribution that ar reasonably uniform.
bool Dist(const Point3f &point, ScalarType &mindist, Point3f &result); How to use it:
which return true if the distance from point to the object is < mindist ContainerType must have a 'value_type' typedef inside.
and set mindist to said distance, and result must be set as the closest (stl containers already have it)
point of the object to point)
void GetBBox(Box3<ScalarType> &b) Objects pointed by cells (of kind 'value_type') must have
which return the bounding box of the object a 'ScalarType' typedef (float or double usually)
and 2 member functions:
*/
template < typename ContainerType >
class GridStaticPtr
{
public:
/** Internal class for keeping the first pointer of object. bool Dist(const Point3f &point, ScalarType &mindist, Point3f &result);
Definizione Link dentro la griglia. Classe di supporto per GridStaticObj. which return true if the distance from point to the object is < mindist
*/ and set mindist to said distance, and result must be set as the closest
class Link point of the object to point)
{
public:
/// Costruttore di default
inline Link(){};
/// Costruttore con inizializzatori
inline Link( typename ContainerType::iterator const nt, const int ni ){
assert(ni>=0);
t = nt;
i = ni;
};
inline bool operator < ( const Link & l ) const{ return i < l.i; }
inline bool operator <= ( const Link & l ) const{ return i <= l.i; }
inline bool operator > ( const Link & l ) const{ return i > l.i; }
inline bool operator >= ( const Link & l ) const{ return i >= l.i; }
inline bool operator == ( const Link & l ) const{ return i == l.i; }
inline bool operator != ( const Link & l ) const{ return i != l.i; }
inline typename ContainerType::iterator & Elem() {
return t;
}
inline int & Index() {
return i;
}
private:
/// Puntatore all'elemento T
typename ContainerType::iterator t;
/// Indirizzo del voxel dentro la griglia
int i;
};//end class Link
typedef typename ContainerType::value_type ObjType;
typedef ObjType* ObjPtr;
typedef typename ObjType::ScalarType ScalarType;
typedef Point3<ScalarType> Point3x;
typedef Box3<ScalarType> Box3x;
typedef Line3<ScalarType> Line3x;
typedef Link* Cell;
Box3x bbox;
Point3x dim; /// Dimensione spaziale (lunghezza lati) del bbox
Point3i siz; /// Dimensioni griglia in celle
Point3x voxel; /// Dimensioni di una cella
std::vector<Link> links; /// Insieme di tutti i links void GetBBox(Box3<ScalarType> &b)
std::vector<Cell> grid; /// Griglia vera e propria which return the bounding box of the object
/// Dato un punto, ritorna la cella che lo contiene
inline Cell* Grid( const Point3d & p )
{
int x = int( (p[0]-bbox.min[0])/voxel[0] );
int y = int( (p[1]-bbox.min[1])/voxel[1] );
int z = int( (p[2]-bbox.min[2])/voxel[2] );
#ifndef NDEBUG
if ( x<0 || x>=siz[0] || y<0 || y>=siz[1] || z<0 || z>=siz[2] )
return NULL;
else
#endif
return grid.begin() + ( x+siz[0]*(y+siz[1]*z) );
}
/// Date le coordinate ritorna la cella
inline Cell* Grid( const int x, const int y, const int z )
{
#ifndef NDEBUG
if ( x<0 || x>=siz[0] || y<0 || y>=siz[1] || z<0 || z>=siz[2] )
assert(0);
//return NULL;
else
#endif
assert(((unsigned int)x+siz[0]*y+siz[1]*z)<grid.size());
return &*grid.begin() + ( x+siz[0]*(y+siz[1]*z) );
}
/// Date le coordinate di un grid point (corner minx,miy,minz) ritorna le celle che condividono
/// l'edge cell che parte dal grid point in direzione axis
inline void Grid( Point3i p, const int axis,
std::vector<Cell*> & cl)
{
#ifndef NDEBUG
if ( p[0]<0 || p[0]>siz[0] ||
p[1]<0 || p[1]>siz[1] ||
p[2]<0 || p[2]>siz[2] )
assert(0);
//return NULL;
else
#endif
assert(((unsigned int) p[0]+siz[0]*p[1]+siz[1]*p[2])<grid.size());
int axis0 = (axis+1)%3;
int axis1 = (axis+2)%3;
int i,j,x,y;
x = p[axis0];
y = p[axis1];
for(i = max(x-1,0); i <= min( x,siz[axis0]-1);++i)
for(j = max(y-1,0); j <= min( y,siz[axis1]-1);++j){
p[axis0]=i;
p[axis1]=j;
cl.push_back(Grid(p[0]+siz[0]*(p[1]+siz[1]*p[2])));
}
}
Cell* Grid(const int i) {
return &grid[i];
}
void Grid( const Point3d & p, Cell & first, Cell & last )
{
Cell* g = Grid(p);
first = *g;
last = *(g+1);
}
void Grid( const Cell* g, Cell & first, Cell & last )
{
first = *g;
last = *(g+1);
}
void Grid( const int x, const int y, const int z, Cell & first, Cell & last )
{
Cell* g = Grid(x,y,z);
first = *g;
last = *(g+1);
}
/// Set the bounding box of the grid
///We need some extra space for numerical precision.
void SetBBox( const Box3x & b )
{
bbox = b;
ScalarType t = bbox.Diag()/100.0;
if(t == 0) t = ScalarType(1e20); // <--- Some doubts on this (Cigno 5/1/04)
bbox.Offset(t);
dim = bbox.max - bbox.min;
}
/// Dato un punto 3d ritorna l'indice del box corrispondente
inline void PToIP(const Point3x & p, Point3i &pi ) const
{
Point3x t = p - bbox.min;
pi[0] = int( t[0]/voxel[0] );
pi[1] = int( t[1]/voxel[1] );
pi[2] = int( t[2]/voxel[2] );
}
/// Dato un box reale ritorna gli indici dei voxel compresi dentro un ibox
void BoxToIBox( const Box3x & b, Box3i & ib ) const
{
PToIP(b.min,ib.min);
PToIP(b.max,ib.max);
}
void ShowStats(FILE *fp)
{
// Conto le entry
//int nentry = 0;
//Hist H;
//H.SetRange(0,1000,1000);
//int pg;
//for(pg=0;pg<grid.size()-1;++pg)
// if( grid[pg]!=grid[pg+1] )
// {
// ++nentry;
// H.Add(grid[pg+1]-grid[pg]);
// }
// fprintf(fp,"Uniform Grid: %d x %d x %d (%d voxels), %.1f%% full, %d links \nNon empty Cell Occupancy Distribution Avg: %f (%4.0f %4.0f %4.0f) \n",
// siz[0],siz[1],siz[2],grid.size()-1,
// double(nentry)*100.0/(grid.size()-1),links.size(),H.Avg(),H.Percentile(.25),H.Percentile(.5),H.Percentile(.75)
//
//);
}
/** Returns the closest posistion of a point p and its distance
@param p a 3d point
@return The closest element
*/
ObjPtr GetClosest( const Point3x & p, ScalarType & min_dist, Point3x & res)
{
ScalarType dx = ( (p[0]-bbox.min[0])/voxel[0] );
ScalarType dy = ( (p[1]-bbox.min[1])/voxel[1] );
ScalarType dz = ( (p[2]-bbox.min[2])/voxel[2] );
int ix = int( dx );
int iy = int( dy );
int iz = int( dz );
double voxel_min=voxel[0];
if (voxel_min<voxel[1]) voxel_min=voxel[1];
if (voxel_min<voxel[2]) voxel_min=voxel[2];
ScalarType radius=(dx-ScalarType(ix));
if (radius>0.5) radius=(1.0-radius); radius*=voxel[0];
ScalarType tmp=dy-ScalarType(iy);
if (tmp>0.5) tmp=1.0-tmp;
tmp*=voxel[1];
if (radius>tmp) radius=tmp;
tmp=dz-ScalarType(iz);
if (tmp>0.5) tmp=1.0-tmp;
tmp*=voxel[2];
if (radius>tmp) radius=tmp;
Point3x t_res;
//ScalarType min_dist=1e10;
ObjPtr winner=NULL;
Link *first, *last; */
Link *l;
if ((ix>=0) && (iy>=0) && (iz>=0) &&
(ix<siz[0]) && (iy<siz[1]) && (iz<siz[2])) {
Grid( ix, iy, iz, first, last );
for(l=first;l!=last;++l)
{
if (!l->Elem()->IsD() && l->Elem()->Dist(p,min_dist,t_res)) {
winner=&*(l->Elem());
res=t_res;
}
};
};
//return winner;
Point3i done_min=Point3i(ix,iy,iz), done_max=Point3i(ix,iy,iz);
//printf("."); template < typename ContainerType,class FLT=float >
class GridStaticPtr:public BasicGrid<FLT>
while (min_dist>radius) {
//if (dy-ScalarType(iy))
done_min[0]--; if (done_min[0]<0) done_min[0]=0;
done_min[1]--; if (done_min[1]<0) done_min[1]=0;
done_min[2]--; if (done_min[2]<0) done_min[2]=0;
done_max[0]++; if (done_max[0]>=siz[0]-1) done_max[0]=siz[0]-1;
done_max[1]++; if (done_max[1]>=siz[1]-1) done_max[1]=siz[1]-1;
done_max[2]++; if (done_max[2]>=siz[2]-1) done_max[2]=siz[2]-1;
radius+=voxel_min;
//printf("+");
for (ix=done_min[0]; ix<=done_max[0]; ix++)
for (iy=done_min[1]; iy<=done_max[1]; iy++)
for (iz=done_min[2]; iz<=done_max[2]; iz++)
{
Grid( ix, iy, iz, first, last );
for(l=first;l!=last;++l)
{
if (!l->Elem()->IsD() && l->Elem()->Dist(p,min_dist,t_res)) {
winner=&*(l->Elem());
res=t_res;
};
};
}
};
return winner;
};
/// Inserisce una mesh nella griglia. Nota: prima bisogna
/// chiamare SetBBox che setta dim in maniera corretta
void Set( ContainerType & s )
{
Set(s,s.size());
}
/// Inserisce una mesh nella griglia. Nota: prima bisogna
/// chiamare SetBBox che setta dim in maniera corretta
void Set( ContainerType & s,int _size )
{
Point3i _siz;
BestDim( _size, dim, _siz );
Set(s,_siz);
}
void Set(ContainerType & s, Point3i _siz)
{
siz=_siz;
// Calcola la dimensione della griglia
voxel[0] = dim[0]/siz[0];
voxel[1] = dim[1]/siz[1];
voxel[2] = dim[2]/siz[2];
// "Alloca" la griglia: +1 per la sentinella
grid.resize( siz[0]*siz[1]*siz[2]+1 );
// Ciclo inserimento dei tetraedri: creazione link
links.clear();
typename ContainerType::iterator pt;
for(pt=s.begin(); pt!=s.end(); ++pt)
{ {
Box3x bb; // Boundig box del tetraedro corrente public:
(*pt).GetBBox(bb);
bb.Intersect(bbox);
if(! bb.IsNull() )
{
Box3i ib; // Boundig box in voxels typedef typename ContainerType::value_type ObjType;
BoxToIBox( bb,ib ); typedef ObjType* ObjPtr;
int x,y,z; typedef typename ObjType::ScalarType ScalarType;
for(z=ib.min[2];z<=ib.max[2];++z) typedef Point3<ScalarType> CoordType;
typedef Box3<ScalarType> Box3x;
typedef Line3<ScalarType> Line3x;
/** Internal class for keeping the first pointer of object.
Definizione Link dentro la griglia. Classe di supporto per GridStaticObj.
*/
class Link
{ {
int bz = z*siz[1]; public:
for(y=ib.min[1];y<=ib.max[1];++y) /// Costruttore di default
{ inline Link(){};
int by = (y+bz)*siz[0]; /// Costruttore con inizializzatori
for(x=ib.min[0];x<=ib.max[0];++x) inline Link(ObjPtr nt, const int ni ){
// Inserire calcolo cella corrente assert(ni>=0);
// if( pt->Intersect( ... ) t = nt;
links.push_back( Link(pt,by+x) ); i = ni;
} };
inline bool operator < ( const Link & l ) const{ return i < l.i; }
inline bool operator <= ( const Link & l ) const{ return i <= l.i; }
inline bool operator > ( const Link & l ) const{ return i > l.i; }
inline bool operator >= ( const Link & l ) const{ return i >= l.i; }
inline bool operator == ( const Link & l ) const{ return i == l.i; }
inline bool operator != ( const Link & l ) const{ return i != l.i; }
inline typename ObjPtr & Elem() {
return t;
}
inline int & Index() {
return i;
}
private:
/// Puntatore all'elemento T
ObjPtr t;
/// Indirizzo del voxel dentro la griglia
int i;
};//end class Link
typedef Link* Cell;
typedef typename Cell CellIterator;
std::vector<Link> links; /// Insieme di tutti i links
std::vector<Cell> grid; /// Griglia vera e propria
/// Dato un punto, ritorna la cella che lo contiene
inline Cell* Grid( const Point3d & p )
{
int x = int( (p[0]-bbox.min[0])/voxel[0] );
int y = int( (p[1]-bbox.min[1])/voxel[1] );
int z = int( (p[2]-bbox.min[2])/voxel[2] );
#ifndef NDEBUG
if ( x<0 || x>=siz[0] || y<0 || y>=siz[1] || z<0 || z>=siz[2] )
return NULL;
else
#endif
return grid.begin() + ( x+siz[0]*(y+siz[1]*z) );
} }
} /// Date le coordinate ritorna la cella
} inline Cell* Grid( const int x, const int y, const int z )
// Push della sentinella {
links.push_back( Link((typename ContainerType::iterator)NULL, #ifndef NDEBUG
(grid.size()-1))); if ( x<0 || x>=siz[0] || y<0 || y>=siz[1] || z<0 || z>=siz[2] )
assert(0);
// Ordinamento dei links //return NULL;
sort( links.begin(), links.end() ); else
#endif
// Creazione puntatori ai links assert(((unsigned int)x+siz[0]*y+siz[1]*z)<grid.size());
typename std::vector<Link>::iterator pl; return &*grid.begin() + ( x+siz[0]*(y+siz[1]*z) );
unsigned int pg; }
pl = links.begin();
for(pg=0;pg<grid.size();++pg)
{ /// Date le coordinate di un grid point (corner minx,miy,minz) ritorna le celle che condividono
assert(pl!=links.end()); /// l'edge cell che parte dal grid point in direzione axis
inline void Grid( Point3i p, const int axis,
grid[pg] = &*pl; std::vector<Cell*> & cl)
while( (int)pg == pl->Index() ) // Trovato inizio {
{ #ifndef NDEBUG
++pl; // Ricerca prossimo blocco if ( p[0]<0 || p[0]>siz[0] ||
if(pl==links.end()) p[1]<0 || p[1]>siz[1] ||
break; p[2]<0 || p[2]>siz[2] )
} assert(0);
} //return NULL;
else
} #endif
assert(((unsigned int) p[0]+siz[0]*p[1]+siz[1]*p[2])<grid.size());
int MemUsed() int axis0 = (axis+1)%3;
{ int axis1 = (axis+2)%3;
return sizeof(GridStaticPtr)+ sizeof(Link)*links.size() + int i,j,x,y;
sizeof(Cell) * grid.size(); x = p[axis0];
} y = p[axis1];
}; //end class GridStaticObj for(i = max(x-1,0); i <= min( x,siz[axis0]-1);++i)
for(j = max(y-1,0); j <= min( y,siz[axis1]-1);++j){
p[axis0]=i;
p[axis1]=j;
cl.push_back(Grid(p[0]+siz[0]*(p[1]+siz[1]*p[2])));
}
}
Cell* Grid(const int i) {
return &grid[i];
}
void Grid( const Point3d & p, Cell & first, Cell & last )
{
Cell* g = Grid(p);
first = *g;
last = *(g+1);
}
void Grid( const Cell* g, Cell & first, Cell & last )
{
first = *g;
last = *(g+1);
}
void Grid( const int x, const int y, const int z, Cell & first, Cell & last )
{
Cell* g = Grid(x,y,z);
first = *g;
last = *(g+1);
}
/// Set the bounding box of the grid
///We need some extra space for numerical precision.
void SetBBox( const Box3x & b )
{
bbox = b;
ScalarType t = bbox.Diag()/100.0;
if(t == 0) t = ScalarType(1e20); // <--- Some doubts on this (Cigno 5/1/04)
bbox.Offset(t);
dim = bbox.max - bbox.min;
}
void ShowStats(FILE *fp)
{
// Conto le entry
//int nentry = 0;
//Hist H;
//H.SetRange(0,1000,1000);
//int pg;
//for(pg=0;pg<grid.size()-1;++pg)
// if( grid[pg]!=grid[pg+1] )
// {
// ++nentry;
// H.Add(grid[pg+1]-grid[pg]);
// }
// fprintf(fp,"Uniform Grid: %d x %d x %d (%d voxels), %.1f%% full, %d links \nNon empty Cell Occupancy Distribution Avg: %f (%4.0f %4.0f %4.0f) \n",
// siz[0],siz[1],siz[2],grid.size()-1,
// double(nentry)*100.0/(grid.size()-1),links.size(),H.Avg(),H.Percentile(.25),H.Percentile(.5),H.Percentile(.75)
//
//);
}
/** Returns the closest posistion of a point p and its distance
@param p a 3d point
@return The closest element
*/
ObjPtr GetClosest( const CoordType & p, ScalarType & min_dist, CoordType & res)
{
ScalarType dx = ( (p[0]-bbox.min[0])/voxel[0] );
ScalarType dy = ( (p[1]-bbox.min[1])/voxel[1] );
ScalarType dz = ( (p[2]-bbox.min[2])/voxel[2] );
int ix = int( dx );
int iy = int( dy );
int iz = int( dz );
double voxel_min=voxel[0];
if (voxel_min<voxel[1]) voxel_min=voxel[1];
if (voxel_min<voxel[2]) voxel_min=voxel[2];
ScalarType radius=(dx-ScalarType(ix));
if (radius>0.5) radius=(1.0-radius); radius*=voxel[0];
ScalarType tmp=dy-ScalarType(iy);
if (tmp>0.5) tmp=1.0-tmp;
tmp*=voxel[1];
if (radius>tmp) radius=tmp;
tmp=dz-ScalarType(iz);
if (tmp>0.5) tmp=1.0-tmp;
tmp*=voxel[2];
if (radius>tmp) radius=tmp;
CoordType t_res;
//ScalarType min_dist=1e10;
ObjPtr winner=NULL;
Link *first, *last;
Link *l;
if ((ix>=0) && (iy>=0) && (iz>=0) &&
(ix<siz[0]) && (iy<siz[1]) && (iz<siz[2])) {
Grid( ix, iy, iz, first, last );
for(l=first;l!=last;++l)
{
if (!l->Elem()->IsD() && l->Elem()->Dist(p,min_dist,t_res)) {
winner=&*(l->Elem());
res=t_res;
}
};
};
//return winner;
Point3i done_min=Point3i(ix,iy,iz), done_max=Point3i(ix,iy,iz);
//printf(".");
while (min_dist>radius) {
//if (dy-ScalarType(iy))
done_min[0]--; if (done_min[0]<0) done_min[0]=0;
done_min[1]--; if (done_min[1]<0) done_min[1]=0;
done_min[2]--; if (done_min[2]<0) done_min[2]=0;
done_max[0]++; if (done_max[0]>=siz[0]-1) done_max[0]=siz[0]-1;
done_max[1]++; if (done_max[1]>=siz[1]-1) done_max[1]=siz[1]-1;
done_max[2]++; if (done_max[2]>=siz[2]-1) done_max[2]=siz[2]-1;
radius+=voxel_min;
//printf("+");
for (ix=done_min[0]; ix<=done_max[0]; ix++)
for (iy=done_min[1]; iy<=done_max[1]; iy++)
for (iz=done_min[2]; iz<=done_max[2]; iz++)
{
Grid( ix, iy, iz, first, last );
for(l=first;l!=last;++l)
{
if (!l->Elem()->IsD() && l->Elem()->Dist(p,min_dist,t_res)) {
winner=&*(l->Elem());
res=t_res;
};
};
}
};
return winner;
};
/// Inserisce una mesh nella griglia. Nota: prima bisogna
/// chiamare SetBBox che setta dim in maniera corretta
void Set( ContainerType & s )
{
Set(s,s.size());
}
/// Inserisce una mesh nella griglia. Nota: prima bisogna
/// chiamare SetBBox che setta dim in maniera corretta
void Set( ContainerType & s,int _size )
{
Point3i _siz;
BestDim( _size, dim, _siz );
Set(s,_siz);
}
void Set(ContainerType & s, Point3i _siz)
{
siz=_siz;
// Calcola la dimensione della griglia
voxel[0] = dim[0]/siz[0];
voxel[1] = dim[1]/siz[1];
voxel[2] = dim[2]/siz[2];
// "Alloca" la griglia: +1 per la sentinella
grid.resize( siz[0]*siz[1]*siz[2]+1 );
// Ciclo inserimento dei tetraedri: creazione link
links.clear();
typename ContainerType::iterator pt;
for(pt=s.begin(); pt!=s.end(); ++pt)
{
Box3x bb; // Boundig box del tetraedro corrente
(*pt).GetBBox(bb);
bb.Intersect(bbox);
if(! bb.IsNull() )
{
Box3i ib; // Boundig box in voxels
BoxToIBox( bb,ib );
int x,y,z;
for(z=ib.min[2];z<=ib.max[2];++z)
{
int bz = z*siz[1];
for(y=ib.min[1];y<=ib.max[1];++y)
{
int by = (y+bz)*siz[0];
for(x=ib.min[0];x<=ib.max[0];++x)
// Inserire calcolo cella corrente
// if( pt->Intersect( ... )
links.push_back( Link(&(*pt),by+x) );
}
}
}
}
// Push della sentinella
/*links.push_back( Link((typename ContainerType::iterator)NULL,
(grid.size()-1)));*/
links.push_back( Link(NULL,
(grid.size()-1)));
// Ordinamento dei links
sort( links.begin(), links.end() );
// Creazione puntatori ai links
typename std::vector<Link>::iterator pl;
unsigned int pg;
pl = links.begin();
for(pg=0;pg<grid.size();++pg)
{
assert(pl!=links.end());
grid[pg] = &*pl;
while( (int)pg == pl->Index() ) // Trovato inizio
{
++pl; // Ricerca prossimo blocco
if(pl==links.end())
break;
}
}
}
int MemUsed()
{
return sizeof(GridStaticPtr)+ sizeof(Link)*links.size() +
sizeof(Cell) * grid.size();
}
}; //end class GridStaticObj
}; // end namespace }; // end namespace
#endif #endif