Works out the planes, corners and edges of the free-form section volumes that cut the 3D view.
How the precision is kept
A section volume decides what the 3D view shows; it changes no model value. Planes and corners are computed in 64-bit in meters from the model's minimum corner and then handed to the viewer's clipping planes.
Calculations under this policy
3 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.
The outward normal and a point of each plane that bounds a section cell, with tilted faces and extra planes.
n = unit(n₀ + tan a × e_u + tan b × e_v); extra: n = (cos el cos az, cos el sin az, sin el)
64-bit trigonometry on angles in degrees times π ÷ 180. The result positions the view's clipping planes only.
section-volume.js
lines 79 to 100
BimCraft 0.8.1
/** Outward normal and point of each plane bounding the cell. */
export function cellPlanes(cell) {
const { min, max } = cell.box, c = centreOf(cell.box), out = [];
for (const f of FACES) {
const k = 'xyz'.indexOf(f[0]), hi = f[1] === '1';
const n0 = [0, 0, 0]; n0[k] = hi ? 1 : -1;
const p = [...c]; p[k] = hi ? max[k] : min[k];
const [ta, tb] = cell.tilt?.[f] || [0, 0];
let n = n0;
if (ta || tb) {
const [u, v] = FACE_AXES[f];
n = [...n0]; n[u] += Math.tan(rad(ta)); n[v] += Math.tan(rad(tb));
n = unit(n);
}
out.push({ n, p, face: f });
}
(cell.planes || []).forEach((q, i) => {
const n = unit([Math.cos(rad(q.el)) * Math.cos(rad(q.az)), Math.cos(rad(q.el)) * Math.sin(rad(q.az)), Math.sin(rad(q.el))]);
out.push({ n, p: add(c, mul(n, q.off)), extra: i });
});
return out;
}
Corners and edges of a section cell
32-bit, display only
The corners, faces and edges of the convex cell its planes bound, drawn as the cell's frame.
Every triple of planes is intersected and the points inside all planes are kept, with a tolerance scaled to the largest plane coordinate. The frame is drawn on screen; no stored value depends on it.
section-volume.js
lines 113 to 143
BimCraft 0.8.1
/**
* The convex polyhedron the planes bound: its corners, each plane's face as a loop of corner indices (counter-clockwise
* seen from outside; empty when the plane does not touch the cell) and the edges (pairs, once each). Empty cell → no verts.
*/
export function polyhedron(planes) {
const verts = [], on = [];
const scale = planes.reduce((m, q) => Math.max(m, Math.abs(q.p[0]), Math.abs(q.p[1]), Math.abs(q.p[2])), 1);
const tol = scale * 1e-7 + EPS;
for (let i = 0; i < planes.length; i++) for (let j = i + 1; j < planes.length; j++) for (let k = j + 1; k < planes.length; k++) {
const x = meet(planes[i], planes[j], planes[k]);
if (!x || !inside(planes, x, tol * 10)) continue;
let at = verts.findIndex((v) => Math.hypot(v[0] - x[0], v[1] - x[1], v[2] - x[2]) <= tol * 100);
if (at < 0) { at = verts.length; verts.push(x); on.push(new Set()); }
on[at].add(i).add(j).add(k);
}
const loops = planes.map((q, i) => {
const ids = [];
verts.forEach((v, vi) => { if (on[vi].has(i) || Math.abs(dot(q.n, sub(v, q.p))) <= tol * 100) ids.push(vi); });
if (ids.length < 3) return [];
const c = mul(ids.reduce((s, vi) => add(s, verts[vi]), [0, 0, 0]), 1 / ids.length);
const u = unit(sub(verts[ids[0]], c)), w = cross(q.n, u);
return ids.map((vi) => { const d = sub(verts[vi], c); return [vi, Math.atan2(dot(d, w), dot(d, u))]; })
.sort((a, b) => a[1] - b[1]).map((x) => x[0]);
});
const seen = new Set(), edges = [];
for (const L of loops) for (let i = 0; i < L.length; i++) {
const a = L[i], b = L[(i + 1) % L.length], key = a < b ? a + ',' + b : b + ',' + a;
if (!seen.has(key)) { seen.add(key); edges.push([a, b]); }
}
return { verts, loops, edges };
}
A plane through a point as an extra plane
32-bit, display only
Turns a plane given by a normal and a point into the azimuth, elevation and offset a section cell keeps.
az = atan2(u_y, u_x); el = asin(clamp(u_z, −1, 1)); off = u · (p − center)
The argument of the arcsine is clamped to [−1, 1]. The result sets a clipping plane of the view.
section-volume.js
lines 145 to 149
BimCraft 0.8.1
/** A plane through point p with outward normal n, as an extra plane of a cell whose box is `box`. */
export function planeFromPoint(n, p, box) {
const u = unit(n), c = centreOf(box);
return { az: ((deg(Math.atan2(u[1], u[0])) % 360) + 360) % 360, el: deg(Math.asin(clamp(u[2], -1, 1))), off: dot(u, sub(p, c)) };
}
In detail
Method
A cell is a box whose faces can be tilted and cut by extra planes; its corners are the points where three planes meet that lie inside all the others.
Number types
64-bit numbers in this file; the viewer clips on the GPU.
Tolerances
Three planes meet when their triple product is at least 1e-9. Corner tolerance = largest plane coordinate × 1e-7 + 1e-7; corners within 100 times that are merged. Tilts are limited to 80 degrees.
Approach: Display only
Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.