Resources Numerical accuracy Calculation policy

Takeoff lengths and areas

Lengths, perimeters and areas of measured runs and outlines, with arcs and cut-outs.

How the precision is kept

Straight outlines use the shoelace formula in 64-bit. Arcs are measured in closed form (radius times angle for length, the circular segment for area) and are never replaced by short chords for a quantity. Chords are used only for drawing and picking.

Calculations under this policy

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

geom.js TakeoffCraft 0.8.1 64-bit 6 calculations Reviewed

Tests bin/test-takeoffcraft.mjs

Length of a segment or arc

64-bit

The true length of one straight or curved piece of a run.

θ = 4 · atan(b); r = c ÷ (2 · sin(θ ÷ 2)); length = r × |θ|

Closed form from the chord c and the bulge b. No chords are added up.

  • geom.js
  • lines 21 to 27
  • TakeoffCraft 0.8.1
/** Exact length of one segment p → q with bulge b. */
export function segLength(p, q, b = 0) {
  const c = dist(p, q);
  if (!b || !c) return c;
  const th = 4 * Math.atan(b);
  return Math.abs((c / 2) / Math.sin(th / 2) * th);
}

Area under an arc

64-bit

The area between an arc and its chord.

area = r² × (θ − sin θ) ÷ 2

For a very shallow arc θ − sin θ subtracts two nearly equal numbers and loses digits. The area concerned is itself very small, so the total is barely affected.

  • geom.js
  • lines 29 to 36
  • TakeoffCraft 0.8.1
/** Signed area between the chord p → q and its arc (adds to signedArea; positive bulge = CCW). */
export function segArea(p, q, b = 0) {
  if (!b) return 0;
  const c = dist(p, q);
  if (!c) return 0;
  const th = 4 * Math.atan(b), r = c / (2 * Math.sin(th / 2));
  return r * r * (th - Math.sin(th)) / 2;
}

Area of an outline

64-bit

The area inside a closed outline by the shoelace formula, arcs included.

A = ½ Σ (xᵢ · yᵢ₊₁ − xᵢ₊₁ · yᵢ) + Σ arc areas

Computed in the page's own coordinates as drawn. On a drawing page those are small numbers, so the products in the sum keep their digits.

  • geom.js
  • lines 126 to 135
  • TakeoffCraft 0.8.1
/** Signed area (positive counter-clockwise in a y-up space): shoelace + each arc's segment. */
export function signedArea(pts) {
  let a = 0, s = 0;
  for (let i = 0, n = pts.length; i < n; i++) {
    const p = pts[i], q = pts[(i + 1) % n];
    a += p.x * q.y - q.x * p.y;
    if (p.b) s += segArea(p, q, p.b);
  }
  return a / 2 + s;
}

Perimeter

64-bit

The length around a closed outline.

P = Σ segment lengths, the closing segment included

Each piece is the exact segment or arc length above; the pieces are added in order.

  • geom.js
  • lines 120 to 124
  • TakeoffCraft 0.8.1
/** Closed outline's perimeter (the closing segment uses the last point's bulge). */
export function perimeter(pts) {
  if (pts.length < 2) return 0;
  return polylineLength(pts) + segLength(pts[pts.length - 1], pts[0], pts[pts.length - 1].b);
}

Net area with cut-outs

64-bit

Gross area, cut-outs, net area and perimeter of an area mark.

net = gross − Σ cut-outs

Each cut-out is measured by the same outline calculation and subtracted once.

  • geom.js
  • lines 138 to 144
  • TakeoffCraft 0.8.1
/** An area mark: outline minus cut-outs, perimeter of the outline plus each cut-out. */
export function areaWithHoles(outer, holes = []) {
  const gross = polygonArea(outer);
  const cut = holes.reduce((s, h) => s + polygonArea(h), 0);
  const p = perimeter(outer) + holes.reduce((s, h) => s + perimeter(h), 0);
  return { gross, cut, net: gross - cut, perimeter: p, outerPerimeter: perimeter(outer) };
}

Centroid

64-bit

The center of area of an outline, where its label goes.

C_x = Σ (xᵢ + xᵢ₊₁) · fᵢ ÷ 6A, fᵢ = xᵢ · yᵢ₊₁ − xᵢ₊₁ · yᵢ

An outline with arcs is first broken into 5° chords, so its centroid is approximate. Lengths and areas never use those chords.

  • geom.js
  • lines 250 to 260
  • TakeoffCraft 0.8.1
export function centroid(pts0) {
  const pts = hasArcs(pts0) ? flatten(pts0, true) : pts0;
  const a = signedArea(pts);
  if (Math.abs(a) < 1e-12) { const b = bounds(pts); return { x: (b.minX + b.maxX) / 2, y: (b.minY + b.maxY) / 2 }; }
  let cx = 0, cy = 0;
  for (let i = 0, n = pts.length; i < n; i++) {
    const p = pts[i], q = pts[(i + 1) % n], f = p.x * q.y - q.x * p.y;
    cx += (p.x + q.x) * f; cy += (p.y + q.y) * f;
  }
  return { x: cx / (6 * a), y: cy / (6 * a) };
}

In detail

Method
Area is the shoelace sum over the corners plus the circular segment of each arc, r²/2 × (θ − sin θ). Arc length is r × θ. Cut-outs are subtracted from the gross area, and their edges are added to the perimeter.
Number types
64-bit numbers in the page's own coordinates. The drawing scale and units are applied to the result afterwards.
Tolerances
An outline with an area under 1e-9 square units is reported as empty. Crossing checks use 1e-9 relative to the size of the segments.
Checked by
The TakeoffCraft test suite checks lengths and areas of straight and arc outlines against known values.
Limits
Totals are ordinary 64-bit sums, added in order. The centroid of an outline with arcs is found on 5° chords.

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