Works out the area and perimeter of circles, polylines and hatches and the length of lines, arcs, polylines and splines.
How the precision is kept
Areas of polygons are the shoelace sum over the stored coordinates in 64-bit numbers, with a circular-segment term for each arc segment. The sum uses the raw coordinates, not coordinates relative to the shape, so its terms are of the size of the coordinates squared and cancel down to the area. The further a shape is from the origin compared with its own size, the fewer digits the area keeps.
Open item. Subtract the first vertex from every coordinate before forming the shoelace products.
Calculations under this policy
5 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.jsCadCraft 0.8.264-bit5 calculationsReviewed
Tests bin/test-cadcraft.mjs
Polygon area and perimeter
64-bit
The signed area and the perimeter of a closed run of points.
area = ½ Σ (xᵢ·yᵢ₊₁ − xᵢ₊₁·yᵢ); perimeter = Σ |pᵢ₊₁ − pᵢ|
The products use the raw coordinates and are added in vertex order. Each product is of the size of the coordinates squared and is rounded before the subtraction, so for a shape far from the origin the area loses digits: a 1 × 1 square with its first corner at (10⁷ + 0.1, 10⁷ + 0.1) returns 0.984375. The perimeter is a sum of Math.hypot distances and is not affected.
geom.js
lines 1519 to 1528
CadCraft 0.8.2
/** Signed area of a closed run of points (counter-clockwise positive) and its perimeter, closing segment included. */
export function polygonMeasure(pts) {
let a = 0, p = 0;
for (let i = 0, n = pts.length; i < n; i++) {
const u = pts[i], v = pts[(i + 1) % n];
a += u.x * v.y - v.x * u.y;
p += dist(u, v);
}
return { area: a / 2, perimeter: pts.length >= 2 ? p : 0 };
}
Polyline area and length
64-bit
The area enclosed by a polyline with arc segments, and its length.
area = ½ |Σ (xᵢ·yᵢ₊₁ − xᵢ₊₁·yᵢ) + Σ ± r²(θ − sin θ)|; θ = 4·atan|b|; r = c ÷ (2·sin(θ ÷ 2)); arc length = r·θ
The same raw-coordinate shoelace sum as the polygon, with the same loss of digits far from the origin. For a shallow arc θ − sin θ subtracts nearly equal numbers while r² is large, so that segment's area keeps fewer digits; the area concerned is itself small. An open polyline is closed with a straight line for the area, and that closing line is left out of the length.
geom.js
lines 1530 to 1547
CadCraft 0.8.2
/** Signed area enclosed by a polyline (closed or not: an open one is closed by a straight line, as AutoCAD's AREA does). */
function polyMeasure(e) {
const v = e.pts, n = v.length;
let a = 0, len = 0;
for (let i = 0; i < n; i++) {
const u = v[i], w = v[(i + 1) % n];
a += u.x * w.y - w.x * u.y;
const last = i === n - 1;
const b = last && !e.closed ? 0 : (u.b || 0);
const c = dist(u, w);
if (Math.abs(b) > 1e-12 && c > EPS) {
const th = 4 * Math.atan(Math.abs(b)), r = c / (2 * Math.sin(th / 2));
a += Math.sign(b) * r * r * (th - Math.sin(th)); // ×2, like the shoelace terms
if (!last || e.closed) len += r * th;
} else if (!last || e.closed) len += c;
}
return { area: Math.abs(a / 2), length: len };
}
Circle area and circumference
64-bit
The area and circumference of a circle.
area = π·r²; circumference = 2π·r
One 64-bit expression each, with Math.PI and TAU = Math.PI × 2.
geom.js
line 1555
CadCraft 0.8.2
case 'circle': return { area: Math.PI * e.r * e.r, perimeter: TAU * e.r, kind: 'circle' };
Hatch area and perimeter
64-bit
The filled area of a hatch with holes, and the total length of its boundaries.
area = |Σ ± |ring area||, − for a ring inside an odd number of other rings; perimeter = Σ ring perimeters
Whether a ring is a hole is decided from its first vertex only, with a ray-crossing test that has no tolerance. Ring areas come from polygonMeasure and share its loss of digits far from the origin. The perimeter adds every ring, holes included.
geom.js
lines 1573 to 1583
CadCraft 0.8.2
// even-odd: a loop inside an odd number of others is a hole
const rings = e.loops.map((l) => { const pts = []; for (let i = 0; i + 1 < l.length; i += 2) pts.push({ x: l[i], y: l[i + 1] }); return pts; }).filter((r) => r.length >= 3);
if (!rings.length) return null;
let area = 0, perimeter = 0;
rings.forEach((r, i) => {
const m = polygonMeasure(r);
const depth = rings.filter((o, j) => j !== i && pointInPoly(r[0], o)).length;
area += (depth % 2 ? -1 : 1) * Math.abs(m.area);
perimeter += m.perimeter;
});
return { area: Math.abs(area), perimeter, kind: 'closed' };
Length of a line, arc, polyline or spline
64-bit
The length used by the length and lengthen commands.
line: √(Δx² + Δy²); arc: r × ((a1 − a0) mod 2π); polyline: Σ segment lengths
An arc whose reduced sweep is exactly 0 is measured as a full turn. Polyline segment lengths are added in order. A spline's length is the sum of the straight chords between the points spline.js returns for it, not an integral of the curve.
geom.js
lines 1294 to 1301
CadCraft 0.8.2
/** Length of a line, arc or open polyline (null for others). */
export function curveLength(e) {
if (e.type === 'line') return Math.hypot(e.x2 - e.x1, e.y2 - e.y1);
if (e.type === 'arc') return e.r * (normAngle(e.a1 - e.a0) || TAU);
if (e.type === 'poly') return polyPrims(e).reduce((s, p) => s + primLength(p), 0);
if (e.type === 'spline') return prims(e).reduce((s, p) => s + primLength(p), 0);
return null;
}
In detail
Method
Shoelace formula; arc segment area r²(θ − sin θ) ÷ 2 with θ = 4·atan|bulge|; circle π·r² and 2π·r; hatch by the even-odd rule.
Number types
64-bit numbers; π is Math.PI.
Tolerances
A bulge of 1e-12 or less, or a chord of 1e-9 or less, is measured as a straight segment.
Limits
A 1 × 1 square whose first corner is at (10⁷ + 0.1, 10⁷ + 0.1) measures 0.984375, and at (10⁸ + 0.1, 10⁸ + 0.1) it measures 2. A spline's length is the sum of the straight chords of its sampled points.
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.