diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..1377554ebea6f98a2c748183bc5a96852af12ac2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.swp
diff --git a/Examples/CreateEnclosingMesh/CreateEnclosingMesh.cpp b/Examples/CreateEnclosingMesh/CreateEnclosingMesh.cpp
index 9115c3ae57233230129c838c38bf4f9cecd4a4de..251f6b9169450c7deebd4df8458ad56669bd64b9 100644
--- a/Examples/CreateEnclosingMesh/CreateEnclosingMesh.cpp
+++ b/Examples/CreateEnclosingMesh/CreateEnclosingMesh.cpp
@@ -25,6 +25,7 @@
 #include "imstkTetrahedralMesh.h"
 #include "imstkMeshIO.h"
 #include "imstkVTKMeshIO.h"
+#include "imstkGeometryUtilities.h"
 
 using namespace imstk;
 
@@ -56,7 +57,7 @@ main()
     // add the scene object to the scene
     scene->addSceneObject(surfaceObject);
 
-    auto tetMesh = TetrahedralMesh::createTetrahedralMeshCover(surfMesh, nx, ny, nz);
+    auto tetMesh = GeometryUtils::createTetrahedralMeshCover(*surfMesh, nx, ny, nz);
 
     // add scene object for surface object
     auto volObject = std::make_shared<VisualObject>("VolObj");
@@ -74,4 +75,4 @@ main()
     simManager->setActiveScene(scene);
     simManager->getViewer()->setBackgroundColors(Vec3d(0.3285, 0.3285, 0.6525), Vec3d(0.13836, 0.13836, 0.2748), true);
     simManager->start();
-}
\ No newline at end of file
+}
diff --git a/Examples/RCM/CMakeLists.txt b/Examples/RCM/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..eac125b3b7c5653d399ad604dc950250c36ff47e
--- /dev/null
+++ b/Examples/RCM/CMakeLists.txt
@@ -0,0 +1,43 @@
+###########################################################################
+#
+# Copyright (c) Kitware, Inc.
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0.txt
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+###########################################################################
+
+project(Example-RCMDragon)
+
+#-----------------------------------------------------------------------------
+# Create executable
+#-----------------------------------------------------------------------------
+imstk_add_executable(${PROJECT_NAME} RCM.cpp)
+
+#-----------------------------------------------------------------------------
+# Add the target to Examples folder
+#-----------------------------------------------------------------------------
+SET_TARGET_PROPERTIES (${PROJECT_NAME} PROPERTIES FOLDER Examples)
+
+#-----------------------------------------------------------------------------
+# Link libraries to executable
+#-----------------------------------------------------------------------------
+target_link_libraries(${PROJECT_NAME} apiUtilities)
+
+#-----------------------------------------------------------------------------
+# Set MSVC working directory to the install/bin directory
+#-----------------------------------------------------------------------------
+if(MSVC) # Configure running executable out of MSVC
+  set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${iMSTK_INSTALL_BIN_DIR}")
+endif()
+
+
diff --git a/Examples/RCM/RCM.cpp b/Examples/RCM/RCM.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1079abacae6e6db2378de054bbfc5b60273f8fa8
--- /dev/null
+++ b/Examples/RCM/RCM.cpp
@@ -0,0 +1,141 @@
+/*=========================================================================
+
+   Library: iMSTK
+
+   Copyright (c) Kitware, Inc. & Center for Modeling, Simulation,
+   & Imaging in Medicine, Rensselaer Polytechnic Institute.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+=========================================================================*/
+
+#include "imstkMeshIO.h"
+#include "imstkTetrahedralMesh.h"
+#include "imstkGeometryUtilities.h"
+#include "imstkLogUtility.h"
+#include "bandwidth.h"
+#include <chrono>
+#include <thread>
+
+using namespace imstk;
+
+using QuadConn = std::array<size_t, 4>;
+
+///
+/// \brief create a quad mesh
+/// \retval pair of connectivity and num of vertices
+///
+std::pair<std::vector<QuadConn>, size_t> createConn();
+
+template<typename ElemConn>
+void testRCM(const std::vector<ElemConn>& conn, const size_t numVerts);
+
+int
+main(int argc, char** argv)
+{
+    auto logUtil = std::make_shared<LogUtility>();
+    logUtil->createLogger("simulation", "./");
+
+    // a 2D Cartesian mesh
+    {
+        auto p = createConn();
+        testRCM(p.first, p.second);
+    }
+
+    // 3D mesh
+    {
+        auto       tetMesh  = std::dynamic_pointer_cast<TetrahedralMesh>(MeshIO::read(iMSTK_DATA_ROOT "/asianDragon/asianDragon.veg"));
+        const auto numVerts = tetMesh->getNumVertices();
+        std::cout << "Number of vertices = " << numVerts << std::endl;
+        testRCM(tetMesh->getTetrahedraVertices(), numVerts);
+    }
+
+    // a surface mesh cover
+    {
+        auto surfMesh = std::dynamic_pointer_cast<SurfaceMesh>(MeshIO::read(iMSTK_DATA_ROOT "/asianDragon/asianDragon.obj"));
+        auto tetMesh  = GeometryUtils::createTetrahedralMeshCover(*surfMesh, 80, 40, 60);
+        auto conn     = tetMesh->getTetrahedraVertices();
+        auto numVerts = tetMesh->getNumVertices();
+        std::cout << "Number of vertices = " << numVerts << std::endl;
+        testRCM(conn, numVerts);
+    }
+    using namespace std::chrono_literals;
+    std::this_thread::sleep_for(5s);
+    return 0;
+}
+
+template<typename ElemConn>
+void
+testRCM(const std::vector<ElemConn>& conn, const size_t numVerts)
+{
+    std::cout << "Old bandwidth = " << bandwidth(conn, numVerts) << std::endl;
+
+    // new-to-old permutation
+    auto perm = GeometryUtils::reorderConnectivity(conn, numVerts);
+
+    // old-to-new permutation
+    std::vector<size_t> invPerm(perm.size());
+    for (size_t i = 0; i < perm.size(); ++i)
+    {
+        CHECK(perm[i] < numVerts) << "new vertex index should not be greater than number of vertices";
+        invPerm[perm[i]] = i;
+    }
+
+    auto newConn = conn;
+
+    for (auto& vertices : newConn)
+    {
+        for (auto& vid : vertices)
+        {
+            CHECK(vid < numVerts) << "Vertex id invalid since its greater than the number of vertices";
+            vid = invPerm[vid];
+        }
+    }
+
+    std::cout << "New bandwidth = " << bandwidth(newConn, numVerts) << "\n" << std::endl;
+
+    return;
+}
+
+std::pair<std::vector<QuadConn>, size_t>
+createConn()
+{
+    /**
+    6-------9-------7-------8
+    |       |       |       |
+    |   6   |   7   |   8   |
+    |       |       |       |
+    4------11-------5-------10
+    |       |       |       |
+    |   3   |   4   |   5   |
+    |       |       |       |
+    2------13-------3-------12
+    |       |       |       |
+    |   0   |   1   |   2   |
+    |       |       |       |
+    0------15-------1-------14
+    **/
+
+    std::vector<QuadConn> conn(9);
+    conn[0] = { 0, 15, 13, 2 };
+    conn[1] = { 15, 1, 3, 13 };
+    conn[2] = { 1, 14, 12, 3 };
+    conn[3] = { 2, 13, 11, 4 };
+    conn[4] = { 13, 3, 5, 11 };
+    conn[5] = { 3, 12, 10, 5 };
+    conn[6] = { 4, 11, 9, 6 };
+    conn[7] = { 11, 5, 7, 9 };
+    conn[8] = { 5, 10, 8, 7 };
+
+    return std::make_pair(conn, 16);
+}
diff --git a/Examples/RCM/bandwidth.h b/Examples/RCM/bandwidth.h
new file mode 100644
index 0000000000000000000000000000000000000000..3ddf6f1ed76733c427550bf7c88be31f745e5d99
--- /dev/null
+++ b/Examples/RCM/bandwidth.h
@@ -0,0 +1,107 @@
+///
+/// \brief Build the vertex-to-vertex connectivity of a map
+//
+/// \param conn element-to-vertex connectivity of the map
+/// \param numVerts number of vertice in the map
+/// \retval vertToVert vertex-to-vertex connectivity
+///
+template<typename ElemConn>
+static void
+buildVertToVert(const std::vector<ElemConn>&             conn,
+                const size_t                             numVerts,
+                std::vector<std::unordered_set<size_t>>& vertToVert)
+{
+    // constexpr size_t numVertPerElem = ElemConn::size();
+    std::vector<size_t> vertToElemPtr(numVerts + 1, 0);
+    std::vector<size_t> vertToElem;
+
+    // find the number of adjacent elements for each vertex
+    for (const auto& vertices : conn)
+    {
+        for (auto vid : vertices)
+        {
+            vertToElemPtr[vid + 1] += 1;
+        }
+    }
+
+    // accumulate pointer
+    for (size_t i = 0; i < numVerts; ++i)
+    {
+        vertToElemPtr[i + 1] += vertToElemPtr[i];
+    }
+
+    // track the number
+    auto   pos    = vertToElemPtr;
+    size_t totNum = vertToElemPtr.back();
+
+    vertToElem.resize(totNum);
+
+    for (size_t eid = 0; eid < conn.size(); ++eid)
+    {
+        for (auto vid : conn[eid])
+        {
+            vertToElem[pos[vid]] = eid;
+            ++pos[vid];
+        }
+    }
+
+    // connectivity of vertex-to-vertex
+    vertToVert.resize(numVerts);
+    auto getVertexNbrs = [&vertToElem, &vertToElemPtr, &conn, &vertToVert](const size_t i) {
+                             const auto ptr0 = vertToElemPtr[i];
+                             const auto ptr1 = vertToElemPtr[i + 1];
+                             size_t     eid;
+
+                             for (auto ptr = ptr0; ptr < ptr1; ++ptr)
+                             {
+                                 eid = vertToElem[ptr];
+                                 for (auto vid : conn[eid])
+                                 {
+                                     // vertex-i itself is also included.
+                                     vertToVert[i].insert(vid);
+                                 }
+                             }
+                         };
+
+    for (size_t i = 0; i < numVerts; ++i)
+    {
+        getVertexNbrs(i);
+    }
+}
+
+///
+/// \brief Returns the bandwidth of a map
+///
+/// \param neighbors array of neighbors of each vertex; eg, neighbors[i] is a object containing
+///
+template<typename NBR>
+size_t
+bandwidth(const std::vector<NBR>& neighbors)
+{
+    size_t d    = 0;
+    size_t dCur = 0;
+    for (size_t i = 0; i < neighbors.size(); ++i)
+    {
+        for (const auto& j : neighbors[i])
+        {
+            dCur = (i > j) ? (i - j) : (j - i);
+            d    = std::max(d, dCur);
+        }
+    }
+    return d;
+}
+
+///
+/// \brief Returns the bandwidth of a map
+///
+/// \param conn element-to-vertex connectivity of the map
+/// \param numVerts number of vertices in the map
+///
+template<typename ElemConn>
+size_t
+bandwidth(const std::vector<ElemConn>& conn, const size_t numVerts)
+{
+    std::vector<std::unordered_set<size_t>> vertToVert;
+    buildVertToVert(conn, numVerts, vertToVert);
+    return bandwidth(vertToVert);
+}
diff --git a/Source/Geometry/Analytic/imstkSphere.cpp b/Source/Geometry/Analytic/imstkSphere.cpp
index 65af6c65b4aa4811870f89d2197f8ec6f6c2448a..2005977042b6a0f290e24e219b37d2c5ac66dd85 100644
--- a/Source/Geometry/Analytic/imstkSphere.cpp
+++ b/Source/Geometry/Analytic/imstkSphere.cpp
@@ -67,7 +67,7 @@ Sphere::setRadius(const double r)
 }
 
 void
-Sphere::computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent)
+Sphere::computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent) const
 {
     updatePostTransformData();
     const Vec3d span = Vec3d(1, 1, 1) * m_radiusPostTransform;
diff --git a/Source/Geometry/Analytic/imstkSphere.h b/Source/Geometry/Analytic/imstkSphere.h
index aab7b3da6be7ebb8a859e807d827a1fdd4ff6ea8..9b5b848d8f21f9d3c8f055672d763d2a615cc922 100644
--- a/Source/Geometry/Analytic/imstkSphere.h
+++ b/Source/Geometry/Analytic/imstkSphere.h
@@ -62,7 +62,7 @@ public:
     ///
     /// \brief Compute the bounding box for the geometry
     ///
-    virtual void computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent = 0.0) override;
+    virtual void computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent = 0.0) const override;
 
 protected:
     friend class VTKSphereRenderDelegate;
diff --git a/Source/Geometry/Map/imstkTetraTriangleMap.cpp b/Source/Geometry/Map/imstkTetraTriangleMap.cpp
index 70d05df9d33783e50d37821a0adc90520fa3877a..84122c1f223700fe56f9bd728c2b8882ad94b506 100644
--- a/Source/Geometry/Map/imstkTetraTriangleMap.cpp
+++ b/Source/Geometry/Map/imstkTetraTriangleMap.cpp
@@ -246,10 +246,9 @@ TetraTriangleMap::updateBoundingBox()
     m_bBoxMin.resize(tetMesh->getNumTetrahedra());
     m_bBoxMax.resize(tetMesh->getNumTetrahedra());
 
-    for (size_t idx = 0; idx < tetMesh->getNumTetrahedra(); ++idx)
-    {
-        tetMesh->computeTetrahedronBoundingBox(idx, m_bBoxMin[idx], m_bBoxMax[idx]);
-    }
+    ParallelUtils::parallelFor(tetMesh->getNumTetrahedra(), [&](const size_t tid) {
+            tetMesh->computeTetrahedronBoundingBox(tid, m_bBoxMin[tid], m_bBoxMax[tid]);
+                               });
 
     m_boundingBoxAvailable = true;
 }
diff --git a/Source/Geometry/Mesh/imstkPointSet.cpp b/Source/Geometry/Mesh/imstkPointSet.cpp
index b33d9af57cbcb7a54328e4f193d0293aeef13e44..c97e326d5038e6f7f4c73d6bc847e5e385d5f305 100644
--- a/Source/Geometry/Mesh/imstkPointSet.cpp
+++ b/Source/Geometry/Mesh/imstkPointSet.cpp
@@ -53,7 +53,7 @@ PointSet::print() const
 }
 
 void
-PointSet::computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent)
+PointSet::computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent) const
 {
     updatePostTransformData();
     ParallelUtils::findAABB(m_vertexPositions, lowerCorner, upperCorner);
diff --git a/Source/Geometry/Mesh/imstkPointSet.h b/Source/Geometry/Mesh/imstkPointSet.h
index f47b909e989811956a17158c2334f4fe0beb1376..0521489d0ca5f1659716a0fd9f7304794723e2b6 100644
--- a/Source/Geometry/Mesh/imstkPointSet.h
+++ b/Source/Geometry/Mesh/imstkPointSet.h
@@ -64,7 +64,7 @@ public:
     ///
     /// \brief Compute the bounding box for the entire mesh
     ///
-    virtual void computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent = 0.0) override;
+    virtual void computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent = 0.0) const override;
 
     // Accessors
 
diff --git a/Source/Geometry/Mesh/imstkSurfaceMesh.cpp b/Source/Geometry/Mesh/imstkSurfaceMesh.cpp
index 4cf59846585bd02e82179b9ee3e23b75f571bd84..0760cdf30773cb0376de4b7557b3ffcfcb09e1b7 100644
--- a/Source/Geometry/Mesh/imstkSurfaceMesh.cpp
+++ b/Source/Geometry/Mesh/imstkSurfaceMesh.cpp
@@ -609,220 +609,4 @@ SurfaceMesh::getMaxNumTriangles()
 {
     return m_maxNumTriangles;
 }
-
-std::vector<bool>
-SurfaceMesh::markPointsInsideAndOut(const StdVectorOfVec3d& coords)
-{
-    std::vector<bool> isInside;
-    isInside.resize(coords.size(), false);
-
-    Vec3d aabbMin, aabbMax;
-    this->computeBoundingBox(aabbMin, aabbMax, 1.);
-
-    auto genRandomDirection = [](Vec3d& dir)
-                              {
-                                  for (int i = 0; i < 3; ++i)
-                                  {
-                                      dir[i] = rand();
-                                  }
-                                  double mag = dir.norm();
-
-                                  for (int i = 0; i < 3; ++i)
-                                  {
-                                      dir[i] /= mag;
-                                  }
-                                  return;
-                              };
-
-    auto intersectTriangle = [](const Vec3d& xyz, const Vec3d& xyz0, const Vec3d& xyz1, const Vec3d& xyz2, const Vec3d& dir)
-                             {
-                                 // const double eps = 1e-15;
-                                 constexpr const double eps   = std::numeric_limits<double>::epsilon();
-                                 Vec3d                  edge0 = xyz1 - xyz0;
-                                 Vec3d                  edge1 = xyz2 - xyz0;
-                                 Vec3d                  pvec  = dir.cross(edge1);
-                                 double                 det   = edge0.dot(pvec);
-
-                                 if (det > -eps && det < eps)
-                                 {
-                                     return false;
-                                 }
-                                 double inv_det = 1.0 / det;
-                                 Vec3d  tvec    = xyz - xyz0;
-                                 double u       = tvec.dot(pvec) * inv_det;
-                                 if (u < 0.0 || u > 1.0)
-                                 {
-                                     return false;
-                                 }
-                                 Vec3d  qvec = tvec.cross(edge0);
-                                 double v    = dir.dot(qvec) * inv_det;
-                                 if (v < 0.0 || u + v > 1.0)
-                                 {
-                                     return false;
-                                 }
-
-                                 double t = edge1.dot(qvec) * inv_det;
-                                 if (t > 0.0)
-                                 {
-                                     return true;
-                                 }
-                                 else
-                                 {
-                                     return false;
-                                 }
-                             };
-
-#if 1
-    std::vector<Vec3d> bBoxMin;
-    std::vector<Vec3d> bBoxMax;
-
-    bBoxMin.resize(this->getNumTriangles());
-    bBoxMax.resize(this->getNumTriangles());
-
-    for (size_t idx = 0; idx < this->getNumTriangles(); ++idx)
-    {
-        const auto& verts = m_trianglesVertices.at(idx);
-        const auto& xyz0  = m_vertexPositions[verts[0]];
-        const auto& xyz1  = m_vertexPositions[verts[1]];
-        const auto& xyz2  = m_vertexPositions[verts[2]];
-
-        bBoxMin[idx][0] = xyz0[0];
-        bBoxMin[idx][1] = xyz0[1];
-        bBoxMin[idx][2] = xyz0[2];
-        bBoxMax[idx][0] = xyz0[0];
-        bBoxMax[idx][1] = xyz0[1];
-        bBoxMax[idx][2] = xyz0[2];
-
-        bBoxMin[idx][0] = std::min(bBoxMin[idx][0], xyz1[0]);
-        bBoxMin[idx][1] = std::min(bBoxMin[idx][1], xyz1[1]);
-        bBoxMin[idx][2] = std::min(bBoxMin[idx][2], xyz1[2]);
-        bBoxMin[idx][0] = std::min(bBoxMin[idx][0], xyz2[0]);
-        bBoxMin[idx][1] = std::min(bBoxMin[idx][1], xyz2[1]);
-        bBoxMin[idx][2] = std::min(bBoxMin[idx][2], xyz2[2]);
-
-        bBoxMax[idx][0] = std::max(bBoxMax[idx][0], xyz1[0]);
-        bBoxMax[idx][1] = std::max(bBoxMax[idx][1], xyz1[1]);
-        bBoxMax[idx][2] = std::max(bBoxMax[idx][2], xyz1[2]);
-        bBoxMax[idx][0] = std::max(bBoxMax[idx][0], xyz2[0]);
-        bBoxMax[idx][1] = std::max(bBoxMax[idx][1], xyz2[1]);
-        bBoxMax[idx][2] = std::max(bBoxMax[idx][2], xyz2[2]);
-    }
-
-    auto rayTracingFunc = [&coords, &aabbMin, &aabbMax, &bBoxMin, &bBoxMax, &isInside, &intersectTriangle, &genRandomDirection, this](const size_t i) {
-                              bool outBox = coords[i][0] < aabbMin[0] || coords[i][0] > aabbMax[0]
-                                            || coords[i][1] < aabbMin[1] || coords[i][1] > aabbMax[1]
-                                            || coords[i][2] < aabbMin[2] || coords[i][2] > aabbMax[2];
-                              if (outBox)
-                              {
-                                  return;
-                              }
-
-                              // TODO: generate a random direction?
-                              const Vec3d direction = { 0.0, 0.0, 1.0 };
-                              // Vec3d direction;
-                              // genRandomDirection(direction);
-                              // std::cout << direction << std::endl;
-                              int         numIntersections = 0;
-                              const auto& xyz = m_vertexPositions;
-
-                              for (size_t j = 0; j < this->getNumTriangles(); ++j)
-                              {
-                                  const auto& verts = m_trianglesVertices[j];
-
-                                  // consider directed ray
-                                  if (coords[i][2] > bBoxMax[j][2])
-                                  {
-                                      continue;
-                                  }
-
-                                  if (coords[i][0] > bBoxMax[j][0])
-                                  {
-                                      continue;
-                                  }
-                                  if (coords[i][0] < bBoxMin[j][0])
-                                  {
-                                      continue;
-                                  }
-                                  if (coords[i][1] > bBoxMax[j][1])
-                                  {
-                                      continue;
-                                  }
-                                  if (coords[i][1] < bBoxMin[j][1])
-                                  {
-                                      continue;
-                                  }
-
-                                  auto intersected = intersectTriangle(coords[i],
-                                                 xyz[verts[0]],
-                                                 xyz[verts[1]],
-                                                 xyz[verts[2]],
-                                                 direction);
-                                  if (intersected)
-                                  {
-                                      ++numIntersections;
-                                  }
-                              }
-
-                              if (numIntersections % 2 == 1)
-                              {
-                                  isInside[i] = true;
-                              }
-
-                              return;
-                          };
-
-#else
-    auto rayTracingFunc = [&coords, &aabbMin, &aabbMax, &isInside, &intersectTriangle, &genRandomDirection, this](const size_t i) {
-                              bool outBox = coords[i][0] < aabbMin[0] || coords[i][0] > aabbMax[0]
-                                            || coords[i][1] < aabbMin[1] || coords[i][1] > aabbMax[1]
-                                            || coords[i][2] < aabbMin[2] || coords[i][2] > aabbMax[2];
-                              if (outBox)
-                              {
-                                  return;
-                              }
-
-                              // TODO: generate a random direction?
-                              // const Vec3d direction = {1.0, 0.0, 0.0};
-                              Vec3d direction;
-                              genRandomDirection(direction);
-                              // std::cout << direction << std::endl;
-                              int numIntersections = 0;
-
-                              for (size_t j = 0; j < this->getNumTriangles(); ++j)
-                              {
-                                  const auto& verts       = m_trianglesVertices[j];
-                                  auto        intersected = intersectTriangle(coords[i],
-                                  m_vertexPositions[verts[0]],
-                                  m_vertexPositions[verts[1]],
-                                  m_vertexPositions[verts[2]],
-                                  direction);
-                                  if (intersected)
-                                  {
-                                      ++numIntersections;
-                                  }
-                              }
-
-                              if (numIntersections % 2 == 1)
-                              {
-                                  isInside[i] = true;
-                              }
-
-                              return;
-                          };
-
-#endif
-    // for (size_t i = 0; i < coords.size(); ++i)
-    // {
-    //     rayTracingFunc(i);
-    // }
-
-    // CpuTimer timer;
-    StopWatch timer;
-    timer.start();
-    ParallelUtils::parallelFor(coords.size(), rayTracingFunc);
-    timer.stop();
-    timer.printTimeElapsed();
-
-    return isInside;
-}
 }  // namespace imstk
diff --git a/Source/Geometry/Mesh/imstkSurfaceMesh.h b/Source/Geometry/Mesh/imstkSurfaceMesh.h
index fc091f322c8224f9117fe76c621f5ecc34380e30..6a53c5c7a9bc3d664d6db972a1cda35e9e2ff385 100644
--- a/Source/Geometry/Mesh/imstkSurfaceMesh.h
+++ b/Source/Geometry/Mesh/imstkSurfaceMesh.h
@@ -212,12 +212,6 @@ public:
     ///
     size_t getMaxNumTriangles();
 
-    ///
-    /// \brief Given a set of points mark them as inside (true) and outside
-    /// <Explain how this works; limitations etc.>
-    ///
-    /// \note this function cannot be const because PointSet::computeBoundingBox, called inside, is not.
-    std::vector<bool> markPointsInsideAndOut(const StdVectorOfVec3d& coords);
 protected:
 
     friend class VTKSurfaceMeshRenderDelegate;
diff --git a/Source/Geometry/Mesh/imstkTetrahedralMesh.cpp b/Source/Geometry/Mesh/imstkTetrahedralMesh.cpp
index 815099c9abe03c0c7f8942535365fb4ee65c8317..2de16dbebeb1603a4629316dca330ba9322cb28e 100644
--- a/Source/Geometry/Mesh/imstkTetrahedralMesh.cpp
+++ b/Source/Geometry/Mesh/imstkTetrahedralMesh.cpp
@@ -295,278 +295,4 @@ TetrahedralMesh::getNumTetrahedra() const
 {
     return m_tetrahedraVertices.size();
 }
-
-// std::unique_ptr<TetrahedralMesh>
-std::shared_ptr<TetrahedralMesh>
-TetrahedralMesh::createUniformMesh(const Vec3d& aabbMin, const Vec3d& aabbMax, const size_t nx,
-                                   const size_t ny, const size_t nz)
-{
-    Vec3d h = { (aabbMax[0] - aabbMin[0]) / nx,
-                (aabbMax[1] - aabbMin[1]) / ny,
-                (aabbMax[2] - aabbMin[2]) / nz };
-    LOG_IF(FATAL, (h[0] <= 0.0 || h[1] <= 0.0 || h[2] <= 0.0)) << "Invalid bounding box";
-
-    size_t numVertices = (nx + 1) * (ny + 1) * (nz + 1);
-
-    // std::vector<Vec3d> coords;
-    StdVectorOfVec3d coords;
-    coords.resize(numVertices);
-    size_t cnt = 0;
-
-    for (size_t k = 0; k < nz + 1; ++k)
-    {
-        double z = aabbMin[2] + k * h[2];
-        for (size_t j = 0; j < ny + 1; ++j)
-        {
-            double y = aabbMin[1] + j * h[1];
-            for (size_t i = 0; i < nx + 1; ++i)
-            {
-                double x = aabbMin[0] + i * h[0];
-                coords[cnt] = { x, y, z };
-                ++cnt;
-            }
-        }
-    }
-
-    const size_t numDiv  = 6;
-    size_t       numTets = numDiv * nx * ny * nz;
-
-    std::vector<TetrahedralMesh::TetraArray> vertices;
-    vertices.resize(numTets);
-    cnt = 0;
-    std::vector<size_t> indx(8);
-
-    for (size_t k = 0; k < nz; ++k)
-    {
-        for (size_t j = 0; j < ny; ++j)
-        {
-            for (size_t i = 0; i < nx; ++i)
-            {
-                indx[3] = i + j * (nx + 1) + k * (nx + 1) * (ny + 1);
-                indx[2] = indx[3] + 1;
-                indx[0] = indx[3] + nx + 1;
-                indx[1] = indx[0] + 1;
-                indx[4] = indx[0] + (nx + 1) * (ny + 1);
-                indx[5] = indx[1] + (nx + 1) * (ny + 1);
-                indx[6] = indx[2] + (nx + 1) * (ny + 1);
-                indx[7] = indx[3] + (nx + 1) * (ny + 1);
-                vertices[cnt + 0] = { indx[0], indx[2], indx[3], indx[6] };
-                vertices[cnt + 1] = { indx[0], indx[3], indx[7], indx[6] };
-                vertices[cnt + 2] = { indx[0], indx[7], indx[4], indx[6] };
-                vertices[cnt + 3] = { indx[0], indx[5], indx[6], indx[4] };
-                vertices[cnt + 4] = { indx[1], indx[5], indx[6], indx[0] };
-                vertices[cnt + 5] = { indx[1], indx[6], indx[2], indx[0] };
-                cnt += numDiv;
-            }
-        }
-    }
-
-    auto mesh = std::make_shared<TetrahedralMesh>();
-    // auto mesh = std::unique_ptr<TetrahedralMesh>(new TetrahedralMesh());
-
-    mesh->initialize(coords, vertices);
-    return mesh;
-}
-
-#if 0
-std::shared_ptr<TetrahedralMesh>
-TetrahedralMesh::createEnclosingMesh(const SurfaceMesh& surfMesh, const size_t nx, const size_t ny,
-                                     const size_t nz)
-{
-    // Given the index of a tet, return the corresponding index of the hex
-    auto tetIdToHexId = [](const size_t tetId) { return tetId / 5; }
-
-                        Vec3d aabbMin, aabbMax;
-    const double              paddingPerc = 10.0;
-    PointSet.computeBoundingBox(aabbMin, aabbMax, paddingPerc);
-    const Vec3d h = { (aabbMax[0] - aabbMin[0]) / nx,
-                      (aabbMax[1] - aabbMin[1]) / ny,
-                      (aabbMax[2] - aabbMin[2]) / nz };
-
-    auto findHexId = [&aabbMin, &aabbMax, &h](const Vec3d& xyz) {
-                         size_t idX = (xyz[0] - aabbMin[0]) / h[0];
-                         size_t idY = (xyz[1] - aabbMin[1]) / h[1];
-                         size_t idZ = (xyz[2] - aabbMin[2]) / h[2];
-                         return { idX, idY, idZ };
-                     };
-
-    auto uniformMesh = createUniformMesh(aabbMin, aabbMax, nx, ny, nz);
-
-    std::vector<bool> enclosePoint(uniformMesh.getNumHexahedra(), false);
-
-    // ParallelUtils::parallelFor(surfMesh.getNumVertices(), [&](const size_t vid) {
-    //     auto xyz = surfMesh.getVertexPosition(vid);
-    //     size_t idX = (xyz[0] - aabbMin[0]) / h[0];
-    //     size_t idY = (xyz[1] - aabbMin[1]) / h[1];
-    //     size_t idZ = (xyz[2] - aabbMin[2]) / h[2];
-    //     size_t hexId = idX + idY *nx + idZ * nx*ny;
-    //     size_t tetId0 = 5*hexId;
-    //     size_t tetId1 = tetId0 + 5;
-    //
-    //
-    //     static ParallelUtils::SpinLock lock;
-    //     lock.lock();
-    //     for (size_t id = tetId0; id<tetId1; ++id)
-    //     {
-    //         enclosePoint[id] = true;
-    //     }
-    //     lock.unlock();
-    // });
-
-    for (size_t vid = 0; vid < surfMesh.getNumVertices(); ++vid)
-    {
-        auto   xyz    = surfMesh.getVertexPosition(vid);
-        size_t idX    = (xyz[0] - aabbMin[0]) / h[0];
-        size_t idY    = (xyz[1] - aabbMin[1]) / h[1];
-        size_t idZ    = (xyz[2] - aabbMin[2]) / h[2];
-        size_t hexId  = idX + idY * nx + idZ * nx * ny;
-        size_t tetId0 = 5 * hexId;
-        size_t tetId1 = tetId0 + 5;
-
-        for (size_t i = tetId0; i < tetId1; ++i)
-        {
-            enclosePoint[i] = true;
-        }
-    }
-}
-
-#endif
-
-std::shared_ptr<TetrahedralMesh>
-TetrahedralMesh::createTetrahedralMeshCover(std::shared_ptr<SurfaceMesh> surfMesh, const size_t nx, const size_t ny,
-                                            const size_t nz)
-{
-    Vec3d aabbMin, aabbMax;
-
-    // create a background mesh
-    surfMesh->computeBoundingBox(aabbMin, aabbMax, 1. /*percentage padding*/);
-    auto uniformMesh = createUniformMesh(aabbMin, aabbMax, nx, ny, nz);
-
-    // ray-tracing
-    const auto& coords = uniformMesh->getVertexPositions();
-    auto        insideSurfMesh = surfMesh->markPointsInsideAndOut(coords);
-
-    // label elements
-    std::vector<bool> validTet(uniformMesh->getNumTetrahedra(), false);
-    std::vector<bool> validVtx(uniformMesh->getNumVertices(), false);
-
-    // TetrahedralMesh::WeightsArray weights = {0.0, 0.0, 0.0, 0.0};
-    const Vec3d h = { (aabbMax[0] - aabbMin[0]) / nx,
-                      (aabbMax[1] - aabbMin[1]) / ny,
-                      (aabbMax[2] - aabbMin[2]) / nz };
-
-    // TODO: can be parallelized by make NUM_THREADS copies of validTet, or use atomic op on validTet
-    auto labelEnclosingTet = [&surfMesh, &uniformMesh, &aabbMin, &h, nx, ny, nz, &validTet](const size_t i)
-                             {
-                                 const auto& xyz = surfMesh->getVertexPosition(i);
-                                 // find the hex that encloses the point;
-                                 size_t idX   = (size_t)((xyz[0] - aabbMin[0]) / h[0]);
-                                 size_t idY   = (size_t)((xyz[1] - aabbMin[1]) / h[1]);
-                                 size_t idZ   = (size_t)((xyz[2] - aabbMin[2]) / h[2]);
-                                 size_t hexId = idX + idY * nx + idZ * nx * ny;
-
-                                 // the index range of tets inside the enclosing hex
-                                 const int numDiv = 6;
-                                 size_t    tetId0 = numDiv * hexId;
-                                 size_t    tetId1 = tetId0 + numDiv;
-
-                                 // loop over the tets to find the enclosing tets
-                                 bool                          found   = false;
-                                 TetrahedralMesh::WeightsArray weights = { 0.0, 0.0, 0.0, 0.0 };
-                                 for (size_t id = tetId0; id < tetId1; ++id)
-                                 {
-                                     uniformMesh->computeBarycentricWeights(id, xyz, weights);
-
-                                     if ((weights[0] >= 0) && (weights[1] >= 0) && (weights[2] >= 0) && (weights[3] >= 0))
-                                     {
-                                         validTet[id] = true;
-                                         found = true;
-                                         break;
-                                     }
-                                 }
-
-                                 // TODO: not sure how to do thread-safe logging
-                                 CHECK(found) << "Failed to find the enclosing tetrahedron";
-                             };
-
-    // a customized approach to find the enclosing tet for each surface points
-    for (size_t i = 0; i < surfMesh->getNumVertices(); ++i)
-    {
-        labelEnclosingTet(i);
-    }
-
-    for (size_t i = 0; i < validTet.size(); ++i)
-    {
-        const auto& verts = uniformMesh->getTetrahedronVertices(i);
-        if (insideSurfMesh[verts[0]]
-            || insideSurfMesh[verts[1]]
-            || insideSurfMesh[verts[2]]
-            || insideSurfMesh[verts[3]])
-        {
-            validTet[i] = true;
-        }
-    }
-
-    size_t numElems = 0;
-    for (size_t i = 0; i < validTet.size(); ++i)
-    {
-        const auto& verts = uniformMesh->getTetrahedronVertices(i);
-        if (validTet[i])
-        {
-            validVtx[verts[0]] = true;
-            validVtx[verts[1]] = true;
-            validVtx[verts[2]] = true;
-            validVtx[verts[3]] = true;
-            ++numElems;
-        }
-    }
-
-    // discard useless vertices, and build old-to-new index map
-    size_t numVerts = 0;
-    for (auto b : validVtx)
-    {
-        if (b)
-        {
-            ++numVerts;
-        }
-    }
-
-    StdVectorOfVec3d    newCoords(numVerts);
-    std::vector<size_t> oldToNew(coords.size(), std::numeric_limits<size_t>::max());
-    size_t              cnt = 0;
-
-    for (size_t i = 0; i < validVtx.size(); ++i)
-    {
-        if (validVtx[i])
-        {
-            newCoords[cnt] = coords[i];
-            oldToNew[i]    = cnt;
-            ++cnt;
-        }
-    }
-
-    // update tet-to-vert connectivity
-    std::vector<TetraArray> newIndices(numElems);
-    cnt = 0;
-    for (size_t i = 0; i < uniformMesh->getNumTetrahedra(); ++i)
-    {
-        if (validTet[i])
-        {
-            const auto oldIndices = uniformMesh->getTetrahedronVertices(i);
-
-            newIndices[cnt][0] = oldToNew[oldIndices[0]];
-            newIndices[cnt][1] = oldToNew[oldIndices[1]];
-            newIndices[cnt][2] = oldToNew[oldIndices[2]];
-            newIndices[cnt][3] = oldToNew[oldIndices[3]];
-
-            ++cnt;
-        }
-    }
-
-    // ready to create the final mesh
-    auto mesh = std::make_shared<TetrahedralMesh>();
-    mesh->initialize(newCoords, newIndices);
-
-    return mesh;
-}
 }  // namespace imstk
diff --git a/Source/Geometry/Mesh/imstkTetrahedralMesh.h b/Source/Geometry/Mesh/imstkTetrahedralMesh.h
index b667fd2c0fddc045d19f44b9b8d39594feee1f2c..0aeb2c944596d030acb5fef413e7077a7bb0c33b 100644
--- a/Source/Geometry/Mesh/imstkTetrahedralMesh.h
+++ b/Source/Geometry/Mesh/imstkTetrahedralMesh.h
@@ -123,23 +123,6 @@ public:
     void setTetrahedraAsRemoved(const unsigned int tetId) { m_removedMeshElems[tetId] = true; }
     const std::vector<bool>& getRemovedTetrahedra() const { return m_removedMeshElems; }
 
-    ///
-    /// \brief Create a tetrahedral mesh based on a uniform Cartesian mesh
-    /// \param aabbMin  the small conner of a box
-    /// \param aabbMax  the large conner of a box
-    /// \param nx number of elements in the x-direction
-    /// \param ny number of elements in the y-direction
-    /// \param nz number of elements in the z-direction
-    ///
-    /// \note Refer: Dompierre, Julien & Labbé, Paul & Vallet, Marie-Gabrielle & Camarero, Ricardo. (1999).
-    /// How to Subdivide Pyramids, Prisms, and Hexahedra into Tetrahedra.. 195-204.
-    static std::shared_ptr<TetrahedralMesh> createUniformMesh(const Vec3d& aabbMin, const Vec3d& aabbMax, const size_t nx, const size_t ny, const size_t nz);
-
-    ///
-    /// \brief Create a tetrahedral mesh cover
-    /// \note surfMesh can't be const since the non-const member function rayTracing is called inside.
-    ///
-    static std::shared_ptr<TetrahedralMesh> createTetrahedralMeshCover(std::shared_ptr<SurfaceMesh> surfMesh, const size_t nx, const size_t ny, size_t nz);
 protected:
 
     friend class VTKTetrahedralMeshRenderDelegate;
diff --git a/Source/Geometry/imstkGeometry.cpp b/Source/Geometry/imstkGeometry.cpp
index b03022d1697b224a03cdc00f659482f5153d41bd..48d7cd0432220a2b6ac60f519e88afa43d85adfc 100644
--- a/Source/Geometry/imstkGeometry.cpp
+++ b/Source/Geometry/imstkGeometry.cpp
@@ -63,7 +63,7 @@ Geometry::print() const
 }
 
 void
-Geometry::computeBoundingBox(Vec3d&, Vec3d&, const double)
+Geometry::computeBoundingBox(Vec3d&, Vec3d&, const double) const
 {
     LOG(FATAL) << "computeBoundingBox() must be called from an instance of a specific geometry class";
 }
diff --git a/Source/Geometry/imstkGeometry.h b/Source/Geometry/imstkGeometry.h
index fc9c0993c012b9a769dd7481b71b1e56559f84cf..901165bf6e863a596d39cfbffa850bf5487f1b47 100644
--- a/Source/Geometry/imstkGeometry.h
+++ b/Source/Geometry/imstkGeometry.h
@@ -103,7 +103,7 @@ public:
     ///
     /// \brief Compute the bounding box for the geometry
     ///
-    virtual void computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent = 0.0);
+    virtual void computeBoundingBox(Vec3d& lowerCorner, Vec3d& upperCorner, const double paddingPercent = 0.0) const;
 
     ///
     /// \brief Translate the geometry in Cartesian space
diff --git a/Source/Geometry/imstkGeometryUtilities.cpp b/Source/Geometry/imstkGeometryUtilities.cpp
index 0000f7dc6063760a3ebdc106e862146ff49f0f14..02d47efe403e77ec3e17b3299b21087555f8672f 100644
--- a/Source/Geometry/imstkGeometryUtilities.cpp
+++ b/Source/Geometry/imstkGeometryUtilities.cpp
@@ -20,6 +20,7 @@
 =========================================================================*/
 
 #include "imstkGeometryUtilities.h"
+#include "Mesh/imstkSurfaceMesh.h"
 #include "imstkHexahedralMesh.h"
 #include "imstkLineMesh.h"
 #include "imstkTetrahedralMesh.h"
@@ -358,4 +359,876 @@ GeometryUtils::loopSubdivideSurfaceMesh(const SurfaceMesh& surfaceMesh, const in
 
     return convertVtkPolyDataToSurfaceMesh(filter->GetOutput());
 }
+
+namespace // anonymous namespace
+{
+///
+/// \brief Build the vertex-to-vertex connectivity
+///
+/// \param[in] conn element-to-vertex connectivity
+/// \param[in] numVerts number of vertices
+/// \param[out] vertToVert the vertex-to-vertex connectivity on return
+///
+template<typename ElemConn>
+static void
+buildVertexToVertexConnectivity(const std::vector<ElemConn>&             conn,
+                                const size_t                             numVerts,
+                                std::vector<std::unordered_set<size_t>>& vertToVert)
+{
+    // constexpr size_t numVertPerElem = ElemConn::size();
+    std::vector<size_t> vertToElemPtr(numVerts + 1, 0);
+    std::vector<size_t> vertToElem;
+
+    // find the number of adjacent elements for each vertex
+    for (const auto& vertices : conn)
+    {
+        for (auto vid : vertices)
+        {
+            vertToElemPtr[vid + 1] += 1;
+        }
+    }
+
+    // accumulate pointer
+    for (size_t i = 0; i < numVerts; ++i)
+    {
+        vertToElemPtr[i + 1] += vertToElemPtr[i];
+    }
+
+    // track the front position for each vertex in \p vertToElem
+    auto pos = vertToElemPtr;
+    // the total number of element neighbors of all vertices, ie, size of \p vertToElem
+    const size_t totNum = vertToElemPtr.back();
+
+    vertToElem.resize(totNum);
+
+    for (size_t eid = 0; eid < conn.size(); ++eid)
+    {
+        for (auto vid : conn[eid])
+        {
+            vertToElem[pos[vid]] = eid;
+            ++pos[vid];
+        }
+    }
+
+    // connectivity of vertex-to-vertex
+    vertToVert.resize(numVerts);
+    auto getVertexNbrs = [&vertToElem, &vertToElemPtr, &conn, &vertToVert](const size_t i)
+                         {
+                             const auto ptr0 = vertToElemPtr[i];
+                             const auto ptr1 = vertToElemPtr[i + 1];
+                             size_t     eid;
+
+                             for (auto ptr = ptr0; ptr < ptr1; ++ptr)
+                             {
+                                 eid = vertToElem[ptr];
+                                 for (auto vid : conn[eid])
+                                 {
+                                     // vertex-i itself is also included.
+                                     vertToVert[i].insert(vid);
+                                 }
+                             }
+                         };
+
+    // for (size_t i = 0; i < numVerts; ++i)
+    // {
+    //     getVertexNbrs(i);
+    // }
+    ParallelUtils::parallelFor(numVerts, getVertexNbrs);
 }
+
+///
+/// \brief Reverse Cuthill-Mckee (RCM) based reording to reduce bandwidth
+///
+/// \param[in] neighbors array of neighbors of each vertex; eg, neighbors[i] is an object containing all neighbors of vertex-i
+/// \return the permutation vector that map from new indices to old indices
+///
+/// \cite https://en.wikipedia.org/wiki/Cuthill%E2%80%93McKee_algorithm
+///
+template<typename NeighborContainer>
+static std::vector<size_t>
+RCM(const std::vector<NeighborContainer>& neighbors)
+{
+    const size_t invalid = std::numeric_limits<size_t>::max();
+
+    // is greater in terms of degrees
+    auto isGreater = [&neighbors](const size_t i, const size_t j) {
+                         return neighbors[i].size() > neighbors[j].size() ? true : false;
+                     };
+
+    const size_t numVerts = neighbors.size();
+
+    std::vector<size_t> P(numVerts);
+    for (size_t i = 0; i < numVerts; ++i)
+    {
+        P[i] = i;
+    }
+    std::sort(P.begin(), P.end(), isGreater);
+
+    // \todo an alternative is to use std::set for P
+    // std::set<size_t, isGreater> P;
+    // for (size_t i=0; i<numVerts; ++i)
+    // {
+    //     P.insert(i);
+    // }
+
+    std::vector<size_t> R(numVerts, invalid);  // permutation
+    std::queue<size_t>  Q;                     // queue
+    std::vector<bool>   isInP(numVerts, true); // if a vertex is still in P
+    size_t              pos = 0;               // track how many vertices are put into R
+
+    ///
+    /// \brief Move a vertex into R, and enque its neighbors into Q in ascending order.
+    /// \param vid index of vertex to be moved into R
+    ///
+    auto moveVertexIntoRAndItsNeighborsIntoQ = [&neighbors, &isInP, &pos, &R, &Q](const size_t vid)
+                                               {
+                                                   R[pos] = vid;
+                                                   ++pos;
+                                                   isInP[vid] = false;
+
+                                                   // Put the unordered neighbors into Q, in ascending order.
+                                                   // first find unordered neighbors
+                                                   std::set<size_t> unorderedNbrs;
+                                                   for (auto nbr : neighbors[vid])
+                                                   {
+                                                       if (isInP[nbr])
+                                                       {
+                                                           unorderedNbrs.insert(nbr);
+                                                       }
+                                                   }
+
+                                                   for (auto nbr : unorderedNbrs)
+                                                   {
+                                                       Q.push(nbr);
+                                                       isInP[nbr] = false;
+                                                   }
+                                               };
+
+    size_t pCur = 0;
+
+    // main loop
+    while (true)
+    {
+        size_t parent = invalid;
+        for (size_t vid = pCur; vid < isInP.size(); ++vid)
+        {
+            if (isInP[vid])
+            {
+                isInP[vid] = false;
+                pCur       = vid;
+                parent     =  vid;
+                break;
+            }
+        }
+        if (parent == invalid)
+        {
+            break;
+        }
+
+        moveVertexIntoRAndItsNeighborsIntoQ(parent);
+
+        while (!Q.empty())
+        {
+            parent = Q.front();
+            Q.pop();
+            moveVertexIntoRAndItsNeighborsIntoQ(parent);
+        }
+
+        // here we have empty Q
+    }
+
+    CHECK(pos == numVerts) << "RCM ordering: we should never get here";
+
+    std::reverse(R.begin(), R.end());
+    return R;
+}
+
+///
+/// \brief Reorder using Reverse Cuthill-Mckee algorithm
+//
+/// \param conn element-to-vertex connectivity
+/// \param numVerts number of vertices
+///
+/// \return the permutation vector that maps from new indices to old indices
+///
+template<typename ElemConn>
+static std::vector<size_t>
+RCM(const std::vector<ElemConn>& conn, const size_t numVerts)
+{
+    // connectivity of vertex-to-vertex
+    std::vector<std::unordered_set<size_t>> vertToVert;
+    buildVertexToVertexConnectivity(conn, numVerts, vertToVert);
+    return RCM(vertToVert);
+}
+
+///
+/// \brief Given a set of points mark them as inside (true) and outside
+/// \param surfaceMesh a \ref SurfaceMesh
+/// \param coords a set of points to be tested
+///
+/// \note this function cannot be const because PointSet::computeBoundingBox, called inside, is not.
+///
+std::vector<bool>
+markPointsInsideAndOut(const SurfaceMesh& surfaceMesh, const StdVectorOfVec3d& coords)
+{
+    std::vector<bool> isInside;
+    isInside.resize(coords.size(), false);
+
+    Vec3d aabbMin, aabbMax;
+    surfaceMesh.computeBoundingBox(aabbMin, aabbMax, 1.);
+
+    auto genRandomDirection = [](Vec3d& direction)
+                              {
+                                  for (int i = 0; i < 3; ++i)
+                                  {
+                                      direction[i] = rand();
+                                  }
+                                  double mag = direction.norm();
+
+                                  for (int i = 0; i < 3; ++i)
+                                  {
+                                      direction[i] /= mag;
+                                  }
+                                  return;
+                              };
+
+    auto triangleRayIntersection = [](const Vec3d& xyz,
+                                      const Vec3d& triVert0,
+                                      const Vec3d& triVert1,
+                                      const Vec3d& triVert2,
+                                      const Vec3d& direction)
+                                   {
+                                       // const double eps = 1e-15;
+                                       constexpr const double eps   = std::numeric_limits<double>::epsilon();
+                                       Vec3d                  edge0 = triVert1 - triVert0;
+                                       Vec3d                  edge1 = triVert2 - triVert0;
+                                       Vec3d                  pvec  = direction.cross(edge1);
+                                       double                 det   = edge0.dot(pvec);
+
+                                       if (det > -eps && det < eps)
+                                       {
+                                           return false;
+                                       }
+                                       double inv_det = 1.0 / det;
+                                       Vec3d  tvec    = xyz - triVert0;
+                                       double u       = tvec.dot(pvec) * inv_det;
+                                       if (u < 0.0 || u > 1.0)
+                                       {
+                                           return false;
+                                       }
+                                       Vec3d  qvec = tvec.cross(edge0);
+                                       double v    = direction.dot(qvec) * inv_det;
+                                       if (v < 0.0 || u + v > 1.0)
+                                       {
+                                           return false;
+                                       }
+
+                                       double t = edge1.dot(qvec) * inv_det;
+                                       if (t > 0.0)
+                                       {
+                                           return true;
+                                       }
+                                       else
+                                       {
+                                           return false;
+                                       }
+                                   };
+
+    std::vector<Vec3d> bBoxMin;
+    std::vector<Vec3d> bBoxMax;
+
+    bBoxMin.resize(surfaceMesh.getNumTriangles());
+    bBoxMax.resize(surfaceMesh.getNumTriangles());
+
+    for (size_t idx = 0; idx < surfaceMesh.getNumTriangles(); ++idx)
+    {
+        const auto& verts = surfaceMesh.getTrianglesVertices().at(idx);
+        const auto& xyz0  = surfaceMesh.getVertexPosition(verts[0]);
+        const auto& xyz1  = surfaceMesh.getVertexPosition(verts[1]);
+        const auto& xyz2  = surfaceMesh.getVertexPosition(verts[2]);
+        // const auto& xyz0  = m_vertexPositions[verts[0]];
+        // const auto& xyz1  = m_vertexPositions[verts[1]];
+        // const auto& xyz2  = m_vertexPositions[verts[2]];
+
+        bBoxMin[idx][0] = xyz0[0];
+        bBoxMin[idx][1] = xyz0[1];
+        bBoxMin[idx][2] = xyz0[2];
+        bBoxMax[idx][0] = xyz0[0];
+        bBoxMax[idx][1] = xyz0[1];
+        bBoxMax[idx][2] = xyz0[2];
+
+        bBoxMin[idx][0] = std::min(bBoxMin[idx][0], xyz1[0]);
+        bBoxMin[idx][1] = std::min(bBoxMin[idx][1], xyz1[1]);
+        bBoxMin[idx][2] = std::min(bBoxMin[idx][2], xyz1[2]);
+        bBoxMin[idx][0] = std::min(bBoxMin[idx][0], xyz2[0]);
+        bBoxMin[idx][1] = std::min(bBoxMin[idx][1], xyz2[1]);
+        bBoxMin[idx][2] = std::min(bBoxMin[idx][2], xyz2[2]);
+
+        bBoxMax[idx][0] = std::max(bBoxMax[idx][0], xyz1[0]);
+        bBoxMax[idx][1] = std::max(bBoxMax[idx][1], xyz1[1]);
+        bBoxMax[idx][2] = std::max(bBoxMax[idx][2], xyz1[2]);
+        bBoxMax[idx][0] = std::max(bBoxMax[idx][0], xyz2[0]);
+        bBoxMax[idx][1] = std::max(bBoxMax[idx][1], xyz2[1]);
+        bBoxMax[idx][2] = std::max(bBoxMax[idx][2], xyz2[2]);
+    }
+
+    auto rayTracingFunc = [&coords,
+                           &aabbMin,
+                           &aabbMax,
+                           &bBoxMin,
+                           &bBoxMax,
+                           &isInside,
+                           &triangleRayIntersection,
+                           &genRandomDirection,
+                           &surfaceMesh](const size_t i)
+                          {
+                              bool outBox = coords[i][0] < aabbMin[0] || coords[i][0] > aabbMax[0]
+                                            || coords[i][1] < aabbMin[1] || coords[i][1] > aabbMax[1]
+                                            || coords[i][2] < aabbMin[2] || coords[i][2] > aabbMax[2];
+                              if (outBox)
+                              {
+                                  return;
+                              }
+
+                              /// \todo generate a random direction?
+                              const Vec3d direction = { 0.0, 0.0, 1.0 };
+                              // Vec3d direction;
+                              // genRandomDirection(direction);
+                              // std::cout << direction << std::endl;
+                              int         numIntersections = 0;
+                              const auto& xyz      = surfaceMesh.getVertexPositions();
+                              const auto& triVerts = surfaceMesh.getTrianglesVertices();
+
+                              for (size_t j = 0; j < surfaceMesh.getNumTriangles(); ++j)
+                              {
+                                  const auto& verts = triVerts[j];
+
+                                  // consider directed ray
+                                  if (coords[i][2] > bBoxMax[j][2])
+                                  {
+                                      continue;
+                                  }
+
+                                  if (coords[i][0] > bBoxMax[j][0])
+                                  {
+                                      continue;
+                                  }
+                                  if (coords[i][0] < bBoxMin[j][0])
+                                  {
+                                      continue;
+                                  }
+                                  if (coords[i][1] > bBoxMax[j][1])
+                                  {
+                                      continue;
+                                  }
+                                  if (coords[i][1] < bBoxMin[j][1])
+                                  {
+                                      continue;
+                                  }
+
+                                  auto intersected = triangleRayIntersection(coords[i],
+                           xyz[verts[0]],
+                           xyz[verts[1]],
+                           xyz[verts[2]],
+                           direction);
+                                  if (intersected)
+                                  {
+                                      ++numIntersections;
+                                  }
+                              }
+
+                              if (numIntersections % 2 == 1)
+                              {
+                                  isInside[i] = true;
+                              }
+
+                              return;
+                          };
+
+    // for (size_t i = 0; i < coords.size(); ++i)
+    // {
+    //     rayTracingFunc(i);
+    // }
+
+    ParallelUtils::parallelFor(coords.size(), rayTracingFunc);
+
+    return isInside;
+}
+
+///
+/// \brief Given a set of uniformly spaced points, mark them as inside (true) and outside.
+/// It makes uses of ray-tracing but skips points based on the nearest distance between current point and the surface.
+///
+/// \param surfaceMesh a \ref SurfaceMesh
+/// \param coords a set of points to be tested
+/// \param nx number of points in x-direction
+/// \param ny number of points in y-direction
+/// \param nz number of points in z-direction
+///
+/// \note this function cannot be const because PointSet::computeBoundingBox, called inside, is not.
+///
+std::vector<bool>
+markPointsInsideAndOut(const SurfaceMesh& surfaceMesh, const StdVectorOfVec3d& coords, const size_t nx, const size_t ny, const size_t nz)
+{
+    std::vector<bool> isInside;
+    isInside.resize(coords.size(), false);
+
+    Vec3d aabbMin, aabbMax;
+    surfaceMesh.computeBoundingBox(aabbMin, aabbMax, 1.);
+    // space between two adjacent points
+    const Vec3d h = { coords[1][0] - coords[0][0], coords[nx][1] - coords[0][1], coords[nx * ny][2] - coords[0][2] };
+
+    /// \brief if a ray intersects with a triangle.
+    /// \param xyz starting point of ray
+    /// \param triVert0 triVert0, triVert1, triVert2 are 3 vertices of the triangle
+    /// \param direction direction of the ray
+    /// \param[out] distance on return it is the distance of the point and the triangle
+    auto triangleRayIntersection = [](const Vec3d& xyz, const Vec3d& triVert0, const Vec3d& triVert1, const Vec3d& triVert2, const Vec3d& direction, double& distance)
+                                   {
+                                       const double eps   = std::numeric_limits<double>::epsilon();
+                                       const Vec3d  edge0 = triVert1 - triVert0;
+                                       const Vec3d  edge1 = triVert2 - triVert0;
+                                       const Vec3d  pvec  = direction.cross(edge1);
+                                       const double det   = edge0.dot(pvec);
+
+                                       if (det > -eps && det < eps)
+                                       {
+                                           return false;
+                                       }
+                                       const double inv_det = 1.0 / det;
+                                       const Vec3d  tvec    = xyz - triVert0;
+                                       const double u       = tvec.dot(pvec) * inv_det;
+                                       if (u < 0.0 || u > 1.0)
+                                       {
+                                           return false;
+                                       }
+                                       const Vec3d  qvec = tvec.cross(edge0);
+                                       const double v    = direction.dot(qvec) * inv_det;
+                                       if (v < 0.0 || u + v > 1.0)
+                                       {
+                                           return false;
+                                       }
+
+                                       distance = edge1.dot(qvec) * inv_det;
+                                       if (distance > 0.0)
+                                       {
+                                           return true;
+                                       }
+                                       else
+                                       {
+                                           return false;
+                                       }
+                                   };
+
+    std::vector<Vec3d> bBoxMin;
+    std::vector<Vec3d> bBoxMax;
+
+    bBoxMin.resize(surfaceMesh.getNumTriangles());
+    bBoxMax.resize(surfaceMesh.getNumTriangles());
+
+    /// \brief find the bounding boxes of each surface triangle
+    auto findBoundingBox = [&](const size_t idx)
+                           {
+                               const auto& verts   = surfaceMesh.getTrianglesVertices().at(idx);
+                               const auto& vertXyz = surfaceMesh.getVertexPositions();
+                               const auto& xyz0    = vertXyz[verts[0]];
+                               const auto& xyz1    = vertXyz[verts[1]];
+                               const auto& xyz2    = vertXyz[verts[2]];
+
+                               bBoxMin[idx][0] = xyz0[0];
+                               bBoxMin[idx][1] = xyz0[1];
+                               bBoxMin[idx][2] = xyz0[2];
+                               bBoxMax[idx][0] = xyz0[0];
+                               bBoxMax[idx][1] = xyz0[1];
+                               bBoxMax[idx][2] = xyz0[2];
+
+                               bBoxMin[idx][0] = std::min(bBoxMin[idx][0], xyz1[0]);
+                               bBoxMin[idx][1] = std::min(bBoxMin[idx][1], xyz1[1]);
+                               bBoxMin[idx][2] = std::min(bBoxMin[idx][2], xyz1[2]);
+                               bBoxMin[idx][0] = std::min(bBoxMin[idx][0], xyz2[0]);
+                               bBoxMin[idx][1] = std::min(bBoxMin[idx][1], xyz2[1]);
+                               bBoxMin[idx][2] = std::min(bBoxMin[idx][2], xyz2[2]);
+
+                               bBoxMax[idx][0] = std::max(bBoxMax[idx][0], xyz1[0]);
+                               bBoxMax[idx][1] = std::max(bBoxMax[idx][1], xyz1[1]);
+                               bBoxMax[idx][2] = std::max(bBoxMax[idx][2], xyz1[2]);
+                               bBoxMax[idx][0] = std::max(bBoxMax[idx][0], xyz2[0]);
+                               bBoxMax[idx][1] = std::max(bBoxMax[idx][1], xyz2[1]);
+                               bBoxMax[idx][2] = std::max(bBoxMax[idx][2], xyz2[2]);
+                           };
+
+    ParallelUtils::parallelFor(surfaceMesh.getNumTriangles(), findBoundingBox);
+
+    // ray tracing for all points in the x-axis. These points are those start with indices (0,j,k)
+    // and jk = j + k*ny
+    auto rayTracingLine = [&](const size_t jk)
+                          {
+                              size_t idx0   = jk * nx;
+                              bool   outBox = coords[idx0][0] < aabbMin[0] || coords[idx0][0] > aabbMax[0]
+                                              || coords[idx0][1] < aabbMin[1] || coords[idx0][1] > aabbMax[1]
+                                              || coords[idx0][2] < aabbMin[2] || coords[idx0][2] > aabbMax[2];
+                              if (outBox)
+                              {
+                                  return;
+                              }
+
+                              const Vec3d direction = { 1.0, 0.0, 0.0 };
+                              const auto& xyz       = surfaceMesh.getVertexPositions();
+                              const auto& triVerts  = surfaceMesh.getTrianglesVertices();
+
+                              size_t i = 0;
+                              while (i < nx)
+                              {
+                                  size_t idx = idx0 + i;
+                                  int    numIntersections = 0;
+                                  double dist    = 0.0;
+                                  double distMin = h[0] * (nz + 1);
+
+                                  for (size_t j = 0; j < surfaceMesh.getNumTriangles(); ++j)
+                                  {
+                                      const auto& verts = triVerts[j];
+
+                                      // consider directed ray
+                                      if (coords[idx][0] > bBoxMax[j][0])
+                                      {
+                                          continue;
+                                      }
+
+                                      if (coords[idx][1] > bBoxMax[j][1])
+                                      {
+                                          continue;
+                                      }
+                                      if (coords[idx][1] < bBoxMin[j][1])
+                                      {
+                                          continue;
+                                      }
+                                      if (coords[idx][2] > bBoxMax[j][2])
+                                      {
+                                          continue;
+                                      }
+                                      if (coords[idx][2] < bBoxMin[j][2])
+                                      {
+                                          continue;
+                                      }
+
+                                      auto intersected = triangleRayIntersection(coords[idx],
+                               xyz[verts[0]],
+                               xyz[verts[1]],
+                               xyz[verts[2]],
+                               direction,
+                               dist);
+                                      if (intersected)
+                                      {
+                                          ++numIntersections;
+                                          distMin = std::min(dist, distMin);
+                                      }
+                                  }
+
+                                  // core of the algorithm: points between the current one and iEnd share the same label so we can skip them.
+                                  size_t iEnd = i + static_cast<size_t>(distMin / h[0]) + 1;
+                                  iEnd = std::min(iEnd, nx);
+
+                                  if (numIntersections % 2 == 1)
+                                  {
+                                      for (size_t ii = idx; ii < idx0 + iEnd; ++ii)
+                                      {
+                                          isInside[ii] = true;
+                                      }
+                                  }
+
+                                  i = iEnd;
+                              }
+                          };
+
+    ParallelUtils::parallelFor(ny * nz, rayTracingLine);
+
+    return isInside;
+}
+} // anonymous namespace
+
+std::shared_ptr<TetrahedralMesh>
+GeometryUtils::createUniformMesh(const Vec3d& aabbMin,
+                                 const Vec3d& aabbMax,
+                                 const size_t nx,
+                                 const size_t ny,
+                                 const size_t nz)
+{
+    const Vec3d h = { (aabbMax[0] - aabbMin[0]) / nx,
+                      (aabbMax[1] - aabbMin[1]) / ny,
+                      (aabbMax[2] - aabbMin[2]) / nz };
+    LOG_IF(FATAL, (h[0] <= 0.0 || h[1] <= 0.0 || h[2] <= 0.0)) << "Invalid bounding box";
+
+    const size_t numVertices = (nx + 1) * (ny + 1) * (nz + 1);
+
+    // std::vector<Vec3d> coords;
+    StdVectorOfVec3d coords;
+    coords.resize(numVertices);
+    size_t cnt = 0;
+
+    for (size_t k = 0; k < nz + 1; ++k)
+    {
+        double z = aabbMin[2] + k * h[2];
+        for (size_t j = 0; j < ny + 1; ++j)
+        {
+            double y = aabbMin[1] + j * h[1];
+            for (size_t i = 0; i < nx + 1; ++i)
+            {
+                double x = aabbMin[0] + i * h[0];
+                coords[cnt] = { x, y, z };
+                ++cnt;
+            }
+        }
+    }
+
+    const size_t numDiv  = 6;
+    const size_t numTets = numDiv * nx * ny * nz;
+
+    std::vector<TetrahedralMesh::TetraArray> vertices;
+    vertices.resize(numTets);
+    cnt = 0;
+    std::vector<size_t> indx(8);
+
+    for (size_t k = 0; k < nz; ++k)
+    {
+        for (size_t j = 0; j < ny; ++j)
+        {
+            for (size_t i = 0; i < nx; ++i)
+            {
+                indx[3] = i + j * (nx + 1) + k * (nx + 1) * (ny + 1);
+                indx[2] = indx[3] + 1;
+                indx[0] = indx[3] + nx + 1;
+                indx[1] = indx[0] + 1;
+                indx[4] = indx[0] + (nx + 1) * (ny + 1);
+                indx[5] = indx[1] + (nx + 1) * (ny + 1);
+                indx[6] = indx[2] + (nx + 1) * (ny + 1);
+                indx[7] = indx[3] + (nx + 1) * (ny + 1);
+                vertices[cnt + 0] = { indx[0], indx[2], indx[3], indx[6] };
+                vertices[cnt + 1] = { indx[0], indx[3], indx[7], indx[6] };
+                vertices[cnt + 2] = { indx[0], indx[7], indx[4], indx[6] };
+                vertices[cnt + 3] = { indx[0], indx[5], indx[6], indx[4] };
+                vertices[cnt + 4] = { indx[1], indx[5], indx[6], indx[0] };
+                vertices[cnt + 5] = { indx[1], indx[6], indx[2], indx[0] };
+                cnt += numDiv;
+            }
+        }
+    }
+
+    auto mesh = std::make_shared<TetrahedralMesh>();
+    // auto mesh = std::unique_ptr<TetrahedralMesh>(new TetrahedralMesh());
+
+    mesh->initialize(coords, vertices);
+    return mesh;
+}
+
+std::shared_ptr<TetrahedralMesh>
+GeometryUtils::createTetrahedralMeshCover(const SurfaceMesh& surfMesh,
+                                          const size_t       nx,
+                                          const size_t       ny,
+                                          const size_t       nz)
+{
+    Vec3d aabbMin, aabbMax;
+
+    // create a background mesh
+    surfMesh.computeBoundingBox(aabbMin, aabbMax, 1. /*percentage padding*/);
+    auto uniformMesh = createUniformMesh(aabbMin, aabbMax, nx, ny, nz);
+
+    // ray-tracing
+    const auto& coords = uniformMesh->getVertexPositions();
+    // auto        insideSurfMesh = markPointsInsideAndOut(surfMesh, coords);
+    const auto insideSurfMesh = markPointsInsideAndOut(surfMesh, coords, nx + 1, ny + 1, nz + 1);
+
+    // label elements
+    std::vector<bool> validTet(uniformMesh->getNumTetrahedra(), false);
+    std::vector<bool> validVtx(uniformMesh->getNumVertices(), false);
+
+    // TetrahedralMesh::WeightsArray weights = {0.0, 0.0, 0.0, 0.0};
+    const Vec3d h = { (aabbMax[0] - aabbMin[0]) / nx,
+                      (aabbMax[1] - aabbMin[1]) / ny,
+                      (aabbMax[2] - aabbMin[2]) / nz };
+
+    // a customized approach to find the enclosing tet for each surface points
+    /// \todo can be parallelized by make NUM_THREADS copies of validTet, or use atomic op on validTet
+    auto labelEnclosingTet = [&aabbMin, &h, nx, ny, nz, &uniformMesh, &validTet](const Vec3d& xyz)
+                             {
+                                 const size_t idX   = (xyz[0] - aabbMin[0]) / h[0];
+                                 const size_t idY   = (xyz[1] - aabbMin[1]) / h[1];
+                                 const size_t idZ   = (xyz[2] - aabbMin[2]) / h[2];
+                                 const size_t hexId = idX + idY * nx + idZ * nx * ny;
+
+                                 // the index range of tets inside the enclosing hex
+                                 const int    numDiv = 6;
+                                 const size_t tetId0 = numDiv * hexId;
+                                 const size_t tetId1 = tetId0 + numDiv;
+
+                                 static TetrahedralMesh::WeightsArray weights = { 0.0, 0.0, 0.0, 0.0 };
+
+                                 // loop over the tets to find the enclosing tets
+                                 for (size_t id = tetId0; id < tetId1; ++id)
+                                 {
+                                     if (validTet[id])
+                                     {
+                                         continue;
+                                     }
+                                     uniformMesh->computeBarycentricWeights(id, xyz, weights);
+
+                                     if ((weights[0] >= 0) && (weights[1] >= 0) && (weights[2] >= 0) && (weights[3] >= 0))
+                                     {
+                                         validTet[id] = true;
+                                         break;
+                                     }
+                                 }
+                             };
+
+    auto labelEnclosingTetOfVertices = [&surfMesh, &uniformMesh, &aabbMin, &h, nx, ny, nz, &labelEnclosingTet, &validTet](const size_t i)
+                                       {
+                                           const auto& xyz = surfMesh.getVertexPosition(i);
+                                           labelEnclosingTet(xyz);
+                                       };
+
+    for (size_t i = 0; i < validTet.size(); ++i)
+    {
+        const auto& verts = uniformMesh->getTetrahedronVertices(i);
+        if (insideSurfMesh[verts[0]]
+            || insideSurfMesh[verts[1]]
+            || insideSurfMesh[verts[2]]
+            || insideSurfMesh[verts[3]])
+        {
+            validTet[i] = true;
+        }
+    }
+
+    // find the enclosing tets of a group of points on a surface triangle
+    auto labelEnclosingTetOfInteriorPnt = [&surfMesh, &labelEnclosingTet](const size_t fid)
+                                          {
+                                              auto               verts = surfMesh.getTrianglesVertices()[fid];
+                                              const auto&        vtx0  = surfMesh.getVertexPosition(verts[0]);
+                                              const auto&        vtx1  = surfMesh.getVertexPosition(verts[1]);
+                                              const auto&        vtx2  = surfMesh.getVertexPosition(verts[2]);
+                                              std::vector<Vec3d> pnts(12);
+
+                                              pnts[0]  = 0.75 * vtx0 + 0.25 * vtx1;
+                                              pnts[1]  = 0.50 * vtx0 + 0.50 * vtx1;
+                                              pnts[2]  = 0.25 * vtx0 + 0.75 * vtx1;
+                                              pnts[3]  = 0.75 * vtx1 + 0.25 * vtx2;
+                                              pnts[4]  = 0.50 * vtx1 + 0.50 * vtx2;
+                                              pnts[5]  = 0.25 * vtx1 + 0.75 * vtx2;
+                                              pnts[6]  = 0.75 * vtx2 + 0.25 * vtx0;
+                                              pnts[7]  = 0.50 * vtx2 + 0.50 * vtx0;
+                                              pnts[8]  = 0.25 * vtx2 + 0.75 * vtx0;
+                                              pnts[9]  = 2.0 / 3.0 * pnts[0] + 1.0 / 3.0 * pnts[5];
+                                              pnts[10] = 0.5 * (pnts[1] + pnts[4]);
+                                              pnts[11] = 0.5 * (pnts[4] + pnts[7]);
+
+                                              for (size_t i = 0; i < pnts.size(); ++i)
+                                              {
+                                                  labelEnclosingTet(pnts[i]);
+                                              }
+                                          };
+
+    // enclose all vertices
+    for (size_t i = 0; i < surfMesh.getNumVertices(); ++i)
+    {
+        labelEnclosingTetOfVertices(i);
+    }
+
+    // enclose some interior points on triangles
+    for (size_t i = 0; i < surfMesh.getNumTriangles(); ++i)
+    {
+        labelEnclosingTetOfInteriorPnt(i);
+    }
+
+    size_t numElems = 0;
+    for (size_t i = 0; i < validTet.size(); ++i)
+    {
+        const auto& verts = uniformMesh->getTetrahedronVertices(i);
+        if (validTet[i])
+        {
+            validVtx[verts[0]] = true;
+            validVtx[verts[1]] = true;
+            validVtx[verts[2]] = true;
+            validVtx[verts[3]] = true;
+            ++numElems;
+        }
+    }
+
+    // discard useless vertices, and build old-to-new index map
+    size_t numVerts = 0;
+    for (auto b : validVtx)
+    {
+        if (b)
+        {
+            ++numVerts;
+        }
+    }
+
+    StdVectorOfVec3d    newCoords(numVerts);
+    std::vector<size_t> oldToNew(coords.size(), std::numeric_limits<size_t>::max());
+
+    size_t cnt = 0;
+    for (size_t i = 0; i < validVtx.size(); ++i)
+    {
+        if (validVtx[i])
+        {
+            newCoords[cnt] = coords[i];
+            oldToNew[i]    = cnt;
+            ++cnt;
+        }
+    }
+
+    // update tet-to-vert connectivity
+    std::vector<TetrahedralMesh::TetraArray> newIndices(numElems);
+    cnt = 0;
+    for (size_t i = 0; i < uniformMesh->getNumTetrahedra(); ++i)
+    {
+        if (validTet[i])
+        {
+            const auto oldIndices = uniformMesh->getTetrahedronVertices(i);
+
+            newIndices[cnt][0] = oldToNew[oldIndices[0]];
+            newIndices[cnt][1] = oldToNew[oldIndices[1]];
+            newIndices[cnt][2] = oldToNew[oldIndices[2]];
+            newIndices[cnt][3] = oldToNew[oldIndices[3]];
+
+            ++cnt;
+        }
+    }
+
+    // ready to create the final mesh
+    auto mesh = std::make_shared<TetrahedralMesh>();
+    mesh->initialize(newCoords, newIndices);
+
+    return mesh;
+}
+
+template<typename NeighborContainer>
+std::vector<size_t>
+GeometryUtils::reorderConnectivity(const std::vector<NeighborContainer>& neighbors, const MeshNodeRenumberingStrategy& method)
+{
+    switch (method)
+    {
+    case (MeshNodeRenumberingStrategy::ReverseCuthillMckee):
+        return RCM(neighbors);
+    default:
+        LOG(WARNING) << "Unrecognized reorder method; using RCM instead";
+        return RCM(neighbors);
+    }
+}
+
+template<typename ElemConn>
+std::vector<size_t>
+GeometryUtils::reorderConnectivity(const std::vector<ElemConn>& conn, const size_t numVerts, const MeshNodeRenumberingStrategy& method)
+{
+    switch (method)
+    {
+    case (MeshNodeRenumberingStrategy::ReverseCuthillMckee):
+        return RCM(conn, numVerts);
+    default:
+        LOG(WARNING) << "Unrecognized reorder method; using Reverse Cuthill-Mckee strategy instead";
+        return RCM(conn, numVerts);
+    }
+}
+} // namespace imstk
+
+template std::vector<size_t> imstk::GeometryUtils::reorderConnectivity<std::set<size_t>>(const std::vector<std::set<size_t>>&, const GeometryUtils::MeshNodeRenumberingStrategy&);
+
+template std::vector<size_t> imstk::GeometryUtils::reorderConnectivity<std::array<size_t, 4>>(const std::vector<std::array<size_t, 4>>&, const size_t, const MeshNodeRenumberingStrategy&);
diff --git a/Source/Geometry/imstkGeometryUtilities.h b/Source/Geometry/imstkGeometryUtilities.h
index 55db04a8b6cd86129df266de546442a0ecfb2832..717577c33fddcf9a37f792712ab03b89d8280136 100644
--- a/Source/Geometry/imstkGeometryUtilities.h
+++ b/Source/Geometry/imstkGeometryUtilities.h
@@ -21,9 +21,16 @@
 
 #pragma once
 
+#include "g3log/g3log.hpp"
 #include "imstkMath.h"
-#include <memory>
 #include <vtkSmartPointer.h>
+#include "imstkParallelUtils.h"
+
+#include <memory>
+#include <numeric>
+#include <queue>
+#include <unordered_set>
+#include <set>
 
 class vtkCellArray;
 class vtkPolyData;
@@ -153,5 +160,53 @@ std::unique_ptr<SurfaceMesh> linearSubdivideSurfaceMesh(const SurfaceMesh& surfa
 /// for more details
 ///
 std::unique_ptr<SurfaceMesh> loopSubdivideSurfaceMesh(const SurfaceMesh& surfaceMesh, const int numSubdivisions = 1);
+
+///
+/// \brief Create a tetrahedral mesh based on a uniform Cartesian mesh
+/// \param aabbMin  the small conner of a box
+/// \param aabbMax  the large conner of a box
+/// \param nx number of elements in the x-direction
+/// \param ny number of elements in the y-direction
+/// \param nz number of elements in the z-direction
+///
+/// \note Refer: Dompierre, Julien & Labbé, Paul & Vallet, Marie-Gabrielle & Camarero, Ricardo. (1999).
+/// How to Subdivide Pyramids, Prisms, and Hexahedra into Tetrahedra.. 195-204.
+std::shared_ptr<TetrahedralMesh> createUniformMesh(const Vec3d& aabbMin, const Vec3d& aabbMax, const size_t nx, const size_t ny, const size_t nz);
+
+///
+/// \brief Create a tetrahedral mesh cover
+///
+std::shared_ptr<TetrahedralMesh> createTetrahedralMeshCover(const SurfaceMesh& surfMesh, const size_t nx, const size_t ny, size_t nz);
+
+///
+/// \brief Enumeration for reordering method
+///
+enum class MeshNodeRenumberingStrategy
+{
+    ReverseCuthillMckee // Reverse Cuthill-Mckee
 };
-}
+
+///
+/// \brief Reorder indices in a connectivity to reduce bandwidth
+///
+/// \param[in] neighbors array of neighbors of each vertex; eg, neighbors[i] is an object containing all neighbors of vertex-i
+/// \param[i] method reordering method; see \ref ReorderMethod
+///
+/// \return the permutation vector that map from new indices to old indices
+///
+template<typename NeighborContainer>
+std::vector<size_t> reorderConnectivity(const std::vector<NeighborContainer>& neighbors, const MeshNodeRenumberingStrategy& method = MeshNodeRenumberingStrategy::ReverseCuthillMckee);
+
+///
+/// \brief Reorder using Reverse Cuthill-Mckee
+///
+/// \param[in] conn element-to-vertex connectivity
+/// \param[in] numVerts number of vertices
+/// \param[in] method reordering method; see \ref ReorderMethod
+///
+/// \return the permutation vector that maps from new indices to old indices
+///
+template<typename ElemConn>
+std::vector<size_t> reorderConnectivity(const std::vector<ElemConn>& conn, const size_t numVerts, const MeshNodeRenumberingStrategy& method = MeshNodeRenumberingStrategy::ReverseCuthillMckee);
+} // namespace GeometryUtils
+} // namespace imstk