Resources Numerical accuracy Calculation policy

Voxel grid and lattice struts

The solid cubes of a field grid for the solvers, and the strut lists of lattices.

How the precision is kept

A cube is solid when the mean of its eight corner distance values is negative. Lattice struts are placed by multiplying whole cell indices by the cell size, from the box's low corner, all in 64-bit numbers; a rounded key is used only to list each shared strut once.

Calculations under this policy

2 calculations in 2 source files. 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/voxels.js PartCraft 0.8.1 64-bit 1 calculation Reviewed

Tests None found by the review

Solid cubes from a distance grid

64-bit

Marks each cube solid or not from its corner values.

solid when Σ₈ corner values < 0; origin = lo × h

The eight values are added in order in 64-bit numbers. A wholly inside brick counts as −8 × band × h. The origin and size use the grid's own spacing h.

  • field/voxels.js
  • lines 9 to 36
  • PartCraft 0.8.1
export function toVoxelGrid(grid, { bbox = null } = {}) {
  const h = grid.h;
  let lo, hi;
  if (bbox) { lo = [0, 1, 2].map((a) => Math.floor(bbox[a] / h)); hi = [0, 1, 2].map((a) => Math.ceil(bbox[a + 3] / h)); }
  else {
    const e = grid.extent();
    if (!e) return { h, nx: 0, ny: 0, nz: 0, origin: [0, 0, 0], solid: new Uint8Array(0), count: 0, oddRows: 0 };
    lo = e.lo.map((b) => b * 8); hi = e.hi.map((b) => b * 8 + 8);
  }
  const nx = hi[0] - lo[0], ny = hi[1] - lo[1], nz = hi[2] - lo[2];
  const solid = new Uint8Array(nx * ny * nz);
  let count = 0;
  const bw = grid.bw;
  for (const key of grid.sortedKeys()) {
    const [bx, by, bz] = brickOf(key), b = grid.bricks.get(key);
    for (let k = 0; k < 8; k++) for (let j = 0; j < 8; j++) for (let i = 0; i < 8; i++) {
      const I = bx * 8 + i, J = by * 8 + j, K = bz * 8 + k;
      if (I < lo[0] || I >= hi[0] || J < lo[1] || J >= hi[1] || K < lo[2] || K >= hi[2]) continue;
      let s = 0;
      if (b === INSIDE) s = -8 * bw;
      else for (let c = 0; c < 8; c++) s += grid.at(I + (c & 1), J + (c >> 1 & 1), K + (c >> 2 & 1));
      if (s < 0) { solid[(I - lo[0]) + nx * ((J - lo[1]) + ny * (K - lo[2]))] = 1; count++; }
    }
  }
  // cubes whose own brick is absent but whose corners reach into a stored one are handled by the stored brick's own loop only
  // when they start inside it; the rest are outside by construction (every node of an absent brick is outside the band).
  return { h, nx, ny, nz, origin: [lo[0] * h, lo[1] * h, lo[2] * h], solid, count, oddRows: 0 };
}
field/lattice.js PartCraft 0.8.1 64-bit 1 calculation Reviewed

Tests None found by the review

Lattice struts in a box

64-bit

The strut list of a lattice that fills a box.

cells = ⌈(extent ÷ cell) − 1e-9⌉; end = corner + (i + a) × cell

Coordinates are one multiply-add from the box corner. The rounding to 1e-6 is used only to build the key that lists a shared strut once, not for the stored coordinates.

  • field/lattice.js
  • lines 35 to 58
  • PartCraft 0.8.1
/**
 * Struts of a lattice filling the box [x0,y0,z0,x1,y1,z1] with cubic cells of side `cell` (whole cells, starting at the box's
 * low corner; the last row may stick out past the box by less than a cell). Shared struts are listed once. Radius r.
 * → Float64Array(8·n), row-major beams.
 */
export function latticeBeams(kind, box, cell, r) {
  if (!unit[kind]) throw new FieldError('lattice_kind', 'field: unknown lattice ' + kind + ' (' + LATTICE_KINDS.join(', ') + ')');
  if (!(cell > 0)) throw new FieldError('cell', 'field: lattice cell must be positive');
  const nx = Math.max(1, Math.ceil((box[3] - box[0]) / cell - 1e-9)), ny = Math.max(1, Math.ceil((box[4] - box[1]) / cell - 1e-9)), nz = Math.max(1, Math.ceil((box[5] - box[2]) / cell - 1e-9));
  if (nx * ny * nz > 4e6) throw new FieldError('cells', 'field: lattice would have more than 4 million cells');
  const cellBeams = unit[kind](), seen = new Set(), out = [];
  const q = (v) => Math.round(v * 1e6);
  for (let i = 0; i < nx; i++) for (let j = 0; j < ny; j++) for (let k = 0; k < nz; k++) {
    for (const [a, b] of cellBeams) {
      const A = [q(i + a[0]), q(j + a[1]), q(k + a[2])], B = [q(i + b[0]), q(j + b[1]), q(k + b[2])];
      const lo = A[0] < B[0] || (A[0] === B[0] && (A[1] < B[1] || (A[1] === B[1] && A[2] <= B[2]))) ? [A, B] : [B, A];
      const key = lo[0].join(',') + '>' + lo[1].join(',');
      if (seen.has(key)) continue;
      seen.add(key);
      out.push(box[0] + (i + a[0]) * cell, box[1] + (j + a[1]) * cell, box[2] + (k + a[2]) * cell, box[0] + (i + b[0]) * cell, box[1] + (j + b[1]) * cell, box[2] + (k + b[2]) * cell, r, r);
    }
  }
  return Float64Array.from(out);
}

In detail

Method
Voxel inside test: sum of 8 corner values < 0. Lattice strut end = box corner + (cell index + unit coordinate) × cell.
Number types
Float64Array for the struts; Uint8Array for the solid flags. The corner values come from grid.at (grid.js, not read here).
Tolerances
Cell count rounds up after subtracting 1e-9 of a cell; diamond bonds are found with |d² − 3/16| < 1e-9; struts are matched by coordinates rounded to 1e-6 of a cell; at most 4 million cells.
Limits
The voxel decision is by the sign of a sum, so a corner sum of exactly 0 is outside.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach