The camera, sun, lamps, materials, exposure and frame numbers written into the package that an outside render engine reads.
How the precision is kept
The values are worked out in 64-bit and then every number written to the manifest is rounded to a fixed count of decimals: 7 for matrices, 6 for positions and directions, 5 for colors and material values, 4 for the view angle and exposure, 3 for lumens. The rounding is absolute, not scaled to the size of the number.
Open item. Write the manifest's numbers with their full 64-bit digits instead of a fixed count of decimals.
Calculations under this policy
8 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.
The world and projection matrices are rounded to 7 decimals, the position and directions to 6, the aspect and orthographic height to 5 and the view angle to 4. The division by the projection term is not guarded against zero.
The sun's direction and strength, and each lamp's lumens, color, position, aim and size.
Lumens are rounded to 3 decimals, colors to 5, positions, directions and sizes to 6, a spot's angle to 3. The sun's diameter is written as the fixed value 0.53°. Only the 64 lamps with the most lumens are written.
render/handoff.js
lines 128 to 134
BimCraft 0.8.1
const sun = scene.sun ? { on: true, dir: direction(M, scene.sun), intensity: round(base.sun, 4), tint: base.tint, diameterDeg: 0.53 } : { on: false };
// lamps that are on, as the renderer draws them (the brightest 64), their IES profile beside the USD
const lamps = [], iesFiles = {};
for (const L of (scene.lights || []).filter((x) => x && x.on !== false && x.lumens > 0).sort((a, b) => b.lumens - a.lumens).slice(0, MAX_LAMPS)) {
const o = { name: String(L.name || 'Lamp').slice(0, 120), kind: L.kind === 'spot' || L.kind === 'area' ? L.kind : 'point', lumens: round(L.lumens, 3), color: hexToLinear(typeof L.color === 'string' ? L.color : '#ffe7c4'), p: point(M, L.p || [0, 0, 0]), dir: direction(M, L.dir || [0, 0, -1]) };
if (L.kind === 'spot') o.angle = round(num(L.angle, 90), 3);
if (L.kind === 'area') o.size = vec(L.size, 2, 0.5);
Scene, exposure and white balance entries
64-bit
The ground height, sample count, exposure in stops, shutter time and white-balance gains.
The ground height is rounded to 5 decimals, the exposure to 4, the shutter time to 6 and the white-balance gains to 5. A shutter time that is not a short decimal, such as 1/60 s, is therefore written rounded.
The start and end time, frame rate, frame count and the camera matrix of each frame.
Start and end are rounded to 4 decimals, the frame rate to a whole number from 1 to 240, and each frame's camera matrix to 7 decimals after it is moved to the stage.
The OpenPBR parameters written for each material, where they differ from the defaults.
A value is compared with the default within 1e-9 (1e-6 per color channel) and written only if it differs. Written values are rounded to 5 decimals.
render/handoff.js
lines 54 to 85
BimCraft 0.8.1
/**
* The OpenPBR values of one of the apps' material keys (looks.js looks, materials.js `mat:` ids, `img:` textures, the ground) as
* named parameters, only where they differ from OpenPBR's defaults (USD-CONVENTIONS § 7). Colours are linear Rec.709 triples.
* The same mapping pt-pack.js `materialRecord` packs for our path tracer (RENDER-SPEC § 5.1), so the engines start from one material.
*/
export function openpbrFor(key, settings, scene) {
let p;
if (key === '__ground') p = { base_color: hexToLinear(SKIES[settings && settings.sky]?.plain ? '#f4f4f2' : '#cbc6bb'), specular_roughness: 1 };
else if (typeof key === 'string' && key.startsWith('mat:') && libMaterial(key.slice(4))) {
const m = libMaterial(key.slice(4));
if (m.pbr) p = { ...m.pbr };
else {
p = { base_color: hexToLinear(m.color), specular_roughness: m.rough, base_metalness: m.metal || 0 };
if (m.glass) Object.assign(p, { transmission_weight: m.glass, transmission_color: hexToLinear(m.color), specular_roughness: 0.02, geometry_thin_walled: true });
}
} else if (typeof key === 'string' && key.startsWith('img:')) {
const avg = /#[0-9a-f]{6}$/i.exec(key);
p = { base_color: hexToLinear(avg ? avg[0] : '#b4b4b4'), specular_roughness: 0.7 };
} else {
const L = lookFor(key, settings, scene && scene.defaultColor, scene && scene.looks && scene.looks[key]);
p = { base_color: hexToLinear(L.color), specular_roughness: L.rough, base_metalness: L.metal };
if (L.glass) Object.assign(p, { transmission_weight: L.glass, transmission_color: hexToLinear(L.color), geometry_thin_walled: true });
}
const out = {};
for (const [k, v] of Object.entries(p)) {
const d = OPENPBR_DEFAULTS[k];
if (d === undefined) continue;
const same = Array.isArray(d) ? Array.isArray(v) && v.length === d.length && v.every((x, i) => Math.abs(x - d[i]) < 1e-6) : v === d || (typeof v === 'number' && typeof d === 'number' && Math.abs(v - d) < 1e-9);
if (!same) out[k] = Array.isArray(v) ? v.map((x) => round(x, 5)) : typeof v === 'number' ? round(v, 5) : v;
}
return out;
}
In detail
Method
Matrix products move the scene's camera, points and directions to the USD stage. Colors are decoded from sRGB to linear with the IEC 61966-2-1 curve. The view angle comes from the projection matrix.
Number types
64-bit numbers, written as JSON text.
Tolerances
A projection is taken as orthographic when |P[15] − 1| < 1e-9 and |P[11]| < 1e-9. A material value is left out when it is within 1e-9 of the default (1e-6 per channel for a color).
Limits
Rounding to 7 decimals keeps fewer significant digits for small matrix terms. Shutter times such as 1/60 s are written as 6-decimal values.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.