Declarative Specification for Unstructured Mesh Editing Algorithms

1NYU Courant Institute of Mathematical Sciences SIGGRAPH Asia 2022, ACM TOG 41(6) Art.251
Declarative DSL -> four algorithms: harmonic triang, Qslim, isotropic remesh, TetWild

Declarative – one high-level description drives isotropic remeshing, simplification, harmonic triangulation, and robust TetWild-style filling.
Fig. 1 from paper: <18–32 LoC per algorithm vs ~0.5–3k legacy.

Abstract

Unstructured mesh editing – remeshing, simplification, subdivision, repair – underpins all geometry processing. Every new algorithm re-implements the same tedious core: mesh accessors, link conditions, envelope tests, attribute propagation, conflict detection for parallelism, and manifold bookkeeping. Bugs are common, concurrency is ignored, scaling to 10M elements ad-hoc.

We introduce a declarative specification DSL that separates what to achieve from how the mesh is maintained. The author declares Invariants (must hold after every local op), a Scheduler (priority of ops), and Operation Descriptors (collapse/split/swap/smooth + attribute transfer). The runtime – now the Wild Meshing Toolkit (WMTK) – guarantees invariants, rolls back failed ops, transfers attributes, and provides up to 10× speedup on 16 cores with deterministic output. Four classic lines of C++ express what previously required 1–3k LoC.

One abstraction many algorithms

Why Declarative?

Typical loop (libigl/OpenMesh style):

while (!Q.empty()) {
  auto [op, eid] = Q.pop();
  if (is_removed(eid)) continue;
  if (!link_condition(eid)) continue;
  if (!check_inversion(eid)) continue;
  if (collision_with_envelope) continue;
  lock_one_ring(eid); // hand-rolled
  for (v: one_ring) cache_attr...
  collapse(eid); // manually splice
  for (v: new_ring) recompute, push Q
  unlock();
}

Every project rewrites link_condition, check_inversion, attr lerp, locking. Miss one envelope test → self-intersect. Lock order → deadlock.

Our vision – 15 LoC:

// declarative – no half-edge juggling
auto m = TriMesh::from_file("bunny.obj");
auto invariants = { Manifold, NoInversion,
  Envelope(1e-3*diag), LinkCondition };
auto scheduler = EdgeLengthScheduler<>();
auto op_collapse = EdgeCollapse{
  .energy = [](Edge e){ return -e.length(); },
  .precondition = [](Edge e){
    return e.length() < 4.0/3*target; },
  .transfer = { .pos=Linear, .uv=Wachspress }
};
wmtk::run(m,invariants,scheduler,op_collapse);

Change 4/3 to sqrt(2) and you have a new paper.

System layers DSL to runtime
Fig. 2 – System layers: DSL → registry → WMTK runtime → parallelism. Colors match Bulma palette blue.

Language Design – Operation Descriptors & Invariants as Functors

System layers: DSL -> registry -> WMTK runtime -> parallelism

Mesh Abstraction Erased

We expose simplex tuples (vertex, edge, face, volume) – not half-edges. User code never sees pointers. WMTK stores hashed simplex-to-simplex maps; cache-friendly 8-byte handles. No more splicing hell.

OpDescriptor

struct OpDescriptor {
  std::function<double(Simplex)> energy;
  std::function<bool(Simplex)> can_apply;
  std::function<void(Simplex, AttributeTransfer&)> execute;
  std::vector<Invariant> invariants_before, invariants_after;
};

We pre-instantiate 6 topology ops: EdgeCollapse, EdgeSplit, EdgeSwap, VertexSmooth, FaceSplit, TetSplit. User provides lambda for can_apply and attribute lerp.

  • pos, uv, color, mip-map level are wmtk::Attribute<T> – split/collapse requires linear/harmonic extension, auto-generated.
  • Physics-aware length metric synthesizes Jacobian via Eigen::AutoDiff – never hand-derivate again.

Invariants as Composable Functors

struct Invariant {
  virtual bool before(const Simplex&) const = 0;
  virtual bool after(const Simplex&) const { return true; }
  virtual bool strictly_after(const Primitive&) const;
};

Library ships:

  • ManifoldInvariant – link condition via Euler char of link
  • NoInversionInvariant – signed tet/area >0 filtered by exact predicates (orient2d/3d via Shewchuk)
  • EnvelopeInvariant – AABB tree of input surface, ε-envelope test
  • UVNoFoldInvariant – flip-free UV under operation
  • QualityInvariantBound – AMIPS < threshold, scaled Jacobian
auto safe = make_invariant_collection(
  ManifoldEdge(), NoInversionTet(), EnvelopeSurface{input, 1e-6}
);

Scheduler, Parallel Coloring & Rollback

Pipeline: op tries -> invariant check -> rollback if fail -> attribute transfer -> requeue 1-ring – flowchart text readable
  • Scheduler = Active Set Learning. Two-level priority: geometric energy first, reuse score second for cache locality. update_after_success(op) only re-queues 1-ring (<2% visited/iter).
  • Partitioned locking. Vertices painted by greedy distance-1 coloring of dual graph; each color processes in parallel, no two neighboring ops co-run.
  • Speculative rollback. If invariant fails after op, mesh rewound via copy-on-write log (~12 bytes per simplex change).
  • Determinism. Sorted tie-break by simplex id; same seed → bitwise-identical mesh on same thread count.
  • Envelope safety. TetWild path wraps input surface AABB tree and tests moved vertices against ε-envelope using exact orient predicates.

Code Walkthrough – Isotropic Remeshing (30 lines)

#include <wmtk/TriMesh.h>
using namespace wmtk;

int main(int argc, char** argv) {
  TriMesh mesh(argv[1]);
  double target = std::stod(argv[2]);

  auto long_edges = [&](Edge e){ return e.length() > 4*target/3; };
  auto short_edges = [&](Edge e){ return e.length() < 4*target/5; };

  auto invs = std::make_shared<InvariantCollection>(mesh);
  invs->add(std::make_shared<ManifoldInvariant>(mesh));
  invs->add(std::make_shared<InversionInvariant>(mesh));

  Scheduler scheduler(mesh);
  scheduler.run_operation<EdgeSplit>(mesh, invs, long_edges, [](auto){return true;});
  scheduler.run_operation<EdgeCollapse>(mesh, invs, short_edges, [](auto e){return e.length();});

  auto quality_improve = [&](Edge e){
    double before = min_quality(one_ring(e));
    double after = min_quality_if_swapped(e);
    return after > before;
  };
  scheduler.run_operation<EdgeSwap>(mesh, invs, quality_improve, quality_improve);
  scheduler.run_operation<VertexSmooth>(mesh, invs, [](auto){return true;},
    [](Vertex v){ return laplacian_smooth(v); });

  mesh.save("out.obj");
}

Qslim – 14 lines variant

Attribute<Matrix4d> quadric = compute_quadric(mesh);
auto qslim_err = [&](Edge e){ return quadric[e.v0()] + quadric[e.v1()]; };
auto collapse = EdgeCollapseOp{ .pos = [&](Edge e){ return optimal_qslim_pos(e); } };
wmtk::run(mesh, {Manifold(), LinkCondition()}, EdgeQuadricScheduler{qslim_err}, collapse, /*stop*/ target_faces=5000);

Results – Thingi10K Scaling

Thingi10K scaling 10x on 16 cores, success >99.8% – white bg true composite
AlgorithmOps usedLoC (ours)Est. legacyInvariantsNotes
Harmonic Triangulationcollapse + swap + smooth18~700manifold + no-inv + env 3%Delaunay-like = cot Lapl.
Qslim Simplificationcollapse14~500manifold + link condquadric error scheduler
Isotropic Remeshing (Botsch 2004)4 ops27~1500AMIPS <1004/3, 4/5 thresholds
Robust TetWild-stylecollapse, split, swap32~3000envelope + inv + qual>0.1inherits parallelism free

Success (manifold, no-inversion)

9984/10000 ours vs 8721 libigl baseline – Thingi10K (10k models)

Time geomean (8 threads)

4.2s vs 31s baseline – peak RSS <1.8× input

Scaling

10× on 16 cores, deterministic tie-break

Applications → Wild Meshing Toolkit

This paper is the seed of Wild Meshing Toolkit (WMTK), now a community C++17 library with Python bindings pip install wildmeshing. The DSL ideas persist as wmtk::operations::Operation + wmtk::invariants.

  • Surface repair – removing self-intersections for 3D printing
  • Volume adaptivity – adaptive tetrahedral meshing with sizing field from SDF
  • Non-manifold, open-boundary, mixed tri/tet – generality from simplex erasure
git clone https://github.com/wildmeshing/wildmeshing-toolkit
cd wildmeshing-toolkit; mkdir build && cd build
cmake .. -DWMTK_APP_ISOTROPIC_REMEShing=ON
make -j && ./wmtk_app -j isotropic_remeshing_bunny.json
import wildmeshing as wm
m = wm.TriMesh("bunny.obj")
wm.isotropic_remeshing(m, target_edge=0.02, envelope=1e-3)
m.save("out.obj")

Links & Dataset

BibTeX

@article{jiang2022declarative,
  title     = {Declarative Specification for Unstructured Mesh Editing Algorithms},
  author    = {Jiang, Zhongshi and Dai, Jiacheng and Hu, Yixin and Zhou, Yunfan and Dumas, J{\'e}r{\'e}mie and Zhou, Qingnan and Bajwa, Gurkirat Singh and Zorin, Denis and Panozzo, Daniele and Schneider, Teseo},
  journal   = {ACM Transactions on Graphics (Proc. SIGGRAPH Asia 2022)},
  volume    = {41},
  number    = {6},
  pages     = {251:1--251:14},
  year      = {2022},
  publisher = {ACM},
  doi       = {10.1145/3550454.3555513},
  url       = {https://dl.acm.org/doi/10.1145/3550454.3555513}
}

Changelog

  • 2022-11-30 – ACM TOG publication.
  • 2023-07 – Ported core to wildmeshing-toolkit mainline.
  • 2026-08 – This deep project page rebuilt with Bulma layout on jiangzhongshi.github.io. Assets teaser.png, method.png, pipeline.png, results.png, featured.jpg.

Website template based on the Nerfies project page. If you reuse their source code, please credit them appropriately.