Resources Numerical accuracy Calculation policy

Back-outs clipped out of areas

Works out how much of an area a back-out covers, so that part can be taken off the area's quantity.

How the precision is kept

When the back-out lies wholly inside the area and clear of its cut-outs, its own area is used, arcs included. Otherwise both shapes are broken into 0.5° chords and clipped, so arcs are polygons in that case. All arithmetic is 64-bit.

Calculations under this policy

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

geom.js TakeoffCraft 0.8.1 64-bit 3 calculations Reviewed

Tests bin/test-takeoffcraft.mjs

Convex outline test

64-bit

Whether a closed outline has no inward corner.

Arcs are flattened to 0.5° chords first. Each corner's cross product is ignored when it is at most 1e-12 × scale², where scale is the largest |x| + |y| of the points (at least 1), so the tolerance grows with the distance from the origin, not with the size of the outline.

  • geom.js
  • lines 292 to 306
  • TakeoffCraft 0.8.1
/** Is a closed outline convex (no inward corner; arcs flattened)? Collinear points are fine. */
export function isConvex(pts0) {
  const pts = flatten(pts0, true, FINE);
  const n = pts.length;
  if (n < 3) return false;
  let sign = 0;
  const scale = Math.max(...pts.map((p) => Math.abs(p.x) + Math.abs(p.y)), 1);
  for (let i = 0; i < n; i++) {
    const c = cross(pts[i], pts[(i + 1) % n], pts[(i + 2) % n]);
    if (Math.abs(c) <= 1e-12 * scale * scale) continue;      // rounding noise only: 0.5° chords of a small circle far out still count
    if (!sign) sign = Math.sign(c);
    else if (Math.sign(c) !== sign) return false;
  }
  return sign !== 0;
}

Polygon clipped to a convex polygon

64-bit

The part of a polygon that lies inside a convex polygon.

t = d₁ ÷ (d₁ − d₂); corner = p + (q − p)·t

Each new corner is p + (q − p)·t with t = d₁ ÷ (d₁ − d₂). It is only computed when the two ends are on opposite sides, so d₁ − d₂ is not zero; when both distances are tiny the ratio is poorly determined. The inside test is cross ≥ 0 with no tolerance.

  • geom.js
  • lines 308 to 329
  • TakeoffCraft 0.8.1
/** Sutherland–Hodgman: the part of `subject` (any simple polygon) inside the CONVEX polygon `clip` (both {x,y} lists). */
export function clipConvex(subject, clip) {
  const ccw = signedArea(clip) > 0 ? clip : clip.slice().reverse();
  let out = subject;
  for (let i = 0; i < ccw.length && out.length; i++) {
    const a = ccw[i], b = ccw[(i + 1) % ccw.length];
    const inside = (p) => cross(a, b, p) >= 0;
    const meet = (p, q) => {
      const d1 = cross(a, b, p), d2 = cross(a, b, q), t = d1 / (d1 - d2);
      return { x: p.x + (q.x - p.x) * t, y: p.y + (q.y - p.y) * t };
    };
    const inp = out;
    out = [];
    for (let j = 0; j < inp.length; j++) {
      const p = inp[j], q = inp[(j + 1) % inp.length];
      const pin = inside(p), qin = inside(q);
      if (pin) out.push(p);
      if (pin !== qin) out.push(meet(p, q));
    }
  }
  return out;
}

Area a back-out covers

64-bit

How much of an area mark (outline minus cut-outs) a convex back-out covers, in square PDF units.

overlap = max(0, A(outline ∩ b) − Σ A(cut-out ∩ b))

If no edges cross and every 0.5° chord point of the back-out is inside the outline and outside the cut-outs, the result is the back-out's own area with arcs exact. Otherwise it is the clipped outline's area minus the clipped cut-outs' areas, with every arc as 0.5° chords, and a negative result is set to 0.

  • geom.js
  • lines 331 to 344
  • TakeoffCraft 0.8.1
/**
 * How much of an area mark (outline minus holes) a convex back-out covers, in square PDF units.
 * Exact (arcs included) when the back-out sits wholly inside the area and clear of its holes; else clipped with
 * arcs flattened to 0.5° chords. → 0 when they do not overlap.
 */
export function overlapArea(outer, holes, b) {
  const ob = bounds(outer), bb = bounds(b);
  if (bb.minX >= ob.maxX || bb.maxX <= ob.minX || bb.minY >= ob.maxY || bb.maxY <= ob.minY) return 0;
  const fb = flatten(b, true, FINE);
  const clear = (h) => !outlinesCross(h, b) && !fb.some((v) => pointInPolygon(v, h)) && !flatten(h, true).some((v) => pointInPolygon(v, b));
  if (!outlinesCross(outer, b) && fb.every((v) => pointInPolygon(v, outer)) && holes.every(clear)) return polygonArea(b);
  const part = (ring) => polygonArea(clipConvex(flatten(ring, true, FINE), fb));
  return Math.max(0, part(outer) - holes.reduce((s, h) => s + part(h), 0));
}
takeoffcraft.js TakeoffCraft 0.8.1 64-bit 1 calculation Reviewed

Tests bin/test-takeoffcraft.mjs

Net area shown for a mark

64-bit

The area in a mark's properties after its back-outs are taken off.

net = max(0, area − cut); plan area = max(0, plan − cut ÷ factor)

cut = the back-outs' overlap × the mark's slope factor, subtracted from the mark's area in 64-bit and kept at 0 or above. The numbers are rounded to 2 decimals (the slope factor to 4) for the panel only.

  • takeoffcraft.js
  • lines 2141 to 2144
  • TakeoffCraft 0.8.1
const cut = it.backout ? 0 : backouts(model.state).cut(m, sc.ftPerUnit) * r.factor;
rows.push(...kv(t('mark.f.area'), U.fixed(Math.max(0, r.areaSqft - cut), 2) + ' SF'));
if (cut > 0) rows.push(...kv(t('props.backouts'), '−' + U.fixed(cut, 2) + ' SF'));
if (r.factor > 1) rows.push(...kv(t('props.plan_area'), U.fixed(Math.max(0, r.planSqft - cut / r.factor), 2) + ' SF'), ...kv(t('props.slope_factor'), U.fixed(r.factor, 4)));

In detail

Method
Sutherland–Hodgman clipping of the area's outline and of each cut-out against the convex back-out; shoelace area of each clipped polygon; overlap = outline part − Σ cut-out parts, never below 0.
Number types
64-bit numbers.
Tolerances
Convexity: a corner counts as straight when |cross| ≤ 1e-12 × scale², scale = max(|x| + |y| over the points, 1). The clip's inside test uses cross ≥ 0 with no tolerance. A bounding-box test returns 0 early when the boxes do not overlap.
Limits
In the clipped case a full circle's 0.5° chord polygon is smaller than the circle by about 1.3 parts in 100,000 (φ² ÷ 6 with φ = π ÷ 360). The back-out must be convex; the area need not be.

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