use alloc::vec::Vec;
use core::fmt;
use crate::point::round_half_away_from_zero;
use crate::polygon::EdgeId;
use crate::skeleton::{NodeId, Skeleton};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Point3 {
pub x: i16,
pub y: i16,
pub z: i16,
}
impl Point3 {
pub const ORIGIN: Point3 = Point3 { x: 0, y: 0, z: 0 };
#[inline]
pub const fn new(x: i16, y: i16, z: i16) -> Self {
Point3 { x, y, z }
}
}
impl From<Point3> for [i16; 3] {
#[inline]
fn from(p: Point3) -> Self {
[p.x, p.y, p.z]
}
}
impl From<[i16; 3]> for Point3 {
#[inline]
fn from([x, y, z]: [i16; 3]) -> Self {
Point3::new(x, y, z)
}
}
impl From<Point3> for (i16, i16, i16) {
#[inline]
fn from(p: Point3) -> Self {
(p.x, p.y, p.z)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum RoofError {
InvalidPitch {
pitch: f32,
},
UnwalkableFace {
wall: EdgeId,
},
HeightOverflow {
node: NodeId,
height: f32,
},
}
impl fmt::Display for RoofError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RoofError::InvalidPitch { pitch } => {
write!(f, "pitch {pitch} is not a finite, non-negative number")
}
RoofError::UnwalkableFace { wall } => write!(
f,
"could not walk the face of wall {}; roofs need a plain skeleton, \
not a constrained one",
wall.0
),
RoofError::HeightOverflow { node, height } => write!(
f,
"node {} would stand {height} high, which overflows i16; \
lower the pitch or scale the plan down",
node.0
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for RoofError {}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RoofVertex {
pub position: Point3,
pub exact: [f32; 3],
pub node: NodeId,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Panel {
pub wall: EdgeId,
pub corners: Vec<NodeId>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Roof {
verts: Vec<RoofVertex>,
panels: Vec<Panel>,
pitch: f32,
}
impl Roof {
pub fn new(skeleton: &Skeleton, pitch: f32) -> Result<Roof, RoofError> {
if !pitch.is_finite() || pitch < 0.0 {
return Err(RoofError::InvalidPitch { pitch });
}
let mut verts = Vec::with_capacity(skeleton.node_count());
for (i, node) in skeleton.nodes().iter().enumerate() {
let id = NodeId(i as u32);
let height = node.offset * pitch;
let z = lattice_height(height).ok_or(RoofError::HeightOverflow { node: id, height })?;
verts.push(RoofVertex {
position: Point3::new(node.position.x, node.position.y, z),
exact: [node.exact[0], node.exact[1], height],
node: id,
});
}
let mut panels = Vec::with_capacity(skeleton.input_edge_count());
for i in 0..skeleton.input_edge_count() as u16 {
let wall = EdgeId(i);
let corners = skeleton
.face(wall)
.ok_or(RoofError::UnwalkableFace { wall })?;
panels.push(Panel { wall, corners });
}
Ok(Roof {
verts,
panels,
pitch,
})
}
#[inline]
pub fn verts(&self) -> &[RoofVertex] {
&self.verts
}
#[inline]
pub fn panels(&self) -> &[Panel] {
&self.panels
}
#[inline]
pub fn pitch(&self) -> f32 {
self.pitch
}
#[inline]
pub fn vertex(&self, n: NodeId) -> &RoofVertex {
&self.verts[n.0 as usize]
}
#[inline]
pub fn panel(&self, wall: EdgeId) -> &Panel {
&self.panels[wall.0 as usize]
}
pub fn ridge_height(&self) -> i16 {
self.verts.iter().map(|v| v.position.z).max().unwrap_or(0)
}
pub fn panel_outline(&self, wall: EdgeId) -> Vec<Point3> {
self.panel(wall)
.corners
.iter()
.map(|&n| self.vertex(n).position)
.collect()
}
}
fn lattice_height(h: f32) -> Option<i16> {
if !h.is_finite() {
return None;
}
let r = round_half_away_from_zero(h);
if r < i16::MIN as f32 || r > i16::MAX as f32 {
return None;
}
Some(r as i16)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lattice_height_rounds_to_nearest() {
assert_eq!(lattice_height(0.0), Some(0));
assert_eq!(lattice_height(2.4), Some(2));
assert_eq!(lattice_height(2.5), Some(3));
assert_eq!(lattice_height(2.6), Some(3));
}
#[test]
fn lattice_height_refuses_rather_than_saturating() {
assert_eq!(lattice_height(32767.0), Some(i16::MAX));
assert_eq!(lattice_height(32768.0), None);
assert_eq!(lattice_height(1e9), None);
assert_eq!(lattice_height(f32::INFINITY), None);
assert_eq!(lattice_height(f32::NAN), None);
}
#[test]
fn point3_conversions() {
let p = Point3::new(1, -2, 3);
assert_eq!(<[i16; 3]>::from(p), [1, -2, 3]);
assert_eq!(Point3::from([1, -2, 3]), p);
assert_eq!(<(i16, i16, i16)>::from(p), (1, -2, 3));
assert_eq!(Point3::ORIGIN, Point3::new(0, 0, 0));
}
}