Resources Numerical accuracy Calculation policy

Points and faces picked for 3D measuring

Finds the corner, edge point or face under the pointer for the 3D Measure tool, the area of that face, and the bounding box of each element.

How the precision is kept

The picked corners, edge points, face triangles and element boxes are all read from the viewer's vertex buffer, which ifc-model.js stores as 32-bit meters. They are transformed by the model's placement in 64-bit afterwards, but each coordinate has already been through 32-bit, so distances and areas measured in the 3D view start from 32-bit positions.

Open item. Give the Measure tool its points from 64-bit geometry instead of the viewer's 32-bit vertex buffer.

Calculations under this policy

3 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.

viewer3d.js BimCraft 0.8.1 32-bit in part 3 calculations Reviewed

Tests None found by the review

Corners and edges an element offers to Measure

32-bit in part

Collects an element's feature edges and corners in world space for the pointer to snap to.

p_world = M × p_local; corners merged by round(coordinate × 1e4)

The local points come from b.positions, the batch's Float32Array. They are transformed in 64-bit into a Float64Array, and corners that round to the same 0.1 mm are merged; the kept corner has its unrounded coordinates. A measured distance between two snapped points therefore starts from 32-bit positions.

  • viewer3d.js
  • lines 947 to 952
  • BimCraft 0.8.1
const local = runEdges(b.positions, I, b.elems.t0[run], b.elems.tn[run], 30);
const edges = new Float64Array(local.length);
for (let i = 0; i < local.length; i += 3) edges.set(w(local[i], local[i + 1], local[i + 2]), i);
const seen = new Map();
for (let i = 0; i < edges.length; i += 3) { const p = [edges[i], edges[i + 1], edges[i + 2]]; seen.set(p.map((v) => Math.round(v * 1e4)).join(), p); }
fe = { edges, points: [...seen.values()] };

Area of the face under the pointer

32-bit in part

The area of the flat face the pointer is on, in world units.

area = region area × |det M|^(2 ÷ 3)

The region and its area come from faceRegion in measure.js on the batch's 32-bit positions. The scale factor |det|^(2 ÷ 3) equals the area scale only for a placement that scales equally in every direction. The face's triangles are written to a Float32Array for the highlight.

  • viewer3d.js
  • lines 959 to 962
  • BimCraft 0.8.1
const reg = faceRegion(b, run, tri);
const tris = new Float32Array(reg.tris.length * 9);
reg.tris.forEach((t, j) => { for (let k = 0; k < 3; k++) tris.set(w(...[0, 1, 2].map((c) => b.positions[I[t * 3 + k] * 3 + c])), j * 9 + k * 3); });
face = { area: reg.area * Math.abs(M.determinant()) ** (2 / 3), normal: reg.normal, tris };

Bounding box of each element

32-bit in part

The world-space box of each element, over every loaded model.

box = M × (min, max of the element's vertices)

The corners come from boundsMapOf in ifc-vis.js over the batches' 32-bit positions, and the box is then moved by the model's placement in 64-bit. These boxes are what the scan comparison and the ask tools read as element sizes.

  • viewer3d.js
  • lines 340 to 350
  • BimCraft 0.8.1
/** World-space bounds of each element on its own, over every model: Map key → { min, max } (0.8 AC.8, ask-tools.js). */
keyBoundsMap(keys) {
  const out = new Map(), box = new THREE.Box3();
  for (const L of this.layers.values()) {
    for (const [k, bb] of boundsMapOf(L.batches, keys)) {
      box.set(new THREE.Vector3(...bb.min), new THREE.Vector3(...bb.max)).applyMatrix4(L.group.matrix);
      out.set(k, { min: box.min.toArray(), max: box.max.toArray() });
    }
  }
  return out;
}

In detail

Method
The element's feature edges (creases over 30 degrees) and their ends are moved to world space; the pointer snaps to a corner, else an edge, else the face. Face area is the flat region's area times the placement's scale.
Number types
Vertex positions: Float32Array. Transformed points: 64-bit numbers. Face triangles handed to the overlay: Float32Array.
Tolerances
Snap points are merged when they round to the same 1e-4 m.
Limits
The face area is scaled by |determinant|^(2 ÷ 3), which is right only when the placement scales equally in every direction.

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