From a558f142ae0e4e4b50bcc4719984be30abd0d6a0 Mon Sep 17 00:00:00 2001
From: Pierre-Alexandre Beaufort <pierre-alexandre.beaufort@uclouvain.be>
Date: Wed, 22 Jun 2016 11:41:22 +0000
Subject: [PATCH] Splitting for large aspect ratio

---
 Geo/discreteDiskFace.cpp |  54 +++----------
 Geo/discreteDiskFace.h   |  34 ++++++--
 Geo/discreteFace.cpp     | 170 ++++++++++++++++++++++++++++++++-------
 Geo/discreteFace.h       |   4 +
 4 files changed, 183 insertions(+), 79 deletions(-)

diff --git a/Geo/discreteDiskFace.cpp b/Geo/discreteDiskFace.cpp
index eaf94c0f2d..7094c8fa19 100644
--- a/Geo/discreteDiskFace.cpp
+++ b/Geo/discreteDiskFace.cpp
@@ -220,16 +220,12 @@ discreteDiskFace::discreteDiskFace(GFace *gf, triangulation* diskTriangulation,
   _order = p;
   _N = (p+1)*(p+2)/2;
   discrete_triangles.resize(mesh.size());
-
   std::map<MVertex*,MVertex*> v2v;// mesh vertex |-> face vertex
   std::map<MEdge,MVertex*,Less_Edge> ed2nodes; // edge to interior node(s)
   for (unsigned int i=0;i<mesh.size();i++){ // triangle by triangle
-
     std::vector<MVertex*> vs; // MTriangle vertices
     for (unsigned int j=0; j<3; j++){ // loop over vertices AND edges of the current triangle
-
       MVertex *v = mesh[i]->getVertex(j);// firstly, edge vertices
-      
       if (v->onWhat()->dim() == 2) {
 	std::map<MVertex*,MVertex*> :: iterator it = v2v.find(v);
 	if (it == v2v.end()){
@@ -255,8 +251,7 @@ discreteDiskFace::discreteDiskFace(GFace *gf, triangulation* diskTriangulation,
 	}
 	else vs.push_back(v);
       }
-      else vs.push_back(v);
-      
+      else vs.push_back(v);          
     }
     if (_order == 2) {// then, interior nodes :-)
       for (unsigned int ie=0; ie<3; ie++){ // firstly, edge interior nodes :-)
@@ -269,25 +264,20 @@ discreteDiskFace::discreteDiskFace(GFace *gf, triangulation* diskTriangulation,
 	  discrete_vertices.push_back(mv);
 	  ed2nodes[me]=mv;
 	}
-	else{
-	  vs.push_back(it->second);
-	}
+	else vs.push_back(it->second);	
       }
     }// end order == 2
     if (_order==1)
       discrete_triangles[i] = new MTriangle (vs);
     else if (_order==2)
       discrete_triangles[i] = new MTriangle6 (vs);
+        
   }// end loop over triangles
-  
   geoTriangulation = new triangulation(discrete_triangles,gf);
-  
   allNodes = geoTriangulation->vert;
   _totLength = geoTriangulation->bord.rbegin()->first;
   _U0 = geoTriangulation->bord.rbegin()->second;
-
   orderVertices(_totLength, _U0, _coords);
-
   parametrize(false);
   buildOct(CAD);
   
@@ -295,12 +285,12 @@ discreteDiskFace::discreteDiskFace(GFace *gf, triangulation* diskTriangulation,
     Msg::Info("discreteDiskFace:: parametrization is not one-to-one; fixing "
               "the discrete system.");
     parametrize(true);
-    buildOct(CAD);
-    if(!checkOrientationUV()) Msg::Fatal("discreteDiskFace:: failing to fix the discrete system");
+    buildOct(CAD);    
   }
   
-  putOnView(true,true);
+  putOnView(true,false);
   printParamMesh();
+  if(!checkOrientationUV()) Msg::Fatal("discreteDiskFace:: failing to fix the discrete system");
 }
 /*end BUILDER*/
 
@@ -364,11 +354,11 @@ bool discreteDiskFace::parametrize(bool one2one) const
     if(i%_order==0){
       myAssemblerU.fixVertex(v, 0, 1,cos(theta));
       myAssemblerV.fixVertex(v, 0, 1,sin(theta));
-      }
+    }
     else{//#TEST
       myAssemblerU.fixVertex(v, 0, 1,1./_order*((_order-(i%_order))*cos(2*M_PI*_coords[i-(i%_order)])+(i%_order)*cos(2*M_PI*_coords[i+(_order-(i%_order))])));
       myAssemblerV.fixVertex(v, 0, 1,1./_order*((_order-(i%_order))*sin(2*M_PI*_coords[i-(i%_order)])+(i%_order)*sin(2*M_PI*_coords[i+(_order-(i%_order))])));
-      }
+    }
   }
 
   for(size_t i = 0; i < discrete_triangles.size(); ++i){
@@ -413,7 +403,6 @@ bool discreteDiskFace::parametrize(bool one2one) const
   lsys_u->systemSolve();
   lsys_v->systemSolve();
   Msg::Debug("Systems solved");
-
   for(std::set<MVertex *>::iterator itv = allNodes.begin(); itv !=allNodes.end() ; ++itv){
     MVertex *v = *itv;
     double value_U, value_V;
@@ -443,10 +432,7 @@ void discreteDiskFace::getTriangleUV(const double u,const double v,
 				     double &_xi, double &_eta)const{
   double uv[3] = {u,v,0.};
   *mt = (discreteDiskFaceTriangle*) Octree_Search(uv,oct);
-  if (!(*mt)){ 
-    //Msg::Error("discreteDiskFace::getTriangleUV(), the octree does not have a triangle containing (u;v)=(%f;%f)",u,v);
-    return;
-  }
+  if (!(*mt)) return;
 
   double Xi[2];
   double U[2] = {u,v};
@@ -478,6 +464,7 @@ bool discreteDiskFace::checkOrientationUV()
       p3[0] = ct->p[2].x(); p3[1] = ct->p[2].y();
       current = robustPredicates::orient2d(p1, p2, p3);
       if(initial*current < 0.) {
+	Msg::Error("Triangle UV %d has not the correct orientation",i+1);
 	return false;
 	break;
       }
@@ -787,27 +774,6 @@ void discreteDiskFace::putOnView(bool Xu, bool Ux)
       fclose(UVz);
     }
   }
-  /*
-    #ifdef HAVE_POST
-    std::map<int, std::vector<double> > u;
-    std::map<int, std::vector<double> > v;
-    for(std::set<MVertex*>::iterator iv = allNodes.begin(); iv!=allNodes.end(); ++iv){
-    MVertex *p = *iv;
-    u[p->getNum()].push_back(coordinates[p].x());
-    v[p->getNum()].push_back(coordinates[p].y());
-    }
-    PView* view_u = new PView("u", "NodeData", GModel::current(), u);
-    PView* view_v = new PView("v", "NodeData", GModel::current(), v);
-    view_u->setChanged(true);
-    view_v->setChanged(true);
-    view_u->write("param_u.msh", 5);
-    view_v->write("param_v.msh", 5);
-    delete view_u;
-    delete view_v;
-    #else
-    Msg::Error("discreteDiskFace: cannot export without post module")
-    #endif
-  */
 }
 
 // useful for mesh generators
diff --git a/Geo/discreteDiskFace.h b/Geo/discreteDiskFace.h
index 23a01ebf4d..75292a1be1 100644
--- a/Geo/discreteDiskFace.h
+++ b/Geo/discreteDiskFace.h
@@ -48,6 +48,8 @@ class triangulation {
  public:
 
   // attributes
+  double area;
+  bool seamPoint;
   std::vector<MElement*> tri;// triangles
   std::set<MVertex*> vert;// nodes
   // edge to 1 or 2 triangle(s), their num into the vector of MElement*
@@ -59,7 +61,14 @@ class triangulation {
 
   std::list<GEdge*> my_GEdges;
 
-  //methods
+  //---- methods
+
+  double aspectRatio()
+  { 
+    double L = bord.rbegin()->first;
+    return L*L / (area*M_PI);
+  }
+
   int genus()
   {
     return ( ed2tri.size() - vert.size() - tri.size() + 2 - bord.size() )/2;
@@ -69,11 +78,16 @@ class triangulation {
   {
     for(unsigned int i = 0; i < tri.size(); ++i){
       MElement* t = tri[i];
+      SPoint3 P[3];
       for(int j = 0; j < t->getNumVertices()  ; j++){
 	MVertex* tv = t->getVertex(j);
+	if(j<3)
+	  P[j].setPosition(tv->x(),tv->y(),tv->z());
 	std::set<MVertex*>::iterator it = vert.find(tv);
 	if (it == vert.end()) vert.insert (tv);
       }
+      SVector3 N(crossprod(P[1]-P[0],P[2]-P[0]));
+      area += N.norm();
     }
   }
 
@@ -114,13 +128,16 @@ class triangulation {
       vecver.erase(vecver.begin());
 
       std::map<MVertex*,std::vector<MVertex*> >::iterator im = firstNode2Edge.find(first);
-      if (im != firstNode2Edge.end())
-        Msg::Error("Incorrect topology in discreteDiskFace %d", gf->tag());
+      if (im != firstNode2Edge.end()){
+        Msg::Info("Supposed seam point for discreteFace %d", gf->tag());
+	seamPoint = true;
+	break;
+      }
       firstNode2Edge[first] = vecver;
       firstNode2Edge[first].push_back(last);
     }
 
-    while (!firstNode2Edge.empty()) {
+    while (!firstNode2Edge.empty()&&!seamPoint) {
       std::vector<MVertex*> loop;
       double length = 0.;
 
@@ -148,6 +165,8 @@ class triangulation {
 
   void assign()
   {
+    area = 0.;
+    seamPoint = false;
     assignVert();
     assignEd2tri();
     assignBord();
@@ -194,11 +213,14 @@ class discreteDiskFace : public GFace {
   GPoint intersectionWithCircle(const SVector3 &n1, const SVector3 &n2,
 				const SVector3 &p, const double &d,
 				double uv[2]) const;
+
+  
+  std::vector<MElement*> discrete_triangles;
  protected:
-  // a copy of the mesh that should not be destroyed
+  // a copy of the mesh that should not be destroyed  
   triangulation* initialTriangulation;
   triangulation* geoTriangulation;// parametrized triangulation
-  std::vector<MElement*> discrete_triangles;
+  
   std::vector<MVertex*> discrete_vertices;
 
   int _order;
diff --git a/Geo/discreteFace.cpp b/Geo/discreteFace.cpp
index e42a9414c4..e270b06d75 100644
--- a/Geo/discreteFace.cpp
+++ b/Geo/discreteFace.cpp
@@ -20,6 +20,8 @@ extern "C" {
 }
 #endif
 
+int cp=0;
+
 discreteFace::discreteFace(GModel *model, int num) : GFace(model, num)
 {
   Surface *s = Create_Surface(num, MSH_SURF_DISCRETE);
@@ -110,9 +112,9 @@ void discreteFace::secondDer(const SPoint2 &param,
 void discreteFace::createGeometry()
 {
   checkAndFixOrientation();
-
   int order = 1;
   int nPart = 2;
+  double eta = .125;
 
 #if defined(HAVE_SOLVER) && defined(HAVE_ANN)
 
@@ -124,13 +126,8 @@ void discreteFace::createGeometry()
   std::vector<MElement*> tem(triangles.begin(),triangles.end());
 
   triangulation* init = new triangulation(tem,this);
-
   toSplit.push(init);
-  //int mygen, compteur=1;//#debug
-  //Msg::Info("First Genus Value:");
-  //mygen=1; //#debug
-  //if(mygen!=0){// #debug
-  if((toSplit.top())->genus()!=0){
+  if((toSplit.top())->genus()!=0 || (toSplit.top())->aspectRatio() < eta || (toSplit.top())->seamPoint){
 
     while( !toSplit.empty()){
       std::vector<triangulation*> part;
@@ -138,19 +135,15 @@ void discreteFace::createGeometry()
       toSplit.pop();
 
       split(tosplit,part,nPart);
-      delete tosplit; // #mark
+      delete tosplit;
 
       for(unsigned int i=0; i<part.size(); i++){
-	//Msg::Info("Partition %d Genus:",compteur);//#debug
-	//std::cin>>mygen;//#debug
-	//if (mygen!=0)//#debug
-	if(part[i]->genus()!=0)
+	if(part[i]->genus()!=0 || part[i]->aspectRatio() < eta || part[i]->seamPoint)
 	  toSplit.push(part[i]);
 	else{
 	  toParam.push_back(part[i]);
 	  part[i]->idNum=id++;
 	}
-	//compteur++; //#debug
       }// end for i
     }// !.empty()
   }// end if it is not disk-like
@@ -158,12 +151,11 @@ void discreteFace::createGeometry()
     toParam.push_back(toSplit.top());
     toSplit.top()->idNum=id++;
   }
-
-    updateTopology(toParam);
-
+  updateTopology(toParam);
   for(unsigned int i=0; i<toParam.size(); i++){
+    fillHoles(toParam[i]);
     std::vector<MElement*> mytri = toParam[i]->tri;
-    discreteDiskFace *df = new discreteDiskFace (this,toParam[i], order,(_CAD.empty() ? NULL : &_CAD));
+    discreteDiskFace *df = new discreteDiskFace (this,toParam[i], order,(_CAD.empty() ? NULL : &_CAD));    
     df->replaceEdges(toParam[i]->my_GEdges);
     _atlas.push_back(df);
   }
@@ -181,7 +173,7 @@ void discreteFace::gatherMeshes()
     for (unsigned int j=0;j<_atlas[i]->triangles.size(); j++){
       MTriangle *t = _atlas[i]->triangles[j];
       SPoint2 p0,p1,p2;
-      reparamMeshVertexOnFace(t->getVertex(0),_atlas[i], p0);
+      reparamMeshVertexOnFace(t->getVertex(0),_atlas[i], p0);      
       reparamMeshVertexOnFace(t->getVertex(1),_atlas[i], p1);
       reparamMeshVertexOnFace(t->getVertex(2),_atlas[i], p2);
       SPoint2 pc = (p0+p1+p2)*(1./3.0);
@@ -189,8 +181,7 @@ void discreteFace::gatherMeshes()
       double xi, eta;
       _atlas[i]->getTriangleUV(pc.x(),pc.y(), &mt, xi,eta);
       if (mt && mt->gf)mt->gf->triangles.push_back(t);
-      else Msg::Warning ("FILE %s LINE %d Triangle has no classification",__FILE__,__LINE__);
-
+      else Msg::Warning ("FILE %s LINE %d Triangle from atlas part %u has no classification for (u;v)=(%f;%f)",__FILE__,__LINE__,i+1,pc.x(),pc.y());
 
 
     }
@@ -218,14 +209,96 @@ void discreteFace::mesh(bool verbose)
 {
 #if defined(HAVE_ANN) && defined(HAVE_SOLVER) && defined(HAVE_MESH)
   if (!CTX::instance()->meshDiscrete) return;
-  for (unsigned int i=0;i<_atlas.size();i++)
+  for (unsigned int i=0;i<_atlas.size();i++){
+    Msg::Info("Discrete Face %d is going to be meshed",i);
+    printAtlasMesh(_atlas[i]->discrete_triangles,i);
     _atlas[i]->mesh(verbose);
-
+    //printAtlasMesh(_atlas[i],i);
+  }
+  
   gatherMeshes();
   meshStatistics.status = GFace::DONE;
 #endif
 }
 
+void discreteFace::printAtlasMesh(std::vector<MElement*> elm, int I)
+{
+
+  std::map<MVertex*,int> mv2int;
+  char buffer[16];
+  sprintf(buffer,"atlas_mesh%d.msh",I);
+  FILE* pmesh = Fopen(buffer,"w");
+
+  std::set<MVertex*> meshvertices;
+  
+  for(unsigned int i=0; i<elm.size(); ++i){
+    MElement* tri = elm[i];
+    for(unsigned int j=0; j<3; j++)
+      if (meshvertices.find(tri->getVertex(j))==meshvertices.end()) meshvertices.insert(tri->getVertex(j));
+  }
+
+  fprintf(pmesh,"$MeshFormat\n2.2 0 8\n$EndMeshFormat\n$Nodes\n%u\n",(unsigned int)meshvertices.size());
+  int count = 1;
+  for(std::set<MVertex*>::iterator it = meshvertices.begin(); it!=meshvertices.end(); ++it){
+    fprintf(pmesh,"%d %f %f %f\n",count,(*it)->x(),(*it)->y(),(*it)->z());
+    mv2int[*it] = count;
+    count++;
+  }
+  fprintf(pmesh,"$EndNodes\n$Elements\n%u\n",elm.size());
+  for(unsigned int i=0; i<elm.size(); i++){
+    MElement* tri = elm[i];
+    fprintf(pmesh,"%d 2 2 0 0",i+1);
+    for(int j=0; j<3; j++){
+      MVertex* mv = tri->getVertex(j);
+      fprintf(pmesh," %d",mv2int[mv]);
+    }
+    fprintf(pmesh,"\n");
+  }
+  fprintf(pmesh,"$EndElements\n");
+  fclose(pmesh);
+
+}
+
+
+
+void discreteFace::printAtlasMesh(discreteDiskFace* ddf, int I)
+{
+
+  std::map<MVertex*,int> mv2int;
+  char buffer[16];
+  sprintf(buffer,"atlas_mesh%d.msh",I);
+  FILE* pmesh = Fopen(buffer,"w");
+
+  std::set<MVertex*> meshvertices;
+  
+  for(unsigned int i=0; i<ddf->triangles.size(); ++i){
+    MTriangle* tri = ddf->triangles[i];
+    for(unsigned int j=0; j<3; j++)
+      if (meshvertices.find(tri->getVertex(j))==meshvertices.end()) meshvertices.insert(tri->getVertex(j));
+  }
+
+  fprintf(pmesh,"$MeshFormat\n2.2 0 8\n$EndMeshFormat\n$Nodes\n%u\n",(unsigned int)meshvertices.size());
+  int count = 1;
+  for(std::set<MVertex*>::iterator it = meshvertices.begin(); it!=meshvertices.end(); ++it){
+    fprintf(pmesh,"%d %f %f %f\n",count,(*it)->x(),(*it)->y(),(*it)->z());
+    mv2int[*it] = count;
+    count++;
+  }
+  fprintf(pmesh,"$EndNodes\n$Elements\n%u\n",ddf->triangles.size());
+  for(unsigned int i=0; i<ddf->triangles.size(); i++){
+    MTriangle* tri = ddf->triangles[i];
+    fprintf(pmesh,"%d 2 2 0 0",i+1);
+    for(int j=0; j<3; j++){
+      MVertex* mv = tri->getVertex(j);
+      fprintf(pmesh," %d",mv2int[mv]);
+    }
+    fprintf(pmesh,"\n");
+  }
+  fprintf(pmesh,"$EndElements\n");
+  fclose(pmesh);
+
+}
+
 
 void discreteFace::checkAndFixOrientation(){
 
@@ -296,7 +369,7 @@ void discreteFace::checkAndFixOrientation(){
 	    if (!(current->getVertex(k!=2 ?k+1 : 0 ) == neigs[i]->getVertex(j!=0 ? j-1 : 2) ||
 		  current->getVertex(k!=0 ?k-1 : 2 ) == neigs[i]->getVertex(j!=2 ? j+1 : 0))){
 	      neigs[i]->reverse();
-	      Msg::Info("discreteFace: triangle %d has been reoriented.",neigs[i]->getNum());
+	      //Msg::Info("discreteFace: triangle %d has been reoriented.",neigs[i]->getNum());
 	    }
 	    break;
 	  }
@@ -323,7 +396,7 @@ void discreteFace::setupDiscreteEdge(discreteEdge*de,std::vector<MLine*>mlines,s
     de->mesh_vertices.push_back(mlines[i]->getVertex(0));
     if(trash) trash->insert(mlines[i]->getVertex(0));
   }
-  de->createGeometry();
+  de->createGeometry();  
 }
 
 
@@ -380,7 +453,10 @@ void discreteFace::splitDiscreteEdge ( GEdge *de , GVertex *gv, discreteEdge* ne
 void discreteFace::split(triangulation* trian,std::vector<triangulation*> &partition,int nPartitions)
 {
 #if defined(HAVE_SOLVER) && defined(HAVE_ANN) && defined(HAVE_METIS)
-
+  /*
+  if(trian->aspectRatio() < 4.)
+    printf("it is splitted because of aspect ratio");
+  */
   int nVertex = trian->tri.size(); // number of elements
   int nEdge = trian->ed2tri.size() - trian->borderEdg.size();// number of edges, (without the boundary ones)
 
@@ -422,8 +498,9 @@ void discreteFace::split(triangulation* trian,std::vector<triangulation*> &parti
     elem[part[i]].push_back(trian->tri[i]);
     el2part[trian->tri[i]] = part[i];
   }
+  
   //check connectivity
-  for(int p=0; p<nPartitions; p++){// part by part
+  for(int p=0; p<nPartitions; p++){// part by part 
     std::set<MElement*> celem(elem[p].begin(),elem[p].end());// current elements of the p-th part
     std::queue<MElement*> my_todo; // todo list, for adjacency check - in order to check the connectivity of the part
     std::map<MElement*,bool> check_todo; // help to complete todo list
@@ -449,7 +526,7 @@ void discreteFace::split(triangulation* trian,std::vector<triangulation*> &parti
       }// end for j
     }// end while
     if(!celem.empty()){// if the set is empty, it means that the part was connected
-      Msg::Info("discreteFace::split(), a partition is not connected: it is going to be fixed.");
+      Msg::Info("discreteFace::split(), a partition (%d) is not connected: it is going to be fixed.",p);
       std::vector<MElement*> relem(celem.begin(),celem.end());// new part
       for(unsigned int ie=0; ie<relem.size(); ie++)// updating the id part of the element belonging to the new part
 	el2part[relem[ie]] = nPartitions;
@@ -463,8 +540,6 @@ void discreteFace::split(triangulation* trian,std::vector<triangulation*> &parti
       elem[p] = upe;
     }
   }// end for p
-
-
   for(int i=0; i<nPartitions; i++)// new triangulation of the connected parts
     partition.push_back(new triangulation(elem[i],this));
 
@@ -603,6 +678,43 @@ void discreteFace::updateTopology(std::vector<triangulation*>&partition)
 #endif
 }
 
+
+void discreteFace::fillHoles(triangulation* trian)
+{
+
+  std::map<double,std::vector<MVertex*> > bords = trian->bord;  
+  std::map<double,std::vector<MVertex*> >::reverse_iterator it = bords.rbegin();
+  ++it;
+  for(; it!=bords.rend(); ++it){
+    double x[3] = {0.,0.,0.};
+    std::vector<MVertex*> mv = it->second;
+    for(unsigned int j=0; j<mv.size(); j++){
+      x[0] += mv[j]->x(); 
+      x[1] += mv[j]->y();
+      x[2] += mv[j]->z();
+    }
+    x[0] /= mv.size(); x[1] /= mv.size(); x[2] /= mv.size();
+    MVertex* center = new MVertex(x[0],x[1],x[2]);
+    this->mesh_vertices.push_back(center);
+    center->setEntity(this);
+    trian->vert.insert(center);
+    for(unsigned int j=1; j<mv.size(); j++)
+      addTriangle(trian,new MTriangle(mv[j],mv[j-1],center));    
+    addTriangle(trian,new MTriangle(mv[0],mv[mv.size()-1],center));
+  }
+
+}
+
+void discreteFace::addTriangle(triangulation* trian, MTriangle* t)
+{// #mark quid borders ?
+  for(int i=0; i<3; i++){
+    MEdge ed = t->getEdge(i);
+    trian->ed2tri[ed].push_back(trian->tri.size());
+  }
+  trian->tri.push_back(t);
+}
+
+
 // delete all discrete disk faces
 //void discreteFace::deleteAtlas() {
 //}
diff --git a/Geo/discreteFace.h b/Geo/discreteFace.h
index d043ffc2ba..1f6b0f61af 100644
--- a/Geo/discreteFace.h
+++ b/Geo/discreteFace.h
@@ -36,6 +36,8 @@ class discreteFace : public GFace {
   void splitDiscreteEdge(GEdge*,GVertex*,discreteEdge*[2]);
   void updateTopology(std::vector<triangulation*>&);
   void split(triangulation*,std::vector<triangulation*>&,int);
+  void fillHoles(triangulation*);
+  void addTriangle(triangulation*,MTriangle*);
   GPoint point(double par1, double par2) const;
   SPoint2 parFromPoint(const SPoint3 &p, bool onSurface=true) const;
   SVector3 normal(const SPoint2 &param) const;
@@ -53,6 +55,8 @@ class discreteFace : public GFace {
   void createGeometry();
   void gatherMeshes();
   virtual void mesh (bool verbose);
+  void printAtlasMesh(std::vector<MElement*>, int);
+  void printAtlasMesh(discreteDiskFace*, int);
   std::vector<discreteDiskFace*> _atlas;
   std::vector<GFace*> _CAD;
 };
-- 
GitLab