Skip to main content

ursus_materials/
requirements.rs

1use crate::error::MaterialError;
2use crate::material::Material;
3use crate::value::{MaterialValue, PropertyId};
4
5/// Declares what a `ShadingStrategy` needs from a `Material` before it can
6/// pack it. Checked once when a material is assigned to a strategy, so
7/// mistakes surface immediately instead of as a panic deep in `pack()`.
8#[derive(Debug, Default, Clone)]
9pub struct Requirements {
10    pub required: Vec<PropertyId>,
11    pub optional_with_defaults: Vec<(PropertyId, MaterialValue)>,
12}
13
14impl Requirements {
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    pub fn require(mut self, id: PropertyId) -> Self {
20        self.required.push(id);
21        self
22    }
23
24    pub fn optional(mut self, id: PropertyId, default: MaterialValue) -> Self {
25        self.optional_with_defaults.push((id, default));
26        self
27    }
28
29    /// Validates that `material` has every required property. Does not check
30    /// types - individual `MaterialValue::as_*` accessors are the source of
31    /// truth for type coercion inside `pack()`.
32    pub fn validate(&self, material: &Material, strategy_name: &'static str) -> Result<(), MaterialError> {
33        for &id in &self.required {
34            if !material.contains(id) {
35                return Err(MaterialError::MissingProperty {
36                    material: material.name.clone(),
37                    strategy: strategy_name,
38                    property: id,
39                });
40            }
41        }
42        Ok(())
43    }
44
45    /// Reads a property from `material`, falling back to this requirement's
46    /// declared default for optional properties. Returns `None` if the
47    /// property is neither present nor has a declared default.
48    pub fn get_or_default<'a>(&'a self, material: &'a Material, id: PropertyId) -> Option<&'a MaterialValue> {
49        material
50            .get(id)
51            .or_else(|| self.optional_with_defaults.iter().find(|(pid, _)| *pid == id).map(|(_, default)| default))
52    }
53}