added wrapping to functions defined in GridClosest:

- GetClosest
     - GetKClosest
     - DoRay
This commit is contained in:
Nico Pietroni 2005-09-30 13:15:21 +00:00
parent 6416a20ba2
commit a3c311feda
2 changed files with 160 additions and 225 deletions

View File

@ -24,6 +24,10 @@
History History
$Log: not supported by cvs2svn $ $Log: not supported by cvs2svn $
Revision 1.25 2005/09/21 09:22:51 pietroni
removed closest functions. Closest function is now on index\\Closest.h
Users must use trimesh\\closest.h to perform spatial query.
Revision 1.24 2005/09/16 11:57:15 cignoni Revision 1.24 2005/09/16 11:57:15 cignoni
Removed two wrong typenames Removed two wrong typenames
@ -114,6 +118,7 @@ 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>
#include <vcg/space/index/grid_closest.h>
#include <vcg/simplex/face/distance.h> #include <vcg/simplex/face/distance.h>
namespace vcg { namespace vcg {
@ -149,18 +154,17 @@ namespace vcg {
point of the object to point) point of the object to point)
*/ */
template < typename ContainerType,class FLT=float > template < typename OBJTYPE,class FLT=float >
class GridStaticPtr:public BasicGrid<FLT> class GridStaticPtr:public BasicGrid<OBJTYPE,FLT>
{ {
public: public:
typedef OBJTYPE ObjType;
typedef typename ContainerType::value_type ObjType;
typedef ObjType* ObjPtr; typedef ObjType* ObjPtr;
typedef typename ObjType::ScalarType ScalarType; typedef typename ObjType::ScalarType ScalarType;
typedef Point3<ScalarType> CoordType; typedef Point3<ScalarType> CoordType;
typedef Box3<ScalarType> Box3x; typedef Box3<ScalarType> Box3x;
typedef Line3<ScalarType> Line3x; typedef Line3<ScalarType> Line3x;
typedef typename GridStaticPtr<OBJTYPE,FLT> GridPtrType;
/** Internal class for keeping the first pointer of object. /** Internal class for keeping the first pointer of object.
Definizione Link dentro la griglia. Classe di supporto per GridStaticObj. Definizione Link dentro la griglia. Classe di supporto per GridStaticObj.
@ -328,185 +332,38 @@ namespace vcg {
} }
///** Returns the closest posistion of a point p and its distance
//@param p a 3d point /// Insert a mesh in the grid
//@param max_dist maximum distance not to search beyond. template <class OBJITER>
//@param dist_funct (templated type) a functor object used to calculate distance from a grid object to the point p. void Set(const OBJITER & _oBegin, const OBJITER & _oEnd)
//@param min_dist the returned closest distance
//@param res the returned closest point
//@return The closest element
//*/
///*
// A DISTFUNCT object must implement an operator () with signature:
// bool operator () (const ObjType& obj, const CoordType & p, ScalarType & min_dist, CoordType & res);
//*/
//template <class DISTFUNCTOR,class TMARKER>
// ObjPtr GetClosest( const CoordType & p, const ScalarType & max_dist, DISTFUNCTOR & dist_funct, ScalarType & min_dist, CoordType & res,TMARKER tm)
//{
// // Initialize min_dist with max_dist to exploit early rejection test.
// min_dist = max_dist;
// 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 );
// if (!bbox.IsIn(p))
// assert (0);///the grid has to be extended until the point
// 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;
// tm.UnMarkAll();
// 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).IsD())
// {
// if( ! tm.IsMarked(l->Elem()))
// {
// //if (!l->Elem()->IsD() && l->Elem()->Dist(p,min_dist,t_res)) {
// //if (!l->Elem()->IsD() && dist_funct(*(l->Elem()), p, min_dist, t_res)) { // <-- NEW: use of distance functor
// if (dist_funct((**l), p, min_dist, t_res)) // <-- NEW: use of distance functor
// {
// winner=l->Elem();
// res=t_res;
// }
// tm.Mark(l->Elem());
// }
// }
// };
// //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).IsD())
// {
// if( ! tm.IsMarked(l->Elem()))
// {
// //if (!l->Elem()->IsD() && l->Elem()->Dist(p,min_dist,t_res)) {
// if (dist_funct((**l), p, min_dist, t_res)) // <-- NEW: use of distance functor
// {
// winner=l->Elem();
// res=t_res;
// };
// tm.Mark(l->Elem());
// }
// }
// };
// }
// };
// return winner;
//};
///** Returns the closest posistion of a point p and its distance (OLD VERSION)
//@param p a 3d point
//@param min_dist the returned closest distance
//@param res the returned closest point
//@return The closest element
//*/
///*
// NOTE: kept for backward compatibility.
// Same as template <class DISTFUNCT> GetClosest() but without maximum distance rejection
// and distance functor and with the assumption that ObjType expose a Dist() method
// acting like a DISTFUNCT funcor;
//*/
//class BackCompDist {
// public:
// inline bool operator () (const ObjType & obj, const CoordType & pt, ScalarType & mindist, CoordType & result) {
// return (vcg::face::PointDistance<ObjType>(obj,pt, mindist, result));
// }
// };
//template <class TMARKER>
//ObjPtr GetClosest( const CoordType & p, ScalarType & min_dist, CoordType & res,TMARKER &tm) {
//
// const ScalarType max_dist = min_dist;
// return (this->GetClosest<BackCompDist,TMARKER>(p, max_dist, BackCompDist(), min_dist, res,tm));
//}
/// Inserisce una mesh nella griglia. Nota: prima bisogna
/// chiamare SetBBox che setta dim in maniera corretta
void Set( ContainerType & s )
{ {
Set(s,s.size()); OBJITER i;
} bbox.min=Point3<FLT>(0,0,0);
bbox.max=Point3<FLT>(0,0,0);
Box3<FLT> b;
/// Inserisce una mesh nella griglia. Nota: prima bisogna for(i = _oBegin; i!= _oEnd; ++i)
/// chiamare SetBBox che setta dim in maniera corretta {
void Set( ContainerType & s,int _size ) (*i).GetBBox(b);
{ bbox.Add(b);
Point3i _siz; }
int _size=std::distance<OBJITER>(_oBegin,_oEnd);
BestDim( _size, dim, _siz ); dim = bbox.max - bbox.min;
Set(s,_siz); BestDim( _size, dim, siz );
} // find voxel size
void Set(ContainerType & s, Point3i _siz)
{
siz=_siz;
// Calcola la dimensione della griglia
voxel[0] = dim[0]/siz[0]; voxel[0] = dim[0]/siz[0];
voxel[1] = dim[1]/siz[1]; voxel[1] = dim[1]/siz[1];
voxel[2] = dim[2]/siz[2]; voxel[2] = dim[2]/siz[2];
// "Alloca" la griglia: +1 per la sentinella // "Alloca" la griglia: +1 per la sentinella
grid.resize( siz[0]*siz[1]*siz[2]+1 ); grid.resize( siz[0]*siz[1]*siz[2]+1 );
// Ciclo inserimento dei tetraedri: creazione link // Ciclo inserimento dei tetraedri: creazione link
links.clear(); links.clear();
typename ContainerType::iterator pt; for(i=_oBegin; i!=_oEnd; ++i)
for(pt=s.begin(); pt!=s.end(); ++pt)
{ {
Box3x bb; // Boundig box del tetraedro corrente Box3x bb; // Boundig box del tetraedro corrente
(*pt).GetBBox(bb); (*i).GetBBox(bb);
bb.Intersect(bbox); bb.Intersect(bbox);
if(! bb.IsNull() ) if(! bb.IsNull() )
{ {
@ -523,7 +380,7 @@ namespace vcg {
for(x=ib.min[0];x<=ib.max[0];++x) for(x=ib.min[0];x<=ib.max[0];++x)
// Inserire calcolo cella corrente // Inserire calcolo cella corrente
// if( pt->Intersect( ... ) // if( pt->Intersect( ... )
links.push_back( Link(&(*pt),by+x) ); links.push_back( Link(&(*i),by+x) );
} }
} }
} }
@ -563,7 +420,31 @@ namespace vcg {
return sizeof(GridStaticPtr)+ sizeof(Link)*links.size() + return sizeof(GridStaticPtr)+ sizeof(Link)*links.size() +
sizeof(Cell) * grid.size(); sizeof(Cell) * grid.size();
} }
}; //end class GridStaticObj
template <class OBJPOINTDISTFUNCTOR, class OBJMARKER>
ObjPtr GetClosest(OBJPOINTDISTFUNCTOR & _getPointDistance, OBJMARKER & _marker,
const CoordType & _p, const ScalarType & _maxDist,ScalarType & _minDist, CoordType & _closestPt)
{
return (vcg::GridClosest<GridPtrType,OBJPOINTDISTFUNCTOR,OBJMARKER>(*this,_getPointDistance,_marker, _p,_maxDist,_minDist,_closestPt));
}
template <class OBJPOINTDISTFUNCTOR, class OBJMARKER, class OBJPTRCONTAINER,class DISTCONTAINER, class POINTCONTAINER>
unsigned int GetKClosest(OBJPOINTDISTFUNCTOR & _getPointDistance,OBJMARKER & _marker,
const unsigned int _k, const CoordType & _p, const ScalarType & _maxDist,OBJPTRCONTAINER & _objectPtrs,
DISTCONTAINER & _distances, POINTCONTAINER & _points)
{
return (vcg::GridGetKClosest<GridPtrType,
OBJPOINTDISTFUNCTOR,OBJMARKER,OBJPTRCONTAINER,DISTCONTAINER,POINTCONTAINER>(*this,_getPointDistance,_marker,_k,_p,_maxDist,_objectPtrs,_distances,_points));
}
template <class OBJRAYISECTFUNCTOR, class OBJMARKER>
ObjPtr DoRay(OBJRAYISECTFUNCTOR & _rayIntersector, OBJMARKER & _marker, const Ray3<ScalarType> & _ray, const ScalarType & _maxDist, ScalarType & _t)
{
return(vcg::GridDoRay<GridPtrType,OBJRAYISECTFUNCTOR,OBJMARKER>(*this,_rayIntersector,_marker,_ray,_maxDist,_t));
}
}; //end class GridStaticPtr
}; // end namespace }; // end namespace

View File

@ -21,9 +21,12 @@
* * * *
****************************************************************************/ ****************************************************************************/
/**************************************************************************** /****************************************************************************
History History
$Log: not supported by cvs2svn $ $Log: not supported by cvs2svn $
Revision 1.9 2005/09/21 14:22:49 pietroni
Added DynamicSpatialHAshTable class
Revision 1.8 2005/09/19 13:35:45 pietroni Revision 1.8 2005/09/19 13:35:45 pietroni
use of standard grid interface use of standard grid interface
use of vector instead of map inside the cell use of vector instead of map inside the cell
@ -61,6 +64,7 @@ added vcg header
#define P2 83492791 #define P2 83492791
#include <vcg/space/index/grid_util.h> #include <vcg/space/index/grid_util.h>
#include <vcg/space/index/grid_closest.h>
//#include <map> //#include <map>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
@ -81,17 +85,19 @@ namespace vcg{
"Optimized Spatial Hashing for Coll ision Detection of Deformable Objects", "Optimized Spatial Hashing for Coll ision Detection of Deformable Objects",
Matthias Teschner and Bruno Heidelberger and Matthias Muller and Danat Pomeranets and Markus Gross Matthias Teschner and Bruno Heidelberger and Matthias Muller and Danat Pomeranets and Markus Gross
*/ */
template < typename ContainerType,class FLT=double> template < typename OBJTYPE,class FLT=double>
class SpatialHashTable:public BasicGrid<FLT> class SpatialHashTable:public BasicGrid<OBJTYPE,FLT>
{ {
public: public:
typedef typename ContainerType::value_type ObjType; typedef OBJTYPE ObjType;
typedef ObjType* ObjPtr; typedef ObjType* ObjPtr;
typedef typename ObjType::ScalarType ScalarType; typedef typename ObjType::ScalarType ScalarType;
typedef Point3<ScalarType> CoordType; typedef Point3<ScalarType> CoordType;
typedef typename SpatialHashTable<ObjType,FLT> SpatialHashType;
//typedef typename SpatialHashTable<ObjType,FLT> GridType;
//type of container of pointer to object in a Cell //type of container of pointer to object in a Cell
//typedef typename std::pair<ObjType*,int> EntryType ; //typedef typename std::pair<ObjType*,int> EntryType ;
@ -149,10 +155,10 @@ namespace vcg{
///find the simplex into the cell ///find the simplex into the cell
bool Find(ObjType* sim,IteMap &I) bool Find(ObjType* sim,IteMap &I)
{ {
for (I=_entries.begin();I<_entries.end();I++) for (I=_entries.begin();I<_entries.end();I++)
if ((*I).first==sim) if ((*I).first==sim)
return true; return true;
return false; return false;
} }
///update or insert an element into a cell ///update or insert an element into a cell
@ -161,7 +167,7 @@ namespace vcg{
IteMap I; IteMap I;
if (Find(sim,I)) if (Find(sim,I))
{///update temporary mark {///update temporary mark
(*I).second=_tempMark; (*I).second=_tempMark;
} }
else else
_entries.push_back(EntryType(sim,_tempMark)); _entries.push_back(EntryType(sim,_tempMark));
@ -209,7 +215,7 @@ namespace vcg{
bool operator !=(const Cell &h) bool operator !=(const Cell &h)
{return ((cell_n!=h.CellN()));} {return ((cell_n!=h.CellN()));}
protected: protected:
virtual void UpdateHMark(ObjType* s){} virtual void UpdateHMark(ObjType* s){}
}; // end struct Cell }; // end struct Cell
@ -229,7 +235,7 @@ namespace vcg{
protected: protected:
Htable hash_table; Htable hash_table;
///number of possible hash code [0...HashSpace] ///number of possible hash code [0...HashSpace]
int HashSpace; int HashSpace;
@ -243,7 +249,7 @@ namespace vcg{
hash_table.insert(HRecord(h,Cell(s,cell,tempMark))); hash_table.insert(HRecord(h,Cell(s,cell,tempMark)));
//Assert(); //Assert();
} }
///return true and return the iterator to the cell if exist ///return true and return the iterator to the cell if exist
bool _IsInHtable(Point3i cell,IteHtable &result) bool _IsInHtable(Point3i cell,IteHtable &result)
{ {
@ -273,7 +279,7 @@ namespace vcg{
} }
virtual void _UpdateHMark(ObjType* s){} virtual void _UpdateHMark(ObjType* s){}
///insert an element in a specified cell if the cell doesn't exist than ///insert an element in a specified cell if the cell doesn't exist than
///create it. ///create it.
void _InsertInCell(ObjType* s,Point3i cell) void _InsertInCell(ObjType* s,Point3i cell)
@ -293,20 +299,20 @@ namespace vcg{
return ((p.V(0)*P0 ^ p.V(1)*P1 ^ p.V(2)*P2)%HashSpace); return ((p.V(0)*P0 ^ p.V(1)*P1 ^ p.V(2)*P2)%HashSpace);
} }
public: public:
///We need some extra space for numerical precision. /////We need some extra space for numerical precision.
template <class Box3Type> //template <class Box3Type>
void SetBBox( const Box3Type & b ) // void SetBBox( const Box3Type & b )
{ //{
bbox.Import( b ); // bbox.Import( b );
ScalarType t = bbox.Diag()/100.0; // ScalarType t = bbox.Diag()/100.0;
if(t == 0) t = ScalarType(1e20); // <--- Some doubts on this (Cigno 5/1/04) // if(t == 0) t = ScalarType(1e20); // <--- Some doubts on this (Cigno 5/1/04)
bbox.Offset(t); // bbox.Offset(t);
dim = bbox.max - bbox.min; // dim = bbox.max - bbox.min;
} //}
virtual vcg::Box3i Add( ObjType* s) virtual vcg::Box3i Add( ObjType* s)
{ {
/*std::vector<Point3i> box; /*std::vector<Point3i> box;
@ -325,34 +331,59 @@ namespace vcg{
return bb; return bb;
} }
/// Insert a mesh in the grid.SetBBox() function must be called before ///// Insert a mesh in the grid.SetBBox() function must be called before
/// Hash space is cardinality of hash key set ///// Hash space is cardinality of hash key set
void Set( ContainerType & s) //void Set( ContainerType & s)
{ //{
Set(s,s.size()); // Set(s,s.size());
} //}
///// Insert a mesh in the grid.SetBBox() function must be called before
//void Set( ContainerType & s,int _size )
//{
// Point3i _siz;
// BestDim( _size, dim, _siz );
// Set(s,_siz);
//}
/// Insert a mesh in the grid.SetBBox() function must be called before /// Insert a mesh in the grid.SetBBox() function must be called before
void Set( ContainerType & s,int _size ) template <class OBJITER>
void Set(const OBJITER & _oBegin, const OBJITER & _oEnd)
{ {
Point3i _siz; OBJITER i;
BestDim( _size, dim, _siz ); bbox.min=Point3<FLT>(0,0,0);
Set(s,_siz); bbox.max=Point3<FLT>(0,0,0);
} Box3<FLT> b;
for(i = _oBegin; i!= _oEnd; ++i)
/// Insert a mesh in the grid.SetBBox() function must be called before {
void Set(ContainerType & s, Point3i _siz) (*i).GetBBox(b);
{ bbox.Add(b);
siz=_siz; }
int _size=std::distance<OBJITER>(_oBegin,_oEnd);
dim = bbox.max - bbox.min;
BestDim( _size, dim, siz );
// find voxel size // find voxel size
voxel[0] = dim[0]/siz[0]; voxel[0] = dim[0]/siz[0];
voxel[1] = dim[1]/siz[1]; voxel[1] = dim[1]/siz[1];
voxel[2] = dim[2]/siz[2]; voxel[2] = dim[2]/siz[2];
typename ContainerType::iterator i;
for(i = s.begin(); i!= s.end(); ++i) for(i = _oBegin; i!= _oEnd; ++i)
Add(&(*i)); Add(&(*i));
} }
//void Set(ContainerType & s, Point3i _siz)
//{
// siz=_siz;
// // find voxel size
// voxel[0] = dim[0]/siz[0];
// voxel[1] = dim[1]/siz[1];
// voxel[2] = dim[2]/siz[2];
// typename ContainerType::iterator i;
// for(i = s.begin(); i!= s.end(); ++i)
// Add(&(*i));
//}
//
/////initialize the structure HashSpace is one estimation about /////initialize the structure HashSpace is one estimation about
/////how many keys the system have to generate in order to obtain as less /////how many keys the system have to generate in order to obtain as less
@ -367,7 +398,7 @@ namespace vcg{
// conflicts=0; // conflicts=0;
//} //}
/*void AddElem( ObjType* s) /*void AddElem( ObjType* s)
{ {
@ -376,7 +407,7 @@ namespace vcg{
for (std::vector<Point3i>::iterator bi=box.begin();bi<box.end();bi++) for (std::vector<Point3i>::iterator bi=box.begin();bi<box.end();bi++)
_InsertInCell(s,*bi); _InsertInCell(s,*bi);
}*/ }*/
///return the simplexes of the cell that contain p ///return the simplexes of the cell that contain p
void Grid( const Point3d & p, CellIterator & first, CellIterator & last ) void Grid( const Point3d & p, CellIterator & first, CellIterator & last )
{ {
@ -423,12 +454,12 @@ namespace vcg{
/*inline Point3i MinCell() /*inline Point3i MinCell()
{ {
return PointToCell(min); return PointToCell(min);
} }
inline Point3i MaxCell() inline Point3i MaxCell()
{ {
return PointToCell(max); return PointToCell(max);
}*/ }*/
///return the number of elemnts in the cell and the iterator to the cell ///return the number of elemnts in the cell and the iterator to the cell
@ -462,8 +493,31 @@ namespace vcg{
{tempMark++;} {tempMark++;}
template <class OBJPOINTDISTFUNCTOR, class OBJMARKER>
ObjPtr GetClosest(OBJPOINTDISTFUNCTOR & _getPointDistance, OBJMARKER & _marker,
const CoordType & _p, const ScalarType & _maxDist,ScalarType & _minDist, CoordType & _closestPt)
{
return (vcg::GridClosest<SpatialHashType,OBJPOINTDISTFUNCTOR,OBJMARKER>(*this,_getPointDistance,_marker, _p,_maxDist,_minDist,_closestPt));
}
template <class OBJPOINTDISTFUNCTOR, class OBJMARKER, class OBJPTRCONTAINER,class DISTCONTAINER, class POINTCONTAINER>
unsigned int GetKClosest(OBJPOINTDISTFUNCTOR & _getPointDistance,OBJMARKER & _marker,
const unsigned int _k, const CoordType & _p, const ScalarType & _maxDist,OBJPTRCONTAINER & _objectPtrs,
DISTCONTAINER & _distances, POINTCONTAINER & _points)
{
return (vcg::GridGetKClosest<SpatialHashType,
OBJPOINTDISTFUNCTOR,OBJMARKER,OBJPTRCONTAINER,DISTCONTAINER,POINTCONTAINER>(*this,_getPointDistance,_marker,_k,_p,_maxDist,_objectPtrs,_distances,_points));
}
template <class OBJRAYISECTFUNCTOR, class OBJMARKER>
ObjPtr DoRay(OBJRAYISECTFUNCTOR & _rayIntersector, OBJMARKER & _marker, const Ray3<ScalarType> & _ray, const ScalarType & _maxDist, ScalarType & _t)
{
return(vcg::GridDoRay<SpatialHashType,OBJRAYISECTFUNCTOR,OBJMARKER>(*this,_rayIntersector,_marker,_ray,_maxDist,_t));
}
}; // end class }; // end class
/** Spatial Hash Table Dynamic /** Spatial Hash Table Dynamic
Update the Hmark value on the simplex for dynamic updating of contents of the cell. Update the Hmark value on the simplex for dynamic updating of contents of the cell.
The simplex must have the HMark() function. The simplex must have the HMark() function.
@ -473,7 +527,7 @@ namespace vcg{
{ {
void _UpdateHMark(ObjType* s){s->HMark()=tempMark;} void _UpdateHMark(ObjType* s){s->HMark()=tempMark;}
}; };
}// end namespace }// end namespace
#undef P0 #undef P0