Resources Numerical accuracy Calculation policy

Points and heights picked in the 3D view

Turns a click in the 3D tab into a point or a height in drawing units, and lays the 2D drawing on the ground as edges the 3D snaps use.

How the precision is kept

The 3D model works in meters and the drawing in its own units; every value is multiplied by k (meters per drawing unit) on the way in and divided by k on the way out, in 64-bit numbers. The ray and plane work is done in m3d/infer.js. Arcs of the 2D drawing are handed to the 3D snaps as chords of at most 7.5 degrees, so a snap on an arc lands on a chord.

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.

ws3d-core.js CadCraft 0.8.2 64-bit 5 calculations Reviewed

Tests bin/test-cadcraft-3d.mjs

Meters to drawing units

64-bit

A 3D point in meters as a point in drawing units.

x = pₓ ÷ k; y = pᵧ ÷ k; z = p_z ÷ k

Three 64-bit divisions by k, the model's meters per drawing unit. k is read from the model and is not checked for zero here.

  • ws3d-core.js
  • line 258
  • CadCraft 0.8.2
toUnits(p) { const k = this.k; return { x: p[0] / k, y: p[1] / k, z: p[2] / k }; }

Point on a level plane

64-bit

Where the ray under the pointer meets the plane at height z.

plane: n = (0, 0, 1), d = z × k; point = ray hit ÷ k

The hit comes from rayPlane in m3d/infer.js in meters and x and y are divided by k. The returned z is the z that was asked for, not the computed one, so it is exact.

  • ws3d-core.js
  • lines 265 to 270
  • CadCraft 0.8.2
/** Where the cursor's ray meets the level plane z (drawing units). */
groundAt(ev, z = 0) {
  const k = this.k, ray = this.cam.ray(ev.x, ev.y);
  const g = rayPlane(ray.o, ray.d, { n: [0, 0, 1], d: z * k });
  return g && g.t > 0 ? { x: g.p[0] / k, y: g.p[1] / k, z } : null;
}

Height following the cursor

64-bit

A height above a base outline, taken from where the pointer's ray passes nearest the vertical line through the outline's middle.

c = Σ ring points ÷ n; height = s ÷ k, with s the distance along the vertical line from (cₓ·k, cᵧ·k, z₀·k)

The middle is the plain mean of the first ring's corners, summed in order and divided once. The value given to the command is s ÷ k unrounded; only the tip beside the cursor is rounded.

  • ws3d-core.js
  • lines 271 to 280
  • CadCraft 0.8.2
/** A height along Z from a base (the first ring's middle at z0) following the cursor (drawing units). */
heightAt(ev, spec) {
  const k = this.k;
  const ring = spec?.rings?.[0];
  let cx = 0, cy = 0;
  if (ring?.length) { for (const [x, y] of ring) { cx += x; cy += y; } cx /= ring.length; cy /= ring.length; }
  else if (spec?.base) { cx = spec.base.x; cy = spec.base.y; }
  const a = alongLine(this.cam, ev.x, ev.y, [cx * k, cy * k, (spec?.z0 || 0) * k], [0, 0, 1]);
  return a ? a.s / k : null;
}

2D drawing as snap edges

64-bit

The visible lines and arcs of the 2D drawing as edges on the ground, in meters, for the 3D snaps.

edge = (x·k, y·k, 0); arc: m = max(2, ⌈sweep ÷ 7.5°⌉) chords

Line ends are multiplied by k once. An arc is cut into chords of at most 7.5 degrees, so an endpoint or midpoint snap on an arc in the 3D view lands on a chord vertex or chord middle. Only the first 50,000 pieces are kept.

  • ws3d-core.js
  • lines 118 to 135
  • CadCraft 0.8.2
/** The model space's visible 2D lines as scene edges (meters, z = 0, soft; arcs in short pieces), at most MAX_SNAP_2D. */
lines2d() {
  const k = this.k, out = [];
  let n = 0;
  const seg = (x1, y1, x2, y2) => { if (n++ < MAX_SNAP_2D) out.push({ path: [], def: null, id: '2d' + n, a: [x1 * k, y1 * k, 0], b: [x2 * k, y2 * k, 0], soft: true, flat2d: true }); };
  walk(this.doc, this.doc.model.values(), {
    stroke: (c, ps) => {
      for (const q of ps) {
        if (q.k === 'L') { seg(q.x1, q.y1, q.x2, q.y2); continue; }
        let sw = q.a1 - q.a0; while (sw <= 0) sw += 2 * Math.PI;
        const m = Math.max(2, Math.ceil(sw / (Math.PI / 24)));
        for (let i = 0; i < m; i++) { const a = q.a0 + sw * i / m, b = q.a0 + sw * (i + 1) / m; seg(q.cx + q.r * Math.cos(a), q.cy + q.r * Math.sin(a), q.cx + q.r * Math.cos(b), q.cy + q.r * Math.sin(b)); }
      }
    },
    fill() {}, text() {}, point: (c, x, y) => seg(x, y, x, y),
  });
  return out;
}

Height shown beside the cursor

64-bit

The text of the height tip while a height is being picked.

|n| < 1e-9 → 0; else n to 4 decimals, trailing zeros removed

toFixed(4) is applied to the label only. The height passed on to the command is the unrounded 64-bit number.

  • ws3d-core.js
  • line 495
  • CadCraft 0.8.2
const fmtNum = (n) => (Math.abs(n) < 1e-9 ? '0' : Number(n.toFixed(4)).toString());

In detail

Method
Ray from the camera through the pointer met with a level plane, or the nearest point on a vertical line through the base.
Number types
64-bit numbers; points are arrays [x, y, z] in meters or {x, y, z} in drawing units.
Tolerances
A plane hit is used only in front of the camera (t > 0). The height shown beside the cursor prints 0 below 1e-9 and otherwise 4 decimals.
Limits
At most 50,000 line pieces of the 2D drawing are offered to the snaps. Arcs are chords of 7.5 degrees or less. A value multiplied by k and divided by k again can differ from the original in the last bit.

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.

Every policy that takes this approach