Commit f243d02b authored by untereiner's avatar untereiner

Adaptive Primal subdivision into Algo/Multiresolution/

parent a91ee675
......@@ -29,6 +29,7 @@
#include "Topology/generic/traversorCell.h"
#include "Topology/generic/traversor2.h"
#include "Container/attributeContainer.h"
#include "Algo/Decimation/selector.h"
#include "Algo/Decimation/edgeSelector.h"
......
......@@ -136,15 +136,20 @@ void Map2MR_PM<PFP>::createPM(Algo::Decimation::SelectorType s, Algo::Decimation
template <typename PFP>
void Map2MR_PM<PFP>::addNewLevel(unsigned int percentWantedVertices)
{
// level handling
m_map.pushLevel() ;
m_map.addLevelBack();
m_map.duplicateDarts(m_map.getMaxLevel());
m_map.setCurrentLevel(m_map.getMaxLevel());
unsigned int nbVertices = m_map.template getNbOrbits<VERTEX>() ;
unsigned int nbWantedVertices = nbVertices * percentWantedVertices / 100 ;
CGoGNout << " creating PM (" << nbVertices << " vertices).." << /* flush */ CGoGNendl ;
unsigned int nbDeletedVertex=0;
unsigned int percentWantedPerLevel = 20;
unsigned int nbWantedPerLevel = nbWantedVertices * percentWantedPerLevel / 100 ;
//create the new level
m_map.addLevelFront();
m_map.setCurrentLevel(0);
m_map.printMR();
DartMarkerStore me(m_map); //mark edges not to collapse
bool finished = false ;
Dart d ;
......@@ -153,39 +158,194 @@ void Map2MR_PM<PFP>::addNewLevel(unsigned int percentWantedVertices)
if(!m_selector->nextEdge(d))
break ;
--nbVertices ;
Dart d2 = m_map.phi2(m_map.phi_1(d)) ;
Dart dd2 = m_map.phi2(m_map.phi_1(m_map.phi2(d))) ;
for(typename std::vector<Algo::Decimation::ApproximatorGen<PFP>*>::iterator it = m_approximators.begin(); it != m_approximators.end(); ++it)
if(!me.isMarked(d))
{
(*it)->approximate(d) ; // compute approximated attributes with its associated detail
(*it)->saveApprox(d) ;
}
m_selector->updateBeforeCollapse(d) ; // update selector
//me.mark le 1 voisinage
Dart dt = d;
do
{
Dart dit = dt;
do
{
me.mark(m_map.phi1(dit));
me.mark(m_map.phi1(m_map.phi1(dit)));
dit = m_map.phi2(m_map.phi_1(dit));
}
while(dit != dt);
dt = m_map.phi1(dt);
}while(dt != d);
dt = m_map.phi2(d);
do
{
Dart dit = dt;
do
{
me.mark(m_map.phi1(dit));
me.mark(m_map.phi1(m_map.phi1(dit)));
dit = m_map.phi2(m_map.phi_1(dit));
}
while(dit != dt);
dt = m_map.phi1(dt);
}while(dt != m_map.phi2(d));
collapseEdge(d);
//incremente le dartLevel des brins des faces supprime
m_map.incDartLevel(d);
m_map.incDartLevel(m_map.phi1(d));
m_map.incDartLevel(m_map.phi_1(d));
m_map.incDartLevel(m_map.phi2(d));
m_map.incDartLevel(m_map.phi_1(m_map.phi2(d)));
m_map.incDartLevel(m_map.phi1(m_map.phi2(d)));
unsigned int newV = m_map.template embedNewCell<VERTEX>(d2) ;
unsigned int newE1 = m_map.template embedNewCell<EDGE>(d2) ;
unsigned int newE2 = m_map.template embedNewCell<EDGE>(dd2) ;
// vs->setApproxV(newV) ;
// vs->setApproxE1(newE1) ;
// vs->setApproxE2(newE2) ;
++nbDeletedVertex ;
for(typename std::vector<Algo::Decimation::ApproximatorGen<PFP>*>::iterator it = m_approximators.begin(); it != m_approximators.end(); ++it)
(*it)->affectApprox(d2); // affect data to the resulting vertex
Dart d2 = m_map.phi2(m_map.phi_1(d)) ;
Dart dd2 = m_map.phi2(m_map.phi_1(m_map.phi2(d))) ;
m_selector->updateAfterCollapse(d2, dd2) ; // update selector
m_selector->updateBeforeCollapse(d) ; // update selector
if(nbVertices <= nbWantedVertices)
finished = true ;
collapseEdge(d);
m_selector->updateAfterCollapse(d2, dd2) ; // update selector
if(nbDeletedVertex <= nbWantedPerLevel)
finished = true ;
}
}
m_map.popLevel();
CGoGNout << "..done (" << nbVertices << " vertices)" << CGoGNendl ;
CGoGNout << "..done (" << nbDeletedVertex << " vertices)" << CGoGNendl ;
m_map.printMR();
// DartMarkerStore me(m_map); //mark edges not to collapse
// DartMarkerStore mc(m_map); //mark darts to collapse
// std::vector<Dart> ec; //save a dart from edge to collapse
//
// for(Dart d = m_map.begin() ; d != m_map.end() ; m_map.next(d))
// {
// if(!me.isMarked(d))
// {
// Dart dt = d;
// do
// {
// Dart dit = dt;
// do
// {
// me.mark(m_map.phi1(dit));
// me.mark(m_map.phi1(m_map.phi1(dit)));
// dit = m_map.phi2(m_map.phi_1(dit));
// }
// while(dit != dt);
//
// dt = m_map.phi1(dt);
// }while(dt != d);
//
// dt = m_map.phi2(d);
// do
// {
// Dart dit = dt;
// do
// {
// me.mark(m_map.phi1(dit));
// me.mark(m_map.phi1(m_map.phi1(dit)));
// dit = m_map.phi2(m_map.phi_1(dit));
// }
// while(dit != dt);
//
// dt = m_map.phi1(dt);
// }while(dt != m_map.phi2(d));
//
//
// ec.push_back(d);
//
// mc.mark(d);
// mc.mark(m_map.phi1(d));
// mc.mark(m_map.phi_1(d));
// mc.mark(m_map.phi2(d));
// mc.mark(m_map.phi_1(m_map.phi2(d)));
// mc.mark(m_map.phi1(m_map.phi2(d)));
// }
// }
// //create the new level
// m_map.addLevelFront();
//
// AttributeContainer& cont = m_map.getMRAttributeContainer();
// AttributeMultiVector<unsigned int>* amv = m_map.getMRDartAttributeVector(m_map.getCurrentLevel());
// for(unsigned int i = cont.begin() ; i < cont.end() ; cont.next(i))
// {
// if(mc.isMarked((*amv)[i]) || (*amv)[i] == NULL)
// {
// //mrlevel++
// }
//
//
// //(*amv)[i] = MRNULL ;
// }
//
//
// for(std::vector<Dart>::iterator it = ec.begin() ; it != ec.end() ; ++it)
// {
//
// }
// // level handling
// m_map.pushLevel() ;
// m_map.addLevelBack();
// m_map.duplicateDarts(m_map.getMaxLevel());
// m_map.setCurrentLevel(m_map.getMaxLevel());
//
// unsigned int nbVertices = m_map.template getNbOrbits<VERTEX>() ;
// unsigned int nbWantedVertices = nbVertices * percentWantedVertices / 100 ;
// CGoGNout << " creating PM (" << nbVertices << " vertices).." << /* flush */ CGoGNendl ;
//
// bool finished = false ;
// Dart d ;
// while(!finished)
// {
// if(!m_selector->nextEdge(d))
// break ;
//
// --nbVertices ;
// Dart d2 = m_map.phi2(m_map.phi_1(d)) ;
// Dart dd2 = m_map.phi2(m_map.phi_1(m_map.phi2(d))) ;
//
// for(typename std::vector<Algo::Decimation::ApproximatorGen<PFP>*>::iterator it = m_approximators.begin(); it != m_approximators.end(); ++it)
// {
// (*it)->approximate(d) ; // compute approximated attributes with its associated detail
// (*it)->saveApprox(d) ;
// }
//
// m_selector->updateBeforeCollapse(d) ; // update selector
//
// collapseEdge(d);
//
// unsigned int newV = m_map.template embedNewCell<VERTEX>(d2) ;
// unsigned int newE1 = m_map.template embedNewCell<EDGE>(d2) ;
// unsigned int newE2 = m_map.template embedNewCell<EDGE>(dd2) ;
//// vs->setApproxV(newV) ;
//// vs->setApproxE1(newE1) ;
//// vs->setApproxE2(newE2) ;
//
// for(typename std::vector<Algo::Decimation::ApproximatorGen<PFP>*>::iterator it = m_approximators.begin(); it != m_approximators.end(); ++it)
// (*it)->affectApprox(d2); // affect data to the resulting vertex
//
// m_selector->updateAfterCollapse(d2, dd2) ; // update selector
//
// if(nbVertices <= nbWantedVertices)
// finished = true ;
// }
//
// m_map.popLevel();
//
// CGoGNout << "..done (" << nbVertices << " vertices)" << CGoGNendl ;
}
......@@ -193,21 +353,14 @@ template <typename PFP>
void Map2MR_PM<PFP>::collapseEdge(Dart d)
{
//duplication :
m_map.duplicateMRDart(m_map.phi2(m_map.phi1(d)));
m_map.duplicateMRDart(m_map.phi2(m_map.phi_1(d)));
m_map.duplicateMRDart(m_map.phi2(m_map.phi1(m_map.phi2(d))));
m_map.duplicateMRDart(m_map.phi2(m_map.phi_1(m_map.phi2(d))));
m_map.duplicateDart(m_map.phi2(m_map.phi1(d)));
m_map.duplicateDart(m_map.phi2(m_map.phi_1(d)));
m_map.duplicateDart(m_map.phi2(m_map.phi1(m_map.phi2(d))));
m_map.duplicateDart(m_map.phi2(m_map.phi_1(m_map.phi2(d))));
//effacer :
// m_map.unrefMRDart(m_map.phi1(m_map.phi2(d)));
// m_map.unrefMRDart(m_map.phi_1(m_map.phi2(d)));
// m_map.unrefMRDart(m_map.phi2(d));
//
// m_map.unrefMRDart(m_map.phi1(d));
// m_map.unrefMRDart(m_map.phi_1(d));
// m_map.unrefMRDart(d);
//m_map.collapseEdge(d);
m_map.collapseEdge(d);
m_map.extractTrianglePair(d);
}
......
......@@ -34,7 +34,6 @@
namespace CGoGN
{
}
namespace Algo
{
......@@ -124,19 +123,6 @@ public:
* SUBDIVISION *
***************************************************/
/**
* add a new resolution level
*/
void addNewLevel(bool embedNewVertices = true) ;
void propagateDartRelation(Dart d, AttributeMultiVector<Dart>* rel) ;
template <unsigned int ORBIT>
void propagateDartEmbedding(Dart d) ;
template <unsigned int ORBIT>
void propagateOrbitEmbedding(Dart d) ;
Dart cutEdge(Dart d) ;
void splitFace(Dart d, Dart e) ;
......@@ -155,7 +141,9 @@ public:
/**
* subdivide the face of d to the next level
*/
unsigned int subdivideFace(Dart d) ;
unsigned int subdivideFace(Dart d, bool triQuad = true) ;
unsigned int subdivideFace2(Dart d) ;
/**
* coarsen the face of d from the next level
......
......@@ -120,7 +120,8 @@ void TopoRender::updateDataMap(typename PFP::MAP& mapx, const VertexAttribute<ty
const VEC3& P = positions[d];
vecPos.push_back(P);
center += P;
d = map.phi1(d);
Dart e = map.phi1(d);
d = e;
} while (d != dd);
center /= REAL(vecPos.size());
......
......@@ -230,11 +230,11 @@ public:
*/
unsigned int getMaxLevel() ;
private:
/*
* add a resolution level
*/
AttributeMultiVector<unsigned int>* addLevel();
//private:
// /*
// * add a resolution level
// */
// AttributeMultiVector<unsigned int>* addLevel();
public:
/**
......@@ -268,14 +268,9 @@ public:
void duplicateDarts(unsigned int level);
/**
*
*/
void duplicateMRDart(Dart d);
/**
*
* duplicate a dart starting from current level
*/
void unrefMRDart(Dart d);
void duplicateDart(Dart d) ;
/****************************************
* DARTS MANAGEMENT *
......@@ -296,11 +291,6 @@ protected:
*/
unsigned int copyDartLine(unsigned int index) ;
/**
* duplicate a dart starting from current level
*/
void duplicateDart(Dart d) ;
/**
* Properly deletes a dart in m_attribs[DART]
*/
......@@ -317,6 +307,12 @@ public:
*/
unsigned int getDartLevel(Dart d) const ;
/**
*
*/
void incDartLevel(Dart d) const ;
/**
* get the number of darts inserted in the given leveldart (use only in MRMaps)
*/
......@@ -463,6 +459,16 @@ public:
template <unsigned int ORBIT>
AttributeMultiVector<unsigned int>* getEmbeddingAttributeVector() ;
/**
* get the MR attribute container
*/
AttributeContainer& getMRAttributeContainer() ;
/**
* get the MR attribute container
*/
AttributeMultiVector<unsigned int>* getMRDartAttributeVector(unsigned int level) ;
// /**
// * swap two attribute containers
// */
......@@ -486,6 +492,8 @@ public:
*/
void viewAttributesTables() ;
void printDartsTable();
protected:
/****************************************
* EMBEDDING ATTRIBUTES MANAGEMENT *
......
......@@ -77,13 +77,14 @@ inline unsigned int GenericMap::getMaxLevel()
return m_mrDarts.size() - 1 ;
}
/****************************************
* DARTS MANAGEMENT *
****************************************/
inline Dart GenericMap::newDart()
{
unsigned int di = m_attribs[DART].insertLine(); // insert a new dart line
unsigned int di = m_attribs[DART].insertLine(); // insert a new dart line
for(unsigned int i = 0; i < NB_ORBITS; ++i)
{
if (m_embeddings[i]) // set all its embeddings
......@@ -97,16 +98,11 @@ inline Dart GenericMap::newDart()
m_mrNbDarts[m_mrCurrentLevel]++ ;
for(unsigned int i = 0; i < m_mrCurrentLevel; ++i) // for all previous levels
(*m_mrDarts[i])[mrdi] = MRNULL ; // this MRdart does not exist
(*m_mrDarts[i])[mrdi] = MRNULL ; // this MRdart does not exist
for(unsigned int i = m_mrCurrentLevel; i < m_mrDarts.size(); ++i) // for all levels from current to max
(*m_mrDarts[i])[mrdi] = di ; // make this MRdart point to the new dart line
(*m_mrDarts[m_mrCurrentLevel])[mrdi] = di ; // for the current level, this MRdart points to the new dart line
// for(unsigned int i = m_mrCurrentLevel + 1; i < m_mrDarts.size(); ++i) // for all levels from current + 1 to max
// (*m_mrDarts[i])[mrdi] = copyDartLine(di) ; // make this MRdart point to a copy of the new dart line
return Dart::create(mrdi) ;
}
......@@ -119,10 +115,17 @@ inline void GenericMap::deleteDart(Dart d)
{
unsigned int index = (*m_mrDarts[m_mrCurrentLevel])[d.index] ;
//a Dart can only be deleted if all mrDarts have been deleted
if(getDartLevel(d) == m_mrCurrentLevel)
if(getDartLevel(d) > m_mrCurrentLevel)
{
//for(unsigned int i = getDartLevel(d) - 1; i >= m_mrCurrentLevel; --i)
//{
(*m_mrDarts[m_mrCurrentLevel])[d.index] = MRNULL ;
//}
}
// a MRdart can only be deleted on its insertion level
if(getDartLevel(d) == m_mrCurrentLevel)
{
if(isDartValid(index))
deleteDartLine(index) ;
......@@ -131,27 +134,19 @@ inline void GenericMap::deleteDart(Dart d)
}
else
{
unsigned int di = m_mrDarts[m_mrCurrentLevel - 1]->operator[](d.index) ;
//si le brin de niveau i pointe sur le meme brin que le niveau i-1
unsigned int di = (*m_mrDarts[m_mrCurrentLevel - 1])[d.index]; //m_mrDarts[m_mrCurrentLevel - 1]->operator[](d.index) ;
// si le brin de niveau i pointe sur le meme brin que le niveau i-1
if(di != index)
{
if(isDartValid(index))
deleteDartLine(index) ;
}
m_mrDarts[m_mrCurrentLevel]->operator[](index) = MRNULL ;
}
// a MRdart can only be deleted on its insertion level
// assert(getDartLevel(d) == m_mrCurrentLevel || !"deleteDart : try to delete a dart on a level greater than its insertion level") ;
// for(unsigned int i = m_mrCurrentLevel; i < m_mrDarts.size(); ++i)
// {
// unsigned int index = (*m_mrDarts[i])[d.index] ;
// if(isDartValid(index))
// deleteDartLine(index) ;
// }
// m_mrattribs.removeLine(d.index) ;
// m_mrNbDarts[m_mrCurrentLevel]-- ;
for(unsigned int i = m_mrCurrentLevel; i <= getMaxLevel(); ++i) // for all levels from current to max
{
(*m_mrDarts[i])[d.index] = di ; //copy the index from previous level
}
}
}
else
deleteDartLine(dartIndex(d)) ;
......@@ -199,9 +194,9 @@ inline unsigned int GenericMap::copyDartLine(unsigned int index)
inline void GenericMap::duplicateDart(Dart d)
{
assert(getDartLevel(d) <= getCurrentLevel() || !"duplicateDart : called with a dart inserted after current level") ;
assert(getDartLevel(d) <= m_mrCurrentLevel || !"duplicateDart : called with a dart inserted after current level") ;
if(getDartLevel(d) == getCurrentLevel()) // no need to duplicate
if(getDartLevel(d) == m_mrCurrentLevel) // no need to duplicate
return ; // a dart from its insertion level
unsigned int oldindex = dartIndex(d) ;
......@@ -211,7 +206,7 @@ inline void GenericMap::duplicateDart(Dart d)
unsigned int newindex = copyDartLine(oldindex) ;
for(unsigned int i = getCurrentLevel(); i <= getMaxLevel(); ++i) // for all levels from current to max
for(unsigned int i = m_mrCurrentLevel; i <= getMaxLevel(); ++i) // for all levels from current to max
{
assert((*m_mrDarts[i])[d.index] == oldindex || !"duplicateDart : dart was already duplicated on a greater level") ;
(*m_mrDarts[i])[d.index] = newindex ; // make this MRdart points to the new dart line
......@@ -230,6 +225,12 @@ inline unsigned int GenericMap::getDartLevel(Dart d) const
return (*m_mrLevels)[d.index] ;
}
inline void GenericMap::incDartLevel(Dart d) const
{
++(*m_mrLevels)[d.index] ;
}
inline unsigned int GenericMap::getNbInsertedDarts(unsigned int level)
{
if(level < m_mrDarts.size())
......@@ -447,6 +448,17 @@ inline AttributeMultiVector<unsigned int>* GenericMap::getEmbeddingAttributeVect
return m_embeddings[ORBIT] ;
}
inline AttributeContainer& GenericMap::getMRAttributeContainer()
{
return m_mrattribs ;
}
inline AttributeMultiVector<unsigned int>* GenericMap::getMRDartAttributeVector(unsigned int level)
{
assert(level <= getMaxLevel() || !"Invalid parameter: level does not exist");
return m_mrDarts[level] ;
}
template <typename R>
bool GenericMap::registerAttribute(const std::string &nameType)
{
......@@ -637,5 +649,4 @@ inline bool GenericMap::isBoundaryMarked(Dart d) const
return m_markTables[DART][0]->operator[](dartIndex(d)).testMark(m_boundaryMarker);
}
} //namespace CGoGN
......@@ -174,7 +174,7 @@ void GenericMap::printMR()
for(unsigned int i = m_mrattribs.begin(); i != m_mrattribs.end(); m_mrattribs.next(i))
{
std::cout << (*m_mrLevels)[i] << " / " ;
std::cout << i << " : " << (*m_mrLevels)[i] << " / " ;
for(unsigned int j = 0; j < m_mrDarts.size(); ++j)
std::cout << (*m_mrDarts[j])[i] << " ; " ;
std::cout << std::endl ;
......@@ -202,8 +202,24 @@ void GenericMap::initMR()
setCurrentLevel(0) ;
}
AttributeMultiVector<unsigned int>* GenericMap::addLevel()
//AttributeMultiVector<unsigned int>* GenericMap::addLevel()
//{
// unsigned int newLevel = m_mrDarts.size() ;
// std::stringstream ss ;
// ss << "MRdart_"<< newLevel ;
// AttributeMultiVector<unsigned int>* newAttrib = m_mrattribs.addAttribute<unsigned int>(ss.str()) ;
// AttributeMultiVector<unsigned int>* prevAttrib = m_mrDarts[newLevel - 1];
//
// // copy the indices of previous level into new level
// m_mrattribs.copyAttribute(newAttrib->getIndex(), prevAttrib->getIndex()) ;
//
// return newAttrib;
//}
void GenericMap::addLevelBack()
{
//AttributeMultiVector<unsigned int>* newAttrib = addLevel();
unsigned int newLevel = m_mrDarts.size() ;
std::stringstream ss ;
ss << "MRdart_"<< newLevel ;
......@@ -213,19 +229,23 @@ AttributeMultiVector<unsigned int>* GenericMap::addLevel()
// copy the indices of previous level into new level
m_mrattribs.copyAttribute(newAttrib->getIndex(), prevAttrib->getIndex()) ;
return newAttrib;
}
void GenericMap::addLevelBack()
{
AttributeMultiVector<unsigned int>* newAttrib = addLevel();
m_mrDarts.push_back(newAttrib) ;
m_mrNbDarts.push_back(0) ;
}
void GenericMap::addLevelFront()
{
AttributeMultiVector<unsigned int>* newAttrib = addLevel();
//AttributeMultiVector<unsigned int>* newAttrib = addLevel();
unsigned int newLevel = m_mrDarts.size() ;
std::stringstream ss ;
ss << "MRdart_"<< newLevel ;
AttributeMultiVector<unsigned int>* newAttrib = m_mrattribs.addAttribute<unsigned int>(ss.str()) ;
AttributeMultiVector<unsigned int>* prevAttrib = m_mrDarts[0];
// copy the indices of previous level into new level
m_mrattribs.copyAttribute(newAttrib->getIndex(), prevAttrib->getIndex()) ;
m_mrDarts.insert(m_mrDarts.begin(), newAttrib) ;
m_mrNbDarts.insert(m_mrNbDarts.begin(), 0) ;
}
......@@ -277,39 +297,15 @@ void GenericMap::copyLevel(unsigned int level)
void GenericMap::duplicateDarts(unsigned int level)
{
AttributeMultiVector<unsigned int>* newAttrib = m_mrDarts[level] ;
AttributeMultiVector<unsigned int>* prevAttrib = m_mrDarts[level - 1];
AttributeMultiVector<unsigned int>* attrib = m_mrDarts[level] ;
for(unsigned int i = m_mrattribs.begin(); i != m_mrattribs.end(); m_mrattribs.next(i))
{
unsigned int oldi = (*prevAttrib)[i] ; // get the index of the dart in previous level
(*newAttrib)[i] = copyDartLine(oldi) ; // copy the dart and affect it to the new level
unsigned int oldi = (*attrib)[i] ; // get the index of the dart in previous level
(*attrib)[i] = copyDartLine(oldi) ; // copy the dart and affect it to the new level
}
}
void GenericMap::duplicateMRDart(Dart d)
{
assert(getDartLevel(d) <= getCurrentLevel() || !"duplicateDart : called with a dart inserted after current level") ;
// if(getDartLevel(d) == getCurrentLevel()) // no need to duplicate
// return ; // a dart from its insertion level
unsigned int oldindex = dartIndex(d) ;
// if((*m_mrDarts[m_mrCurrentLevel - 1])[d.index] != oldindex) // no need to duplicate if the dart is already
// return ; // duplicated with respect to previous level
unsigned int newindex = copyDartLine(oldindex) ;
assert((*m_mrDarts[getCurrentLevel()])[d.index] == oldindex || !"duplicateDart : dart was already duplicated on a greater level") ;
(*m_mrDarts[getCurrentLevel()])[d.index] = newindex ;
}
void GenericMap::unrefMRDart(Dart d)
{
m_mrDarts[m_mrCurrentLevel]->operator[](d.index) = MRNULL ;
}
/****************************************
* ATTRIBUTES MANAGEMENT *
****************************************/
......@@ -375,6 +371,13 @@ void GenericMap::viewAttributesTables()
std::cout << std::endl << "-------------------------" << std::endl << std::dec ;
}
void GenericMap::printDartsTable()
{
std::cout << "======================="<< std::endl ;
//m_attribs[DART]
}
/****************************************
* THREAD MANAGEMENT *
****************************************/
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment