use crate::{Component, Entity, Geometry, JsonError, World};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct MaterialParameters(pub BTreeMap<String, Vec<f32>>);
impl Component for MaterialParameters {
const NAME: &'static str = "core.material_parameters";
const GEOMETRY: Geometry = Geometry::None;
fn to_json(&self) -> Result<Option<Value>, JsonError> {
crate::json::to_value(self).map(Some)
}
fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
crate::world::Linearity::Invariant
}
fn verify(&self, _e: Entity, _w: &World) -> Result<(), String> {
for (name, values) in &self.0 {
if name.trim().is_empty() {
return Err("empty material parameter name".into());
}
if !(1..=4).contains(&values.len()) {
return Err(format!("{name}: {} values, expected 1 to 4", values.len()));
}
if !values.iter().all(|value| value.is_finite()) {
return Err(format!("{name}: non-finite value"));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn widths_are_one_to_four_and_values_are_finite() {
let mut w = World::new();
let e = w.spawn();
for width in 0..=5 {
let p = MaterialParameters(BTreeMap::from([("opaque name".into(), vec![1.; width])]));
assert_eq!(p.verify(e, &w).is_ok(), (1..=4).contains(&width));
}
for value in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let p = MaterialParameters(BTreeMap::from([("value".into(), vec![value])]));
assert!(p.verify(e, &w).is_err());
assert!(p.to_json().is_err());
}
assert!(
MaterialParameters(BTreeMap::from([(" ".into(), vec![0.])]))
.verify(e, &w)
.is_err()
);
assert!(MaterialParameters::default().verify(e, &w).is_ok());
}
#[test]
fn json_round_trip_preserves_names_values_and_signed_zero() {
let p = MaterialParameters(BTreeMap::from([
("a tint".into(), vec![0.1, 0.2, 0.3, 1.]),
("a scalar".into(), vec![-0.]),
]));
let restored: MaterialParameters = serde_json::from_value(p.to_json().unwrap().unwrap()).unwrap();
assert_eq!(restored, p);
assert_eq!(restored.0["a scalar"][0].to_bits(), (-0f32).to_bits());
assert!(p.remap_primitives(&[2, 0, 2]).is_none());
}
}