libmesh
Unified mesh library: templated mesh representation, multi-format I/O, and processing pipeline
Overview
libmesh is a unified, templated mesh library that provides consistent mesh representation, multi-format I/O, halfedge topology operations, and a composable processing pipeline.
Features
- Templated Precision:
Mesh<float>for GPU/STL,Mesh<double>for FEM analysis - Multi-Format I/O: STL (binary/ASCII), OBJ, PLY (binary/ASCII), VTK export
- Processing Pipeline: Normal generation, vertex merging, triangulation, transforms
- Halfedge Topology: Efficient topology queries and modification operations
- Advanced Processors: Loop subdivision, quadric simplification, hole filling
- FEM Integration: Eigen conversion utilities, boundary condition support
Quick Start
Basic Mesh Operations
#include "mesh/core/Mesh.hpp"
// Create a mesh
mesh::Mesh<double> m;
m.addVertex(0, 0, 0);
m.addVertex(1, 0, 0);
m.addVertex(0.5, 1, 0);
m.addElement(mesh::ElementType::Tri3, {0, 1, 2});
// Compute normals
m.recalculateNormals();
// Get bounding box
mesh::Vector3<double> min, max;
m.computeBounds(min, max);
Loading and Saving Files
#include "mesh/io/IORegistry.hpp"
// Load from file (format auto-detected)
auto result = mesh::io::load<double>("model.stl");
if (result.isValid()) {
auto& mesh = result.value();
// Use mesh...
}
// Save to file
mesh::io::save(mesh, "output.obj");
Processing Pipeline
#include "mesh/process/Pipeline.hpp"
mesh::Mesh<double> mesh = loadMesh();
// Apply processing operations
mesh::process::process(mesh,
mesh::process::GenerateNormals |
mesh::process::MergeVertices |
mesh::process::CenterOrigin);
Halfedge Topology
#include "mesh/topology/HalfedgeMesh.hpp"
// Build halfedge structure
auto he = mesh::topology::HalfedgeMesh<double>::fromMesh(mesh);
// Query topology
bool closed = he.isClosed();
int euler = he.eulerCharacteristic();
auto boundaries = he.boundaryLoops();
// Iterate vertex neighbors
for (auto vit = he.vertexVertices(v); vit; ++vit) {
auto neighbor = *vit;
// Process neighbor...
}
Subdivision and Simplification
#include "mesh/process/LoopSubdivision.hpp"
#include "mesh/process/QuadricSimplification.hpp"
// Subdivide (4x face count)
mesh::process::LoopSubdivision<double> subdiv;
subdiv.process(mesh, {});
// Simplify to 50% faces
mesh::process::ProcessOptions opts;
opts.extra["targetRatio"] = 0.5;
mesh::process::QuadricSimplification<double> simplify;
simplify.process(mesh, opts);
QuadricSimplification is feature-aware (construct-mesh-quality
§AR-5.3): dihedral-angle analysis tags crease and corner vertices, crease
constraint planes keep decimation sliding along sharp edges instead of
eroding them, corners are pinned, and only link-condition-safe collapses
run — the closed/manifold/orientable predicates are re-validated
afterwards and the input restored if they regressed. Tune with
setFeatureAngleDeg() / setPreserveFeatures() / setFeatureWeight().
SDF-referenced post-processing
Two processors consume a signed-distance field alongside the mesh (construct-mesh-quality §AR-5); both preserve watertight-manifoldness:
#include "mesh/process/SdfProjection.hpp"
#include "mesh/process/CoplanarMerge.hpp"
// Snap vertices onto the field's zero level set (Newton, ÷‖∇Φ‖²),
// with tangential relaxation. Crease vertices move only along their
// feature line; corners stay pinned.
mesh::process::SdfProjection<float> project;
project.setField([&](const mesh::Vector3<float> &p) {
mesh::process::SdfSample<float> s;
s.value = phi(p); // Φ(p)
s.gradient = gradPhi(p); // raw ∇Φ (need not be unit length)
return s;
});
project.process(mesh, {});
// Merge near-coplanar facets and retriangulate each patch from its
// boundary loop (flat caps become fans).
mesh::process::CoplanarMerge<float> merge;
merge.setAngleToleranceDeg(1.0f);
merge.process(mesh, {});
#include "mesh/process/IsotropicRemesh.hpp"
// Full feature-aware Botsch-Kobbelt loop toward a uniform edge length:
// split long / collapse short / flip to valence 6 / smooth + project.
mesh::process::IsotropicRemesh<float> remesh;
remesh.setField(sdf); // same callback shape as SdfProjection
remesh.setTargetEdgeLength(0.3f);
remesh.process(mesh, {});
Shared feature tagging lives in mesh/process/FeatureDetection.hpp
(analyzeFeatures): dihedral-angle feature edges plus the
smooth/crease/corner vertex classification used by the processors above.
confirmFeaturesAgainstField verifies those tags against the SDF's
gradient (a real crease turns the gradient sharply across a small probe;
coarse-mesh curvature does not), so false features never pin vertices
off the surface.
Supported File Formats
| Format | Extension | Import | Export | Notes |
|---|---|---|---|---|
| STL Binary | .stl | Yes | Yes | Default format |
| STL ASCII | .stl | Yes | Yes | Detected automatically |
| Wavefront OBJ | .obj | Yes | Yes | Normals, tex coords, groups |
| Stanford PLY | .ply | Yes | Yes | ASCII and binary |
| VTK Legacy | .vtk | No | Yes | Visualization export |
Element Types
enum class ElementType : uint8_t {
Point1 = 0, // 0D
Bar2 = 10, Bar3 = 11, // 1D
Tri3 = 20, Tri6 = 21, // 2D triangles
Quad4 = 22, Quad8 = 23, Quad9 = 24, // 2D quads
Tet4 = 30, Tet10 = 31, // 3D tetrahedra
Hex8 = 32, Hex20 = 33, Hex27 = 34, // 3D hexahedra
Wedge6 = 35, Pyramid5 = 36 // 3D other
};
Processing Flags
enum ProcessFlags : uint32_t {
Triangulate = 1 << 0, // Convert polygons to triangles
GenerateNormals = 1 << 1, // Compute vertex normals
MergeVertices = 1 << 2, // Deduplicate within tolerance
FlipNormals = 1 << 3, // Reverse winding order
RemoveDegenerates = 1 << 4, // Remove zero-area elements
CenterOrigin = 1 << 6, // Move centroid to origin
NormalizeScale = 1 << 7, // Scale to unit bounding box
Subdivide = 1 << 11, // Loop subdivision
Simplify = 1 << 12, // Quadric decimation
FillHoles = 1 << 14, // Fill boundary holes
SdfProject = 1 << 20, // Newton-project onto an SDF zero set
MergeCoplanar = 1 << 21, // Merge near-coplanar facets
};
FEM Integration
For finite element analysis, use the conversion utilities:
#include "mesh/convert/ToEigen.hpp"
#include "mesh/convert/FEMBridge.hpp"
// Convert to Eigen vectors
Eigen::Vector3d ev = mesh::convert::toEigen(meshVec);
// Convert mesh vertices to matrix
Eigen::MatrixXd verts = mesh::convert::toEigenMatrix(mesh.vertices);
// Apply FEM displacements back to mesh
mesh::convert::applyDisplacements(mesh, displacements, dofsPerNode);
Boundary Conditions
#include "mesh/core/BoundaryCondition.hpp"
mesh::BoundaryConditionSet<double> bcs;
// Fix a node
bcs.fixNode(nodeIndex, {true, true, true});
// Prescribe displacement
bcs.prescribeDisplacement(nodeIndex, 0, 0.001); // x-direction
// Apply force
bcs.applyForce(nodeIndex, 1, 100.0); // y-direction force
Architecture
libmesh/
├── mesh/core/ - Vector3, Matrix3, Mesh<T>, ElementType
├── mesh/topology/ - HalfedgeMesh, handles, circulators
├── mesh/io/ - STL, OBJ, PLY, VTK importers/exporters
├── mesh/process/ - Pipeline, processors
└── mesh/convert/ - ToEigen, FEMBridge
Dependencies
- Qt6 Core: File I/O, containers, strings
- No Eigen: Eigen is contained to libfem only
Test Coverage
286 unit tests covering:
- Core primitives (Vector3, Matrix3)
- Element types and mesh operations
- All I/O formats with round-trip verification
- Halfedge topology construction and queries
- Processing pipeline and all processors
- FEM integration utilities