Resources Numerical accuracy Calculation policy

Model points written to USD

Writes the vertices and edges of a model into USD meshes and curves.

How the precision is kept

Vertex positions are taken from 64-bit model numbers and stored in Float32Array before they are written as point3f, so the exported coordinates are 32-bit values. Loose edge points are written the same way.

Open item. State on export that mesh points are 32-bit, and offer to re-center the model near the origin before writing so the 32-bit steps stay small.

Calculations under this policy

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

usd/m3d.js CadCraft 0.8.2 32-bit in part 2 calculations Reviewed

Tests None found by the review

Vertices and loose edges

32-bit in part

The point positions of a model definition and its loose edges.

Each coordinate is assigned from the model value into a Float32Array, which rounds it to the nearest 32-bit number; loose edges are rounded the same way and written as point3f[]. Counts and indexes stay integers.

  • usd/m3d.js
  • lines 68 to 104
  • CadCraft 0.8.2
const writeGeometry = (p, def) => {
  if (def.mesh && typeof def.mesh.toArrays === 'function') return writeMesh(p, def);
  if (!def.verts || !def.verts.size) return;
  const vids = [...def.verts.keys()];
  const ix = new Map(vids.map((v, i) => [v, i]));
  const pts = new Float32Array(vids.length * 3);
  vids.forEach((v, i) => { const q = def.verts.get(v); pts[i * 3] = q[0]; pts[i * 3 + 1] = q[1]; pts[i * 3 + 2] = q[2]; });
  const counts = [], indices = [], byPaint = new Map();
  let nf = 0;
  for (const f of def.faces.values()) {
    const polys = f.loops.length === 1 ? [f.loops[0]] : faceTris(def, f);
    for (const poly of polys) {
      counts.push(poly.length);
      for (const v of poly) indices.push(ix.get(v));
      if (f.color) { let a = byPaint.get(f.color); if (!a) byPaint.set(f.color, (a = [])); a.push(nf); }
      nf++;
    }
  }
  if (nf) {
    const g = b.mesh(p, 'Geometry', { points: pts, counts, indices }, subsetsOf(byPaint, nf));
    onGeometry(g, def.id);
  }
  if (edgeFaces) {
    const ef = edgeFaces(def);
    const loose = [...def.edges].filter(([id]) => !(ef.get(id) || []).length);
    if (loose.length) {
      const c = b.prim(p, 'Edges', 'BasisCurves');
      const lp = new Float32Array(loose.length * 6);
      loose.forEach(([, e], k) => { const a = def.verts.get(e.a), q = def.verts.get(e.b); lp.set([a[0], a[1], a[2], q[0], q[1], q[2]], k * 6); });
      b.attr(c, 'type', 'token', 'linear', { uniform: true });
      b.attr(c, 'curveVertexCounts', 'int[]', new Int32Array(loose.length).fill(2));
      b.attr(c, 'points', 'point3f[]', lp);
      b.attr(c, 'widths', 'float[]', [0.005], { meta: { interpolation: V('token', 'constant') } });
      b.attr(c, 'cadcraft:edges', 'string', def.id, { custom: true, uniform: true });
    }
  }
};

Mesh arrays

32-bit in part

The point, normal, UV and color arrays of a mesh.

Points are copied with Float32Array.from, so 64-bit values are rounded to 32 bits; colors are rebuilt as a 3-per-vertex Float32Array from the 4-value data. Normals and UVs are passed on as they are in the source arrays.

  • usd/m3d.js
  • lines 105 to 124
  • CadCraft 0.8.2
const writeMesh = (p, def) => {
  const a = def.mesh.toArrays();
  if (!a.nf) return;
  const geo = { points: Float32Array.from(a.p), counts: a.fc, indices: a.fi, uvs: [] };
  for (const at of a.attrs) {
    if (at.domain !== 'corner' && at.domain !== 'vertex') continue;
    const interp = at.domain === 'corner' ? 'faceVarying' : 'vertex';
    if (at.name === 'normal' && at.size === 3) geo.normals = { values: at.data, interp };
    else if ((at.name === 'uv' || /^uv\.\d+$/.test(at.name)) && at.size === 2) geo.uvs.push({ name: at.name === 'uv' ? 'st' : 'st_' + at.name.slice(3), values: at.data, interp });
    else if (at.name === 'color' && at.size === 4) {
      const rgb = new Float32Array((at.data.length / 4) * 3);
      for (let i = 0, o = 0; i < at.data.length; i += 4, o += 3) { rgb[o] = at.data[i]; rgb[o + 1] = at.data[i + 1]; rgb[o + 2] = at.data[i + 2]; }
      geo.colors = { values: rgb, interp };
    }
  }
  const byPaint = new Map();
  for (let f = 0; f < a.nf; f++) { const k = a.fm[f]; if (k >= 0) { const pt = a.paints[k]; let s = byPaint.get(pt); if (!s) byPaint.set(pt, (s = [])); s.push(f); } }
  const g = b.mesh(p, 'Geometry', geo, subsetsOf(byPaint, a.nf));
  onGeometry(g, def.id);
};

In detail

Method
Vertex coordinates are copied into Float32Array and handed to the USD builder as point3f[] with face counts and indexes.
Number types
points, loose-edge points and the color rgb are Float32Array; counts and indexes are plain integer lists; widths are the constant 0.005.
Tolerances
None.
Limits
The builder (build.js, not reviewed here) takes the extent from these same 32-bit points, so the extent carries the same rounding. A model far from the origin loses more absolute accuracy than one near it.

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