Resources Numerical accuracy Calculation policy

PLY import and export

Reads and writes PLY meshes with their colors, in meters with Z up.

How the precision is kept

On import the points go into a Float64Array in whatever type the file declares, so a file with double coordinates keeps them. On export the header declares x, y and z as 'float' and the world coordinates are written with setFloat32, although the format also allows 'double'. An exported point therefore keeps about 7 digits of its distance from the model's origin.

Open item. Declare x, y and z as 'double' in the exported header and write them with setFloat64.

Calculations under this policy

5 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.

io/ply.js ModelCraft 0.8.1 32-bit in part 5 calculations Reviewed

Tests tools/modelcraft-test/meshfiles.mjs

Reading one number

64-bit

Reads a value of the type the header names, from text or from binary.

ASCII values are converted with Number to 64-bit. Binary values are read through a DataView in their declared type, so a 'double' stays 64-bit and a 'float' is widened without loss.

  • io/ply.js
  • lines 86 to 93
  • ModelCraft 0.8.1
const read = (type) => {
  if (format === 'ascii') return tok();
  const [g, n] = TYPES[type];
  if (pos + n > b.length) throw fail(t('ply.why.short'));
  const v = n === 1 ? dv[g](pos) : dv[g](pos, little);
  pos += n;
  return v;
};

Imported positions

64-bit

Stores each vertex's x, y and z.

The values are stored unchanged in a Float64Array: no unit factor and no axis change. Their accuracy is that of the file's declared type.

  • io/ply.js
  • lines 120 to 122
  • ModelCraft 0.8.1
case 'x': out.positions[i * 3] = v; break;
case 'y': out.positions[i * 3 + 1] = v; break;
case 'z': out.positions[i * 3 + 2] = v; break;

Points and normals in world coordinates

64-bit

Carries a point and a normal through a placement's matrix for export.

p′ = M × p; n′ = (M × n without translation), made unit

Written out as 64-bit sums of products. The normal is turned by the matrix itself, not by its inverse transpose, so under a non-uniform scale it is not exactly square to the surface.

  • io/ply.js
  • lines 262 to 263
  • ModelCraft 0.8.1
const P = (p) => [wm[0] * p[0] + wm[4] * p[1] + wm[8] * p[2] + wm[12], wm[1] * p[0] + wm[5] * p[1] + wm[9] * p[2] + wm[13], wm[2] * p[0] + wm[6] * p[1] + wm[10] * p[2] + wm[14]];
const D = (n) => { const x = wm[0] * n[0] + wm[4] * n[1] + wm[8] * n[2]; const y = wm[1] * n[0] + wm[5] * n[1] + wm[9] * n[2]; const z = wm[2] * n[0] + wm[6] * n[1] + wm[10] * n[2]; const l = Math.hypot(x, y, z) || 1; return [x / l, y / l, z / l]; };

Exported positions

32-bit in part

Writes each vertex's world position and normal into the file.

The 64-bit world coordinates are written with setFloat32 under a header that declares 'property float x'. PLY also has a 'double' type, so the 32-bit rounding is this writer's choice and not the format's limit.

  • io/ply.js
  • lines 341 to 343
  • ModelCraft 0.8.1
for (const v of verts) {
  dv.setFloat32(p, v.pos[0], true); dv.setFloat32(p + 4, v.pos[1], true); dv.setFloat32(p + 8, v.pos[2], true);
  dv.setFloat32(p + 12, v.nor[0], true); dv.setFloat32(p + 16, v.nor[1], true); dv.setFloat32(p + 20, v.nor[2], true);

Shared vertices on export

64-bit

Gives corners with the same position, normal, color and UV one vertex.

key = x, y, z as numbers + normal to 5 decimals + color + UV

Positions are compared as 64-bit numbers before they are rounded to 32-bit, so two points that differ only beyond 32-bit become two vertices with the same stored position.

  • io/ply.js
  • line 319
  • ModelCraft 0.8.1
const k = c.pos.join(',') + '|' + c.nor.map((x) => x.toFixed(5)).join(',') + '|' + (c.col ? c.col.join(',') : '') + (c.uv ? '|' + c.uv.join(',') : '');

In detail

Method
Header-driven reader for ASCII and both binary byte orders; export of every face in world coordinates with shared vertices.
Number types
Float64Array for imported positions; Float32Array for imported normals, colors and UVs; 32-bit floats for every exported position, normal and UV.
Tolerances
An imported coordinate must be below 1e7 in size. Exported corners share a vertex when positions are exactly equal and normals agree to 5 decimals.
Limits
Exported coordinates are 32-bit: at 100 m from the origin the spacing between representable values is about 0.008 mm.

Approach: 32-bit step, flagged

One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.

Every policy that takes this approach