use core::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SweptFace {
StartCap,
EndCap,
Side(u32),
}
impl fmt::Display for SweptFace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::StartCap => f.write_str("start-cap"),
Self::EndCap => f.write_str("end-cap"),
Self::Side(index) => write!(f, "side[{index}]"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Operand {
Subject,
Tool,
}
impl fmt::Display for Operand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Subject => f.write_str("subject"),
Self::Tool => f.write_str("tool"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FaceName {
Swept(SweptFace),
Blend(Box<EdgeName>),
Fragment {
operand: Operand,
source: Box<FaceName>,
},
Anonymous(u32),
}
impl FaceName {
pub const fn swept(part: SweptFace) -> Self {
Self::Swept(part)
}
pub fn blend(edge: EdgeName) -> Self {
Self::Blend(Box::new(edge))
}
pub fn fragment(self, operand: Operand) -> Self {
Self::Fragment {
operand,
source: Box::new(self),
}
}
pub fn origin(&self) -> &Self {
match self {
Self::Fragment { source, .. } => source.origin(),
other => other,
}
}
pub fn is_anonymous(&self) -> bool {
matches!(self.origin(), Self::Anonymous(_))
}
}
impl fmt::Display for FaceName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Swept(part) => write!(f, "{part}"),
Self::Blend(edge) => write!(f, "blend({edge})"),
Self::Fragment { operand, source } => write!(f, "{operand}/{source}"),
Self::Anonymous(index) => write!(f, "anon#{index}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EdgeName {
Between(Box<FaceName>, Box<FaceName>),
Anonymous(u32),
}
impl EdgeName {
pub fn between(first: FaceName, second: FaceName) -> Self {
let (low, high) = if first <= second {
(first, second)
} else {
(second, first)
};
Self::Between(Box::new(low), Box::new(high))
}
pub fn is_anonymous(&self) -> bool {
match self {
Self::Between(first, second) => first.is_anonymous() || second.is_anonymous(),
Self::Anonymous(_) => true,
}
}
}
impl fmt::Display for EdgeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Between(first, second) => write!(f, "{first}|{second}"),
Self::Anonymous(index) => write!(f, "anon#{index}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_fragment_reports_the_face_it_came_from() {
let wall = FaceName::swept(SweptFace::Side(2));
let once = wall.clone().fragment(Operand::Subject);
let twice = once.clone().fragment(Operand::Tool);
assert_eq!(once.origin(), &wall);
assert_eq!(
twice.origin(),
&wall,
"a fragment of a fragment still came from the original wall"
);
}
#[test]
fn anonymity_survives_being_wrapped() {
let unnamed = FaceName::Anonymous(3);
assert!(unnamed.is_anonymous());
assert!(
unnamed.fragment(Operand::Subject).is_anonymous(),
"wrapping an unnamed face must not make it look named"
);
}
#[test]
fn an_edge_is_anonymous_when_either_side_is() {
let known = FaceName::swept(SweptFace::StartCap);
let unknown = FaceName::Anonymous(0);
assert!(EdgeName::between(known.clone(), unknown).is_anonymous());
assert!(!EdgeName::between(known.clone(), known).is_anonymous());
}
#[test]
fn different_origins_are_different_names() {
assert_ne!(
FaceName::swept(SweptFace::Side(1)),
FaceName::swept(SweptFace::Side(2))
);
assert_ne!(
FaceName::swept(SweptFace::StartCap),
FaceName::swept(SweptFace::EndCap)
);
let wall = FaceName::swept(SweptFace::Side(1));
assert_ne!(
wall.clone().fragment(Operand::Subject),
wall.fragment(Operand::Tool),
"the same wall cut from each operand yields distinct fragments"
);
}
}