Writes the model as a binary glTF file: each definition's triangles, each placement's matrix, lamps and saved views.
How the precision is kept
Vertex positions, normals and texture coordinates are written as 32-bit floats, the component type glTF uses for them. They are in each definition's own coordinates; the placement matrices are written into the file's JSON as 64-bit numbers. So the 32-bit rounding applies to a point's distance from its definition's origin, not from the model's origin.
Calculations under this policy
7 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.
Writes a primitive's positions, normals, texture coordinates, colors and indices.
The 64-bit corner lists are converted with Float32Array.from when they are written: glTF stores these attributes as 32-bit floats (component type 5126), so the rounding is the format's. Indices are 16-bit up to 65,535 vertices and 32-bit above.
The smallest and largest x, y and z written with the positions.
The minimum and maximum are taken from the 32-bit array after conversion, so they match the stored data exactly; they are the bounds of the rounded positions.
io/gltf-write.js
lines 58 to 69
ModelCraft 0.8.1
const accessor = (arr, type, comp, target, minmax = false) => {
const nc = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4 }[type];
const a = { bufferView: pushView(arr, target), componentType: comp, count: arr.length / nc, type };
if (minmax) {
const min = new Array(nc).fill(Infinity);
const max = new Array(nc).fill(-Infinity);
for (let i = 0; i < arr.length; i++) { const k = i % nc; if (arr[i] < min[k]) min[k] = arr[i]; if (arr[i] > max[k]) max[k] = arr[i]; }
a.min = min; a.max = max;
}
gltf.accessors.push(a);
return gltf.accessors.length - 1;
};
Shared vertices
64-bit
Gives corners with the same position, normal, UV and color one vertex.
key = x, y, z as numbers + normal to 5 decimals + UV + color
Positions must be exactly equal to share a vertex. Normals are compared after rounding to 5 decimals with toFixed, so two normals closer than that share the first one's value; the values written are not rounded by this step.
The 16 numbers go into the JSON as 64-bit values. Entries smaller than 1e-15 are set to 0; that limit is absolute, so a translation that small would also be dropped.
A matrix at a point whose −Z looks along a direction with +Y as near world up as it can be.
f = dir made unit; r = (f × up) made unit; u = r × f
The up reference switches to +Y when |f_z| is above 0.999. Entries under 1e-15 in size are written as 0.
io/gltf-write.js
lines 246 to 254
ModelCraft 0.8.1
/** A node matrix (Z-up model coordinates) at `p` whose −Z looks along `dir` and whose +Y is as near world up (+Z) as it can be. */
function aim(p, dir) {
const f = norm(dir);
let up = [0, 0, 1];
if (Math.abs(f[2]) > 0.999) up = [0, 1, 0];
const r = norm(cross(f, up));
const u = cross(r, f);
return [r[0], r[1], r[2], 0, u[0], u[1], u[2], 0, -f[0], -f[1], -f[2], 0, p[0], p[1], p[2], 1].map((v) => (Math.abs(v) < 1e-15 ? 0 : v));
}
In detail
Method
One mesh per definition and paint; corners that share position, normal, UV and color become one vertex; a root node turns Z up into Y up.
Number types
64-bit numbers until the vertex arrays are built; Float32Array for positions, normals, UVs and colors; 16- or 32-bit integers for indices; 64-bit numbers in the JSON.
Tolerances
A matrix within 1e-12 of the identity is left out; matrix entries under 1e-15 in size are written as 0; a push/pull triangle with an edge cross product not above 1e-18 is skipped (all absolute).
Limits
A coordinate keeps about 7 digits in the file. Geometry drawn far from its definition's origin loses the most.
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.