Works out the height of a hand-drawn roof plane at any point, its sloped slab, and its sloped and projected areas.
How the precision is kept
A plane is a plan polygon whose first side is the eave; heights rise inward at tan(pitch). Everything is closed-form 64-bit arithmetic in millimeters and degrees. The polygon's points are stored as text with 3 decimals of the file's length unit and the pitch with 4 decimals of a degree, and the plane is rebuilt from those stored values.
Open item. Store the plane's points with 6 decimals, as its base and thickness already are, instead of 3.
Calculations under this policy
4 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.
Finds the eave direction and the inward direction of a roof plane from its polygon.
u = (p₁ − p₀) ÷ |p₁ − p₀|; n = (−u_y, u_x), flipped when Σ (p − p₀) · n < 0
The inward side is decided by the sign of a sum of signed distances of all points from the eave line, added in order. A zero eave length is replaced by 1.
res-roofplane.js
lines 27 to 37
BimCraft 0.8.1
/** The eave (first side) frame of a polygon: origin p0, u along the eave, n inward (towards the polygon's side). */
export function planeFrame(points) {
const p0 = points[0], p1 = points[1], L = Math.hypot(p1[0] - p0[0], p1[1] - p0[1]) || 1;
const u = [(p1[0] - p0[0]) / L, (p1[1] - p0[1]) / L];
let n = [-u[1], u[0]];
// inward: towards where most of the polygon lies
const side = points.reduce((s, p) => s + (p[0] - p0[0]) * n[0] + (p[1] - p0[1]) * n[1], 0);
// keep n to the left of u (so the frame's Z points up): when the polygon lies to the right, u turns round instead
if (side < 0) return { o: p0, u: [-u[0], -u[1]], n: [-n[0], -n[1]] };
return { o: p0, u, n };
}
Height of the plane at a point
64-bit
The height of the plane's underside over a plan point.
z = base + tan θ × ((p − o) · n)
One 64-bit expression using Math.tan of the pitch in radians. The pitch is limited to less than 80 degrees when the plane is placed or changed, not in this function.
res-roofplane.js
lines 38 to 41
BimCraft 0.8.1
export function planeZ(rp, p) {
const f = planeFrame(rp.points), s = Math.tan(rp.pitch * D2R);
return rp.base + s * ((p[0] - f.o[0]) * f.n[0] + (p[1] - f.o[1]) * f.n[1]);
}
Polygon area
64-bit
The signed plan area of the plane's polygon.
A = ½ Σ (xᵢ yᵢ₊₁ − yᵢ xᵢ₊₁)
The shoelace sum on the stored millimeter coordinates, added in order. For a polygon far from the origin each product is much larger than the area, so the differences cancel leading digits.
res-roofplane.js
line 42
BimCraft 0.8.1
function area(p) { let s = 0; for (let i = 0; i < p.length; i++) { const a = p[i], b = p[(i + 1) % p.length]; s += a[0] * b[1] - a[1] * b[0]; } return s / 2; }
Sloped slab and areas written to the model
64-bit
Builds the plane's slab and writes its sloped area, projected area, points, pitch, base and thickness.
profile y = ((p − o) · n) ÷ cos θ; depth = thickness ÷ cos θ; GrossArea = projected ÷ cos θ
The sloped area is the projected area divided by cos(pitch). The points are stored as text rounded to 3 decimals of the file's length unit (1 mm in a file in meters) and the pitch to 4 decimals; readRoofPlanes reads those values back, so a later change rebuilds the plane from the rounded points.