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.
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.
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));
}
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.
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.