Cuts a section line through the ground, walls, paving, planted areas, steps and plants and lays the result out to scale on a sheet, and draws wall, paving and step details from their numbers.
How the precision is kept
Arithmetic is 64-bit. Where the section line meets outlines is found by segment intersection, with distances along the line rounded to 9 decimals. What is drawn at those places is partly sampled: a wall takes the values of its nearest station, a flight of steps is sampled along the cut, and a build-up follows up to 200 points of the proposed ground. Sheets use standard scales with 304.8 mm per project foot.
Calculations under this policy
11 calculations in 3 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.
The stretches of the section line, as distances along it, that lie inside a closed outline.
d = distance to the segment start + t × segment length, for every crossing t
Crossing distances are rounded with Math.round(d × 1e9) ÷ 1e9 before they are sorted and paired, and that rounded value is what the bands are drawn from. A stretch is kept when a point-in-polygon test at its midpoint passes.
landscape/section.js
lines 20 to 40
CivilCraft 0.8.1
export function crossIntervals(line, poly) {
if (!poly || poly.length < 3 || line.length < 2) return [];
const ds = [];
let acc = 0;
for (let i = 1; i < line.length; i++) {
const a = line[i - 1], b = line[i], l = Math.hypot(b.x - a.x, b.y - a.y);
ds.push(acc);
for (let j = 0; j < poly.length; j++) { const t = segCross(a, b, poly[j], poly[(j + 1) % poly.length]); if (t !== null) ds.push(acc + t * l); }
acc += l;
}
ds.push(acc);
const D = [...new Set(ds.map((v) => Math.round(v * 1e9) / 1e9))].sort((p, q) => p - q), out = [];
const at = (d) => { let s = 0; for (let i = 1; i < line.length; i++) { const a = line[i - 1], b = line[i], l = Math.hypot(b.x - a.x, b.y - a.y); if (d <= s + l || i === line.length - 1) { const f = l ? (d - s) / l : 0; return { x: a.x + f * (b.x - a.x), y: a.y + f * (b.y - a.y) }; } s += l; } return line[0]; };
for (let k = 0; k + 1 < D.length; k++) {
if (D[k + 1] - D[k] < 1e-9) continue;
const m = at((D[k] + D[k + 1]) / 2);
if (!pointIn(m.x, m.y, poly)) continue;
if (out.length && Math.abs(out[out.length - 1][1] - D[k]) < 1e-9) out[out.length - 1][1] = D[k + 1]; else out.push([D[k], D[k + 1]]);
}
return out;
}
Where the section line crosses an open line
64-bit
The distances along the section line where it crosses a wall or other open line.
d = distance to the segment start + t × segment length
Uses segCross from areas.js for each pair of segments. Distances are rounded to 9 decimals and duplicates removed.
landscape/section.js
lines 42 to 51
CivilCraft 0.8.1
export function crossAt(line, pl) {
const out = [];
let acc = 0;
for (let i = 1; i < line.length; i++) {
const a = line[i - 1], b = line[i], l = Math.hypot(b.x - a.x, b.y - a.y);
for (let j = 1; j < pl.length; j++) { const t = segCross(a, b, pl[j - 1], pl[j]); if (t !== null) out.push(acc + t * l); }
acc += l;
}
return [...new Set(out.map((v) => Math.round(v * 1e9) / 1e9))].sort((p, q) => p - q);
}
Distance along and across the section line
64-bit
How far along the section line a point is and how far to its side, used to pick the plants drawn in the section.
One clamped projection per segment; the side comes from the sign of a cross product. The nearest segment wins.
landscape/section.js
lines 53 to 63
CivilCraft 0.8.1
export function offsetFrom(line, p) {
let best = { d: 0, off: Infinity }, acc = 0;
for (let i = 1; i < line.length; i++) {
const a = line[i - 1], b = line[i], dx = b.x - a.x, dy = b.y - a.y, L2 = dx * dx + dy * dy, l = Math.sqrt(L2);
const t = L2 ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / L2)) : 0;
const off = Math.hypot(p.x - a.x - t * dx, p.y - a.y - t * dy);
if (off < Math.abs(best.off)) best = { d: acc + t * l, off: ((dx * (p.y - a.y) - dy * (p.x - a.x)) >= 0 ? 1 : -1) * off };
acc += l;
}
return best;
}
Proposed ground level at a distance
64-bit
The level of the proposed ground at a distance along the section, between the cut's points.
z = z₀ + (d − d₀) ÷ (d₁ − d₀) × (z₁ − z₀)
Linear interpolation between neighboring cut points, accepted up to 1e-9 beyond either end. A zero-length step returns the first point's level.
landscape/section.js
lines 65 to 69
CivilCraft 0.8.1
/** The proposed ground's z at distance d (linear between the cut's points), or null off the surface. */
function zOn(runs, d) {
for (const r of runs || []) for (let i = 1; i < r.length; i++) if (d >= r[i - 1].d - 1e-9 && d <= r[i].d + 1e-9) { const f = r[i].d - r[i - 1].d ? (d - r[i - 1].d) / (r[i].d - r[i - 1].d) : 0; return r[i - 1].z + f * (r[i].z - r[i - 1].z); }
return null;
}
Build-up layers under the ground line
64-bit
Each layer of a paving or planted-area build-up drawn as a band hanging below the proposed ground.
top of layer = ground − Σ thickness of the layers above; y = z × vertical exaggeration
The ground is sampled at between 2 and 200 evenly spaced points across the band, so a break in the ground between samples is cut straight. Layer depths are added in order.
landscape/section.js
lines 76 to 86
CivilCraft 0.8.1
// build-ups: each layer a band hanging below the proposed ground, sampled along the interval
for (const b of input.bands || []) {
const n = Math.max(2, Math.min(200, Math.ceil((b.d1 - b.d0) / Math.max(1e-6, (input.length || 1) / 200)) + 1)), ds = Array.from({ length: n }, (_, i) => b.d0 + ((b.d1 - b.d0) * i) / (n - 1));
const top = ds.map((d) => zOn(input.proposed, d));
if (top.some((z) => z === null)) continue;
let depth = 0;
for (const L of b.layers) {
const a = ds.map((d, i) => ({ x: d, y: Y(top[i] - depth) })), c = ds.map((d, i) => ({ x: d, y: Y(top[i] - depth - L.thickness) })).reverse();
P([...a, ...c], 'L-SECT-BLDP', { closed: true });
depth += L.thickness;
}
The top and footing levels of a wall where the section line crosses it.
values of the wall station nearest the crossing point
The crossing point is placed by its distance ÷ the line length along the section's two end points. The wall's top and footing are copied from the nearest station of the wall layout, not interpolated between stations. The footing depth drawn is capped at 0.3 m or 1 ft.
landscape/section-ls.js
lines 55 to 58
CivilCraft 0.8.1
for (const d of crossAt(line, w.pts)) {
const f = d / L, p = { x: line[0].x + f * (line[1].x - line[0].x), y: line[0].y + f * (line[1].y - line[0].y) };
const st = lay.stations.reduce((b, s) => (!b || Math.hypot(s.x - p.x, s.y - p.y) < Math.hypot(b.x - p.x, b.y - p.y) ? s : b), null);
if (st) input.walls.push({ d, top: st.top, footing: st.footing, thickness: w.thickness, footingWidth: w.footing.width, footingDepth: Math.min(w.footing.depth, units === 'm' ? 0.3 : 1), name: w.name });
Steps in profile
64-bit
The stepped outline of a flight where the section line passes through it.
risers climbed = ⌊distance along the flight ÷ going⌋ + 1; z = bottom + risers × riser
The profile is sampled at steps of max(stretch ÷ 400, going ÷ 8), built by repeated addition, so each riser is drawn within one such step of its true position. Math.floor decides the riser count with no tolerance.
landscape/section-ls.js
lines 73 to 78
CivilCraft 0.8.1
const prof = [], step = Math.max((d1 - d0) / 400, lay.going / 8);
for (let d = d0; d <= d1 + 1e-9; d += step) {
const f = Math.min(d, d1) / L, p = { x: line[0].x + f * (line[1].x - line[0].x), y: line[0].y + f * (line[1].y - line[0].y) };
const sAlong = (p.x - s.pts[0].x) * lay.ux + (p.y - s.pts[0].y) * lay.uy;
const k = sAlong >= lay.run ? lay.n : Math.max(0, Math.min(lay.n, Math.floor(sAlong / lay.going) + 1));
prof.push({ d: Math.min(d, d1), z: lay.zB + k * lay.riser });
Plants near the section line
64-bit
The plants drawn in a section, with their height and spread in project units.
h = height (m) ÷ k; k = 0.3048 for feet
Plant sizes in meters are divided by 0.3048 in a feet project, while planting.js converts with 1200 ÷ 3937. A plant is drawn when its offset from the line is within the section's band and it lies strictly between the line's ends.
landscape/section-ls.js
lines 89 to 95
CivilCraft 0.8.1
const show = ls.show || 'mature', k = units === 'm' ? 1 : 0.3048;
for (const p of placed(ls, index, units, sf ? (x, y) => sf.elevationAt(x, y) : null)) {
const sp = index.get(p.key);
if (!sp) continue;
const o = offsetFrom(line, p), sz = sizeOf(sp, show);
if (Math.abs(o.off) > sec.band || o.d <= 0 || o.d >= L || !fin(p.z)) continue;
input.plants.push({ d: o.d, z: p.z, h: sz.h / k, s: sz.s / k, tree: sp.type === 'tree', name: sp.botanical });
Scale of a section on its sheet
64-bit
Picks the standard scale a section is drawn at and the paper millimeters per project unit.
k = real ÷ n; real = 1000 mm per m or 304.8 mm per ft; n = first of 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000 with real ÷ n ≤ fit
The scale is one of a fixed list, or 1:5000 when none fits. A project foot is drawn as 304.8 mm, the international foot. The drawing's width and height are floored at 1e-6 before the fit is divided out.
The batter angle in degrees is multiplied by Math.PI ÷ 180. The footing thickness drawn is schematic: the smaller of 0.6 of the footing depth and 0.3 m, with the feet value written as 0.3 × 3.28.
landscape/sheets.js
lines 129 to 133
CivilCraft 0.8.1
const w = d.wall, H = st.top - st.footing, W = w.footing.width * 2.2;
const { k, label } = drawIn(W, H + 0.3 * H);
const bx = cx0 + cellW / 2, by = cy1 - cellH + 18, z = (v) => by + (v - st.footing) * k;
const half = (w.thickness / 2) * k, fw = (w.footing.width / 2) * k, ft = Math.min(w.footing.depth * 0.6, 0.3 * (o.units === 'm' ? 1 : 3.28)) * k;
const bat = Math.tan((w.batter * Math.PI) / 180) * (st.top - st.footing) * k;
In detail
Method
Intervals inside an outline come from all crossings of the line with the outline's edges, kept where the midpoint is inside. The proposed ground between cut points is linear. Sheet scale is the first standard scale that fits the cell.
Number types
64-bit numbers.
Tolerances
Distances along the section line rounded to 1e-9; intervals shorter than 1e-9 dropped; ground lookup allows 1e-9 beyond a run's ends. A section line shorter than 1e-6 is refused.
Limits
Wall top and footing are those of the nearest wall station, not interpolated. Plant sizes in a feet project are divided by 0.3048, and sheet scales use 304.8 mm per foot, while the landscape engine's project foot is 1200 ÷ 3937 m. The details sheet draws some dimensions schematically.
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.