Resources Numerical accuracy Calculation policy

IFC geometry handed to the apps

Gives each app an element's triangles and the matrix that places them, in meters with Y up, moved near the origin.

How the precision is kept

The placement matrices are 64-bit: the file's placement, a scale by the length unit, an axis turn and a shift to the first element's center. The vertex data itself is returned as the mesh's interleaved Float32Array, in the file's length unit. Code that measures from these vertices (ifc-util.js productTriangles reads them) starts from 32-bit positions.

Open item. Offer the positions in a Float64Array beside the 32-bit buffer, and have measuring code read that one.

Calculations under this policy

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

ifc/api.js BimCraft 0.8.1 32-bit in part 4 calculations Reviewed

Tests tools/ifc-test/test.mjs

Vertex data of one geometry

32-bit in part

Returns a geometry's positions and normals for drawing and for measuring.

The data comes from mesh.interleaved() (geo-mesh.js), which copies the mesh's 64-bit positions into a Float32Array. Every consumer of GetVertexArray, including the triangle reader used for quantities, receives about 7 digits per coordinate.

  • ifc/api.js
  • lines 324 to 332
  • BimCraft 0.8.1
GetGeometry(mid, gid) {
  const mesh = this.geometry(mid).mesh(gid);
  const { vertices, indices } = mesh ? mesh.interleaved() : { vertices: new Float32Array(0), indices: new Uint32Array(0) };
  return {
    GetVertexData: () => vertices, GetVertexDataSize: () => vertices.length,
    GetIndexData: () => indices, GetIndexDataSize: () => indices.length,
    delete() {},
  };
}

Placement matrix of each piece

64-bit

The matrix that takes a geometry's vertices to model space in meters, Y up.

M = C × Y-up × scale(u) × placement

Three 4 × 4 products of 64-bit numbers. The axis turn holds only 0, 1 and −1, so it adds no rounding; the unit scale multiplies by the file's length unit factor.

  • ifc/api.js
  • lines 291 to 300
  • BimCraft 0.8.1
flatMesh(mid, id) {
  const geo = this.geometry(mid);
  const C = this.ensureC(mid);
  const pcs = geo.pieces(id);
  return {
    expressID: id,
    geometries: new Vector(pcs.map((p) => ({ color: p.color, flatTransformation: mul(C, this.toYupMetres(mid, p.matrix)), geometryExpressID: p.geomId }))),
    delete() {},
  };
}

Shift to the origin

64-bit

Finds the translation that brings the model near the origin.

c = (box min + box max) ÷ 2 of the first element; C = translate(−(Y-up × scale(u) × placement × c))

The box center and the matrix products are 64-bit. The shift is whatever the first element with geometry gives, not a rounded value, so undoing it needs the stored matrix; a matrix passed in by the caller is used as given when all sixteen values are finite.

  • ifc/api.js
  • lines 261 to 279
  • BimCraft 0.8.1
ensureC(mid) {
  const m = this.m(mid);
  if (m.C) return m.C;
  m.C = I4();
  if (!m.toOrigin) return m.C;
  const geo = this.geometry(mid);
  for (const p of geo.products()) {
    const pcs = geo.pieces(p);
    if (!pcs.length) continue;
    // move the first element's centre to the origin (metres, Y up), as models far from it lose float precision
    const b = geo.mesh(pcs[0].geomId).bounds();
    if (!b) continue;
    const c = [(b.min[0] + b.max[0]) / 2, (b.min[1] + b.max[1]) / 2, (b.min[2] + b.max[2]) / 2];
    const w = mul(this.toYupMetres(mid, pcs[0].matrix), [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, c[0], c[1], c[2], 1]);
    m.C = translation(-w[12], -w[13], -w[14]);
    break;
  }
  return m.C;
}

File units to meters, Z up to Y up

64-bit

Scales a placement by the file's length unit and turns its axes.

M′ = Y-up × scale(u) × M

u is the length unit factor read by geo-ctx.js (meters per file unit). The scale is one multiplication per matrix entry, so a factor that has no exact binary value brings its rounding in here.

  • ifc/api.js
  • line 281
  • BimCraft 0.8.1
toYupMetres(mid, matrix) { const u = this.geometry(mid).g.lengthUnit; return mul(YUP, mul(scaling(u), matrix)); }

In detail

Method
flat transformation = C × Y-up × scale(length unit) × placement. C moves the center of the first element's box to the origin when the model was opened with COORDINATE_TO_ORIGIN, or is the matrix given by the caller.
Number types
Matrices as arrays of sixteen 64-bit numbers; vertices as a Float32Array of x y z nx ny nz; indices as a Uint32Array.
Limits
The 32-bit rounding applies to each geometry's coordinates before its placement matrix, so a geometry stored far from its own origin loses more than one stored near it.

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