hex_roof API

← hex_roof · version 0.1.9

17 public items, in source order. Generated from the registry index, which re-derives them from the package source at publish time — so a signature here is the one that version actually ships.

pub fn roof_cone(s: HexSet, f: Heights, cx: float, cy: float, peak: float, slope: float, dmax: float)

z = peak - slope*d about a point. Clamping d at dmax is not a detail: it is what makes an eave LEVEL on a quantised footprint, where the boundary cells sit at a spread of true radii (eave_spread measures the difference).

AND THE CLAMP HAS ITS OWN COST, worth knowing before reaching for it: the cells it clamps become a flat plateau, and an interior cell inside that plateau PONDS. On a quantised footprint the boundary and the interior overlap in radius, so there is no dmax that levels the eave and still drains — measured below. roof_hip has neither problem, because it never measures a radius: the boundary is ring 0 by definition.

cx/cy are WORLD coordinates, not cell indices.

pub fn roof_ridge(s: HexSet, f: Heights, t: Track, peak: float, slope: float, dmax: float)

The same profile about a ridge. A full-length ridge gives a gable; shorten it and the ends hip in, because track_distance clamps to the segment.

pub fn vault_arc(s: HexSet, f: Heights, t: Track, spring: float, radius: float)

The SOFFIT of a barrel vault or an arch: spring + sqrt(R^2 - d^2), d measured from the crown line. Beyond the springing the opening has closed, so the soffit is the springing itself. Pass a radius larger than the half-span for a segmental arch; equal to it for a semicircular one.

pub fn roof_hip(s: HexSet, f: Heights, eave: float, per_ring: float) -> integer

THE DISTANCE-TO-BOUNDARY ROOF — the one that actually matters on a quantised footprint.

Every cell rises at a constant slope from its NEAREST EAVE, which is the definition of a hip roof. On a circular footprint that is a cone; on an octagonal one a pyramid; on an L-shaped hall a properly mitred hip-and-valley. The FORM is entirely the footprint's shape — there is one function here, not a catalogue, and that is the whole reason "roof forms" stopped being a list.

Driving it from the FOOTPRINT rather than from the ideal circle is also what makes the eave level. Boundary cells are at ring 0 by definition, whatever their true radius. Drive a cone from each cell's own radius instead and the eave inherits the footprint's roundness deviation, wobbling by slope x that spread — eave_spread measures it, because the failure is invisible until you look at the eave rather than the apex.

Multi-source BFS inward from the boundary, on the pre-allocated array + index-write idiom (appending while a head pointer walks the same vector is the broken shape).

pub fn dome(s: HexSet, f: Heights, cx: float, cy: float, base: float, radius: float)

A DOME — a point source with a CIRCULAR profile, where a cone is a point source with a linear one. That single swap is the whole difference, and it is also why a dome is the harder object: a cone's slope is constant, a dome's diverges at the springing (dz/dr = -r/sqrt(R^2-r^2)). Past the 45-degree point r = R/sqrt(2) the surface is more wall than roof and a per-cell height stops being able to carry it — which is exactly why real domes sit on a vertical DRUM and only the cap is vaulted.

pub fn vault_groin(s: HexSet, f: Heights, t1: Track, t2: Track, spring: float, radius: float)
pub fn vault_cloister(s: HexSet, f: Heights, t1: Track, t2: Track, spring: float, radius: float)
pub fn clear_height(fl: Heights, so: Heights, q: integer, r: integer) -> float

What can pass here: the gap between floor and soffit.

pub fn roof_ponds(s: HexSet, f: Heights) -> integer

PONDING — the roof's correctness invariant, and the analogue of a stair's monotonicity. An INTERIOR cell (all six neighbours in the footprint) every one of whose neighbours is strictly higher is a local minimum: water collects and never leaves. A roof that sheds has none. Boundary cells are exempt — they drain off the eave, which is their job.

A zero here is not "no local minima": a hollow on the BOUNDARY is not counted.

pub fn eave_spread(s: HexSet, f: Heights) -> float

The spread of eave heights around a footprint's boundary — 0 for a level eave.

THE INSTRUMENT FOR THE FAILURE NOTHING ELSE SEES: a roof built from the wrong distance source still has an apex, still is not flat and still sheds water. This is the number that moves.

pub struct RoofFit { rf_kind: integer, rf_x: float, rf_y: float, rf_z: float, rf_slope: float, rf_res: float, rf_x2: float = 0.0, rf_y2: float = 0.0 }

A roof surface fitted to a footprint: its kind (ROOF_*), that kind's parameters and the worst residual over the footprint. roof_plane_fit, roof_cone_fit, roof_dome_fit and roof_ridge_fit answer one.

pub fn roof_plane_fit(s: HexSet, f: Heights) -> RoofFit

Least squares z = a*x + b*y + c. Covers flat and every shed/mono-pitch.

pub fn roof_ridge_fit(s: HexSet, f: Heights) -> RoofFit

Fit a ridge — the gable / hip profile roof_ridge draws — to the heights over these cells.

FOUR SEEDS, AND THE BEST RESIDUAL WINS. The one that works on a gable comes from the data: the ridge is the LOCUS OF THE MAXIMUM, so the two highest cells furthest apart give its direction, and the seed is that line extended across the whole footprint. The climb then SHORTENS it for a hipped roof, which is the direction it handles well. The other three — both ends at the highest cell, both at roof_cone_fit's centre, one at each — are cheap insurance: a climb grown out of a POINT can lower its residual by running the segment ACROSS the ridge until the fit is "flat at the mean", and a cone fitted to a gable puts its apex far outside the footprint. Taking the better of several starts is not a threshold; when they converge it is the same answer.

Each end is climbed in the eight directions roof_cone_fit uses, on the same step schedule.

pub fn roof_cone_fit(s: HexSet, f: Heights) -> RoofFit

Fit a cone to the heights over these cells: apex, slope and how well it fits. The centre is seeded at the highest cell and then refined — the apex cell is only within one circumradius of the true apex, so the refinement is what makes the recovery exact. roof_match asks this, the plane, the dome and the ridge, and keeps what fits.

pub fn roof_dome_fit(s: HexSet, f: Heights) -> RoofFit

Fit a dome to the same heights, reported the same way, so the two are comparable.

pub fn roof_match(s: HexSet, f: Heights, tol: float) -> RoofFit

Decide what the field IS. A plane wins ties: it is the simpler statement, and a cone fitted to a flat roof would put its apex somewhere arbitrary.

It tries plane, cone, dome, then ridge, and answers the first whose worst residual is within tol. The ridge is LAST because a cone is a ridge whose segment has zero length: trying it first would answer ROOF_RIDGE for a true cone, the less specific statement.

⛔ **WHEN NOTHING FITS, IT SAYS SO — ROOF_UNKNOWN**, never the least-bad shape under a kind it is not; roof_eval draws nothing for it. Every other field is the closest candidate's — a ridge's both ends included — because which shape came closest and by how much is what a caller diagnoses with.

pub fn roof_eval(fit: RoofFit, x: float, y: float) -> float

The recovered ANALYTIC surface — what a renderer should draw, at any resolution, instead of the cells it came from.