Skip to content
Shapemetry

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. Call Mesh.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 full TriangleMesh wire (positions/normals/indices/uvs/groups/edges) as a JSON string, from a live handle.
  • Mesh.tessellate(meshGeometry) — one-shot: takes the n-gon Mesh document ({ positions, faces }, what Mesh.load/Mesh.dump speak), loads and frees an arena handle inside the call, and returns a parsed TriangleMesh. See Tessellate for display.
const json = Mesh.tessellateJson(mesh) // string — JSON.parse when you need the object

Round-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 it

Weld 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

Last updated: 📖 1 min readEdit on GitHub