Resources Numerical accuracy Calculation policy

Field shapes: signed distance functions

Turns a shape description (spheres, boxes, beams, revolved profiles, lattices, booleans, offsets, shells) into a signed distance at any point.

How the precision is kept

Primitive distances are closed-form 64-bit expressions. Three are estimates by design: a tapered beam, a smooth blend, and a triply periodic surface, whose distance is g ÷ |∇g| improved by two projection steps. Sine and cosine come from detmath.js so every engine gives the same bits.

Open item. Guard the divisions by a zero length in the plane, cylinder and rotation cases.

Calculations under this policy

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

field/field.js PartCraft 0.8.1 64-bit 17 calculations Reviewed

Tests tools/field-test/booleans.mjs, tools/field-test/determinism.mjs

Sphere

64-bit

Signed distance to a sphere.

d = √((x − cx)² + (y − cy)² + (z − cz)²) − r

A sum of squares, one square root and one subtraction. Near the surface the subtraction takes two nearly equal numbers, so the distance there carries the rounding of the square root, about one unit in the last place of r.

  • field/field.js
  • lines 123 to 126
  • PartCraft 0.8.1
case 'sphere': {
  const [cx, cy, cz] = spec.c, r = spec.r;
  return leaf((x, y, z) => Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz)) - r, 1, boxBounds(spec.c, r));
}

Box with rounded edges

64-bit

Signed distance to a box, optionally with rounded edges and corners.

q = |p − c| − (half − round); d = |max(q, 0)| + min(max(q_x, q_y, q_z), 0) − round

Outside, the length of the positive parts of q; inside, the largest component of q. Additions, comparisons and one square root; the rounding radius is subtracted last.

  • field/field.js
  • lines 127 to 136
  • PartCraft 0.8.1
case 'box': {
  const [cx, cy, cz] = spec.c, [hx, hy, hz] = spec.half, rd = spec.round || 0;
  const ix = hx - rd, iy = hy - rd, iz = hz - rd;
  return leaf((x, y, z) => {
    const qx = Math.abs(x - cx) - ix, qy = Math.abs(y - cy) - iy, qz = Math.abs(z - cz) - iz;
    const ox = qx > 0 ? qx : 0, oy = qy > 0 ? qy : 0, oz = qz > 0 ? qz : 0;
    const inner = Math.max(qx, qy, qz);
    return Math.sqrt(ox * ox + oy * oy + oz * oz) + (inner < 0 ? inner : 0) - rd;
  }, 1, [cx - hx, cy - hy, cz - hz, cx + hx, cy + hy, cz + hz]);
}

Cylinder

64-bit

Signed distance to a cylinder between two points.

t = (p − m)·u; d_r = |p − m − t·u| − r; d_t = |t| − L ÷ 2; d = |max(d_r, d_t, 0)| + min(max(d_r, d_t), 0)

The axis is divided by its length L with no check for L = 0. The radial offset is the point minus its projection on the axis, a subtraction that loses digits when the point is far along the axis compared with its distance from it.

  • field/field.js
  • lines 138 to 149
  • PartCraft 0.8.1
case 'cylinder': {
  const [ax, ay, az] = spec.a, [bx, by, bz] = spec.b, r = spec.r;
  const dx = bx - ax, dy = by - ay, dz = bz - az, L2 = dx * dx + dy * dy + dz * dz, L = Math.sqrt(L2), hl = L / 2;
  const ux = dx / L, uy = dy / L, uz = dz / L, mx = (ax + bx) / 2, my = (ay + by) / 2, mz = (az + bz) / 2;
  return leaf((x, y, z) => {
    const px = x - mx, py = y - my, pz = z - mz, t = px * ux + py * uy + pz * uz;
    const rx = px - t * ux, ry = py - t * uy, rz = pz - t * uz;
    const dr = Math.sqrt(rx * rx + ry * ry + rz * rz) - r, dt = Math.abs(t) - hl;
    const o1 = dr > 0 ? dr : 0, o2 = dt > 0 ? dt : 0;
    return Math.sqrt(o1 * o1 + o2 * o2) + Math.min(Math.max(dr, dt), 0);
  }, 1, boxBounds([mx, my, mz], hl + r));
}

Torus

64-bit

Signed distance to a torus about the z axis.

d = √((√(x² + y²) − R)² + z²) − r

Two square roots and two subtractions, each of which takes nearly equal numbers close to the tube's surface.

  • field/field.js
  • lines 150 to 154
  • PartCraft 0.8.1
case 'torus': {
  const [cx, cy, cz] = spec.c, R = spec.R, r = spec.r;
  return leaf((x, y, z) => { const px = x - cx, py = y - cy, pz = z - cz; const q = Math.sqrt(px * px + py * py) - R; return Math.sqrt(q * q + pz * pz) - r; }, 1,
    [cx - R - r, cy - R - r, cz - r, cx + R + r, cy + R + r, cz + r]);
}

Half space

64-bit

Signed distance to a plane.

d = (n·p − d₀) ÷ |n|

The normal and the offset are divided by the normal's length once, with no check for a zero normal. Then one dot product and one subtraction per point.

  • field/field.js
  • lines 155 to 158
  • PartCraft 0.8.1
case 'plane': {
  let [nx, ny, nz] = spec.n; const n = Math.sqrt(nx * nx + ny * ny + nz * nz); nx /= n; ny /= n; nz /= n; const d = spec.d / n;
  return leaf((x, y, z) => nx * x + ny * y + nz * z - d, 1, [-INF, -INF, -INF, INF, INF, INF]);
}

Solid of revolution

64-bit

Signed distance to the solid under a profile r(x) turned about the x axis.

ρ = √(y² + z²); d = least distance from (x, ρ) to the profile's segments; inside when ρ ≤ r(x)

Squared distances to each segment are compared and one square root is taken at the end. The sign comes from comparing ρ with the profile's radius at x, interpolated linearly between the two profile points. Profile points must have increasing x, so the interpolation's divisor is positive.

  • field/field.js
  • lines 165 to 191
  • PartCraft 0.8.1
case 'revolve': {
  const P = spec.pts, n = P.length;
  let rmax = 0; for (const q of P) rmax = Math.max(rmax, q[1]);
  const segs = [[P[0][0], 0, P[0][0], P[0][1]]];                                 // the flat first end
  for (let i = 0; i + 1 < n; i++) segs.push([P[i][0], P[i][1], P[i + 1][0], P[i + 1][1]]);
  segs.push([P[n - 1][0], P[n - 1][1], P[n - 1][0], 0]);                         // the flat last end
  const f = (x, y, z) => {
    const rho = Math.sqrt(y * y + z * z);
    let d2 = INF;
    for (let i = 0; i < segs.length; i++) {
      const s0 = segs[i], ax = s0[0], ay = s0[1], dx = s0[2] - ax, dy = s0[3] - ay, L2 = dx * dx + dy * dy;
      const px = x - ax, py = rho - ay;
      let t = L2 > 0 ? (px * dx + py * dy) / L2 : 0; t = t < 0 ? 0 : t > 1 ? 1 : t;
      const qx = px - t * dx, qy = py - t * dy, q2 = qx * qx + qy * qy;
      if (q2 < d2) d2 = q2;
    }
    let inside = false;
    if (x >= P[0][0] && x <= P[n - 1][0]) {
      let i = 0; while (i + 2 < n && x > P[i + 1][0]) i++;
      const u = (x - P[i][0]) / (P[i + 1][0] - P[i][0]), r = P[i][1] + (P[i + 1][1] - P[i][1]) * u;
      inside = rho <= r;
    }
    const d = Math.sqrt(d2);
    return inside ? -d : d;
  };
  return leaf(f, 1, [P[0][0], -rmax, -rmax, P[n - 1][0], rmax, rmax]);
}

Union, intersection and subtraction

64-bit

Combines shapes by union, intersection or subtraction.

union = min(a, b); intersection = max(a, b); subtraction = max(a, −b)

min and max return one of their operands unchanged, so a boolean adds no rounding of its own. The intersect and subtract cases that follow use max the same way.

  • field/field.js
  • lines 194 to 198
  • PartCraft 0.8.1
case 'union': {
  const k = spec.a.map(compile), fs = k.map((n) => n.f), n = fs.length;
  return {
    f: n === 2 ? (x, y, z) => Math.min(fs[0](x, y, z), fs[1](x, y, z)) : (x, y, z) => { let m = INF; for (let i = 0; i < n; i++) { const v = fs[i](x, y, z); if (v < m) m = v; } return m; },
    bounds: mergeBounds(k.map((q) => q.bounds)),

Smooth minimum

64-bit

Blends two shapes with a radius k.

h = clamp(½ + (b − a) ÷ 2k, 0, 1); s = b + (a − b)h − k·h(1 − h)

For k ≤ 0 it is the plain minimum, so the division by k never sees zero. The blend lowers the value by at most k ÷ 4, which the bounds and the brick classification allow for.

  • field/field.js
  • lines 276 to 280
  • PartCraft 0.8.1
function smin(a, b, k) {                                 // polynomial smooth minimum
  if (!(k > 0)) return Math.min(a, b);
  const h0 = 0.5 + 0.5 * (b - a) / k, hh = h0 < 0 ? 0 : h0 > 1 ? 1 : h0;
  return b + (a - b) * hh - k * hh * (1 - hh);
}

Offset and shell

64-bit

Grows or shrinks a shape, or hollows it to a wall thickness.

offset: d − δ; shell inward: max(d, −d − t); shell centered: |d| − t ÷ 2

One subtraction for an offset. A shell is the larger of two shifted copies of the distance, or |d| − t ÷ 2 for a centered wall.

  • field/field.js
  • lines 240 to 247
  • PartCraft 0.8.1
case 'offset': {
  const A = compile(spec.a), fa = A.f, d = spec.d;
  return { f: (x, y, z) => fa(x, y, z) - d, bounds: A.bounds.map((v, i) => (i < 3 ? v - d : v + d)), cls: (cx, cy, cz, r, band) => A.cls(cx, cy, cz, r, band + d) };
}
case 'shell': {
  const A = compile(spec.a), fa = A.f, th = spec.th, side = spec.side || 'in';
  const f = side === 'mid' ? (x, y, z) => Math.abs(fa(x, y, z)) - th / 2 : side === 'out' ? (x, y, z) => { const v = fa(x, y, z); return Math.max(-v, v - th); } : (x, y, z) => { const v = fa(x, y, z); return Math.max(v, -v - th); };
  const pad = side === 'out' ? th : side === 'mid' ? th / 2 : 0;

Move, turn and scale

64-bit

A shape under a rigid motion and a uniform scale.

q = Rᵀ(p − move) ÷ s; d = d₀(q) × s

The point is moved back, multiplied by the transpose of the 3 × 3 and divided by the scale; the distance found is multiplied by the scale. The transpose is the inverse only if the 3 × 3 is a rotation, which is not checked.

  • field/field.js
  • lines 258 to 268
  • PartCraft 0.8.1
case 'xf': {
  const A = compile(spec.a), fa = A.f, s = spec.scale, R = spec.rot, m = spec.move;
  const inv = (x, y, z) => { const px = x - m[0], py = y - m[1], pz = z - m[2]; return [(R[0] * px + R[3] * py + R[6] * pz) / s, (R[1] * px + R[4] * py + R[7] * pz) / s, (R[2] * px + R[5] * py + R[8] * pz) / s]; };
  const fwd = (x, y, z) => [s * (R[0] * x + R[1] * y + R[2] * z) + m[0], s * (R[3] * x + R[4] * y + R[5] * z) + m[1], s * (R[6] * x + R[7] * y + R[8] * z) + m[2]];
  const b = A.bounds; let nb;
  if (b.every(Number.isFinite)) {
    nb = [INF, INF, INF, -INF, -INF, -INF];
    for (let i = 0; i < 8; i++) { const p = fwd(b[i & 1 ? 3 : 0], b[i & 2 ? 4 : 1], b[i & 4 ? 5 : 2]); for (let a = 0; a < 3; a++) { if (p[a] < nb[a]) nb[a] = p[a]; if (p[a] > nb[a + 3]) nb[a + 3] = p[a]; } }
  } else nb = [-INF, -INF, -INF, INF, INF, INF];
  return {
    f: (x, y, z) => { const p = inv(x, y, z); return fa(p[0], p[1], p[2]) * s; }, bounds: nb,

Rotation matrix

64-bit

The 3 × 3 matrix of a turn about an axis by an angle.

R = c·I + s·[u]ₓ + (1 − c)·u uᵀ, c = cos θ, s = sin θ

The axis is divided by its length with no zero check. Cosine and sine come from detmath.js dcos and dsin, so the matrix has the same bits in every engine.

  • field/field.js
  • lines 72 to 76
  • PartCraft 0.8.1
export function rotationMatrix(axis, angle) {
  let [x, y, z] = axis; const n = Math.sqrt(x * x + y * y + z * z); x /= n; y /= n; z /= n;
  const c = dcos(angle), s = dsin(angle), t = 1 - c;
  return [t * x * x + c, t * x * y - s * z, t * x * z + s * y, t * x * y + s * z, t * y * y + c, t * y * z - s * x, t * x * z - s * y, t * y * z + s * x, t * z * z + c];
}

Beam or round cone

64-bit

Signed distance to a beam whose radius changes from one end to the other.

t = clamp((p − a)·(b − a) ÷ |b − a|², 0, 1); d = |p − a − t(b − a)| − (r₀ + (r₁ − r₀)t)

The code's own comment: exact for a capsule, a close estimate for a cone. A zero-length beam becomes a sphere of the larger radius, so the division by |b − a|² is guarded.

  • field/field.js
  • lines 282 to 294
  • PartCraft 0.8.1
function beamNode(spec) {
  const [ax, ay, az] = spec.a, [bx, by, bz] = spec.b, r0 = spec.r0, r1 = spec.r1;
  const dx = bx - ax, dy = by - ay, dz = bz - az, L2 = dx * dx + dy * dy + dz * dz;
  const f = L2 === 0 ? (x, y, z) => Math.sqrt((x - ax) * (x - ax) + (y - ay) * (y - ay) + (z - az) * (z - az)) - Math.max(r0, r1)
    : (x, y, z) => {
      const px = x - ax, py = y - ay, pz = z - az;
      let t = (px * dx + py * dy + pz * dz) / L2; t = t < 0 ? 0 : t > 1 ? 1 : t;
      const qx = px - t * dx, qy = py - t * dy, qz = pz - t * dz;
      return Math.sqrt(qx * qx + qy * qy + qz * qz) - (r0 + (r1 - r0) * t);   // exact for a capsule, a close estimate for a cone
    };
  const R = Math.max(r0, r1);
  return leaf(f, 1.0 + Math.abs(r1 - r0) / Math.sqrt(L2 || 1), [Math.min(ax, bx) - R, Math.min(ay, by) - R, Math.min(az, bz) - R, Math.max(ax, bx) + R, Math.max(ay, by) + R, Math.max(az, bz) + R]);
}

Lattice of beams

64-bit

The least distance to many beams.

Beams are entered in every bin they come within reach of; the bin size is twice the larger of the mean beam length and the largest diameter, and a sample reads its own bin. Where no listed beam is nearer than the bin size the function returns the bin size instead of the distance.

  • field/field.js
  • lines 296 to 332
  • PartCraft 0.8.1
/** Many beams, binned on a coarse lattice so each sample only looks at the nearby ones. */
function beamsNode(spec) {
  const list = spec.list, n = list.length;
  if (!n) return { f: () => INF, bounds: [INF, INF, INF, -INF, -INF, -INF], cls: () => OUT };
  const bb = [INF, INF, INF, -INF, -INF, -INF];
  let maxR = 0;
  for (const s of list) { const R = Math.max(s[6], s[7]); maxR = Math.max(maxR, R); for (let a = 0; a < 3; a++) { bb[a] = Math.min(bb[a], s[a], s[a + 3]); bb[a + 3] = Math.max(bb[a + 3], s[a], s[a + 3]); } }
  let meanLen = 0; for (const s of list) meanLen += Math.sqrt((s[3] - s[0]) ** 2 + (s[4] - s[1]) ** 2 + (s[5] - s[2]) ** 2);
  const cell = Math.max(meanLen / n, maxR * 2, 1e-9) * 2;     // bin size ≈ two typical beam lengths
  const reach = cell;                                         // a sample looks at the 27 bins around it; beams are entered in every bin they touch ± reach
  const bins = new Map();
  const bkey = (i, j, k) => (i + 4096) * 16777216 + (j + 4096) * 4096 + (k + 4096);
  list.forEach((s, idx) => {
    const lo = [0, 1, 2].map((a) => Math.floor((Math.min(s[a], s[a + 3]) - Math.max(s[6], s[7]) - reach) / cell));
    const hi = [0, 1, 2].map((a) => Math.floor((Math.max(s[a], s[a + 3]) + Math.max(s[6], s[7]) + reach) / cell));
    for (let i = lo[0]; i <= hi[0]; i++) for (let j = lo[1]; j <= hi[1]; j++) for (let k = lo[2]; k <= hi[2]; k++) { const q = bkey(i, j, k); let l = bins.get(q); if (!l) bins.set(q, (l = [])); l.push(idx); }
  });
  const dist = (s, x, y, z) => {
    const ax = s[0], ay = s[1], az = s[2], dx = s[3] - ax, dy = s[4] - ay, dz = s[5] - az, L2 = dx * dx + dy * dy + dz * dz;
    const px = x - ax, py = y - ay, pz = z - az;
    let t = L2 ? (px * dx + py * dy + pz * dz) / L2 : 0; t = t < 0 ? 0 : t > 1 ? 1 : t;
    const qx = px - t * dx, qy = py - t * dy, qz = pz - t * dz;
    return Math.sqrt(qx * qx + qy * qy + qz * qz) - (s[6] + (s[7] - s[6]) * t);
  };
  const far = reach;                                          // beyond this from every beam: report +far (a valid lower bound inside the band checks below)
  const f = (x, y, z) => {
    const l = bins.get(bkey(Math.floor(x / cell), Math.floor(y / cell), Math.floor(z / cell)));
    if (!l) return far;
    let m = far;
    for (let i = 0; i < l.length; i++) { const v = dist(list[l[i]], x, y, z); if (v < m) m = v; }
    return m;
  };
  return {
    f, bounds: [bb[0] - maxR, bb[1] - maxR, bb[2] - maxR, bb[3] + maxR, bb[4] + maxR, bb[5] + maxR],
    cls(cx, cy, cz, r, band) { return band + r < reach && !bins.get(bkey(Math.floor(cx / cell), Math.floor(cy / cell), Math.floor(cz / cell))) ? OUT : MIX; },
  };
}

Ramped cell size and wall

64-bit

A number that changes linearly along an axis or with radius.

v = v₀ + (v₁ − v₀) × clamp((p − a) ÷ (b − a), 0, 1)

One division by the ramp's length, not checked for zero, then a clamp and a linear blend.

  • field/field.js
  • lines 91 to 92
  • PartCraft 0.8.1
if (s.t === 'ramp') { const ax = s.axis, a = s.a, b = s.b, v0 = s.v0, v1 = s.v1; return (x, y, z) => { const p = ax === 0 ? x : ax === 1 ? y : z; const u = clamp01((p - a) / (b - a)); return v0 + (v1 - v0) * u; }; }
if (s.t === 'radialramp') { const [cx, cy, cz] = s.c, r0 = s.r0, r1 = s.r1, v0 = s.v0, v1 = s.v1; return (x, y, z) => { const d = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz)); const u = clamp01((d - r0) / (r1 - r0)); return v0 + (v1 - v0) * u; }; }

Phase of a graded lattice

64-bit

Maps a point to the lattice's own period coordinates when the cell size changes.

fixed cell: X = 2π·p ÷ cell; ramp: X = ∫k dp with k linear in p

2π is typed as 2 × 3.141592653589793. For a ramp the frequency is interpolated linearly, so the integral is a closed-form quadratic. Any other map scales by the local frequency, which the comment calls crude where it changes fast.

  • field/field.js
  • lines 334 to 372
  • PartCraft 0.8.1
/**
 * The phase map of a graded TPMS: world point → the point (X, Y, Z) in the surface's own period-2π coordinates. A plain number
 * is X = 2π·p/cell. A ramp (cell size changing along one axis) integrates the local frequency along that axis and scales the
 * other two by it; a radial ramp does the same along the radius. The cell size is interpolated in 1/cell (so the frequency is linear
 * and the integral needs no logarithm): the end values are exact, the sizes between are harmonic. Anything else (a sampled
 * map) falls back to scaling by the local frequency, which is crude where it changes fast.
 */
function phaseMap(cell) {
  const TWO_PI = 2 * 3.141592653589793;
  if (typeof cell === 'number') return { fixed: true, cellAt: () => cell, psi(x, y, z, o) { const k = TWO_PI / cell; o[0] = x * k; o[1] = y * k; o[2] = z * k; } };
  if (cell.t === 'ramp') {
    const ax = cell.axis, a = cell.a, b = cell.b, k0 = TWO_PI / cell.v0, k1 = TWO_PI / cell.v1, L = b - a;
    return {
      fixed: false,
      cellAt(x, y, z) { const p = ax === 0 ? x : ax === 1 ? y : z, u = clamp01((p - a) / L); return TWO_PI / (k0 + (k1 - k0) * u); },
      psi(x, y, z, o) {
        const p = ax === 0 ? x : ax === 1 ? y : z, u = clamp01((p - a) / L), k = k0 + (k1 - k0) * u;
        // phase along the axis: ∫k dp, with k constant outside the ramp
        const q = p < a ? k0 * (p - a) : p > b ? 0.5 * (k0 + k1) * L + k1 * (p - b) : k0 * (p - a) + 0.5 * (k1 - k0) * (p - a) * (p - a) / L;
        o[ax] = q; o[(ax + 1) % 3] = (ax === 0 ? y : ax === 1 ? z : x) * k; o[(ax + 2) % 3] = (ax === 0 ? z : ax === 1 ? x : y) * k;
      },
    };
  }
  if (cell.t === 'radialramp') {
    const [cx, cy, cz] = cell.c, r0 = cell.r0, r1 = cell.r1, k0 = TWO_PI / cell.v0, k1 = TWO_PI / cell.v1, L = r1 - r0;
    return {
      fixed: false,
      cellAt(x, y, z) { const r = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz)), u = clamp01((r - r0) / L); return TWO_PI / (k0 + (k1 - k0) * u); },
      psi(x, y, z, o) {
        const dx = x - cx, dy = y - cy, dz = z - cz, r = Math.sqrt(dx * dx + dy * dy + dz * dz);
        const q = r < r0 ? k0 * r : r > r1 ? k0 * r0 + 0.5 * (k0 + k1) * L + k1 * (r - r1) : k0 * r0 + k0 * (r - r0) + 0.5 * (k1 - k0) * (r - r0) * (r - r0) / L;
        const s = r > 1e-12 ? q / r : k0;
        o[0] = dx * s; o[1] = dy * s; o[2] = dz * s;
      },
    };
  }
  const cf = scalar(cell);
  return { fixed: false, cellAt: cf, psi(x, y, z, o) { const k = TWO_PI / cf(x, y, z); o[0] = x * k; o[1] = y * k; o[2] = z * k; } };
}

Gyroid, Schwarz P and diamond

64-bit

Signed distance to a triply periodic minimal surface, as a sheet or a network.

d ≈ g ÷ |∇g|; near the surface: two projections q ← q − g∇g ÷ |∇g|², d = ±|p − q|

Sines and cosines come from detmath.js. The gradient size is floored at 1e-3 × 2π ÷ cell so the division cannot blow up. Within 0.3 of a cell the distance is replaced by the length to a point found by exactly two projection steps (not repeated to a tolerance), blended in between 0.3 and 0.2 of a cell.

  • field/field.js
  • lines 374 to 425
  • PartCraft 0.8.1
function tpmsNode(spec) {
  const kind = spec.kind, wallF = scalar(spec.wall), mode = spec.mode, iso = spec.iso;
  if (!['gyroid', 'schwarzp', 'diamond'].includes(kind)) throw new Error('field: unknown TPMS ' + kind);
  const pm = phaseMap(spec.cell), O = [0, 0, 0], O1 = [0, 0, 0], O2 = [0, 0, 0];
  const G = [0, 0, 0, 0];                               // g − iso and its gradient in period coordinates, written by gev()
  const gev = (X, Y, Z) => {
    const sx = dsin(X), cx = dcos(X), sy = dsin(Y), cy = dcos(Y), sz = dsin(Z), cz = dcos(Z);
    if (kind === 'gyroid') { G[0] = sx * cy + sy * cz + sz * cx; G[1] = cx * cy - sz * sx; G[2] = -sx * sy + cy * cz; G[3] = -sy * sz + cz * cx; }
    else if (kind === 'schwarzp') { G[0] = cx + cy + cz; G[1] = -sx; G[2] = -sy; G[3] = -sz; }
    else {
      G[0] = sx * sy * sz + sx * cy * cz + cx * sy * cz + cx * cy * sz;
      G[1] = cx * sy * sz + cx * cy * cz - sx * sy * cz - sx * cy * sz;
      G[2] = sx * cy * sz - sx * sy * cz + cx * cy * cz - cx * sy * sz;
      G[3] = sx * sy * cz - sx * cy * sz - cx * sy * sz + cx * cy * cz;
    }
    G[0] -= iso;
  };
  const W = [0, 0, 0, 0];                               // g − iso and the gradient in world units, written by ev()
  const ev = (x, y, z) => {
    pm.psi(x, y, z, O); gev(O[0], O[1], O[2]);
    W[0] = G[0];
    if (pm.fixed) { const kk = 2 * 3.141592653589793 / pm.cellAt(); W[1] = G[1] * kk; W[2] = G[2] * kk; W[3] = G[3] * kk; return; }
    // chain rule through the phase map: ∇g(world) = Jᵀ ∇g(period), J by central differences
    const e = 0.01 * pm.cellAt(x, y, z), J = [0, 0, 0, 0, 0, 0, 0, 0, 0];
    pm.psi(x + e, y, z, O1); pm.psi(x - e, y, z, O2); for (let r = 0; r < 3; r++) J[r * 3] = (O1[r] - O2[r]) / (2 * e);
    pm.psi(x, y + e, z, O1); pm.psi(x, y - e, z, O2); for (let r = 0; r < 3; r++) J[r * 3 + 1] = (O1[r] - O2[r]) / (2 * e);
    pm.psi(x, y, z + e, O1); pm.psi(x, y, z - e, O2); for (let r = 0; r < 3; r++) J[r * 3 + 2] = (O1[r] - O2[r]) / (2 * e);
    W[1] = J[0] * G[1] + J[3] * G[2] + J[6] * G[3]; W[2] = J[1] * G[1] + J[4] * G[2] + J[7] * G[3]; W[3] = J[2] * G[1] + J[5] * G[2] + J[8] * G[3];
  };
  const f = (x, y, z) => {
    const cell = pm.cellAt(x, y, z);
    ev(x, y, z);
    const g0 = W[0], gx = W[1], gy = W[2], gz = W[3], floor = 1e-3 * 2 * 3.141592653589793 / cell;
    let gm = Math.sqrt(gx * gx + gy * gy + gz * gz); if (gm < floor) gm = floor;
    let d = g0 / gm;
    const ad = d < 0 ? -d : d, lim = 0.3 * cell;
    if (ad < lim) {
      // Near a wall the first-order estimate g/|∇g| is off by a few percent (thin walls come out thin). Two projections onto the
      // surface (Newton steps along the gradient) give the distance to the nearest point of it; blended back to the first-order value
      // between 0.2 and 0.3 of a cell so the field stays continuous.
      let qx = x - g0 * gx / (gm * gm), qy = y - g0 * gy / (gm * gm), qz = z - g0 * gz / (gm * gm);
      ev(qx, qy, qz);
      let m2 = W[1] * W[1] + W[2] * W[2] + W[3] * W[3]; if (m2 < floor * floor) m2 = floor * floor;
      qx -= W[0] * W[1] / m2; qy -= W[0] * W[2] / m2; qz -= W[0] * W[3] / m2;
      const dx = x - qx, dy = y - qy, dz = z - qz, d2 = Math.sqrt(dx * dx + dy * dy + dz * dz) * (g0 < 0 ? -1 : 1);
      const s0 = (lim - ad) / (0.1 * cell), s = s0 > 1 ? 1 : s0;
      d += (d2 - d) * s;
    }
    return mode === 'sheet' ? Math.abs(d) - wallF(x, y, z) / 2 : d;
  };
  return { f, bounds: [-INF, -INF, -INF, INF, INF, INF], cls: () => MIX };
}

Brick classification

64-bit

Decides whether a ball of space is wholly outside, wholly inside or near the surface.

outside when f − L·r > band; inside when f + L·r < −band

f is the shape's value at the ball's center and L its Lipschitz bound (1 for the true distances, 1 + |r₁ − r₀| ÷ length for a tapered beam). The comparisons are in 64-bit with no tolerance.

  • field/field.js
  • lines 112 to 116
  • PartCraft 0.8.1
// ---- compile --------------------------------------------------------------------------------------------------------------
const leaf = (f, lip, bounds) => ({
  f, bounds,
  cls(cx, cy, cz, r, band) { const v = f(cx, cy, cz), d = lip * r; return v - d > band ? OUT : v + d < -band ? IN : MIX; },
});

In detail

Method
Textbook distance formulas; min and max for booleans; the polynomial smooth minimum for blends (the file credits Quilez); gyroid, Schwarz P and diamond level sets with a first-order distance and two Newton projections.
Number types
64-bit numbers. A density map is kept in a Float32Array; it is recorded under the distance grid policy.
Tolerances
TPMS: gradient size floored at 1e-3 × 2π ÷ cell; projections used within 0.3 of a cell and blended out between 0.2 and 0.3; the Jacobian of a graded cell by central differences with step 0.01 × cell. Radial ramp: below r = 1e-12 the base frequency is used. Beam bins: size at least 2e-9.
Limits
Plane, cylinder and rotation divide by a length without a zero check. A transform uses the transpose of its 3 × 3 as the inverse, which is right only for a rotation. Out of reach of every beam the beams field returns the bin size, not the distance.

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