Opens ModelCraft's own USD files back to the exact model, takes in changes another program made, and imports any other USD scene in meters with Z up.
How the precision is kept
A file ModelCraft saved carries each definition twice: its exact JSON with 64-bit points, and native USD points written as floats for other programs. On opening, the exact data are used unless the native points no longer equal the model's points rounded to 32-bit; then the definition is rebuilt from the native points, which have the precision the file stores. Other scenes are transformed in 64-bit through the stage's frame.
Calculations under this policy
6 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.
Decides whether a definition's native USD points still match the model's own.
changed if any fround(own coordinate) ≠ file coordinate, or the counts differ
Each of the model's 64-bit coordinates is rounded to 32-bit with Math.fround and compared for exact equality with the file's value, with no tolerance. An unchanged file therefore opens from the exact 64-bit data, and any change of one 32-bit step is seen.
usd-modelcraft.js
lines 200 to 215
ModelCraft 0.8.1
/** The native points no longer the definition's own (as floats)? */
function geometryChanged(g, dj) {
const pv = valueAt(g.props.get('points'));
if (!pv) return true;
const pts = pv.v;
let mine;
if (dj.kind === 'mesh' && dj.mesh) {
try { mine = Mesh.fromJSON(dj.mesh).toArrays().p; } catch { return true; }
} else {
mine = [];
for (const v of dj.verts || []) mine.push(v[1], v[2], v[3]);
}
if (mine.length !== pts.length) return true;
for (let i = 0; i < pts.length; i++) if (Math.fround(mine[i]) !== pts[i]) return true;
return false;
}
Mesh from the native points
64-bit
Rebuilds a definition that was changed elsewhere from the file's native points and faces.
The file's points are copied into a Float64Array without any arithmetic. They are the native points that geometryChanged compares with 32-bit values, so the rebuilt mesh has the file's 32-bit precision, not the model's.
usd-modelcraft.js
lines 216 to 227
ModelCraft 0.8.1
function meshFromPrim(g) {
const pv = valueAt(g.props.get('points')), c = valueAt(g.props.get('faceVertexCounts')), x = valueAt(g.props.get('faceVertexIndices'));
if (!pv || !c || !x) return null;
const p = pv.v, n = p.length / 3;
for (const k of x.v) if (k < 0 || k >= n) return null;
let s = 0;
for (const k of c.v) s += k;
if (s !== x.v.length) return null;
const m = new Mesh();
m._bulk(Float64Array.from(p), n, Int32Array.from(c.v), Int32Array.from(x.v));
return m;
}
Was a placement moved elsewhere
64-bit
Decides whether a placement's matrix in the file differs from the model's and takes the file's if so.
moved if any |mₖ(file) − mₖ(own)| > 10⁻⁷ × max(1, |mₖ(own)|)
The tolerance is relative for entries above 1 and absolute (1e-7) below. A placement moved by less keeps its own matrix; one moved by more takes all 16 entries from the file.
usd-modelcraft.js
lines 184 to 185
ModelCraft 0.8.1
const m = localMatrix(c).m;
if (!Array.isArray(i.m) || m.some((x, k) => Math.abs(x - i.m[k]) > 1e-7 * Math.max(1, Math.abs(i.m[k])))) { i.m = m; report.moved++; }
Placements through the stage's frame
64-bit
Brings a placement matrix from the file's units and up axis into meters with Z up.
M′ = F · M · F⁻¹
The frame is inverted once and each placement is multiplied on both sides in 64-bit. The inverse comes from the USD library's invert.
usd-modelcraft.js
lines 241 to 242
ModelCraft 0.8.1
const F = scene.frame, FI = invert(F);
const conj = (m) => mul(F, mul(m, FI));
Imported mesh points
64-bit
The points of an imported mesh in meters with Z up.
p′ = F · p (rotation, scale and translation of the frame)
Each point is transformed with nine products and three sums into a Float64Array. The source points are as precise as the file stores them.
usd-modelcraft.js
lines 333 to 339
ModelCraft 0.8.1
const p = new Float64Array(P.length);
for (let i = 0; i < n; i++) {
const x = P[i * 3], y = P[i * 3 + 1], z = P[i * 3 + 2];
p[i * 3] = F[0] * x + F[4] * y + F[8] * z + F[12];
p[i * 3 + 1] = F[1] * x + F[5] * y + F[9] * z + F[13];
p[i * 3 + 2] = F[2] * x + F[6] * y + F[10] * z + F[14];
}
Lamp output from a USD light
64-bit
The lumens of an imported lamp when the file does not carry ModelCraft's own value.
The scale is the length of the first column of the light's matrix, so a light scaled differently along its axes uses the first axis only. The area has a floor of 1e-6. A lumens value ModelCraft wrote itself is used unchanged when it is a finite number.
Exact comparison of Math.fround(own point) with the file's point; placement matrices compared with a relative tolerance; import through the frame F as F·p and F·M·F⁻¹.
Number types
64-bit numbers and Float64Array for imported points; the native USD points being compared are 32-bit.
Tolerances
A placement counts as moved when any matrix entry differs by more than 1e-7 × max(1, |entry|).
Limits
A definition changed in another program opens from 32-bit points. A placement moved by less than the tolerance keeps its old matrix.
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.