The vertex positions and placements read from an OBJ, glTF or Collada file, turned to the model's Z-up meters.
How the precision is kept
Text formats (OBJ, Collada) are parsed to 64-bit numbers. glTF stores positions as 32-bit floats, so those coordinates arrive with the 7 digits the file holds and are widened to 64-bit. Axis changes are sign changes and swaps, which are exact; unit scale and node transforms are 64-bit matrix products. The points are then welded on a 1e-5 m grid (see Welding of imported geometry).
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 three fields are parsed with parseFloat and kept in 64-bit; the change from Y up to Z up is a swap and a sign change, which is exact. A value that is not finite, or 1e7 or more in size, stops the import.
m3d/io.js
lines 290 to 293
CadCraft 0.8.2
if (tag === 'v') {
const p = [parseFloat(parts[1]), parseFloat(parts[2]), parseFloat(parts[3])];
if (!p.every((x) => Number.isFinite(x) && Math.abs(x) < 1e7)) throw new Error(t('m3d.io.obj_vertex', { line: s.slice(0, 40) }));
verts.push(yupToZup(p));
glTF accessor values
64-bit
The numbers of a glTF buffer, such as vertex positions.
Each component is read with the accessor's own type and stored in a Float64Array. glTF positions are 32-bit floats (getFloat32), so the imported coordinate has the digits the file held. The accessor's normalized flag is not read.
m3d/io.js
lines 385 to 397
CadCraft 0.8.2
const out = new Float64Array(count * nc);
if (a.bufferView === undefined) return { data: out, nc, count };
const v = views[a.bufferView];
if (!v || typeof v !== 'object') fail(t('m3d.io.a_buffer_view_is_missing'));
const buf = buffers[v.buffer];
if (!buf) fail(t('m3d.io.a_buffer_view_points_at_a'));
const vo = v.byteOffset || 0, vl = v.byteLength, ao = a.byteOffset || 0;
if (!Number.isInteger(vo) || !Number.isInteger(vl) || vo < 0 || vo + vl > buf.length) fail(t('m3d.io.a_buffer_view_runs_past_its'));
const stride = v.byteStride || size * nc;
if (!Number.isInteger(stride) || stride < size * nc) fail(t('m3d.io.a_buffer_view_stride_is_wrong'));
if (count && (!Number.isInteger(ao) || ao < 0 || ao + stride * (count - 1) + size * nc > vl)) fail(t('m3d.io.an_accessor_runs_past_its_buffer'));
const dv = new DataView(buf.buffer, buf.byteOffset + vo, vl);
for (let i = 0; i < count; i++) for (let c = 0; c < nc; c++) out[i * nc + c] = dv[getter](ao + i * stride + c * size, true);
glTF node transform
64-bit
The matrix of a glTF node from its matrix, or from its translation, rotation and scale.
M = T · R(q) · S
A 16-number matrix is used as given. Otherwise the rotation matrix is built from the quaternion's components as they are in the file; the quaternion is not normalized, so one that is not unit length gives a matrix that also scales.
m3d/io.js
lines 463 to 475
CadCraft 0.8.2
const nodeMatrix = (n) => {
if (Array.isArray(n.matrix) && n.matrix.length === 16 && n.matrix.every(Number.isFinite)) return n.matrix.slice();
const tr = Array.isArray(n.translation) ? n.translation : [0, 0, 0];
const r = Array.isArray(n.rotation) ? n.rotation : [0, 0, 0, 1];
const s = Array.isArray(n.scale) ? n.scale : [1, 1, 1];
if (![...tr, ...r, ...s].every(Number.isFinite)) fail(t('m3d.io.a_node_transform_is_not_a'));
const [x, y, z, w] = r;
const R = [1 - 2 * (y * y + z * z), 2 * (x * y + z * w), 2 * (x * z - y * w), 0,
2 * (x * y - z * w), 1 - 2 * (x * x + z * z), 2 * (y * z + x * w), 0,
2 * (x * z + y * w), 2 * (y * z - x * w), 1 - 2 * (x * x + y * y), 0, 0, 0, 0, 1];
const S = [s[0], 0, 0, 0, 0, s[1], 0, 0, 0, 0, s[2], 0, 0, 0, 0, 1];
return mmul(translation(tr), mmul(R, S));
};
glTF vertex in the model
64-bit
A glTF vertex moved by its node transforms and turned to Z up.
p′ = M · p, then (x, y, z) → (x, −z, y)
One matrix times point in 64-bit with the accumulated node matrix, then the exact axis change.
Each token is converted with Number(), which reads every digit of the text into a 64-bit value.
m3d/io.js
lines 1519 to 1526
CadCraft 0.8.2
const nums = (s) => {
const str = String(s || '').trim();
if (!str) return [];
const a = str.split(/\s+/);
budget -= a.length;
if (budget < 0) throw new Error(t('m3d.io.this_collada_file_is_too_big'));
return a.map(Number);
};
Collada unit and up axis
64-bit
The change from the file's unit and up axis to Z-up meters.
C = R_up · S(meter); node matrix → C · M · C⁻¹; point → C · p
The unit is parsed with parseFloat and used when it is above 0 and below 1e6, else 1. The up-axis turn has entries 0 and ±1. C⁻¹ comes from the general cofactor inverse, and each node matrix is multiplied on both sides, two matrix products per node.
The matrix of a Collada node from its matrix, translate, rotate and scale elements.
rotate: angle in radians = degrees × π ÷ 180
The elements are multiplied in file order. A matrix is read row by row and reordered; a rotation takes its angle in degrees and converts it once, about the axis given (normalized in vec.js).
One matrix times point with the unit and up-axis matrix; a result that is not finite or is 1e7 or more in size stops the import.
m3d/io.js
lines 1597 to 1598
CadCraft 0.8.2
const p = apply(C, [data[off + i * stride], data[off + i * stride + 1], data[off + i * stride + 2]]);
if (!okPt(p)) fail(t('m3d.io.a_vertex_is_not_a_number'));
In detail
Method
Node transforms as 4×4 matrix products down the scene tree; glTF rotation from its quaternion; Collada unit and up axis as a similarity C applied as C · M · C⁻¹.
Number types
64-bit numbers; glTF accessors read with their own component type into a Float64Array.
Tolerances
A coordinate of 1e7 or more is refused. A Collada unit is used when 0 < meter < 1e6.
Limits
glTF positions carry 32-bit precision from the file. A glTF quaternion is used as given, without normalizing. Integer accessors marked normalized are read as plain integers.
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.