pub struct Roof { /* private fields */ }Expand description
A roof over a floor plan.
Build one with Roof::new for a hip, Roof::mansard for two pitches, or
Roof::with_profile for either — and for the truncated versions of both,
over a constrained skeleton.
Vertices are indexed by RoofVertexId, and the first
Skeleton::node_count of those stand directly over the skeleton nodes of
the same number, so the two structures stay in step and provenance survives
into 3D.
§Why every panel is flat
A panel is the region swept by one wall’s wavefront, so every point on it is
offset away from that wall’s supporting line — an affine function of
position. Each band of a Profile is affine in that distance, so the
composition is affine too, and the panel is a plane.
That is why a Profile::Mansard’s break has to cut panels rather than
merely bend them: the profile is only affine within a band, so a panel
spanning the break would be a fold, not a plane. Cut at the break, both
halves are planes again.
Planarity is therefore guaranteed by construction rather than fitted, and the crate’s tests re-derive every corner’s height from its wall to check it.
§Examples
use straight_skeleton::{skeleton, Point, Polygon, Roof};
// An L-shaped house.
let plan = Polygon::from_outer(&[
Point::new(0, 0),
Point::new(160, 0),
Point::new(160, 70),
Point::new(70, 70),
Point::new(70, 150),
Point::new(0, 150),
])?;
let roof = Roof::new(&skeleton(&plan)?, 0.6)?;
// Six walls, six panels.
assert_eq!(roof.panels().len(), 6);
// Every panel knows which wall it rises from.
for (i, panel) in roof.panels().iter().enumerate() {
assert_eq!(panel.wall().unwrap().0 as usize, i);
}
// Eaves sit at zero; nothing is below them.
assert!(roof.verts().iter().all(|v| v.position.z >= 0));Implementations§
Source§impl Roof
impl Roof
Sourcepub fn new(skeleton: &Skeleton, pitch: f32) -> Result<Roof, RoofError>
pub fn new(skeleton: &Skeleton, pitch: f32) -> Result<Roof, RoofError>
Raises a hip roof over a skeleton.
pitch is rise over run: 1.0 gives 45°, 0.5 gives a shallower roof
half as tall, 0.0 gives a flat one.
Shorthand for Roof::with_profile with a Profile::Hip. Use
Roof::mansard for two pitches with a break between them, and see
Roof::with_profile for what a constrained skeleton does here.
§Errors
§Examples
use straight_skeleton::{skeleton, skeleton_constrained, Point, Polygon, Roof, RoofError};
let plan = Polygon::from_outer(&[
Point::new(0, 0), Point::new(80, 0), Point::new(80, 80), Point::new(0, 80),
])?;
let skel = skeleton(&plan)?;
// A square plan gives a pyramid: its apex is 40 in from every wall.
assert_eq!(Roof::new(&skel, 1.0)?.ridge_height(), 40);
assert_eq!(Roof::new(&skel, 0.5)?.ridge_height(), 20);
assert_eq!(Roof::new(&skel, 0.0)?.ridge_height(), 0);
// A pitch that would push the apex past i16 is refused, not clamped.
assert!(matches!(
Roof::new(&skel, 1000.0),
Err(RoofError::HeightOverflow { .. })
));
// Stopping every wall at 10 cuts the apex off, leaving a flat.
let truncated = skeleton_constrained(&plan, &[10.0; 4])?;
let roof = Roof::new(&truncated, 1.0)?;
assert_eq!(roof.ridge_height(), 10);
assert_eq!(roof.flat().count(), 1);Sourcepub fn mansard(
skeleton: &Skeleton,
lower_pitch: f32,
break_offset: f32,
upper_pitch: f32,
) -> Result<Roof, RoofError>
pub fn mansard( skeleton: &Skeleton, lower_pitch: f32, break_offset: f32, upper_pitch: f32, ) -> Result<Roof, RoofError>
Raises a mansard roof: steep to break_offset, shallow above it.
Shorthand for Roof::with_profile with a Profile::Mansard. See
there for what a mansard is and why the skeleton underneath is the same
one a hip roof uses.
§Errors
§Examples
use straight_skeleton::{skeleton, Point, Polygon, Roof};
// A 120 x 80 plan. Its ridge is 40 in from the long walls.
let plan = Polygon::from_outer(&[
Point::new(0, 0), Point::new(120, 0), Point::new(120, 80), Point::new(0, 80),
])?;
let skel = skeleton(&plan)?;
// Steep (2:1) for the first 10, then shallow (1:4) to the ridge.
let roof = Roof::mansard(&skel, 2.0, 10.0, 0.25)?;
// 10 * 2 = 20 at the kerb, then 30 more of run at 0.25 = 7.5 -> 28.
assert_eq!(roof.ridge_height(), 28);
// Each of the four walls now carries two panels rather than one: the
// steep skirt, and the shallow slope above it.
assert_eq!(roof.panels().len(), 8);
assert_eq!(roof.panels_of(straight_skeleton::EdgeId(0)).count(), 2);
// A hip roof of the same plan is much taller for the same lower pitch.
assert_eq!(Roof::new(&skel, 2.0)?.ridge_height(), 80);Sourcepub fn with_profile(
skeleton: &Skeleton,
profile: Profile,
) -> Result<Roof, RoofError>
pub fn with_profile( skeleton: &Skeleton, profile: Profile, ) -> Result<Roof, RoofError>
Raises a roof over a skeleton, with any Profile.
§Constrained skeletons
A skeleton_constrained with one uniform limit gives a truncated
roof: the slopes rise to the limit and stop, and the residual the
wavefront stopped as becomes a PanelKind::Flat on top. That works
with any profile, so a truncated mansard is steep, then shallow, then
flat.
Uneven limits are refused — see RoofError::UnevenLimits, which
explains why there is no such roof rather than merely no implementation.
§Errors
RoofError::InvalidPitchif a pitch is negative, NaN, or infinite.RoofError::InvalidBreaklikewise for a mansard’s break.RoofError::UnevenLimitsif the skeleton’s edges stopped at different distances.RoofError::BreakSplitsPanelif a mansard’s break leaves a face in pieces that cannot be closed back up (a safety net that should not fire for the face of a valid polygon).RoofError::HeightOverflowif the plan is too wide for the pitch to fit ini16.RoofError::UnwalkableFaceif a wall’s face is not a closed region, which should not happen for a skeleton of a valid polygon.
§Examples
use straight_skeleton::{skeleton_constrained, PanelKind, Point, Polygon, Profile, Roof};
let plan = Polygon::from_outer(&[
Point::new(0, 0), Point::new(100, 0), Point::new(100, 100), Point::new(0, 100),
])?;
// Every wall stopped at 20: a hip roof with its apex cut off.
let skel = skeleton_constrained(&plan, &[20.0; 4])?;
let roof = Roof::with_profile(&skel, Profile::Hip { pitch: 0.5 })?;
// Four slopes, and the flat they stop at.
assert_eq!(roof.panels().len(), 5);
assert_eq!(roof.flat().count(), 1);
// The flat stands at 20 * 0.5, and that is the top of the roof.
assert_eq!(roof.ridge_height(), 10);Sourcepub fn verts(&self) -> &[RoofVertex]
pub fn verts(&self) -> &[RoofVertex]
Every corner of the roof, indexed by RoofVertexId.
Sourcepub fn vertex(&self, v: RoofVertexId) -> &RoofVertex
pub fn vertex(&self, v: RoofVertexId) -> &RoofVertex
Sourcepub fn vertex_at(&self, n: NodeId) -> &RoofVertex
pub fn vertex_at(&self, n: NodeId) -> &RoofVertex
The corner standing over a given skeleton node.
§Panics
Panics if n does not belong to the skeleton this roof was built from.
Sourcepub fn panels_of(&self, wall: EdgeId) -> impl Iterator<Item = &Panel> + '_
pub fn panels_of(&self, wall: EdgeId) -> impl Iterator<Item = &Panel> + '_
The panels rising from a given wall, from the eaves up.
A Profile::Hip gives exactly one; a Profile::Mansard gives two
where its break crosses the panel, and one where it does not reach.
§Examples
use straight_skeleton::{skeleton, EdgeId, Point, Polygon, Roof};
let plan = Polygon::from_outer(&[
Point::new(0, 0), Point::new(120, 0), Point::new(120, 80), Point::new(0, 80),
])?;
let skel = skeleton(&plan)?;
assert_eq!(Roof::new(&skel, 0.5)?.panels_of(EdgeId(0)).count(), 1);
assert_eq!(Roof::mansard(&skel, 2.0, 10.0, 0.25)?.panels_of(EdgeId(0)).count(), 2);Sourcepub fn flat(&self) -> impl Iterator<Item = &Panel> + '_
pub fn flat(&self) -> impl Iterator<Item = &Panel> + '_
The flat panels, if this roof is truncated. Empty otherwise.
More than one only when the flat has a hole in it — see
PanelKind::Flat.
Sourcepub fn ridge_height(&self) -> i16
pub fn ridge_height(&self) -> i16
The height of the highest point: the ridge, or a pyramid’s apex.
§Examples
use straight_skeleton::{skeleton, Point, Polygon, Roof};
// A 120-wide, 80-deep plan. The ridge runs down the middle of the long
// axis, 40 in from each long wall, so at pitch 0.5 it stands 20 high.
let plan = Polygon::from_outer(&[
Point::new(0, 0), Point::new(120, 0), Point::new(120, 80), Point::new(0, 80),
])?;
assert_eq!(Roof::new(&skeleton(&plan)?, 0.5)?.ridge_height(), 20);