Resources Numerical accuracy Calculation policy

3D tab drawing of the 2D lines and kept solids

Sends the 2D drawing's lines and the solids kept from DWG files to the graphics card for the 3D tab.

How the precision is kept

The coordinates are 64-bit until the last step. Each batch is moved to its own lower corner in 64-bit arithmetic and only the difference is stored in a 32-bit array for the graphics card. Nothing here is read back into the drawing.

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.

ws3d.js CadCraft 0.8.2 32-bit, display only 2 calculations Reviewed

Tests None found by the review

Kept solids as screen meshes

32-bit, display only

The vertex positions and edge lines of the kept 3D solids as drawn on the 3D tab.

position = (P − corner) × k, stored as 32-bit

The subtraction of the solids' lowest corner and the multiplication by k are done in 64-bit numbers; the result is then stored in a Float32Array for the graphics card. The 64-bit mesh from the ACIS reader is not changed.

  • ws3d.js
  • lines 45 to 95
  • CadCraft 0.8.2
/** The drawing's kept 3D solids (acis-cad.js) in their layer colours, with their own edges; layers off or frozen left out. */
buildSolids() {
  const sm = this.ws.solidMeshes?.();
  const doc = this.ws.doc;
  const layerState = sm ? sm.list.map((x) => { const l = doc.layers.get(x.layer); return l ? (l.on && !l.frozen ? 1 : 0) + ':' + l.color : '1:7'; }).join(',') : '';
  const key = (sm ? sm.list.length : 0) + '|' + this.ws.k + '|' + this.style.mode + '|' + this.style.edges + '|' + this.c.ink + '|' + this.c.front + '|' + this.c.bg + '|' + layerState;
  if (key === this.solidsKey) return;
  this.solidsKey = key;
  for (const o of [...this.solidGroup.children]) { this.solidGroup.remove(o); o.geometry?.dispose(); o.material?.dispose(); }
  if (!sm || !sm.list.length) return;
  const k = this.ws.k;
  let ox = Infinity, oy = Infinity, oz = Infinity;
  for (const x of sm.list) { ox = Math.min(ox, x.body.box[0]); oy = Math.min(oy, x.body.box[1]); oz = Math.min(oz, x.body.box[2]); }
  // around the solids' own corner, so survey-sized coordinates keep their precision on the GPU
  this.solidGroup.position.set(ox * k, oy * k, oz * k);
  const hidden = this.style.mode === 'hidden';
  for (const x of sm.list) {
    const l = doc.layers.get(x.layer);
    if (l && (!l.on || l.frozen)) continue;
    const c = x.color === null || x.color === undefined ? (l ? l.color : 7) : x.color === 0 ? 7 : x.color;
    const hex = typeof c === 'string' ? c : aciHex(c);
    const P = x.body.positions, pos = new Float32Array(P.length);
    for (let i = 0; i < P.length; i += 3) { pos[i] = (P[i] - ox) * k; pos[i + 1] = (P[i + 1] - oy) * k; pos[i + 2] = (P[i + 2] - oz) * k; }
    const g = new THREE.BufferGeometry();
    g.setAttribute('position', new THREE.BufferAttribute(pos, 3));
    g.setIndex(new THREE.BufferAttribute(x.body.triangles, 1));
    g.computeVertexNormals();
    const common = { side: THREE.DoubleSide, polygonOffset: true, polygonOffsetFactor: 1, polygonOffsetUnits: 1 };
    const mat = hidden ? new THREE.MeshBasicMaterial({ color: this.c.bg, ...common }) : new THREE.MeshLambertMaterial({ color: hex === 'ink' ? this.c.front : hex, ...common });
    this.solidGroup.add(new THREE.Mesh(g, mat));
    if (this.style.edges || hidden) {
      // the solid's own edges: where two of its faces meet, and open edges
      const T = x.body.triangles, F = x.body.faceOf, seen = new Map();
      for (let i = 0; i < T.length; i += 3) for (let j = 0; j < 3; j++) {
        const a = T[i + j], b = T[i + (j + 1) % 3], kk = a < b ? a * 4294967296 + b : b * 4294967296 + a;
        const f = F[i / 3], s = seen.get(kk);
        seen.set(kk, s === undefined ? f : s === f ? -2 : -1);
      }
      const L = [];
      for (const [kk, s] of seen) if (s !== -2) { const a = Math.floor(kk / 4294967296), b = kk % 4294967296; L.push(pos[a * 3], pos[a * 3 + 1], pos[a * 3 + 2], pos[b * 3], pos[b * 3 + 1], pos[b * 3 + 2]); }
      if (L.length) {
        const lg = new THREE.BufferGeometry();
        lg.setAttribute('position', new THREE.BufferAttribute(new Float32Array(L), 3));
        this.solidGroup.add(new THREE.LineSegments(lg, new THREE.LineBasicMaterial({ color: this.c.ink })));
      }
    }
  }
  this.solidGroup.updateMatrixWorld(true);
  const b = this.ws.solidsBox?.();
  if (b) this.radius = Math.max(this.radius || 0, Math.hypot(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2]));
}

2D lines as screen lines

32-bit, display only

The 2D drawing's line pieces as drawn on the ground of the 3D tab.

position = (x − min x, y − min y, 0), stored as 32-bit

The drawing's lower-left corner is subtracted in 64-bit numbers before the values go into a Float32Array. The line group is then placed at that corner.

  • ws3d.js
  • lines 96 to 114
  • CadCraft 0.8.2
buildLines() {
  for (const o of [...this.lineGroup.children]) { this.lineGroup.remove(o); o.geometry?.dispose(); o.material?.dispose(); }
  const { batches, bbox } = this.ws.lines();
  if (!bbox) return;
  const ox = bbox.min[0], oy = bbox.min[1];
  // around the drawing's own corner, so survey-sized coordinates keep their precision on the GPU
  this.lineGroup.position.set(ox, oy, 0);
  for (const [color, a] of batches) {
    const pos = new Float32Array((a.length / 4) * 6);
    for (let i = 0, j = 0; i < a.length; i += 4, j += 6) { pos[j] = a[i] - ox; pos[j + 1] = a[i + 1] - oy; pos[j + 3] = a[i + 2] - ox; pos[j + 4] = a[i + 3] - oy; }
    const g = new THREE.BufferGeometry();
    g.setAttribute('position', new THREE.BufferAttribute(pos, 3));
    const col = color === 'ink' ? this.c.ink : color;
    this.lineGroup.add(new THREE.LineSegments(g, new THREE.LineBasicMaterial({ color: col })));
  }
  this.lineGroup.updateMatrixWorld(true);
  const r = Math.hypot(bbox.max[0] - bbox.min[0], bbox.max[1] - bbox.min[1]);
  this.radius = Math.max(this.radius || 0, r, 10);
}

In detail

Method
Subtract the bounding box corner, scale to meters, store as 32-bit vertex positions; the group is placed at the corner.
Number types
64-bit input; Float32Array vertex buffers; 32-bit triangle indexes from the solid's mesh.
Limits
On screen a vertex is as fine as a 32-bit number relative to the size of the drawing or solid, not relative to its distance from the origin.

Approach: Display only

Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.

Every policy that takes this approach