Resources Numerical accuracy Calculation policy

Boolean modifier

Unites, subtracts or intersects the mesh with another object and returns the result as a new mesh.

How the precision is kept

Both meshes are triangulated in 64-bit, but their vertex positions are converted to a Float32Array for the Manifold library and the result's positions are read back from the library's vertex array into a Float64Array. The mesh that shows and exports after a boolean modifier therefore has positions that passed through 32-bit.

Open item. Hand the positions to the boolean in 64-bit and read the result back in 64-bit instead of converting them with Float32Array.from.

Calculations under this policy

2 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

mesh/modifiers.js ModelCraft 0.8.1 32-bit in part 2 calculations Reviewed

Tests tools/modelcraft-test/mesh.mjs

Boolean of two meshes

32-bit in part

Runs the union, intersection or difference and copies the result's vertex positions.

Float32Array.from rounds every input coordinate of both meshes to 32-bit before Manifold sees it. The result is copied from the library's vertex array into a Float64Array, which stores what it is given but cannot restore the digits dropped on the way in, so the positions of the modified mesh carry about 7 digits, not 15.

  • mesh/modifiers.js
  • lines 434 to 442
  • ModelCraft 0.8.1
const a = trianglesOf(mesh), b = ref.tris;
try {
  const { Manifold, Mesh: MMesh } = manifold;
  const mk = (t) => new Manifold(new MMesh({ numProp: 3, vertProperties: Float32Array.from(t.pos), triVerts: Uint32Array.from(t.tri) }));
  const A = mk(a), Bm = mk(b);
  const R = p.op === 'union' ? Manifold.union(A, Bm) : p.op === 'intersect' ? Manifold.intersection(A, Bm) : Manifold.difference(A, Bm);
  const g = R.getMesh(), np = g.numProp, pos = new Float64Array((g.vertProperties.length / np) * 3);
  for (let i = 0; i < pos.length / 3; i++) { pos[i * 3] = g.vertProperties[i * np]; pos[i * 3 + 1] = g.vertProperties[i * np + 1]; pos[i * 3 + 2] = g.vertProperties[i * np + 2]; }
  const out = fromTris(pos, g.triVerts);

Mesh from triangles

64-bit

Builds a mesh from triangle positions and indices, welds coincident vertices and marks sharp edges; used after a boolean and after decimate.

weld within 1e-9; sharp when n₁·n₂ < cos(30°)

Vertices closer than 1e-9 (absolute) are merged with mergeByDistance. An edge is flagged sharp when the dot product of its two faces' unit normals is below cos(deg × π ÷ 180), with deg = 30 by default; this is a flag for shading, positions are not changed by it.

  • mesh/modifiers.js
  • lines 138 to 148
  • ModelCraft 0.8.1
/** A mesh from triangles (positions, indices), sharp where faces bend more than `deg`. */
function fromTris(pos, tri, deg = 30) {
  const pts = [], faces = [];
  for (let i = 0; i < pos.length / 3; i++) pts.push([pos[i * 3], pos[i * 3 + 1], pos[i * 3 + 2]]);
  for (let i = 0; i < tri.length; i += 3) faces.push([tri[i], tri[i + 1], tri[i + 2]]);
  const m = Mesh.fromLists(pts, faces);
  O.mergeByDistance(m, 1e-9, null);
  const lim = Math.cos((deg * Math.PI) / 180), A = [0, 0, 0], B = [0, 0, 0];
  for (let h = 0; h < m.nh; h++) { const t = m.hTw[h]; if (m.heDead(h) || t < h) continue; m.faceNormal(m.hF[h], A); m.faceNormal(m.hF[t], B); if (dot(A, B) < lim) { m.hFl[h] |= FL_SHARP; m.hFl[t] |= FL_SHARP; } }
  return m;
}

In detail

Method
Triangles of both meshes are handed to the Manifold library (union, intersection or difference); the result's triangles become a mesh, vertices at the same place are welded and edges are marked sharp by the angle between their faces.
Number types
Float64Array positions before and after; Float32Array for the vertex properties passed to Manifold; Uint32Array indices.
Tolerances
Weld of the result: 1e-9 (absolute, mesh units). Sharp edge: the faces' unit normals have a dot product below cos 30°.
Limits
Every coordinate of the two inputs is rounded to the nearest 32-bit number before the boolean runs, about 7 significant digits: near 100 m from the mesh's origin neighboring 32-bit numbers are 2⁻¹⁷ m apart, about 7.6 micrometers.

Approach: 32-bit step, flagged

One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.

Every policy that takes this approach