Finds where two wall center lines meet and the new ends of a wall trimmed or extended to another.
How the precision is kept
The meeting point is a closed-form line intersection in 64-bit plan millimeters. The parallel test is scaled to the lengths of both walls. Decisions about which part of a wall stays, and whether a door or window would be cut, use fixed margins of half a millimeter.
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.
The meeting point of line a→b and line c→d, with its position along each.
den = r × s; t = ((c − a) × s) ÷ den; u = ((c − a) × r) ÷ den; p = a + t·r
The lines count as parallel when |den| < 1e-3 × |r| × |s|, a test on the sine of the angle between them, so it does not depend on the walls' lengths. The point is computed from the first line only.
model-trim.js
lines 34 to 39
BimCraft 0.8.1
export function axisMeet(a, b, c, d) {
const r = sub(b, a), s = sub(d, c), den = cross(r, s);
if (Math.abs(den) < 1e-3 * Math.hypot(...r) * Math.hypot(...s)) return null; // within ~0.06°: parallel (walls drawn by hand)
const ca = sub(c, a), t = cross(ca, s) / den, u = cross(ca, r) / den;
return { t, u, p: [a[0] + r[0] * t, a[1] + r[1] * t] };
}
New ends of a trimmed wall
64-bit
Which part of the wall stays and where its moved end goes.
inside when 0.5 ÷ L < t < 1 − 0.5 ÷ L; click side by t_c = (click − a)·(b − a) ÷ L²
A meeting point within half a millimeter of an end counts as that end. The result is refused when the wall would be shorter than 50 mm.
model-trim.js
lines 41 to 52
BimCraft 0.8.1
/** The wall's new ends for a meeting point at t (along a→b), keeping the side of `click` when the point is inside the wall. */
function toPoint(wall, t, p, click) {
const L = len(wall.a, wall.b), eps = 0.5 / L; // half a millimetre
let out;
if (t > eps && t < 1 - eps) {
const tc = dot(sub(click, wall.a), sub(wall.b, wall.a)) / (L * L);
out = tc < t ? { a: wall.a, b: p } : { a: p, b: wall.b };
} else if (t <= eps) out = { a: p, b: wall.b };
else out = { a: wall.a, b: p };
if (len(out.a, out.b) < MIN) return err('model.err.trim.short');
return out;
}
Trim or extend to a wall
64-bit
A wall's new ends when it is trimmed or extended to another wall.
allowed when −slack ≤ u ≤ 1 + slack, slack = (t_edge ÷ 2) ÷ L_edge
The meeting point must lie on the edge wall, with half that wall's thickness allowed past each end. The slack is expressed as a fraction of the edge wall's length.
model-trim.js
lines 54 to 62
BimCraft 0.8.1
export function trimPlan(wall, edge, click) {
if (wall.guid && wall.guid === edge.guid) return err('model.err.trim.same');
if (wall.bulge || edge.bulge) return err('comforts.err.curvedtrim'); // 0.6 round 2: curved walls are not trimmed yet
const m = axisMeet(wall.a, wall.b, edge.a, edge.b);
if (!m) return err('model.err.trim.parallel');
const Le = len(edge.a, edge.b), slack = (edge.t || 0) / 2 / Le;
if (m.u < -slack || m.u > 1 + slack) return err('model.err.trim.miss');
return toPoint(wall, m.t, m.p, click);
}
Corner two walls
64-bit
Both walls' new ends at the point their center lines meet.
One call to axisMeet gives the point and both parameters; each wall is then cut with toPoint. Both walls receive the same point, computed from the first wall's line.
model-trim.js
lines 64 to 74
BimCraft 0.8.1
export function cornerPlan(w1, click1, w2, click2) {
if (w1.guid && w1.guid === w2.guid) return err('model.err.trim.same');
if (w1.bulge || w2.bulge) return err('comforts.err.curvedtrim'); // 0.6 round 2: curved walls are not trimmed yet
const m = axisMeet(w1.a, w1.b, w2.a, w2.b);
if (!m) return err('model.err.trim.parallel');
const r1 = toPoint(w1, m.t, m.p, click1);
if (r1.error) return r1;
const r2 = toPoint(w2, m.u, m.p, click2);
if (r2.error) return r2;
return [r1, r2];
}
Whether a trim cuts through an opening
64-bit
Checks each door and window on the wall against the wall's new extent.
s₀ = offset − shift, s₁ = s₀ + width; cut when the opening is not wholly outside and s₀ < −0.5 or s₁ > L + 0.5
The shift is the dot product of the start point's move with the wall's unit direction. All comparisons use a fixed 0.5 mm margin.
model-trim.js
lines 76 to 84
BimCraft 0.8.1
export function cutsOpening(wall, plan, openings) {
const u = [(wall.b[0] - wall.a[0]) / len(wall.a, wall.b), (wall.b[1] - wall.a[1]) / len(wall.a, wall.b)];
const shift = dot(sub(plan.a, wall.a), u), L = len(plan.a, plan.b);
for (const o of openings.filter((x) => x.wallGuid === wall.guid)) {
const s0 = o.offset - shift, s1 = s0 + o.width;
if (!(s1 <= 0.5 || s0 >= L - 0.5) && (s0 < -0.5 || s1 > L + 0.5)) return 'model.err.trim.cuts';
}
return null;
}
In detail
Method
Parametric intersection of two lines by cross products: t = (c − a) × s ÷ (r × s), u = (c − a) × r ÷ (r × s).
Number types
64-bit numbers in plan millimeters.
Tolerances
Parallel when |r × s| < 1e-3 × |r| × |s| (about 0.06°). Inside-the-wall test and opening tests use 0.5 mm. No wall is trimmed shorter than 50 mm. The meeting point may lie up to half the edge wall's thickness past its ends.
Limits
Curved walls are refused. Walls closer to parallel than about 0.06° are refused, so near that limit the meeting point is far away and its position is sensitive to the walls' directions.
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.