In short, no jargonfor non-developersRead the summaryClose
What it is
A tool that analyses the shape of 3D objects (a ring, a sphere, a Möbius strip): how many holes they have, where they bend, how their shape builds up when they are filled from the bottom up.
What it is for
Such measurements are used in medical imaging, simulation and industrial design, to compare and check shapes.
What it shows
My core trade, as a PhD in computer graphics: demanding mathematics turned into a fast, checked tool you handle with the mouse.
Topology (C++20, half-edges) → C API (a single interface): invariants, curvature
C API (a single interface) → WebAssembly (Emscripten)
WebAssembly (Emscripten) → Viewer (three.js, on the page)
C API (a single interface) → Python API (FastAPI, ctypes): shared library
Spin a mesh
Colour shows the Gaussian curvature at each vertex. Invariants and curvature are computed by the project's C++ code, compiled to WebAssembly, in your browser.
Samples:
0
saddle (K < 0)dome (K > 0)
Quantile scale: half of the vertices have a smaller curvature than the middle tick on each side, 90 % smaller than the outer one.
minimum
saddle
maximum
Like ParaView's Elevation filter: the mesh is coloured by its height, and the slider keeps only the part under the threshold (the triangles whose three vertices are lower). A point is critical when its lower link (its lower neighbours) is not in one piece: no lower neighbour for a minimum, several pieces for a saddle. Computed by the topology C++ code, in WebAssembly.
Persistence diagram
Guided explanation
The height threshold rises step by step; at each step, the class that is born or dies is highlighted.
Hover, touch or browse with the keyboard (Tab, then the arrows) a bar, a point or a row of the table: the vertex where the class is born and the one where it dies are circled on the mesh, and the height threshold moves between them.
component (H0)
loop (H1)
cavity (H2)
Persistence barcode
The same diagram as a scatter plot
Why does the loop not close at the second saddle going up? Persistence looks at everything under the threshold (the sublevel sets): between the two saddles of the standing torus, that is a cylinder in one piece, and the second saddle adds a handle to it instead of closing the loop. The Reeb graph only looks at the slice at the threshold (the level set): two circles between the saddles, which meet at the second one. Extended persistence reconciles both: on the way back down, it closes the loop at the second saddle.
Show the pairs
Dimension
Height where it is born
Height where it dies
Persistence
Each component (H0), loop (H1) or cavity (H2) of the sublevel sets is born at one critical point and dies at another going up; those that never die going up, as many as the Betti numbers, are closed on the way back down (extended persistence: after the sublevel sets, the mesh relative to what lies above the threshold). The length of the bar, its persistence, tells real features from noise: the slider greys the pairs shorter than the threshold and keeps on the mesh only the critical points of the remaining pairs. Computed by the topology C++ code, in WebAssembly: boundary matrix reduction over Z/2, then a union-find in cohomology.
Reeb graph
Each component of each level set is shrunk to a point: nodes are where they appear, split, merge or vanish, and each arc follows one of them, drawn through the centres of the level sets. On a closed orientable surface, the graph has as many loops as the genus, whatever the direction. Computed by the topology C++ code, in WebAssembly; inside the mesh, made transparent, it is cut at the height threshold.
Drag to rotate, scroll or pinch to zoom; hover a point to read its curvature. The gizmo at the bottom left follows the camera: x in red, y in green, z in blue.
On the boundary the angle defect measures how the boundary itself bends: boundary vertices take the colour of their interior neighbours.
Computing times
Measured on a standing torus, best of 3 runs, on one thread of an Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz: native (C++ built in Release by GCC) and WebAssembly (the same code, in Node, as in the page). Produced by projects/topologie/scripts/bench.mjs.
Triangles
OBJ file
Reading (Wasm)
Height
Persistence
Reeb graph
Extended persistence
10,000
0.3 MB
24 ms
2 ms native · 2 ms Wasm
8 ms native · 10 ms Wasm
5 ms native · 6 ms Wasm
15 ms native · 17 ms Wasm
30,000
1.0 MB
75 ms
5 ms native · 6 ms Wasm
26 ms native · 37 ms Wasm
14 ms native · 19 ms Wasm
57 ms native · 60 ms Wasm
100,000
3.5 MB
251 ms
24 ms native · 24 ms Wasm
116 ms native · 174 ms Wasm
56 ms native · 68 ms Wasm
231 ms native · 248 ms Wasm
300,000
10.8 MB
788 ms
76 ms native · 76 ms Wasm
502 ms native · 736 ms Wasm
178 ms native · 204 ms Wasm
945 ms native · 968 ms Wasm
1,000,000
37.6 MB
2.72 s
281 ms native · 280 ms Wasm
2.38 s native · refused by the page
648 ms native · refused by the page
4.32 s native · refused by the page
What about a GPU? It would speed up what is done vertex by vertex or triangle by triangle, independently: the height (one dot product per vertex), sorting the vertices (parallel sort), the lower link of each vertex, and H0 persistence by parallel merges. The boundary matrix reduction, though, handles the columns one after the other (each depends on the previous ones), and the Reeb graph relies, like H0 persistence, on union-finds with unpredictable accesses: a GPU would spend its time synchronising. Yet persistence dominates (2.38 s for a million triangles natively) and it is the part that suits a GPU least: no GPU version (D49).
Why 32 MB? Before any computing, reading an OBJ of 37.6 MB (a million triangles) takes 2.72 s in WebAssembly, on the page’s thread, which stays frozen meanwhile; WebAssembly memory climbs to about 190 MB (750 MB for 4 million triangles), capped at 1 GB by the build and at 4 GB in wasm32, and a phone has less. The limit keeps the wait under two and a half seconds; lifting it would take streamed reading in a Web Worker and more memory (D49). Above 300,000 triangles, the page also refuses persistence, its extended version and the Reeb graph: half a second at that cap, one second for extended persistence.
Tech stack
C++
Three.js
GoogleTest
Who does what
C++
The half-edge structure, the invariants (Euler, genus, boundaries, orientability), the Gaussian curvature, the height, its critical points (discrete Morse theory) its persistence (boundary matrix reduction) and its Reeb graph; compiled to WebAssembly by Emscripten for the viewer, and to a shared library for the Python API.
Three.js
The project page viewer: the mesh coloured by its curvature or its height, filtered under a threshold, its critical points marked, its Reeb graph drawn inside, turned with the mouse or a finger.
GoogleTest
The C++ tests: invariants of the reference meshes, curvature against theoretical values, critical points (sphere, torus, monkey saddle, indices summing to χ), persistence (Betti numbers, stability), Reeb graph (loops equal to the genus), on Linux, Windows and macOS.
Definition of Done
Code reviewed, diff by diff
Unit and integration tests green
Green CI on Linux, Windows and macOS when compiled