Mesh and Memory
Two memory models coexist in @huukhanhnguyen/geometry:
- Documents — JSON strings (
Brep.*,NurbsCurve.*,Path2d.*). Owned by JS garbage collection. Nothing to free. - Arena handles — integer ids into the wasm arena (
Mesh.box,Mesh.load, …). Owned by you. CallMesh.free(id)or the memory leaks until the wasm instance dies.
Full background: Concepts: Memory model.
The handle lifecycle
import { Brep, Mesh } from '@huukhanhnguyen/geometry'
const mesh = Mesh.box(100, 100, 100) // handle: number
try {
console.log(Mesh.faceCount(mesh)) // 6
} finally {
Mesh.free(mesh) // always — try/finally is the idiom
}Everything that returns a number from the Mesh namespace is a handle you own: Mesh.box, Mesh.cone, Mesh.load, Mesh.clone, Mesh.fromTriangleMesh, Mesh.fromBrep, …
In-place edits: bevel and subdivide
Handle operations mutate the mesh in the arena:
const mesh = Mesh.box(2, 2, 2)
try {
// Bevel every edge: radius 5, 4 segments
const all = Uint32Array.from({ length: Mesh.edgeCount(mesh) }, (_, i) => i)
Mesh.bevel(mesh, all, 5, 4)
// Catmull–Clark subdivision — each level turns every n-gon into n quads.
// Mutates the handle; Mesh.clone first if you need the control mesh.
Mesh.subdivide(mesh, JSON.stringify({ levels: 2 })) // '{}' or '' = defaults
console.log(Mesh.faceCount(mesh)) // 6 → 24 → 96
} finally {
Mesh.free(mesh)
}Getting triangles out
Mesh.tessellateJson(handle)— the fullTriangleMeshwire (positions/normals/indices/uvs/groups/edges) as a JSON string, from a live handle.Mesh.tessellate(meshGeometry)— one-shot: takes the n-gonMeshdocument ({ positions, faces }, whatMesh.load/Mesh.dumpspeak), loads and frees an arena handle inside the call, and returns a parsedTriangleMesh. See Tessellate for display.
const json = Mesh.tessellateJson(mesh) // string — JSON.parse when you need the objectRound-tripping a mesh through the document form is also how you persist one:
const saved = Mesh.dump(mesh) // Mesh JSON string
const restored = Mesh.load(saved) // new handle — you own itWeld and triangle-soup utilities
Weld lives on the TriangleMesh namespace, not on Mesh — it works on packed soups (Float64Array), no handle involved:
import { Brep, TriangleMesh } from '@huukhanhnguyen/geometry'
const packed = TriangleMesh.weld(new Float64Array(positions), new Uint32Array(indices))
// tolerance defaults to 1e-4; the packed soup's header is
// [positionFloatCount, indexCount] followed by positions then indices
const smoother = TriangleMesh.loopSubdivide(new Float64Array(positions), new Uint32Array(indices), 2)
// Loop subdivision for triangle soups — a different algorithm from Mesh.subdivide (Catmull–Clark)When free actually matters
- Long-lived process (server, CLI batch): always. The arena grows monotonically until the wasm instance is torn down.
- One-shot script that exits: leaks die with the process — still use
try/finally, it costs one line. - Hot loops (boolean per frame, subdivision previews): reuse or free aggressively; handle churn is the common OOM cause.
Where next
- Concepts: Memory model — why the two models exist.
- Mesh formats (STL/OBJ/PLY) — documents across the file boundary.