MeshLib is a C++/Python geometry library focused on robust boolean operations, offsets, voxel-based reconstruction, decimation and remeshing. If you already work with CGAL, libigl, Open3D, PCL or VTK, the pairs below show the equivalent MeshLib code for the operations users most often port over. Each snippet is minimal and self-contained — copy, adapt, compile. To see these operations running before you port any code, open the live demo: it runs boolean operations, decimation, and ICP registration in the browser.
We do not benchmark on this page. For measured MeshLib vs VTK comparisons (decimation, boolean, subdivision), see the benchmark article.
Every C++ snippet has a one-to-one equivalent in our Python bindings.
CGAL
Boolean union of two triangle meshes
CGAL:
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Polygon_mesh_processing/corefinement.h>
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
namespace PMP = CGAL::Polygon_mesh_processing;
using Mesh = CGAL::Surface_mesh<CGAL::Exact_predicates_inexact_constructions_kernel::Point_3>;
Mesh a, b, out;
PMP::IO::read_polygon_mesh("a.off", a);
PMP::IO::read_polygon_mesh("b.off", b);
PMP::corefine_and_compute_union(a, b, out);
CGAL::IO::write_polygon_mesh("out.off", out);
MeshLib:
#include <MRMesh/MRMeshLoad.h>
#include <MRMesh/MRMeshSave.h>
#include <MRMesh/MRMeshBoolean.h>
auto a = MR::MeshLoad::fromAnySupportedFormat("a.off").value();
auto b = MR::MeshLoad::fromAnySupportedFormat("b.off").value();
auto res = MR::boolean(a, b, MR::BooleanOperation::Union);
MR::MeshSave::toAnySupportedFormat(res.mesh, "out.off");
Connected components of a mesh
CGAL:
#include <CGAL/Polygon_mesh_processing/connected_components.h>
namespace PMP = CGAL::Polygon_mesh_processing;
using face_descriptor = boost::graph_traits<Mesh>::face_descriptor;
auto fccmap = mesh.add_property_map<face_descriptor, std::size_t>("f:CC").first;
std::size_t n = PMP::connected_components(mesh, fccmap);
MeshLib:
#include <MRMesh/MRMeshComponents.h>
auto [labels, n] = MR::MeshComponents::getAllComponentsMap(mesh);
libigl
Decimate a mesh to half its faces
libigl:
#include <igl/read_triangle_mesh.h>
#include <igl/decimate.h>
Eigen::MatrixXd V, U;
Eigen::MatrixXi F, G;
Eigen::VectorXi J, I;
igl::read_triangle_mesh("in.obj", V, F);
igl::decimate(V, F, F.rows() / 2, U, G, J, I);
MeshLib:
#include <MRMesh/MRMeshLoad.h>
#include <MRMesh/MRMesh.h>
#include <MRMesh/MRMeshDecimate.h>
auto mesh = MR::MeshLoad::fromAnySupportedFormat("in.obj").value();
MR::DecimateSettings s;
s.maxDeletedFaces = int(mesh.topology.numValidFaces() / 2);
MR::decimateMesh(mesh, s);
Signed distance from a point to a mesh
libigl:
#include <igl/signed_distance.h>
Eigen::VectorXd S;
Eigen::VectorXi I;
Eigen::MatrixXd C, N;
igl::signed_distance(P, V, F, igl::SIGNED_DISTANCE_TYPE_PSEUDONORMAL, S, I, C, N);
MeshLib:
#include <MRMesh/MRMeshProject.h>
#include <MRMesh/MRMesh.h>
auto sd = MR::findSignedDistance(point, mesh);
float dist = sd->dist;
Open3D
Point-to-point ICP registration
Open3D:
#include <open3d/Open3D.h>
using namespace open3d;
auto src = io::CreatePointCloudFromFile("src.ply");
auto tgt = io::CreatePointCloudFromFile("tgt.ply");
auto result = pipelines::registration::RegistrationICP(
*src, *tgt, 0.05,
Eigen::Matrix4d::Identity(),
pipelines::registration::TransformationEstimationPointToPoint());
Eigen::Matrix4d xf = result.transformation_;
MeshLib:
#include <MRMesh/MRPointsLoad.h>
#include <MRMesh/MRPointCloud.h>
#include <MRMesh/MRICP.h>
auto src = MR::PointsLoad::fromAnySupportedFormat("src.ply").value();
auto tgt = MR::PointsLoad::fromAnySupportedFormat("tgt.ply").value();
MR::ICP icp( {src}, {tgt}, MR::AffineXf3f{}, MR::AffineXf3f{}, 0.05f );
auto xf = icp.calculateTransformation();
Point cloud to mesh (surface reconstruction)
Open3D (Ball Pivoting):
#include <open3d/Open3D.h>
using namespace open3d;
auto pcd = io::CreatePointCloudFromFile("cloud.ply");
pcd->EstimateNormals();
std::vector<double> radii = {0.005, 0.01, 0.02, 0.04};
auto mesh = geometry::TriangleMesh::CreateFromPointCloudBallPivoting(*pcd, radii);
MeshLib (voxel fusion):
#include <MRMesh/MRPointsLoad.h>
#include <MRMesh/MRPointCloud.h>
#include <MRMesh/MRBox.h>
#include <MRVoxels/MRPointsToMeshFusion.h>
auto cloud = MR::PointsLoad::fromAnySupportedFormat("cloud.ply").value();
MR::PointsToMeshParameters p;
p.voxelSize = cloud.computeBoundingBox().diagonal() * 1e-2f;
MR::Mesh mesh = MR::pointsToMeshFusion(cloud, p).value();
PCL
Voxel-grid downsampling of a point cloud
PCL:
#include <pcl/io/pcd_io.h>
#include <pcl/filters/voxel_grid.h>
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("cloud.pcd", *cloud);
pcl::VoxelGrid<pcl::PointXYZ> vg;
vg.setInputCloud(cloud);
vg.setLeafSize(0.05f, 0.05f, 0.05f);
pcl::PointCloud<pcl::PointXYZ>::Ptr out(new pcl::PointCloud<pcl::PointXYZ>);
vg.filter(*out);
MeshLib:
#include <MRMesh/MRPointsLoad.h>
#include <MRMesh/MRPointCloud.h>
#include <MRMesh/MRUniformSampling.h>
auto cloud = MR::PointsLoad::fromAnySupportedFormat("cloud.ply").value();
MR::UniformSamplingSettings s;
s.distance = 0.05f;
MR::PointCloud out = MR::makeUniformSampledCloud(cloud, s).value();
Estimate point-cloud normals
PCL:
#include <pcl/features/normal_3d.h>
pcl::NormalEstimation<pcl::PointXYZ, pcl::Normal> ne;
ne.setInputCloud(cloud);
pcl::search::KdTree<pcl::PointXYZ>::Ptr tree(new pcl::search::KdTree<pcl::PointXYZ>);
ne.setSearchMethod(tree);
ne.setKSearch(20);
pcl::PointCloud<pcl::Normal>::Ptr normals(new pcl::PointCloud<pcl::Normal>);
ne.compute(*normals);
MeshLib:
#include <MRMesh/MRPointCloudMakeNormals.h>
auto normals = MR::makeNormals(cloud, 20);
cloud.normals = normals;
VTK
MeshLib and VTK exchange data through standard mesh formats: write STL / PLY / OBJ from your VTK pipeline and load it in MeshLib — and the same way back. In Python the bridge is even shorter: vtkPolyData arrays go straight into MeshLib via numpy, no files at all (last example below).
Decimate a mesh
VTK:
#include <vtkPolyData.h>
#include <vtkPolyDataReader.h>
#include <vtkDecimatePro.h>
#include <vtkSmartPointer.h>
auto reader = vtkSmartPointer<vtkPolyDataReader>::New();
reader->SetFileName("in.vtk");
reader->Update();
auto dec = vtkSmartPointer<vtkDecimatePro>::New();
dec->SetInputData(reader->GetOutput());
dec->SetTargetReduction(0.5);
dec->Update();
vtkPolyData* out = dec->GetOutput();
MeshLib:
#include <MRMesh/MRMeshLoad.h>
#include <MRMesh/MRMesh.h>
#include <MRMesh/MRMeshDecimate.h>
auto mesh = MR::MeshLoad::fromAnySupportedFormat("in.stl").value();
MR::DecimateSettings s;
s.maxDeletedFaces = int(mesh.topology.numValidFaces() / 2);
MR::decimateMesh(mesh, s);
Boolean union of two meshes
VTK:
#include <vtkBooleanOperationPolyDataFilter.h>
#include <vtkPolyDataReader.h>
#include <vtkSmartPointer.h>
auto ra = vtkSmartPointer<vtkPolyDataReader>::New(); ra->SetFileName("a.vtk"); ra->Update();
auto rb = vtkSmartPointer<vtkPolyDataReader>::New(); rb->SetFileName("b.vtk"); rb->Update();
auto boolOp = vtkSmartPointer<vtkBooleanOperationPolyDataFilter>::New();
boolOp->SetOperationToUnion();
boolOp->SetInputData(0, ra->GetOutput());
boolOp->SetInputData(1, rb->GetOutput());
boolOp->Update();
vtkPolyData* out = boolOp->GetOutput();
MeshLib:
#include <MRMesh/MRMeshLoad.h>
#include <MRMesh/MRMeshBoolean.h>
auto a = MR::MeshLoad::fromAnySupportedFormat("a.stl").value();
auto b = MR::MeshLoad::fromAnySupportedFormat("b.stl").value();
auto res = MR::boolean(a, b, MR::BooleanOperation::Union);
MR::Mesh out = res.mesh;
Read and write mesh files
VTK:
#include <vtkSTLReader.h>
#include <vtkOBJWriter.h>
#include <vtkSmartPointer.h>
auto reader = vtkSmartPointer<vtkSTLReader>::New();
reader->SetFileName("in.stl");
reader->Update();
auto writer = vtkSmartPointer<vtkOBJWriter>::New();
writer->SetFileName("out.obj");
writer->SetInputData(reader->GetOutput());
writer->Write();
MeshLib:
#include <MRMesh/MRMeshLoad.h>
#include <MRMesh/MRMeshSave.h>
auto mesh = MR::MeshLoad::fromAnySupportedFormat("in.stl").value();
MR::MeshSave::toAnySupportedFormat(mesh, "out.obj");
Moving data in memory (Python)
In Python no files are needed: vtkPolyData arrays go straight into MeshLib via numpy and back.
from vtk.util.numpy_support import vtk_to_numpy
from meshlib import mrmeshpy, mrmeshnumpy
verts = vtk_to_numpy(poly.GetPoints().GetData())
faces = vtk_to_numpy(poly.GetPolys().GetConnectivityArray()).reshape(-1, 3)
mesh = mrmeshnumpy.meshFromFacesVerts(faces, verts)
out_verts = mrmeshnumpy.getNumpyVerts(mesh)
out_faces = mrmeshnumpy.getNumpyFaces(mesh.topology)
Missing your case?
Don't see your library or your operation? Open an issue or start a GitHub Discussion — we'll add the snippet.