Resources Numerical accuracy Calculation policy

Section caps: which elements are closed solids

Decides which elements have a closed surface, so the section view paints a cut face only where there is an inside to fill.

How the precision is kept

Used only to draw the section caps in the 3D view. Vertices are matched by position rounded to 0.1 mm (the viewer's meters × 10,000) and an element is closed when every edge is used an even number of times. The positions are the viewer's 32-bit buffers.

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.

caps-closed.js BimCraft 0.8.1 32-bit, display only 2 calculations Reviewed

Tests tools/bimcraft-test/fixes2-test.mjs

Open or closed surface

32-bit, display only

The elements of a batch whose triangles do not close.

key = round(x × 1e4), round(y × 1e4), round(z × 1e4); open if any edge count is odd

Each coordinate is multiplied by 10,000 and rounded, so vertices in the same 0.1 mm cell are one point. The edge key p × nP + q is an integer and stays exact while it is below 2^53.

  • caps-closed.js
  • lines 26 to 63
  • BimCraft 0.8.1
/** Element ids (triIds[t] for triangle t of `indices`) whose triangles do not make a closed surface. */
export function openElements(positions, indices, triIds) {
  const nV = positions.length / 3;
  // One id per distinct position, so split vertices meet again.
  const canon = new Int32Array(nV);
  const seen = new Map();
  for (let v = 0; v < nV; v++) {
    const k = Math.round(positions[v * 3] * Q) + ',' + Math.round(positions[v * 3 + 1] * Q) + ',' + Math.round(positions[v * 3 + 2] * Q);
    let id = seen.get(k);
    if (id === undefined) { id = seen.size; seen.set(k, id); }
    canon[v] = id;
  }
  const nP = seen.size;
  // Triangles by element.
  const byEl = new Map();
  const nT = indices.length / 3;
  for (let t = 0; t < nT; t++) {
    const e = triIds[t];
    let l = byEl.get(e);
    if (!l) { l = []; byEl.set(e, l); }
    l.push(t);
  }
  const open = new Set();
  const edges = new Map();
  for (const [e, tris] of byEl) {
    edges.clear();
    for (const t of tris) {
      const a = canon[indices[t * 3]], b = canon[indices[t * 3 + 1]], c = canon[indices[t * 3 + 2]];
      for (const [p, q] of [[a, b], [b, c], [c, a]]) {
        if (p === q) continue;   // a degenerate edge adds nothing
        const key = p < q ? p * nP + q : q * nP + p;
        edges.set(key, (edges.get(key) || 0) + 1);
      }
    }
    for (const n of edges.values()) if (n % 2) { open.add(e); break; }
  }
  return open;
}

Elements split over several batches

32-bit, display only

Checks again, over all its parts, an element that is drawn in more than one batch.

The triangles of such elements are gathered from every batch and tested together. The gathered positions go into a new Float32Array; they were 32-bit already.

  • caps-closed.js
  • lines 65 to 88
  • BimCraft 0.8.1
/** Element ids open over every batch they are in (the stencil passes count all batches into one buffer). */
export function openAcross(batches) {
  const open = new Set(), seen = new Map();   // element id → number of batches it is in
  const idsOf = (b) => (b.elems ? b.elems.ids : new Set(b.triIds));
  for (const b of batches) {
    for (const e of openElements(b.positions, b.indices, b.triIds)) open.add(e);
    for (const e of new Set(idsOf(b))) seen.set(e, (seen.get(e) || 0) + 1);
  }
  const again = new Set([...open].filter((e) => seen.get(e) > 1));
  if (!again.size) return open;
  // their triangles from every batch, unindexed (openElements meets the corners again by position)
  const pos = [], ids = [];
  for (const b of batches) {
    const P = b.positions, I = b.indices, T = b.triIds;
    for (let t = 0; t < T.length; t++) {
      if (!again.has(T[t])) continue;
      for (let k = 0; k < 3; k++) { const v = I[t * 3 + k] * 3; pos.push(P[v], P[v + 1], P[v + 2]); }
      ids.push(T[t]);
    }
  }
  const still = openElements(Float32Array.from(pos), Uint32Array.from({ length: pos.length / 3 }, (_, i) => i), ids);
  for (const e of again) if (!still.has(e)) open.delete(e);
  return open;
}

In detail

Method
Positions are quantized with Math.round(x × 1e4) and joined into a key; edges are counted per element; an odd count marks the element open.
Number types
Float32Array positions; integer vertex and edge ids.
Tolerances
A 0.1 mm grid in model meters (Q = 1e4), absolute.
Limits
Two vertices closer than the grid but on either side of a rounding boundary get different keys.

Approach: Display only

Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.

Every policy that takes this approach