Nektar++
ProcessSpherigon.cpp
Go to the documentation of this file.
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 // File: ProcessSpherigon.cpp
4 //
5 // For more information, please see: http://www.nektar.info/
6 //
7 // The MIT License
8 //
9 // Copyright (c) 2006 Division of Applied Mathematics, Brown University (USA),
10 // Department of Aeronautics, Imperial College London (UK), and Scientific
11 // Computing and Imaging Institute, University of Utah (USA).
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining a
14 // copy of this software and associated documentation files (the "Software"),
15 // to deal in the Software without restriction, including without limitation
16 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 // and/or sell copies of the Software, and to permit persons to whom the
18 // Software is furnished to do so, subject to the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be included
21 // in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 // DEALINGS IN THE SOFTWARE.
30 //
31 // Description: Apply Spherigon surface smoothing technique to a 3D mesh.
32 //
33 ////////////////////////////////////////////////////////////////////////////////
34 
35 #include <boost/geometry.hpp>
36 #include <boost/geometry/geometries/point.hpp>
37 #include <boost/geometry/geometries/box.hpp>
38 #include <boost/geometry/index/rtree.hpp>
39 
43 
44 #include <LocalRegions/SegExp.h>
45 #include <LocalRegions/QuadExp.h>
46 #include <LocalRegions/TriExp.h>
48 
50 
51 #include "ProcessSpherigon.h"
52 
53 namespace bg = boost::geometry;
54 namespace bgi = boost::geometry::index;
55 
56 using namespace std;
57 using namespace Nektar::NekMeshUtils;
58 
59 #define TOL_BLEND 1.0e-8
60 
61 namespace Nektar
62 {
63 namespace Utilities
64 {
65 ModuleKey ProcessSpherigon::className =
67  ModuleKey(eProcessModule, "spherigon"), ProcessSpherigon::create);
68 
69 /**
70  * @class ProcessSpherigon
71  *
72  * This class implements the spherigon surface smoothing technique which
73  * is documented in
74  *
75  * "The SPHERIGON: A Simple Polygon Patch for Smoothing Quickly your
76  * Polygonal Meshes": P. Volino and N. Magnenat Thalmann, Computer
77  * Animation Proceedings (1998).
78  *
79  * This implementation works in both a 2D manifold setting (for
80  * triangles and quadrilaterals embedded in 3-space) and in a full 3D
81  * enviroment (prisms, tetrahedra and hexahedra).
82  *
83  * No additional information needs to be supplied directly to the module
84  * in order for it to work. However, 3D elements do rely on the mapping
85  * Mesh::spherigonSurfs to be populated by the relevant input modules.
86  *
87  * Additionally, since the algorithm assumes normals are supplied which
88  * are perpendicular to the true surface defined at each vertex. If
89  * these are specified in Mesh::vertexNormals by the input module,
90  * better smoothing results can be obtained. Otherwise, normals are
91  * estimated by taking the average of all edge/face normals which
92  * connect to the vertex.
93  */
94 
95 /**
96  * @brief Default constructor.
97  */
98 ProcessSpherigon::ProcessSpherigon(MeshSharedPtr m) : ProcessModule(m)
99 {
100  m_config["N"] =
101  ConfigOption(false, "5", "Number of points to add to face edges.");
102  m_config["surf"] =
103  ConfigOption(false, "-1", "Tag identifying surface to process.");
104  m_config["BothTriFacesOnPrism"] = ConfigOption(
105  true, "-1", "Curve both triangular faces of prism on boundary.");
106  m_config["usenormalfile"] = ConfigOption(
107  false, "NoFile", "Use alternative file for Spherigon definition");
108  m_config["scalefile"] = ConfigOption(
109  false, "1.0", "Apply scaling factor to coordinates in file ");
110  m_config["normalnoise"] =
111  ConfigOption(false,
112  "NotSpecified",
113  "Add randowm noise to normals of amplitude AMP "
114  "in specified region. input string is "
115  "Amp,xmin,xmax,ymin,ymax,zmin,zmax");
116 }
117 
118 /**
119  * @brief Destructor.
120  */
122 {
123 }
124 
125 /*
126  * @brief Calcuate the normalised cross product \f$ \vec{c} =
127  * \vec{a}\times\vec{b} / \| \vec{a}\times\vec{b} \| \f$.
128  */
130 {
131  double inv_mag;
132 
133  c.m_x = a.m_y * b.m_z - a.m_z * b.m_y;
134  c.m_y = a.m_z * b.m_x - a.m_x * b.m_z;
135  c.m_z = a.m_x * b.m_y - a.m_y * b.m_x;
136 
137  inv_mag = 1.0 / sqrt(c.m_x * c.m_x + c.m_y * c.m_y + c.m_z * c.m_z);
138 
139  c.m_x = c.m_x * inv_mag;
140  c.m_y = c.m_y * inv_mag;
141  c.m_z = c.m_z * inv_mag;
142 }
143 
144 /**
145  * @brief Calculate the magnitude of the cross product \f$
146  * \vec{a}\times\vec{b} \f$.
147  */
149 {
150  double tmp1 = a.m_y * b.m_z - a.m_z * b.m_y;
151  double tmp2 = a.m_z * b.m_x - a.m_x * b.m_z;
152  double tmp3 = a.m_x * b.m_y - a.m_y * b.m_x;
153  return sqrt(tmp1 * tmp1 + tmp2 * tmp2 + tmp3 * tmp3);
154 }
155 
156 /**
157  * @brief Calculate the \f$ C^0 \f$ blending function for spherigon
158  * implementation.
159  *
160  * See equation (5) of the paper.
161  *
162  * @param r Barycentric coordinate.
163  */
164 double ProcessSpherigon::Blend(double r)
165 {
166  return r * r;
167 }
168 
169 /**
170  * @brief Calculate the \f$ C^1 \f$ blending function for spherigon
171  * implementation.
172  *
173  * See equation (10) of the paper.
174  *
175  * @param r Generalised barycentric coordinates of the point P.
176  * @param Q Vector of vertices denoting this triangle/quad.
177  * @param P Point in the triangle to apply blending to.
178  * @param blend The resulting blending components for each vertex.
179  */
180 void ProcessSpherigon::SuperBlend(vector<double> &r,
181  vector<Node> &Q,
182  Node &P,
183  vector<double> &blend)
184 {
185  vector<double> tmp(r.size());
186  double totBlend = 0.0;
187  int i;
188  int nV = r.size();
189 
190  for (i = 0; i < nV; ++i)
191  {
192  blend[i] = 0.0;
193  tmp[i] = (Q[i] - P).abs2();
194  }
195 
196  for (i = 0; i < nV; ++i)
197  {
198  int ip = (i + 1) % nV, im = (i - 1 + nV) % nV;
199 
200  if (r[i] > TOL_BLEND && r[i] < (1 - TOL_BLEND))
201  {
202  blend[i] =
203  r[i] * r[i] * (r[im] * r[im] * tmp[im] / (tmp[im] + tmp[i]) +
204  r[ip] * r[ip] * tmp[ip] / (tmp[ip] + tmp[i]));
205  totBlend += blend[i];
206  }
207  }
208 
209  for (i = 0; i < nV; ++i)
210  {
211  blend[i] /= totBlend;
212  if (r[i] >= (1 - TOL_BLEND))
213  {
214  blend[i] = 1.0;
215  }
216  if (r[i] <= TOL_BLEND)
217  {
218  blend[i] = 0.0;
219  }
220  }
221 }
222 
224  map<int,NodeSharedPtr> &surfverts)
225 {
226  int cnt = 0;
227  int j = 0;
228  int prog=0,cntmin;
229 
230  typedef bg::model::point<NekDouble, 3, bg::cs::cartesian> Point;
231  typedef pair<Point, unsigned int> PointI;
232 
233  int n_neighbs = 1;
234 
235  map<int,int> TreeidtoPlyid;
236 
237  //Fill vertex array into tree format
238  vector<PointI> dataPts;
239  for (auto &it : plymesh->m_vertexSet)
240  {
241  dataPts.push_back(make_pair(Point(it->m_x, it->m_y, it->m_z), j));
242  TreeidtoPlyid[j++] = it->m_id;
243  }
244 
245  //Build tree
246  bgi::rtree<PointI, bgi::rstar<16> > rtree;
247  rtree.insert(dataPts.begin(), dataPts.end());
248 
249  //Find neipghbours
250  for (auto &vIt : surfverts)
251  {
252  if(m_mesh->m_verbose)
253  {
254  prog = LibUtilities::PrintProgressbar(cnt,surfverts.size(),
255  "Nearest ply verts",prog);
256  }
257 
258  Point queryPt(vIt.second->m_x, vIt.second->m_y, vIt.second->m_z);
259  vector<PointI> result;
260  rtree.query(bgi::nearest(queryPt, n_neighbs),
261  std::back_inserter(result));
262 
263  cntmin = TreeidtoPlyid[result[0].second];
264 
265  ASSERTL1(cntmin < plymesh->m_vertexNormals.size(),
266  "cntmin is out of range");
267 
268  m_mesh->m_vertexNormals[vIt.first] =
269  plymesh->m_vertexNormals[cntmin];
270  ++cnt;
271  }
272 }
273 
274 /**
275  * @brief Generate a set of approximate vertex normals to a surface
276  * represented by line segments in 2D and a hybrid
277  * triangular/quadrilateral mesh in 3D.
278  *
279  * This routine approximates the true vertex normals to a surface by
280  * averaging the normals of all edges/faces which connect to the
281  * vertex. It is better to use the exact surface normals which can be
282  * set in Mesh::vertexNormals, but where they are not supplied this
283  * routine calculates an approximation for the spherigon implementation.
284  *
285  * @param el Vector of elements denoting the surface mesh.
286  */
287 void ProcessSpherigon::GenerateNormals(std::vector<ElementSharedPtr> &el,
288  MeshSharedPtr &mesh)
289 {
290  for (int i = 0; i < el.size(); ++i)
291  {
292  ElementSharedPtr e = el[i];
293 
294  // Ensure that element is a line, triangle or quad.
295  ASSERTL0(e->GetConf().m_e == LibUtilities::eSegment ||
296  e->GetConf().m_e == LibUtilities::eTriangle ||
297  e->GetConf().m_e == LibUtilities::eQuadrilateral,
298  "Spherigon expansions must be lines, triangles or "
299  "quadrilaterals.");
300 
301  // Calculate normal for this element.
302  int nV = e->GetVertexCount();
303  vector<NodeSharedPtr> node(nV);
304 
305  for (int j = 0; j < nV; ++j)
306  {
307  node[j] = e->GetVertex(j);
308  }
309 
310  Node n;
311 
312  if (mesh->m_spaceDim == 3)
313  {
314  // Create two tangent vectors and take unit cross product.
315  Node v1 = *(node[1]) - *(node[0]);
316  Node v2 = *(node[2]) - *(node[0]);
317  UnitCrossProd(v1, v2, n);
318  }
319  else
320  {
321  // Calculate gradient vector and invert.
322  Node dx = *(node[1]) - *(node[0]);
323  NekDouble diff = dx.abs2();
324  dx /= sqrt(diff);
325  n.m_x = -dx.m_y;
326  n.m_y = dx.m_x;
327  n.m_z = 0;
328  }
329 
330  // Insert face normal into vertex normal list or add to existing
331  // value.
332  for (int j = 0; j < nV; ++j)
333  {
334  auto nIt = mesh->m_vertexNormals.find(e->GetVertex(j)->m_id);
335  if (nIt == mesh->m_vertexNormals.end())
336  {
337  mesh->m_vertexNormals[e->GetVertex(j)->m_id] = n;
338  }
339  else
340  {
341  nIt->second += n;
342  }
343  }
344  }
345 
346  // Normalize resulting vectors.
347  for (auto &nIt : mesh->m_vertexNormals)
348  {
349  Node &n = nIt.second;
350  n /= sqrt(n.abs2());
351  }
352 
353 }
354 
355 /**
356  * @brief Perform the spherigon smoothing technique on the mesh.
357  */
359 {
360  ASSERTL0(m_mesh->m_spaceDim == 3 || m_mesh->m_spaceDim == 2,
361  "Spherigon implementation only valid in 2D/3D.");
362 
363  std::unordered_set<int> visitedEdges;
364 
365  // First construct vector of elements to process.
366  vector<ElementSharedPtr> el;
367 
368  if (m_mesh->m_verbose)
369  {
370  cout << "ProcessSpherigon: Smoothing mesh..." << endl;
371  }
372 
373  if (m_mesh->m_expDim == 2 && m_mesh->m_spaceDim == 3)
374  {
375  // Manifold case - copy expansion dimension.
376  el = m_mesh->m_element[m_mesh->m_expDim];
377  }
378  else if (m_mesh->m_spaceDim == m_mesh->m_expDim)
379  {
380  // Full 2D or 3D case - iterate over stored edges/faces and
381  // create segments/triangles representing those edges/faces.
382  set<pair<int, int> >::iterator it;
383  vector<int> t;
384  t.push_back(0);
385 
386  // Construct list of spherigon edges/faces from a tag.
387  string surfTag = m_config["surf"].as<string>();
388  bool prismTag = m_config["BothTriFacesOnPrism"].beenSet;
389 
390  if (surfTag != "")
391  {
392  vector<unsigned int> surfs;
393  ParseUtils::GenerateSeqVector(surfTag, surfs);
394  sort(surfs.begin(), surfs.end());
395 
396  m_mesh->m_spherigonSurfs.clear();
397  for (int i = 0; i < m_mesh->m_element[m_mesh->m_expDim].size(); ++i)
398  {
399  ElementSharedPtr el = m_mesh->m_element[m_mesh->m_expDim][i];
400  int nSurf = m_mesh->m_expDim == 3 ? el->GetFaceCount()
401  : el->GetEdgeCount();
402 
403  for (int j = 0; j < nSurf; ++j)
404  {
405  int bl = el->GetBoundaryLink(j);
406  if (bl == -1)
407  {
408  continue;
409  }
410 
411  ElementSharedPtr bEl =
412  m_mesh->m_element[m_mesh->m_expDim - 1][bl];
413  vector<int> tags = bEl->GetTagList();
414  vector<int> inter;
415 
416  sort(tags.begin(), tags.end());
417  set_intersection(surfs.begin(),
418  surfs.end(),
419  tags.begin(),
420  tags.end(),
421  back_inserter(inter));
422 
423  if (inter.size() == 1)
424  {
425  m_mesh->m_spherigonSurfs.insert(make_pair(i, j));
426 
427  // Curve other tri face on Prism. Note could be
428  // problem on pyramid when implemented.
429  if (nSurf == 5 && prismTag)
430  {
431  // add other end of prism on boundary for
432  // smoothing
433  int triFace = j == 1 ? 3 : 1;
434  m_mesh->m_spherigonSurfs.insert(
435  make_pair(i, triFace));
436  }
437  }
438  }
439  }
440  }
441 
442  if (m_mesh->m_spherigonSurfs.size() == 0)
443  {
444  cerr << "WARNING: Spherigon surfaces have not been defined "
445  << "-- ignoring smoothing." << endl;
446  return;
447  }
448 
449  if (m_mesh->m_expDim == 3)
450  {
451  for (it = m_mesh->m_spherigonSurfs.begin();
452  it != m_mesh->m_spherigonSurfs.end();
453  ++it)
454  {
455  FaceSharedPtr f =
456  m_mesh->m_element[m_mesh->m_expDim][it->first]->GetFace(
457  it->second);
458  vector<NodeSharedPtr> nodes = f->m_vertexList;
460  (LibUtilities::ShapeType)(nodes.size());
461  ElmtConfig conf(eType, 1, false, false);
462 
463  // Create 2D element.
464  ElementSharedPtr elmt =
465  GetElementFactory().CreateInstance(eType, conf, nodes, t);
466 
467  // Copy vertices/edges from face.
468  for (int i = 0; i < f->m_vertexList.size(); ++i)
469  {
470  elmt->SetVertex(i, f->m_vertexList[i]);
471  }
472  for (int i = 0; i < f->m_edgeList.size(); ++i)
473  {
474  elmt->SetEdge(i, f->m_edgeList[i]);
475  }
476 
477  el.push_back(elmt);
478  }
479  }
480  else
481  {
482  for (it = m_mesh->m_spherigonSurfs.begin();
483  it != m_mesh->m_spherigonSurfs.end();
484  ++it)
485  {
486  EdgeSharedPtr edge =
487  m_mesh->m_element[m_mesh->m_expDim][it->first]->GetEdge(
488  it->second);
489  vector<NodeSharedPtr> nodes;
491  ElmtConfig conf(eType, 1, false, false);
492 
493  nodes.push_back(edge->m_n1);
494  nodes.push_back(edge->m_n2);
495 
496  // Create 2D element.
497  ElementSharedPtr elmt =
498  GetElementFactory().CreateInstance(eType, conf, nodes, t);
499 
500  // Copy vertices/edges from original element.
501  elmt->SetVertex(0, nodes[0]);
502  elmt->SetVertex(1, nodes[1]);
503  elmt->SetEdge(
504  0,
505  m_mesh->m_element[m_mesh->m_expDim][it->first]->GetEdge(
506  it->second));
507  el.push_back(elmt);
508  }
509  }
510  }
511  else
512  {
513  ASSERTL0(false, "Spherigon expansions must be 2/3 dimensional");
514  }
515 
516  // See if vertex normals have been generated. If they have not,
517  // approximate them by summing normals of surrounding elements.
518  bool normalsGenerated = false;
519 
520  // Read Normal file if one exists
521  std::string normalfile = m_config["usenormalfile"].as<string>();
522  if (normalfile.compare("NoFile") != 0)
523  {
524  NekDouble scale = m_config["scalefile"].as<NekDouble>();
525 
526  if (m_mesh->m_verbose)
527  {
528  cout << "Inputing normal file: " << normalfile
529  << " with scaling of " << scale << endl;
530  }
531 
532  ifstream inplyTmp;
533  io::filtering_istream inply;
534  InputPlySharedPtr plyfile;
535 
536  inplyTmp.open(normalfile.c_str());
537  ASSERTL0(inplyTmp,
538  string("Could not open input ply file: ") + normalfile);
539 
540  inply.push(inplyTmp);
541 
542  MeshSharedPtr m = std::shared_ptr<Mesh>(new Mesh());
543  plyfile = std::shared_ptr<InputPly>(new InputPly(m));
544  plyfile->ReadPly(inply, scale);
545  plyfile->ProcessVertices();
546 
547  MeshSharedPtr plymesh = plyfile->GetMesh();
548  if (m_mesh->m_verbose)
549  {
550  cout << "\t Generating ply normals" << endl;
551  }
552  GenerateNormals(plymesh->m_element[plymesh->m_expDim], plymesh);
553 
554  // finally find nearest vertex and set normal to mesh surface file
555  // normal. probably should have a hex tree search ?
556  Node minx(0, 0.0, 0.0, 0.0), tmp, tmpsav;
557  NodeSet::iterator it;
558  map<int, NodeSharedPtr>::iterator vIt;
559  map<int, NodeSharedPtr> surfverts;
560 
561  // make a map of normal vertices to visit based on elements el
562  for (int i = 0; i < el.size(); ++i)
563  {
564  ElementSharedPtr e = el[i];
565  int nV = e->GetVertexCount();
566  for (int j = 0; j < nV; ++j)
567  {
568  int id = e->GetVertex(j)->m_id;
569  surfverts[id] = e->GetVertex(j);
570  }
571  }
572 
573 
574  if (m_mesh->m_verbose)
575  {
576  cout << "\t Processing surface normals " << endl;
577  }
578 
579  // loop over all element in ply mesh and determine
580  // and set normal to nearest point in ply mesh
581  FindNormalFromPlyFile(plymesh,surfverts);
582 
583  normalsGenerated = true;
584  }
585  else if (m_mesh->m_vertexNormals.size() == 0)
586  {
587  GenerateNormals(el, m_mesh);
588  normalsGenerated = true;
589  }
590 
591  // See if we should add noise to normals
592  std::string normalnoise = m_config["normalnoise"].as<string>();
593  if (normalnoise.compare("NotSpecified") != 0)
594  {
595  vector<NekDouble> values;
596  ASSERTL0(ParseUtils::GenerateVector(normalnoise, values),
597  "Failed to interpret normal noise string");
598 
599  int nvalues = values.size() / 2;
600  NekDouble amp = values[0];
601 
602  if (m_mesh->m_verbose)
603  {
604  cout << "\t adding noise to normals of amplitude " << amp
605  << " in range: ";
606  for (int i = 0; i < nvalues; ++i)
607  {
608  cout << values[2 * i + 1] << "," << values[2 * i + 2] << " ";
609  }
610  cout << endl;
611  }
612 
613  map<int, NodeSharedPtr>::iterator vIt;
614  map<int, NodeSharedPtr> surfverts;
615 
616  // make a map of normal vertices to visit based on elements el
617  for (int i = 0; i < el.size(); ++i)
618  {
619  ElementSharedPtr e = el[i];
620  int nV = e->GetVertexCount();
621  for (int j = 0; j < nV; ++j)
622  {
623  int id = e->GetVertex(j)->m_id;
624  surfverts[id] = e->GetVertex(j);
625  }
626  }
627 
628  for (vIt = surfverts.begin(); vIt != surfverts.end(); ++vIt)
629  {
630  bool AddNoise = false;
631 
632  for (int i = 0; i < nvalues; ++i)
633  {
634  // check to see if point is in range
635  switch (nvalues)
636  {
637  case 1:
638  {
639  if (((vIt->second)->m_x > values[2 * i + 1]) &&
640  ((vIt->second)->m_x < values[2 * i + 2]))
641  {
642  AddNoise = true;
643  }
644  }
645  break;
646  case 2:
647  {
648  if (((vIt->second)->m_x > values[2 * i + 1]) &&
649  ((vIt->second)->m_x < values[2 * i + 2]) &&
650  ((vIt->second)->m_y > values[2 * i + 3]) &&
651  ((vIt->second)->m_y < values[2 * i + 4]))
652  {
653  AddNoise = true;
654  }
655  }
656  break;
657  case 3:
658  {
659  if (((vIt->second)->m_x > values[2 * i + 1]) &&
660  ((vIt->second)->m_x < values[2 * i + 2]) &&
661  ((vIt->second)->m_y > values[2 * i + 3]) &&
662  ((vIt->second)->m_y < values[2 * i + 4]) &&
663  ((vIt->second)->m_z > values[2 * i + 5]) &&
664  ((vIt->second)->m_z < values[2 * i + 6]))
665 
666  {
667  AddNoise = true;
668  }
669  }
670  break;
671  }
672 
673  if (AddNoise)
674  {
675  // generate random unit vector;
676  Node rvec(0, rand(), rand(), rand());
677  rvec *= values[0] / sqrt(rvec.abs2());
678 
679  Node normal = m_mesh->m_vertexNormals[vIt->first];
680 
681  normal += rvec;
682  normal /= sqrt(normal.abs2());
683 
684  m_mesh->m_vertexNormals[vIt->first] = normal;
685  }
686  }
687  }
688  }
689 
690  // Allocate storage for interior points.
691  int nq = m_config["N"].as<int>();
692  int nquad = m_mesh->m_spaceDim == 3 ? nq * nq : nq;
693  Array<OneD, NekDouble> x(nq * nq);
694  Array<OneD, NekDouble> y(nq * nq);
695  Array<OneD, NekDouble> z(nq * nq);
696 
697  Array<OneD, NekDouble> xc(nq * nq);
698  Array<OneD, NekDouble> yc(nq * nq);
699  Array<OneD, NekDouble> zc(nq * nq);
700 
701  ASSERTL0(nq > 2, "Number of points must be greater than 2.");
702 
705  nq,
709  nq,
714 
715  Array<OneD, NekDouble> xnodal(nq * (nq + 1) / 2), ynodal(nq * (nq + 1) / 2);
716  stdtri->GetNodalPoints(xnodal, ynodal);
717 
718  int edgeMap[3][4][2] = {
719  {{0, 1}, {-1, -1}, {-1, -1}, {-1, -1}}, // seg
720  {{0, 1}, {nq - 1, nq}, {nq * (nq - 1), -nq}, {-1, -1}}, // tri
721  {{0, 1}, {nq - 1, nq}, {nq * nq - 1, -1}, {nq * (nq - 1), -nq}} // quad
722  };
723 
724  int vertMap[3][4][2] = {
725  {{0, 1}, {0, 0}, {0, 0}, {0, 0}}, // seg
726  {{0, 1}, {1, 2}, {2, 3}, {0, 0}}, // tri
727  {{0, 1}, {1, 2}, {2, 3}, {3, 0}}, // quad
728  };
729 
730  for (int i = 0; i < el.size(); ++i)
731  {
732  // Construct a Nektar++ element to obtain coordinate points
733  // inside the element. TODO: Add options for various
734  // nodal/tensor point distributions + number of points to add.
735  ElementSharedPtr e = el[i];
736 
739  nq,
741 
742  if (e->GetConf().m_e == LibUtilities::eSegment)
743  {
745  std::dynamic_pointer_cast<SpatialDomains::SegGeom>(
746  e->GetGeom(m_mesh->m_spaceDim));
749  geom);
750  seg->GetCoords(x, y, z);
751  nquad = nq;
752  }
753  else if (e->GetConf().m_e == LibUtilities::eTriangle)
754  {
756  std::dynamic_pointer_cast<SpatialDomains::TriGeom>(
757  e->GetGeom(3));
760  B0, B1, LibUtilities::eNodalTriElec, geom);
761 
762  Array<OneD, NekDouble> coord(2);
763  tri->GetCoords(xc, yc, zc);
764  nquad = nq * (nq + 1) / 2;
765 
766  for (int j = 0; j < nquad; ++j)
767  {
768  coord[0] = xnodal[j];
769  coord[1] = ynodal[j];
770  x[j] = stdtri->PhysEvaluate(coord, xc);
771  }
772 
773  for (int j = 0; j < nquad; ++j)
774  {
775  coord[0] = xnodal[j];
776  coord[1] = ynodal[j];
777  y[j] = stdtri->PhysEvaluate(coord, yc);
778  }
779 
780  for (int j = 0; j < nquad; ++j)
781  {
782  coord[0] = xnodal[j];
783  coord[1] = ynodal[j];
784  z[j] = stdtri->PhysEvaluate(coord, zc);
785  }
786  }
787  else if (e->GetConf().m_e == LibUtilities::eQuadrilateral)
788  {
790  std::dynamic_pointer_cast<SpatialDomains::QuadGeom>(
791  e->GetGeom(3));
794  B2, B2, geom);
795  quad->GetCoords(x, y, z);
796  nquad = nq * nq;
797  }
798  else
799  {
800  ASSERTL0(false, "Unknown expansion type.");
801  }
802 
803  // Zero z-coordinate in 2D.
804  if (m_mesh->m_spaceDim == 2)
805  {
806  Vmath::Zero(nquad, z, 1);
807  }
808 
809  // Find vertex normals.
810  int nV = e->GetVertexCount();
811  vector<Node> v, vN;
812  for (int j = 0; j < nV; ++j)
813  {
814  v.push_back(*(e->GetVertex(j)));
815  ASSERTL1(m_mesh->m_vertexNormals.count(v[j].m_id) != 0,
816  "Normal has not been defined");
817  vN.push_back(m_mesh->m_vertexNormals[v[j].m_id]);
818  }
819 
820  vector<Node> tmp(nV);
821  vector<NekDouble> r(nV);
822  vector<Node> K(nV);
823  vector<Node> Q(nV);
824  vector<Node> Qp(nV);
825  vector<NekDouble> blend(nV);
826  vector<Node> out(nquad);
827 
828  // Calculate segment length for 2D spherigon routine.
829  NekDouble segLength = sqrt((v[0] - v[1]).abs2());
830 
831  // Perform Spherigon method to smooth manifold.
832  for (int j = 0; j < nquad; ++j)
833  {
834  Node P(0, x[j], y[j], z[j]);
835  Node N(0, 0, 0, 0);
836 
837  // Calculate generalised barycentric coordinates r[] and the
838  // Phong normal N = vN . r for this point of the element.
839  if (m_mesh->m_spaceDim == 2)
840  {
841  // In 2D the coordinates are given by a ratio of the
842  // segment length to the distance from one of the
843  // endpoints.
844  r[0] = sqrt((P - v[0]).abs2()) / segLength;
845  r[0] = max(min(1.0, r[0]), 0.0);
846  r[1] = 1.0 - r[0];
847 
848  // Calculate Phong normal.
849  N = vN[0] * r[0] + vN[1] * r[1];
850  }
851  else if (m_mesh->m_spaceDim == 3)
852  {
853  for (int k = 0; k < nV; ++k)
854  {
855  tmp[k] = P - v[k];
856  }
857 
858  // Calculate generalized barycentric coordinate system
859  // (see equation 6 of paper).
860  NekDouble weight = 0.0;
861  for (int k = 0; k < nV; ++k)
862  {
863  r[k] = 1.0;
864  for (int l = 0; l < nV - 2; ++l)
865  {
866  r[k] *= CrossProdMag(tmp[(k + l + 1) % nV],
867  tmp[(k + l + 2) % nV]);
868  }
869  weight += r[k];
870  }
871 
872  // Calculate Phong normal (equation 1).
873  for (int k = 0; k < nV; ++k)
874  {
875  r[k] /= weight;
876  N += vN[k] * r[k];
877  }
878  }
879 
880  // Normalise Phong normal.
881  N /= sqrt(N.abs2());
882 
883  for (int k = 0; k < nV; ++k)
884  {
885  // Perform steps denoted in equations 2, 3, 8 for C1
886  // smoothing.
887  NekDouble tmp1;
888  K[k] = P + N * ((v[k] - P).dot(N));
889  tmp1 = (v[k] - K[k]).dot(vN[k]) / (1.0 + N.dot(vN[k]));
890  Q[k] = K[k] + N * tmp1;
891  Qp[k] = v[k] - N * ((v[k] - P).dot(N));
892  }
893 
894  // Apply C1 blending function to the surface. TODO: Add
895  // option to do (more efficient) C0 blending function.
896  SuperBlend(r, Qp, P, blend);
897  P.m_x = P.m_y = P.m_z = 0.0;
898 
899  // Apply blending (equation 4).
900  for (int k = 0; k < nV; ++k)
901  {
902  P += Q[k] * blend[k];
903  }
904 
905  if ((boost::math::isnan)(P.m_x) || (boost::math::isnan)(P.m_y) ||
906  (boost::math::isnan)(P.m_z))
907  {
908  ASSERTL0(false,
909  "spherigon point is a nan. Check to see if "
910  "ply file is correct if using input normal file");
911  }
912  else
913  {
914  out[j] = P;
915  }
916  }
917 
918  // Push nodes into lines - TODO: face interior nodes.
919  // offset = 0 (seg), 1 (tri) or 2 (quad)
920  int offset = (int)e->GetConf().m_e - 2;
921 
922  for (int edge = 0; edge < e->GetEdgeCount(); ++edge)
923  {
924  auto eIt = visitedEdges.find(e->GetEdge(edge)->m_id);
925  if (eIt == visitedEdges.end())
926  {
927  bool reverseEdge =
928  !(v[vertMap[offset][edge][0]] == *(e->GetEdge(edge)->m_n1));
929 
930  // Clear existing curvature.
931  e->GetEdge(edge)->m_edgeNodes.clear();
932 
933  if (e->GetConf().m_e != LibUtilities::eTriangle)
934  {
935  for (int j = 1; j < nq - 1; ++j)
936  {
937  int v = edgeMap[offset][edge][0] +
938  j * edgeMap[offset][edge][1];
939  e->GetEdge(edge)->m_edgeNodes.push_back(
940  NodeSharedPtr(new Node(out[v])));
941  }
942  }
943  else
944  {
945  for (int j = 0; j < nq - 2; ++j)
946  {
947  int v = 3 + edge * (nq - 2) + j;
948  e->GetEdge(edge)->m_edgeNodes.push_back(
949  NodeSharedPtr(new Node(out[v])));
950  }
951  }
952 
953  if (reverseEdge)
954  {
955  reverse(e->GetEdge(edge)->m_edgeNodes.begin(),
956  e->GetEdge(edge)->m_edgeNodes.end());
957  }
958 
959  e->GetEdge(edge)->m_curveType =
961 
962  visitedEdges.insert(e->GetEdge(edge)->m_id);
963  }
964  }
965 
966  // Add face nodes in manifold and full 3D case, but not for 2D.
967  if (m_mesh->m_spaceDim == 3)
968  {
969  vector<NodeSharedPtr> volNodes;
970 
971  if (e->GetConf().m_e == LibUtilities::eQuadrilateral)
972  {
973  volNodes.resize((nq - 2) * (nq - 2));
974  for (int j = 1; j < nq - 1; ++j)
975  {
976  for (int k = 1; k < nq - 1; ++k)
977  {
978  int v = j * nq + k;
979  volNodes[(j - 1) * (nq - 2) + (k - 1)] =
980  NodeSharedPtr(new Node(out[v]));
981  }
982  }
983  }
984  else
985  {
986  for (int j = 3 + 3 * (nq - 2); j < nquad; ++j)
987  {
988  volNodes.push_back(NodeSharedPtr(new Node(out[j])));
989  }
990  }
991 
992  e->SetVolumeNodes(volNodes);
993  }
994  }
995 
996  // Copy face nodes back into 3D element faces
997  if (m_mesh->m_expDim == 3)
998  {
999  int elmt = 0;
1000  for (auto &it : m_mesh->m_spherigonSurfs)
1001  {
1002  FaceSharedPtr f =
1003  m_mesh->m_element[m_mesh->m_expDim][it.first]->GetFace(
1004  it.second);
1005 
1006  f->m_faceNodes = el[elmt++]->GetVolumeNodes();
1007  f->m_curveType = f->m_vertexList.size() == 3
1010  }
1011  }
1012 
1013  if (normalsGenerated)
1014  {
1015  m_mesh->m_vertexNormals.clear();
1016  }
1017 }
1018 }
1019 }
void UnitCrossProd(NekMeshUtils::Node &a, NekMeshUtils::Node &b, NekMeshUtils::Node &c)
NekDouble CrossProdMag(NekMeshUtils::Node &a, NekMeshUtils::Node &b)
Calculate the magnitude of the cross product .
#define ASSERTL0(condition, msg)
Definition: ErrorUtil.hpp:216
Basic information about an element.
Definition: ElementConfig.h:49
std::shared_ptr< StdNodalTriExp > StdNodalTriExpSharedPtr
void FindNormalFromPlyFile(NekMeshUtils::MeshSharedPtr &plymesh, std::map< int, NekMeshUtils::NodeSharedPtr > &surfverts)
int PrintProgressbar(const int position, const int goal, const std::string message, int lastprogress=-1)
Prints a progressbar.
Definition: Progressbar.hpp:67
NEKMESHUTILS_EXPORT NekDouble abs2() const
Definition: Node.h:156
std::shared_ptr< InputPly > InputPlySharedPtr
Definition: InputPly.h:67
void SuperBlend(std::vector< NekDouble > &r, std::vector< NekMeshUtils::Node > &Q, NekMeshUtils::Node &P, std::vector< NekDouble > &blend)
Calculate the blending function for spherigon implementation.
std::shared_ptr< QuadGeom > QuadGeomSharedPtr
Definition: HexGeom.h:46
void GenerateNormals(std::vector< NekMeshUtils::ElementSharedPtr > &el, NekMeshUtils::MeshSharedPtr &mesh)
Generate a set of approximate vertex normals to a surface represented by line segments in 2D and a hy...
Principle Modified Functions .
Definition: BasisType.h:48
STL namespace.
Converter for Ply files.
Definition: InputPly.h:46
NekDouble m_y
Y-coordinate.
Definition: Node.h:410
NEKMESHUTILS_EXPORT NekDouble dot(const Node &pSrc) const
Definition: Node.h:161
std::shared_ptr< Edge > EdgeSharedPtr
Shared pointer to an edge.
Definition: Edge.h:136
std::shared_ptr< Mesh > MeshSharedPtr
Shared pointer to a mesh.
Definition: Mesh.h:156
ElementFactory & GetElementFactory()
Definition: Element.cpp:44
Represents a point in the domain.
Definition: Node.h:62
std::shared_ptr< Node > NodeSharedPtr
Definition: CADVert.h:49
virtual void Process()
Write mesh to output file.
std::shared_ptr< NodalTriExp > NodalTriExpSharedPtr
Definition: NodalTriExp.h:383
Gauss Radau pinned at x=-1, .
Definition: PointsType.h:58
std::shared_ptr< Face > FaceSharedPtr
Definition: Face.h:155
Principle Orthogonal Functions .
Definition: BasisType.h:46
tBaseSharedPtr CreateInstance(tKey idKey, tParam... args)
Create an instance of the class referred to by idKey.
Definition: NekFactory.hpp:144
#define TOL_BLEND
std::shared_ptr< TriGeom > TriGeomSharedPtr
Definition: TriGeom.h:58
std::pair< ModuleType, std::string > ModuleKey
A 0-dimensional vertex.
Definition: Point.h:48
std::shared_ptr< Element > ElementSharedPtr
Definition: Edge.h:49
Represents a command-line configuration option.
static std::shared_ptr< DataType > AllocateSharedPtr(const Args &...args)
Allocate a shared pointer from the memory pool.
Principle Orthogonal Functions .
Definition: BasisType.h:45
Defines a specification for a set of points.
Definition: Points.h:59
double NekDouble
std::map< std::string, ConfigOption > m_config
List of configuration values.
static bool GenerateVector(const std::string &str, std::vector< T > &out)
Takes a comma-separated string and converts it to entries in a vector.
Definition: ParseUtils.cpp:135
NekDouble m_x
X-coordinate.
Definition: Node.h:408
Abstract base class for processing modules.
NekDouble Blend(NekDouble r)
Calculate the blending function for spherigon implementation.
std::shared_ptr< SegExp > SegExpSharedPtr
Definition: SegExp.h:266
tKey RegisterCreatorFunction(tKey idKey, CreatorFunction classCreator, std::string pDesc="")
Register a class with the factory.
Definition: NekFactory.hpp:199
NekDouble m_z
Z-coordinate.
Definition: Node.h:412
std::pair< ModuleType, std::string > ModuleKey
std::shared_ptr< QuadExp > QuadExpSharedPtr
Definition: QuadExp.h:293
void Zero(int n, T *x, const int incx)
Zero vector.
Definition: Vmath.cpp:376
#define ASSERTL1(condition, msg)
Assert Level 1 – Debugging which is used whether in FULLDEBUG or DEBUG compilation mode...
Definition: ErrorUtil.hpp:250
std::shared_ptr< SegGeom > SegGeomSharedPtr
Definition: Geometry2D.h:62
Describes the specification for a Basis.
Definition: Basis.h:49
1D Gauss-Lobatto-Legendre quadrature points
Definition: PointsType.h:51
2D Nodal Electrostatic Points on a Triangle
Definition: PointsType.h:69
ModuleFactory & GetModuleFactory()
static bool GenerateSeqVector(const std::string &str, std::vector< unsigned int > &out)
Takes a comma-separated compressed string and converts it to entries in a vector. ...
Definition: ParseUtils.cpp:108