Program Listing for File SurfaceMesh.h

Return to documentation for file (include/gamer/SurfaceMesh.h)

// This file is part of the GAMer software.
// Copyright (C) 2016-2021
// by Christopher T. Lee and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>
// or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
// Boston, MA 02111-1307 USA

#pragma once

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <utility>

#include <Eigen/Dense>
#include <Eigen/Eigenvalues>
#include <casc/casc>

#include "gamer/Vertex.h"

namespace gamer {

struct SMGlobal {
  int marker;
  float volumeConstraint;
  bool useVolumeConstraint;
  bool ishole;

  SMGlobal(int marker = -1, float volumeConstraint = -1,
           bool useVolumeConstraint = false, bool ishole = false)
      : marker(marker), volumeConstraint(volumeConstraint),
        useVolumeConstraint(useVolumeConstraint), ishole(ishole) {}
};

struct SMVertex : Vertex {
  Vector normal;
  using Vertex::Vertex;
};

struct SMEdge {
  bool selected;

  SMEdge() : SMEdge(0) {}

  SMEdge(bool select) : selected(select) {}
};

struct SMFaceProperties {
  int marker;
  bool selected;
  Vector normal;
  SMFaceProperties(int marker, bool selected)
      : marker(marker), selected(selected) {}
};

struct SMFace : casc::Orientable, SMFaceProperties {
  SMFace() : SMFace(Orientable{0}, SMFaceProperties{-1, false}) {}

  SMFace(int marker, bool selected)
      : SMFace(Orientable{0}, SMFaceProperties{marker, selected}) {}

  SMFace(int orient, int marker, bool selected)
      : SMFace(Orientable{orient}, SMFaceProperties{marker, selected}) {}

  SMFace(Orientable orient, SMFaceProperties prop)
      : Orientable(orient), SMFaceProperties(prop) {}

  friend std::ostream &operator<<(std::ostream &output, const SMFace &f) {
    output << "SMFace("
           << "m:" << f.marker << ";sel:" << std::boolalpha << f.selected
           << ";o:" << f.orientation << ")";
    return output;
  }

  std::string to_string() const {
    std::ostringstream output;
    output << *this;
    return output.str();
  }
};

namespace surfmesh_detail {
struct surfmesh_traits {
  using KeyType = int;
  using NodeTypes = util::type_holder<SMGlobal, SMVertex, SMEdge, SMFace>;
  using EdgeTypes =
      util::type_holder<casc::Orientable, casc::Orientable, casc::Orientable>;
};
} // end namespace surfmesh_detail

using SurfaceMesh = casc::simplicial_complex<surfmesh_detail::surfmesh_traits>;

tensor<double, 3, 2> getTangent(const SurfaceMesh &mesh,
                                SurfaceMesh::SimplexID<1> vertexID);

tensor<double, 3, 2> getTangent(const SurfaceMesh &mesh,
                                SurfaceMesh::SimplexID<3> faceID);

Vector getNormalFromTangent(const tensor<double, 3, 2> tangent);

Vector getNormal(const SurfaceMesh &mesh, SurfaceMesh::SimplexID<1> vertexID);

Vector getNormal(const SurfaceMesh &mesh, SurfaceMesh::SimplexID<3> faceID);

namespace surfacemesh_detail {
void decimateVertex(SurfaceMesh &mesh, SurfaceMesh::SimplexID<1> vertexID,
                    std::size_t rings = 2);

tensor<double, 3, 2>
computeLocalStructureTensor(const SurfaceMesh &mesh,
                            const SurfaceMesh::SimplexID<1> vertexID,
                            const int rings);

tensor<double, 3, 2>
computeLSTFromCache(const SurfaceMesh &mesh,
                    const SurfaceMesh::SimplexID<1> vertexID, const int rings);

template <std::size_t dimension>
auto getTangentH(const SurfaceMesh &mesh,
                 const tensor<double, dimension, 1> &origin,
                 SurfaceMesh::SimplexID<SurfaceMesh::topLevel> curr) {
  return (*curr).orientation;
}

template <std::size_t level, std::size_t dimension>
auto getTangentH(const SurfaceMesh &mesh,
                 const tensor<double, dimension, 1> &origin,
                 SurfaceMesh::SimplexID<level> curr) {
  tensor<double, dimension, SurfaceMesh::topLevel - level> rval;
  auto cover = mesh.get_cover(curr);
  for (auto alpha : cover) {
    auto edge = *mesh.get_edge_up(curr, alpha);
    const auto &v = (*mesh.get_simplex_up({alpha})).position;
    auto next = mesh.get_simplex_up(curr, alpha);
    rval += edge.orientation * (v - origin) * getTangentH(mesh, origin, next);
  }
  return rval / cover.size();
}

template <std::size_t dimension>
auto getTangentF(const SurfaceMesh &mesh,
                 const tensor<double, dimension, 1> &origin,
                 SurfaceMesh::SimplexID<SurfaceMesh::topLevel> curr,
                 std::set<SurfaceMesh::KeyType> &cover) {
  return (*curr).orientation;
}

template <std::size_t level, std::size_t dimension>
auto getTangentF(const SurfaceMesh &mesh,
                 const tensor<double, dimension, 1> &origin,
                 SurfaceMesh::SimplexID<level> curr,
                 std::set<SurfaceMesh::KeyType> &cover) {
  tensor<double, dimension, SurfaceMesh::topLevel - level> rval;
  for (auto alpha : cover) {
    auto edge = *mesh.get_edge_up(curr, alpha);
    const auto &v = (*mesh.get_simplex_up({alpha})).position;
    auto next = mesh.get_simplex_up(curr, alpha);
    auto coverup = cover;
    coverup.erase(alpha);
    rval += edge.orientation * (v - origin) *
            getTangentF(mesh, origin, next, coverup);
  }
  return rval / cover.size();
}

template <class K> struct initLocalOrientation {};

template <std::size_t k>
struct initLocalOrientation<std::integral_constant<std::size_t, k>> {
  template <typename Iterator>
  static void apply(SurfaceMesh &mesh, const std::set<int> &&names,
                    Iterator begin, Iterator end) {
    std::vector<SurfaceMesh::SimplexID<k + 1>> next;
    for (auto curr = begin; curr != end; ++curr) {
      auto currSimplexID = *curr;
      for (auto a : mesh.get_cover(currSimplexID)) {
        // Look for key a in names
        auto find = names.find(a);
        if (find != names.end()) {
          next.push_back(mesh.get_simplex_up(currSimplexID, a));

          int orient = 1;
          for (auto b : mesh.get_name(currSimplexID)) {
            if (a > b) {
              if (a > b) {
                orient *= -1;
              } else {
                break;
              }
            }
          }
          (*mesh.get_edge_up(currSimplexID, a)).orientation = orient;
        }
      }
    }
    initLocalOrientation<std::integral_constant<std::size_t, k + 1>>::apply(
        mesh, std::move(names), next.begin(), next.end());
  }
};

template <>
struct initLocalOrientation<
    std::integral_constant<std::size_t, SurfaceMesh::topLevel>> {
  template <typename Iterator>
  static void apply(SurfaceMesh &mesh, const std::set<int> &&names,
                    Iterator begin, Iterator end) {}
};

bool computeLocalOrientation(
    SurfaceMesh &mesh, const std::vector<SurfaceMesh::SimplexID<2>> &edgeList);

void weightedVertexSmooth(SurfaceMesh &mesh, SurfaceMesh::SimplexID<1> vertexID,
                          int rings);

Vector weightedVertexSmoothCache(SurfaceMesh &mesh,
                                 SurfaceMesh::SimplexID<1> vertexID,
                                 std::size_t rings);

void barycenterVertexSmooth(SurfaceMesh &mesh,
                            SurfaceMesh::SimplexID<1> vertexID);

void edgeFlip(SurfaceMesh &mesh, SurfaceMesh::SimplexID<2> edgeID);

void edgeFlipCache(SurfaceMesh &mesh, SurfaceMesh::SimplexID<2> edgeID);

template <class Inserter>
void selectFlipEdges(
    const SurfaceMesh &mesh, bool preserveRidges,
    std::function<bool(const SurfaceMesh &, const SurfaceMesh::SimplexID<2> &)>
        &&checkFlip,
    Inserter iter) {
  casc::NodeSet<SurfaceMesh::SimplexID<2>> ignoredEdges;

  for (auto edgeID : mesh.get_level_id<2>()) {
    if ((*edgeID).selected == true) {
      if (!ignoredEdges.count(edgeID)) {
        auto up = mesh.get_cover(edgeID);
        // The mesh is not a surface mesh...
        if (up.size() > 2) {
          // std::cerr << "This edge participates in more than 2
          // faces. "
          //           << "Returning..." << std::endl;
          gamer_runtime_error("SurfaceMesh is not pseudomanifold. Found "
                                   "an edge connected to more than 2 faces.");
        } else if (up.size() < 2) // Edge is a boundary
        {
          // std::cerr << "This edge participates in fewer than 2
          // faces. "
          //           << "Returning..." << std::endl;
          continue;
        }

        // Check if the edge is a part of a tetrahedron.
        if (mesh.exists<2>({up[0], up[1]})) {
          // std::cerr << "Found a tetrahedron cannot edge flip."
          //           << std::endl;
          continue;
        }

        // Check if we're on a ridge. This prevents folding also.
        if (preserveRidges) {
          auto a = getNormal(mesh, mesh.get_simplex_up(edgeID, up[0]));
          auto b = getNormal(mesh, mesh.get_simplex_up(edgeID, up[1]));
          auto val = angle(a, b);
          if (val > 60) {
            continue;
          }
        }

        // Check the flip using user function
        if (checkFlip(mesh, edgeID)) {
          *iter++ = edgeID; // Insert into edges to flip

          // The local topology will be changed by edge flip.
          // Don't flip edges which share a common face.
          std::set<SurfaceMesh::SimplexID<2>> tmpIgnored;
          kneighbors(mesh, edgeID, 3, tmpIgnored);
          ignoredEdges.insert(tmpIgnored.begin(), tmpIgnored.end());

          // Local neighborhood append. Larger neighborhood selected
          // above appears to work better...
          // neighbors(mesh, edgeID, std::inserter(ignoredEdges,
          // ignoredEdges.end()));
        }
      }
    }
  }
}

bool checkEdgeFlip(
    const SurfaceMesh &mesh, bool preserveRidges,
    SurfaceMesh::SimplexID<2> edgeID,
    std::function<bool(const SurfaceMesh &, const SurfaceMesh::SimplexID<2> &)>
        &&checkFlip);
bool checkFlipAngle(const SurfaceMesh &mesh,
                    const SurfaceMesh::SimplexID<2> &edgeID);

int checkFlipValenceExcess(const SurfaceMesh &mesh,
                           const SurfaceMesh::SimplexID<2> &edgeID);

void normalSmoothH(SurfaceMesh &mesh, const SurfaceMesh::SimplexID<1> vertexID,
                   const double k);

void findHoles(const SurfaceMesh &mesh,
               std::vector<std::vector<SurfaceMesh::SimplexID<2>>> &holeList);

bool orderBoundaryEdgeRing(
    const SurfaceMesh &mesh,
    std::set<SurfaceMesh::SimplexID<2>> &unvisitedBdryEdges,
    std::vector<SurfaceMesh::SimplexID<1>> &visitedVerts,
    std::vector<SurfaceMesh::SimplexID<2>> &bdryRing);

void edgeRingToVertices(
    const SurfaceMesh &mesh, std::vector<SurfaceMesh::SimplexID<2>> &edgeRing,
    std::back_insert_iterator<std::vector<SurfaceMesh::SimplexID<1>>> iter);

void triangulateHoleHelper(
    SurfaceMesh &mesh, std::vector<SurfaceMesh::SimplexID<1>> &boundary,
    const SMFace &fdata,
    std::back_insert_iterator<std::vector<SurfaceMesh::SimplexID<2>>> iter);

void triangulateHole(SurfaceMesh &mesh,
                     std::vector<SurfaceMesh::SimplexID<1>> &sortedVerts,
                     const SMFace &fdata,
                     std::vector<SurfaceMesh::SimplexID<2>> &edgeList);

template <typename Complex> struct CopyHelper {
  using SimplexSet = typename casc::SimplexSet<Complex>;
  using KeyType = typename Complex::KeyType;

  template <std::size_t k>
  static void apply(Complex &before, Complex &after, const SimplexSet &S) {
    for (auto sID : casc::get<k>(S)) {
      auto name = before.get_name(sID);
      auto data = *sID;
      after.insert(name, data);
    }
  }
};

template <typename Iterator>
void vertexGrabber(const SurfaceMesh &F, int need,
                   std::vector<SurfaceMesh::SimplexID<1>> &nbors,
                   Iterator begin, Iterator end) {
  if (need <= 0)
    return;
  std::set<SurfaceMesh::SimplexID<1>> next;
  for (; begin != end; ++begin) {
    for (auto a : F.get_cover(*begin)) {
      auto id = F.get_simplex_up(*begin, a);
      for (auto b : F.get_name(id)) {
        if (b != a) {
          auto nbor = F.get_simplex_down(id, b);
          if (std::find(nbors.begin(), nbors.end(), nbor) == nbors.end()) {
            // Haven't visited so push into next ring and
            nbors.push_back(nbor);
            next.insert(nbor);
            if (--need == 0)
              return;
          }
        }
      }
    }
  }
  vertexGrabber(F, need, nbors, next.begin(), next.end());
}

template <class Complex>
void vertexGrabber(const Complex &F, int need,
                   std::vector<typename Complex::template SimplexID<1>> &nbors,
                   typename Complex::template SimplexID<1> vid) {
  if (need <= 0)
    return;
  std::set<SurfaceMesh::SimplexID<1>> next;
  for (auto a : F.get_cover(vid)) {
    auto id = F.get_simplex_up(vid, a);
    for (auto b : F.get_name(id)) {
      if (b != a) {
        auto nbor = F.get_simplex_down(id, b);
        if (std::find(nbors.begin(), nbors.end(), nbor) == nbors.end()) {
          // Haven't visited so push into next ring and
          nbors.push_back(nbor);
          next.insert(nbor);
          if (--need == 0)
            return;
        }
      }
    }
  }
  vertexGrabber(F, need, nbors, next.begin(), next.end());
}
} // end namespace surfacemesh_detail

std::unique_ptr<SurfaceMesh> readOFF(const std::string &filename);

void writeOFF(const std::string &filename, const SurfaceMesh &mesh);

std::unique_ptr<SurfaceMesh> readOBJ(const std::string &filename);

void writeOBJ(const std::string &filename, const SurfaceMesh &mesh);

void writeComsol(const std::string &filename, const SurfaceMesh &mesh);

void writeComsol(const std::string &filename,
                 const std::vector<SurfaceMesh const *> &meshes);

void print(const SurfaceMesh &mesh);

void printQualityInfo(const std::string &filename, const SurfaceMesh &mesh);

void generateHistogram(const SurfaceMesh &mesh);

std::tuple<double, double, int, int> getMinMaxAngles(const SurfaceMesh &mesh,
                                                     double maxMinAngle,
                                                     double minMaxAngle);

double getArea(const SurfaceMesh &mesh);

double getArea(const SurfaceMesh &mesh, SurfaceMesh::SimplexID<3> faceID);

double getArea(Vertex a, Vertex b, Vertex c);

double getArea(std::array<Vertex, 3> t);

double getVolume(const SurfaceMesh &mesh);

bool hasHole(const SurfaceMesh &mesh);

inline std::size_t getValence(const SurfaceMesh &mesh,
                              const SurfaceMesh::SimplexID<1> vertexID) {
  return mesh.get_cover(vertexID).size();
}

std::tuple<
    REAL *, REAL *, REAL *, REAL *,
    std::map<typename SurfaceMesh::KeyType, typename SurfaceMesh::KeyType>>
curvatureViaMDSB(const SurfaceMesh &mesh);

std::tuple<
    REAL *, REAL *, REAL *, REAL *,
    std::map<typename SurfaceMesh::KeyType, typename SurfaceMesh::KeyType>>
curvatureViaJets(const SurfaceMesh &mesh, std::size_t dJet = 2,
                 std::size_t dPrime = 2);

// void osculatingJets(const SurfaceMesh&mesh, std::size_t dJet = 2, std::size_t
// dPrime = 2);

//
// @param      mesh  The mesh
// @param[in]  v     Displacement vector
//
void translate(SurfaceMesh &mesh, Vector v);
void translate(SurfaceMesh &mesh, double dx, double dy, double dz);
void scale(SurfaceMesh &mesh, Vector v);
void scale(SurfaceMesh &mesh, double sx, double sy, double sz);
void scale(SurfaceMesh &mesh, double s);

std::pair<Vector, double> getCenterRadius(SurfaceMesh &mesh);
void centeralize(SurfaceMesh &mesh);

void smoothMesh(SurfaceMesh &mesh, int maxIter, bool preserveRidges,
                std::size_t rings = 2, bool verbose = false);

void coarse(SurfaceMesh &mesh, double coarseRate, double flatRate,
            double denseWeight, std::size_t rings = 2, bool verbose = false);

void coarse_dense(SurfaceMesh &mesh, REAL threshold, REAL weight,
                  std::size_t rings = 2, bool verbose = false);

void coarse_flat(SurfaceMesh &mesh, REAL threshold, REAL weight,
                 std::size_t rings = 2, bool verbose = false);

void normalSmooth(SurfaceMesh &mesh, double k = 1.0);

void fillHoles(SurfaceMesh &mesh);

void flipNormals(SurfaceMesh &mesh);

std::unique_ptr<SurfaceMesh> refineMesh(const SurfaceMesh &mesh);

std::unique_ptr<SurfaceMesh> sphere(int order);

std::unique_ptr<SurfaceMesh> cube(int order);

std::vector<std::unique_ptr<SurfaceMesh>> splitSurfaces(SurfaceMesh &mesh);

void cacheNormals(SurfaceMesh &mesh);

std::tuple<bool, int, int, int> getBettiNumbers(SurfaceMesh &mesh);
} // end namespace gamer