Resources Numerical accuracy Calculation policy

Cut and fill volumes by the grid method

The cut, fill and net volume between two surfaces.

How the precision is kept

Both surfaces are sampled at the center of every square cell and the height difference times the cell area is added up. The arithmetic is 64-bit; the accuracy is set by the cell size. Cells sit on whole multiples of the cell size, so the result does not change when the bounds change.

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.

grading.js CivilCraft 0.8.1 64-bit 2 calculations Reviewed

Tests bin/test-civilcraft.mjs

Cut, fill and net volume

64-bit

The volumes reported between the existing and the proposed surface.

fill = Σ (z₁ − z₀)·c² where z₁ > z₀; cut = Σ (z₀ − z₁)·c² otherwise; net = fill − cut; area = cells × c²

Cell centers are (i + 0.5) × c from whole-number indexes, so positions carry no accumulated step error. Cut and fill are each a running sum of same-sign terms added in row order without compensation; net is their difference, which loses digits when cut and fill nearly balance.

  • grading.js
  • lines 185 to 215
  • CivilCraft 0.8.1
export function gridVolume(base, top, { cell, region = null, bounds = null, maxCells = 4e6 } = {}) {
  const ba = base.bounds, bb = top.bounds;
  let b = bounds || { minX: Math.max(ba.minX, bb.minX), minY: Math.max(ba.minY, bb.minY), maxX: Math.min(ba.maxX, bb.maxX), maxY: Math.min(ba.maxY, bb.maxY) };
  const regions = region ? (Array.isArray(region[0]) ? region : [region]).filter(Boolean) : null;
  if (regions && regions.length) {
    const r = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
    for (const poly of regions) for (const p of poly) { r.minX = Math.min(r.minX, p.x); r.minY = Math.min(r.minY, p.y); r.maxX = Math.max(r.maxX, p.x); r.maxY = Math.max(r.maxY, p.y); }
    b = { minX: Math.max(b.minX, r.minX), minY: Math.max(b.minY, r.minY), maxX: Math.min(b.maxX, r.maxX), maxY: Math.min(b.maxY, r.maxY) };
  }
  const W = b.maxX - b.minX, H = b.maxY - b.minY;
  if (!(W > 0 && H > 0)) return { cut: 0, fill: 0, net: 0, cells: 0, cell: cell || 0, area: 0 };
  let c = cell > 0 ? cell : niceCell(Math.sqrt((W * H) / 250000));
  while ((W / c) * (H / c) > maxCells) c *= 2;
  // cells on a grid anchored at whole multiples of c, so results do not depend on the bounds
  const i0 = Math.floor(b.minX / c), i1 = Math.ceil(b.maxX / c), j0 = Math.floor(b.minY / c), j1 = Math.ceil(b.maxY / c);
  let cutV = 0, fillV = 0, cells = 0;
  const A = c * c;
  for (let j = j0; j < j1; j++) {
    const y = (j + 0.5) * c;
    for (let i = i0; i < i1; i++) {
      const x = (i + 0.5) * c;
      if (regions && !regions.some((poly) => pointInPoly(x, y, poly))) continue;
      const z0 = base.elevationAt(x, y), z1 = top.elevationAt(x, y);
      if (z0 === null || z1 === null) continue;
      cells++;
      const dz = z1 - z0;
      if (dz > 0) fillV += dz * A; else cutV -= dz * A;
    }
  }
  return { cut: cutV, fill: fillV, net: fillV - cutV, cells, cell: c, area: cells * A };
}

Default cell size

64-bit

Rounds the automatic cell size to 1, 2, 5 or 10 times a power of ten.

p = 10^⌊log₁₀ v⌋; cell = {1, 2, 5, 10} × p

The power of ten comes from Math.pow(10, floor(log10 v)); for negative exponents the result is the nearest 64-bit number to the decimal value, not the decimal itself. The cell size chosen is returned with the volumes.

  • grading.js
  • lines 217 to 221
  • CivilCraft 0.8.1
function niceCell(v) {
  if (!(v > 0)) return 1;
  const p = Math.pow(10, Math.floor(Math.log10(v))), m = v / p;
  return (m < 1.5 ? 1 : m < 3.5 ? 2 : m < 7.5 ? 5 : 10) * p;
}

In detail

Method
Grid (prism) method: Σ (top − base) × cell² over cells whose center lies on both surfaces and inside the region.
Number types
64-bit numbers; whole-number cell indexes.
Tolerances
Default cell = √(width × height ÷ 250000) rounded to a 1-2-5 value; doubled while the cell count is over 4,000,000.
Limits
A cell is counted whole or not at all by where its center falls, so the edge of the area is resolved to one cell. Within a cell the difference is taken as constant at the center value. The sums are plain running sums in row order.

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