Writes the open IFC models as an OpenUSD file and works out the transform that places the cameras, sun and lamps of a render hand-off in that file's world.
How the precision is kept
Mesh points are read from the 3D viewer's batches, which are Float32Array, moved by the model's viewer placement in 64-bit and stored again in a Float32Array for the file. They are in meters near the origin; each Site carries a transform of 64-bit numbers that takes them back to the IFC file's coordinates. Storey elevations are written as doubles.
Open item. Write the points from the IFC engine's 64-bit mesh positions instead of the viewer's 32-bit batches.
Calculations under this policy
6 calculations in 2 source files. 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.
usd-bimcraft.jsBimCraft 0.8.1 32-bit in part4 calculationsReviewed
Tests tools/usd/test-apps.mjs
Mesh points of an element
32-bit in part
The vertices of each element as the USD file stores them.
(x′, y′, z′) = P · (x, y, z, 1); stored as (x′, −z′, y′)
pos is the viewer batch's Float32Array; the placement is applied in 64-bit and the result is stored in the Float32Array pts. The exported points are therefore 32-bit values in meters near the viewer's origin; the Site transform carries the offset to the file's coordinates.
usd-bimcraft.js
lines 130 to 141
BimCraft 0.8.1
const pts = new Float32Array(nv * 3), nrm = new Float32Array(nt * 9), idx = new Int32Array(nt * 3), counts = new Int32Array(nt).fill(3);
const subsets = [];
let vo = 0, to = 0;
for (const r of parts) {
const { bt } = r;
const pos = bt.positions, nn = bt.normals, ix = bt.indices;
for (let v = 0; v < r.vn; v++) {
const s = (r.v0 + v) * 3;
const x = pos[s], y = pos[s + 1], z = pos[s + 2];
const wx = P[0] * x + P[4] * y + P[8] * z + P[12], wy = P[1] * x + P[5] * y + P[9] * z + P[13], wz = P[2] * x + P[6] * y + P[10] * z + P[14];
pts[(vo + v) * 3] = wx; pts[(vo + v) * 3 + 1] = -wz; pts[(vo + v) * 3 + 2] = wy;
}
The transform on each Site that returns the exported points to the IFC file's own coordinates.
toFile = A · (P · C)⁻¹ · A⁻¹
Three 4 × 4 products and one inverse in 64-bit. The coordination matrix holds the offset that moved the model to the origin, so the large numbers stay in this matrix and out of the 32-bit points.
usd-bimcraft.js
lines 72 to 76
BimCraft 0.8.1
const P = matrixOf(e.mi) || [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
let C = null;
try { C = [...api.GetCoordinationMatrix(mid)]; } catch { C = null; }
// scene = P·C·file (both Y up), so file = (P·C)⁻¹·scene; in Z up: A·(P·C)⁻¹·A⁻¹
const toFile = C && C.length === 16 ? mmul(A, mmul(inv(mmul(P, C)), AI)) : null;
Storey elevation in meters
64-bit
Each storey's elevation as an attribute of the USD file.
elevation in m = elevation × (mm per file unit ÷ 1000)
lengthToMetre is the unit factor divided by 1000, computed once; the elevation is multiplied by it and written as a double.
The matrix that undoes a model's placement, for the render hand-off.
R⁻¹ = Rᵀ ÷ s²; t⁻¹ = −R⁻¹ · t
The same formula as in usd-bimcraft.js: valid for a rotation with one uniform scale, the scale taken from the first column, a zero s² replaced by 1.
handoff-bim.js
lines 30 to 38
BimCraft 0.8.1
/** The inverse of a rigid placement with a uniform scale (what the viewer's model placements are): as usd-bimcraft.js does it. */
function inv(m) {
const s2 = m[0] * m[0] + m[1] * m[1] + m[2] * m[2] || 1;
const r = [m[0] / s2, m[4] / s2, m[8] / s2, 0, m[1] / s2, m[5] / s2, m[9] / s2, 0, m[2] / s2, m[6] / s2, m[10] / s2, 0, 0, 0, 0, 1];
r[12] = -(r[0] * m[12] + r[4] * m[13] + r[8] * m[14]);
r[13] = -(r[1] * m[12] + r[5] * m[13] + r[9] * m[14]);
r[14] = -(r[2] * m[12] + r[6] * m[13] + r[10] * m[14]);
return r;
}
Viewer scene to the USD stage
64-bit
The transform that takes camera, sun and lamp positions from the viewer's scene to the exported file's world.
toStage = A · (P · C)⁻¹; A alone when the model has no coordination matrix
Two 4 × 4 products and one inverse of 64-bit numbers. Nothing is rounded here.
handoff-bim.js
lines 40 to 52
BimCraft 0.8.1
/**
* scene → composed stage world, for the model whose Site the render is of (the first visible one: the dialog's sun and site come from
* the current model too). In Y up, scene = P·C·file, so file = (P·C)⁻¹·scene. bimUsd writes the viewer's points turned Z up (A) under a
* Site whose own transform is A·(P·C)⁻¹·A⁻¹, so a point's composed position is that transform · A · scene = A·(P·C)⁻¹ · scene.
* A file with no coordination matrix gets no Site transform: composed = A · scene.
*/
export function stageMatrix(entry, matrixOf = () => null) {
let C = null;
try { C = entry && entry.ifc && entry.ifc.api && entry.model ? [...entry.ifc.api.GetCoordinationMatrix(entry.model.modelID)] : null; } catch { C = null; }
if (!C || C.length !== 16) return Y_TO_Z;
const P = matrixOf(entry.mi) || IDENT;
return mmul(Y_TO_Z, inv(mmul(P, C)));
}
In detail
Method
Point = P · v, turned from Y up to Z up as (x, −z, y). Site transform = A · (P · C)⁻¹ · A⁻¹, with the inverse taken as the transposed rotation ÷ s² and the translation moved back. Hand-off transform = A · (P · C)⁻¹.
Number types
Float32Array points and normals, Int32Array indexes; 16-number matrices of 64-bit numbers; elevations as 64-bit numbers.
Tolerances
The Site transform is left out when every entry is within 1e-12 of the identity.
Limits
The inverse assumes a rotation with one uniform scale; a placement with shear or unequal scale would not be inverted correctly. The geometry in the file has the precision of the viewer's 32-bit positions.
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.