The coordinates, normals and placement matrices written when a model is exported as an OBJ or a Collada (.dae) text file.
How the precision is kept
The geometry is computed in 64-bit, but the numbers are rounded when they are written as text: OBJ to 6 decimals of a meter, Collada to 12 significant digits. The rounding of the text sets the accuracy of the file.
Open item. Write OBJ and Collada numbers with 17 significant digits, so the file reads back to the same 64-bit values.
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.
How every coordinate and normal component is written in an OBJ file.
text = x rounded to 6 decimals
toFixed(6) rounds the exported value to 1e-6 (1 micrometer for coordinates in meters); the unary plus then drops trailing zeros, and −0 is written as 0.
A vertex of the model as it goes into the OBJ file.
(x, y, z) → M · p → (x, z, −y)
The vertex is multiplied by its accumulated placement matrix in 64-bit and its axes are swapped to Y up, which is exact. It is rounded only when the line is written.
m3d/io.js
lines 221 to 224
CadCraft 0.8.2
const V = (id) => {
if (!vi.has(id)) { vi.set(id, vBase + vs.length); vs.push(zupToYup(apply(wm, d.verts.get(id)))); }
return vi.get(id);
};
OBJ face normal
64-bit
The flat normal written for a face.
N = Σ pᵢ × pᵢ₊₁, normalized
The normal is a sum of cross products of the world-space corner positions themselves, so far from the origin the terms are much larger than the result. It is then normalized, rounded to 6 decimals, and shared with any other normal that rounds to the same text.
m3d/io.js
lines 229 to 234
CadCraft 0.8.2
// flat normal from the transformed polygon (mirror handled by the reversed winding)
const pts = f.loops[0].map((v) => apply(wm, d.verts.get(v)));
let nn = [0, 0, 0];
for (let i = 0; i < pts.length; i++) nn = add(nn, cross(pts[i], pts[(i + 1) % pts.length]));
let flatI = null;
const flat = () => (flatI ??= N(zupToYup(norm(mirror ? mul(nn, -1) : nn))));
OBJ vertex and normal lines
64-bit
The lines that carry the numbers.
Every component goes through f6: six decimals.
m3d/io.js
lines 244 to 245
CadCraft 0.8.2
for (const p of vs) lines.push('v ' + p.map(f6).join(' '));
for (const n of norms) lines.push('vn ' + n.map(f6).join(' '));
Collada number format
64-bit
How every number is written in a Collada file.
text = x rounded to 12 significant digits
toPrecision(12) keeps 12 significant digits; a value below 1e-12 in size, and −0, are written as 0.
m3d/io.js
line 1376
CadCraft 0.8.2
const fx = (x) => { let v = +(+x).toPrecision(12); if (Object.is(v, -0) || Math.abs(v) < 1e-12) v = 0; return String(v); };
Collada positions and normals
64-bit
The vertex positions and face normals of each definition in the Collada file.
Positions are the definition's own 64-bit coordinates and normals its Newell normals; each number is written with 12 significant digits.
The 4×4 matrix of each placement, written row by row.
The 16 entries are reordered from column-major to row-major and each is written with 12 significant digits, so a rotation's entries are rounded at the twelfth digit.
Decides whether a face is written as one polygon or as triangles in Collada.
every turn (b − a) × (c − b) ≥ −1e-12 × size²
The face is projected to its 2D frame and each corner's turn is compared with a tolerance scaled by the square of the face's largest extent from its first corner.
m3d/io.js
lines 1387 to 1401
CadCraft 0.8.2
/** Is a one-loop face convex (every turn the same way, seen from its front)? */
function convexFace(def, f, n) {
const fr = frame(n);
const ps = f.loops[0].map((v) => to2(fr, def.verts.get(v)));
const k = ps.length;
if (k <= 3) return true;
let size = 0;
for (const p of ps) size = Math.max(size, Math.abs(p[0] - ps[0][0]), Math.abs(p[1] - ps[0][1]));
const tol = -1e-12 * size * size;
for (let i = 0; i < k; i++) {
const a = ps[i], b = ps[(i + 1) % k], c = ps[(i + 2) % k];
if ((b[0] - a[0]) * (c[1] - b[1]) - (b[1] - a[1]) * (c[0] - b[0]) < tol) return false;
}
return true;
}
In detail
Method
OBJ: world coordinates, Y up, one object per placement. Collada: each definition's own coordinates, Z up, meters, with a 4×4 matrix per placement.
Number types
64-bit numbers, written with toFixed(6) (OBJ) or toPrecision(12) (Collada).
Tolerances
Collada: a value below 1e-12 in size is written as 0. OBJ normals that agree to 6 decimals share one entry. Convex test for Collada polygons: −1e-12 × size².
Limits
OBJ coordinates are rounded to 1 micrometer whatever their size. Collada numbers keep 12 digits, three fewer than a 64-bit number holds.
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.