use serde::{Deserialize, Serialize};
use crate::expr::Expr;
use crate::model::Material;
use crate::scope::Vec3;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RuleCall {
pub name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub args: Vec<Expr>,
}
impl RuleCall {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
args: Vec::new(),
}
}
pub fn with_args(name: impl Into<String>, args: Vec<Expr>) -> Self {
Self {
name: name.into(),
args,
}
}
}
impl From<&str> for RuleCall {
fn from(name: &str) -> Self {
Self::new(name)
}
}
impl PartialEq<&str> for RuleCall {
fn eq(&self, other: &&str) -> bool {
self.args.is_empty() && self.name == *other
}
}
impl PartialEq<str> for RuleCall {
fn eq(&self, other: &str) -> bool {
self.args.is_empty() && self.name == other
}
}
impl From<String> for RuleCall {
fn from(name: String) -> Self {
Self::new(name)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SnapBinding {
pub label: String,
pub tolerance: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Axis {
X,
Y,
Z,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SplitSize {
Absolute(Expr),
Relative(Expr),
Floating(Expr),
}
impl SplitSize {
pub fn abs(v: f64) -> Self {
SplitSize::Absolute(Expr::lit(v))
}
pub fn rel(v: f64) -> Self {
SplitSize::Relative(Expr::lit(v))
}
pub fn float(v: f64) -> Self {
SplitSize::Floating(Expr::lit(v))
}
pub fn expr(&self) -> &Expr {
match self {
SplitSize::Absolute(e) | SplitSize::Relative(e) | SplitSize::Floating(e) => e,
}
}
pub fn expr_mut(&mut self) -> &mut Expr {
match self {
SplitSize::Absolute(e) | SplitSize::Relative(e) | SplitSize::Floating(e) => e,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SplitSlot {
pub size: SplitSize,
pub rule: RuleCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SplitEntry {
Slot(SplitSlot),
Group(Vec<SplitSlot>),
}
impl SplitEntry {
pub fn as_slot(&self) -> Option<&SplitSlot> {
match self {
SplitEntry::Slot(s) => Some(s),
SplitEntry::Group(_) => None,
}
}
}
impl From<SplitSlot> for SplitEntry {
fn from(s: SplitSlot) -> Self {
SplitEntry::Slot(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FaceSelector {
Top,
Bottom,
Front,
Back,
Left,
Right,
Side,
All,
}
impl FaceSelector {
pub fn parse(s: &str) -> Option<Self> {
match s {
"top" | "Top" => Some(Self::Top),
"bottom" | "Bottom" => Some(Self::Bottom),
"front" | "Front" => Some(Self::Front),
"back" | "Back" => Some(Self::Back),
"left" | "Left" => Some(Self::Left),
"right" | "Right" => Some(Self::Right),
"side" | "Side" => Some(Self::Side),
"all" | "All" | "_" => Some(Self::All),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompFaceCase {
pub selector: FaceSelector,
pub rule: RuleCall,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EdgeSelector {
Vertical,
Horizontal,
Top,
Bottom,
All,
}
impl EdgeSelector {
pub fn parse(s: &str) -> Option<Self> {
match s {
"vertical" | "Vertical" => Some(Self::Vertical),
"horizontal" | "Horizontal" => Some(Self::Horizontal),
"top" | "Top" => Some(Self::Top),
"bottom" | "Bottom" => Some(Self::Bottom),
"all" | "All" | "_" => Some(Self::All),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CompEdgeCase {
pub selector: EdgeSelector,
pub rule: RuleCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CompTarget {
Faces(Vec<CompFaceCase>),
Edges(Vec<CompEdgeCase>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum OffsetSelector {
Inside,
Border,
All,
}
impl OffsetSelector {
pub fn parse(s: &str) -> Option<Self> {
match s {
"inside" | "Inside" => Some(Self::Inside),
"border" | "Border" => Some(Self::Border),
"all" | "All" | "_" => Some(Self::All),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OffsetCase {
pub selector: OffsetSelector,
pub rule: RuleCall,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RoofType {
Pyramid,
Shed,
Gable,
Hip,
Flat,
OpenGable,
BoxGable,
PyramidHip,
Butterfly,
MShaped,
Gambrel,
Mansard,
Saltbox,
Jerkinhead,
DutchGable,
}
impl RoofType {
pub fn parse(s: &str) -> Option<Self> {
match s {
"pyramid" | "Pyramid" => Some(Self::Pyramid),
"shed" | "Shed" => Some(Self::Shed),
"gable" | "Gable" => Some(Self::Gable),
"hip" | "Hip" => Some(Self::Hip),
"flat" | "Flat" => Some(Self::Flat),
"openGable" | "OpenGable" => Some(Self::OpenGable),
"boxGable" | "BoxGable" => Some(Self::BoxGable),
"pyramidHip" | "PyramidHip" => Some(Self::PyramidHip),
"butterfly" | "Butterfly" => Some(Self::Butterfly),
"mShaped" | "MShaped" => Some(Self::MShaped),
"gambrel" | "Gambrel" => Some(Self::Gambrel),
"mansard" | "Mansard" => Some(Self::Mansard),
"saltbox" | "Saltbox" => Some(Self::Saltbox),
"jerkinhead" | "Jerkinhead" => Some(Self::Jerkinhead),
"dutchGable" | "DutchGable" => Some(Self::DutchGable),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RoofFaceSelector {
Slope,
GableEnd,
LowerSlope,
UpperSlope,
HipEnd,
ValleySlope,
OuterSlope,
InnerSlope,
Back,
Fascia,
All,
}
impl RoofFaceSelector {
pub fn parse(s: &str) -> Option<Self> {
match s {
"slope" | "Slope" => Some(Self::Slope),
"gable" | "GableEnd" | "gableEnd" => Some(Self::GableEnd),
"lowerSlope" | "LowerSlope" => Some(Self::LowerSlope),
"upperSlope" | "UpperSlope" => Some(Self::UpperSlope),
"hipEnd" | "HipEnd" => Some(Self::HipEnd),
"valleySlope" | "ValleySlope" => Some(Self::ValleySlope),
"outerSlope" | "OuterSlope" => Some(Self::OuterSlope),
"innerSlope" | "InnerSlope" => Some(Self::InnerSlope),
"back" | "Back" => Some(Self::Back),
"fascia" | "Fascia" => Some(Self::Fascia),
"all" | "All" | "_" => Some(Self::All),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RoofCase {
pub selector: RoofFaceSelector,
pub rule: RuleCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RoofConfig {
pub roof_type: RoofType,
pub pitch: f64,
pub secondary_pitch: Option<f64>,
pub overhang: f64,
pub ridge_offset: f64,
pub fascia_depth: f64,
pub tier_height: Option<f64>,
}
impl RoofConfig {
pub fn new(roof_type: RoofType, pitch: f64) -> Self {
Self {
roof_type,
pitch,
secondary_pitch: None,
overhang: 0.0,
ridge_offset: 0.5,
fascia_depth: 0.0,
tier_height: None,
}
}
pub fn secondary_pitch_or_default(&self) -> f64 {
self.secondary_pitch.unwrap_or(self.pitch / 2.0)
}
pub fn tier_height_or(&self, default: f64) -> f64 {
self.tier_height.unwrap_or(default)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RoofSpec {
pub roof_type: RoofType,
pub pitch: Expr,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub height: Option<Expr>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub secondary_pitch: Option<Expr>,
pub overhang: Expr,
pub ridge_offset: Expr,
pub fascia_depth: Expr,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tier_height: Option<Expr>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ridge_axis: Option<Axis>,
}
impl From<RoofConfig> for RoofSpec {
fn from(c: RoofConfig) -> Self {
Self {
roof_type: c.roof_type,
pitch: Expr::lit(c.pitch),
height: None,
secondary_pitch: c.secondary_pitch.map(Expr::lit),
overhang: Expr::lit(c.overhang),
ridge_offset: Expr::lit(c.ridge_offset),
fascia_depth: Expr::lit(c.fascia_depth),
tier_height: c.tier_height.map(Expr::lit),
ridge_axis: None,
}
}
}
impl RoofSpec {
pub fn new(roof_type: RoofType, pitch: f64) -> Self {
Self {
roof_type,
pitch: Expr::lit(pitch),
height: None,
secondary_pitch: None,
overhang: Expr::lit(0.0),
ridge_offset: Expr::lit(0.5),
fascia_depth: Expr::lit(0.0),
tier_height: None,
ridge_axis: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AttachSelector {
Surface,
All,
}
impl AttachSelector {
pub fn parse(s: &str) -> Option<Self> {
match s {
"surface" | "Surface" => Some(Self::Surface),
"all" | "All" | "_" => Some(Self::All),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AttachCase {
pub selector: AttachSelector,
pub rule: RuleCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum VariantSelector {
Weight(f64),
When(Expr),
Else,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RuleVariant {
pub selector: VariantSelector,
pub ops: Vec<ShapeOp>,
}
impl RuleVariant {
pub fn weighted(weight: f64, ops: Vec<ShapeOp>) -> Self {
Self {
selector: VariantSelector::Weight(weight),
ops,
}
}
pub fn weight(&self) -> Option<f64> {
match self.selector {
VariantSelector::Weight(w) => Some(w),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CarveSelector {
Shape,
Remainder,
All,
}
impl CarveSelector {
pub fn parse(s: &str) -> Option<Self> {
match s {
"shape" | "Shape" => Some(Self::Shape),
"remainder" | "Remainder" | "rest" | "Rest" => Some(Self::Remainder),
"all" | "All" | "_" => Some(Self::All),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CarveCase {
pub selector: CarveSelector,
pub rule: RuleCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FitCandidate {
pub min_size: Expr,
pub rule: RuleCall,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ShapeOp {
Extrude(Expr),
Taper(Expr),
Rotate([Expr; 4]),
Translate([Expr; 3]),
Scale([Expr; 3]),
Split {
axis: Axis,
entries: Vec<SplitEntry>,
snap: Option<SnapBinding>,
},
SplitArea { axis: Axis, slots: Vec<SplitSlot> },
Fit {
axis: Axis,
candidates: Vec<FitCandidate>,
},
Repeat {
axis: Axis,
tile_sizes: Vec<Expr>,
rule: RuleCall,
},
Comp(CompTarget),
I(String),
Mat(Material),
Rule(RuleCall),
Size([Expr; 3]),
Center { x: bool, y: bool, z: bool },
Mirror,
ShapeL {
front: Expr,
side: Expr,
cases: Vec<CarveCase>,
},
ShapeU {
front: Expr,
left: Expr,
right: Expr,
cases: Vec<CarveCase>,
},
Align { local_axis: Axis, target: Vec3 },
Offset {
distance: Expr,
cases: Vec<OffsetCase>,
},
Roof {
spec: RoofSpec,
cases: Vec<RoofCase>,
},
RegSnap(String),
IfClear {
rule: RuleCall,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
IfOccluded {
rule: RuleCall,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
IfInside {
rule: RuleCall,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
IfTouches {
rule: RuleCall,
#[serde(default, skip_serializing_if = "Option::is_none")]
label: Option<String>,
},
Pick {
key: String,
choices: Vec<(f64, RuleCall)>,
},
Label(String),
Scatter {
volume: bool,
count: Expr,
rule: RuleCall,
},
Polygon(Vec<glam::DVec2>),
Attach {
world_axis: Vec3,
cases: Vec<AttachCase>,
},
}