use serde::{Deserialize, Serialize};
pub const KINDS: &[&str] = &[
"fbm", "voronoi", "bricks", "checker", "wood", "veins", "flat",
];
pub const MAX_SIZE: u32 = 512;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TextureRecipe {
pub kind: String,
#[serde(default = "four")]
pub scale: f32,
#[serde(default = "four_u")]
pub octaves: u32,
#[serde(default)]
pub seed: u32,
#[serde(default = "grey")]
pub colors: Vec<[f32; 3]>,
#[serde(default = "rough_default")]
pub roughness: [f32; 2],
#[serde(default, skip_serializing_if = "Option::is_none")]
pub smoothness: Option<[f32; 2]>,
#[serde(default)]
pub metallic: [f32; 2],
#[serde(default)]
pub height: f32,
#[serde(default)]
pub ao: f32,
#[serde(default = "size_default")]
pub size: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub over: Option<Box<TextureRecipe>>,
#[serde(default = "half_f")]
pub mix: f32,
#[serde(default = "three")]
pub mask_scale: f32,
#[serde(default)]
pub mask_seed: u32,
#[serde(default)]
pub triplanar: f32,
}
fn half_f() -> f32 {
0.5
}
fn three() -> f32 {
3.0
}
fn four() -> f32 {
4.0
}
fn four_u() -> u32 {
4
}
fn grey() -> Vec<[f32; 3]> {
vec![[0.6, 0.6, 0.6]]
}
fn rough_default() -> [f32; 2] {
[0.9, 0.9]
}
fn size_default() -> u32 {
256
}
impl TextureRecipe {
pub fn rough_band(&self) -> [f32; 2] {
match self.smoothness {
Some([a, b]) => [1.0 - a, 1.0 - b],
None => self.roughness,
}
}
pub fn validate(&self) -> Result<(), String> {
if !KINDS.contains(&self.kind.as_str()) {
return Err(format!(
"unknown texture kind '{}' — one of {KINDS:?}",
self.kind
));
}
if self.colors.is_empty() {
return Err("texture needs at least one color".into());
}
if self.colors.len() > 8 {
return Err("texture ramp: at most 8 colors".into());
}
if let Some(over) = &self.over {
if over.over.is_some() {
return Err(
"texture layering is one level deep (the overlay cannot itself have `over`)"
.into(),
);
}
over.validate()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minimal_and_full_forms_parse_and_validate() {
let t: TextureRecipe = serde_json::from_str(r#"{ "kind": "fbm" }"#).unwrap();
t.validate().unwrap();
assert_eq!(t.size, 256);
let t: TextureRecipe = serde_json::from_str(
r#"{ "kind": "voronoi", "scale": 6, "seed": 3,
"colors": [[0.4,0.4,0.42],[0.55,0.53,0.5]],
"roughness": [0.95, 0.8], "height": 0.6, "ao": 0.5 }"#,
)
.unwrap();
t.validate().unwrap();
let bad: TextureRecipe = serde_json::from_str(r#"{ "kind": "plaid" }"#).unwrap();
assert!(bad.validate().is_err());
}
}