1use crate::components::Component;
17use crate::reflect::{AssetKind, Field};
18use serde::{Deserialize, Serialize};
19
20crate::choice_enum! {
21 DecalBlend { Normal, Multiply, Screen, Lighten, Darken, Overlay, Add, SoftLight, ReliefOnly, ColorDodge, ColorBurn, Tint } default Normal
23}
24
25crate::choice_enum! {
26 DecalShape { Image, Blob, Scorch, BulletHole, Cracks, Puddle, Grunge, PaintSplat, Moss, Rust, Leaks, Ring } default Image
28}
29
30#[derive(Clone, Serialize, Deserialize)]
32#[serde(default)]
33pub struct Decal {
34 pub texture: String,
36 pub normal_map: String,
38 pub orm_map: String,
40 pub shape: DecalShape,
42 pub blend: DecalBlend,
44 pub color: [f32; 4],
46 pub opacity: f32,
48 pub roughness: f32,
50 pub metallic: f32,
52 pub emission: f32,
54 pub normal_strength: f32,
56 pub angle_fade: f32,
59 pub seed: i32,
61 pub order: i32,
63 pub max_distance: f32,
65}
66
67impl Default for Decal {
68 fn default() -> Self {
69 Self {
70 texture: String::new(),
71 normal_map: String::new(),
72 orm_map: String::new(),
73 shape: DecalShape::Blob,
74 blend: DecalBlend::Normal,
75 color: [0.35, 0.05, 0.04, 1.0],
76 opacity: 1.0,
77 roughness: 0.6,
78 metallic: 0.0,
79 emission: 0.0,
80 normal_strength: 1.0,
81 angle_fade: 0.3,
82 seed: 1,
83 order: 0,
84 max_distance: 80.0,
85 }
86 }
87}
88
89impl Decal {
90 pub fn shader_params(&self) -> [f32; 8] {
92 [
93 self.blend as i32 as f32,
94 self.shape as i32 as f32,
95 self.angle_fade.clamp(0.0, 1.0),
96 self.normal_strength,
97 self.seed as f32,
98 self.emission,
99 (!self.normal_map.is_empty()) as i32 as f32,
100 (!self.orm_map.is_empty()) as i32 as f32,
101 ]
102 }
103}
104
105impl Component for Decal {
106 fn fields(&mut self) -> Vec<Field<'_>> {
107 vec![
108 Field::asset("texture", &mut self.texture, AssetKind::Texture),
109 Field::asset("normal_map", &mut self.normal_map, AssetKind::Texture),
110 Field::asset("orm_map", &mut self.orm_map, AssetKind::Texture),
111 Field::choice("shape", &mut self.shape),
112 Field::choice("blend", &mut self.blend),
113 Field::color("color", &mut self.color),
114 Field::f32("opacity", &mut self.opacity),
115 Field::f32("roughness", &mut self.roughness),
116 Field::f32("metallic", &mut self.metallic),
117 Field::f32("emission", &mut self.emission),
118 Field::f32("normal_strength", &mut self.normal_strength),
119 Field::f32("angle_fade", &mut self.angle_fade),
120 Field::i32("seed", &mut self.seed),
121 Field::i32("order", &mut self.order),
122 Field::f32("max_distance", &mut self.max_distance),
123 ]
124 }
125}
126
127pub fn preset(name: &str) -> Decal {
130 let d = Decal::default();
131 match name {
132 "scorch" => Decal { shape: DecalShape::Scorch, blend: DecalBlend::Multiply, color: [0.05, 0.04, 0.035, 1.0], roughness: 0.95, ..d },
133 "bullet_hole" => Decal { shape: DecalShape::BulletHole, color: [0.06, 0.055, 0.05, 1.0], normal_strength: 2.0, roughness: 0.9, ..d },
134 "cracks" => Decal { shape: DecalShape::Cracks, blend: DecalBlend::Multiply, color: [0.12, 0.11, 0.1, 1.0], normal_strength: 1.5, ..d },
135 "puddle" => Decal { shape: DecalShape::Puddle, color: [0.25, 0.24, 0.22, 1.0], roughness: 0.04, opacity: 0.85, angle_fade: 0.7, ..d },
136 "dirt" => Decal { shape: DecalShape::Grunge, blend: DecalBlend::Multiply, color: [0.45, 0.38, 0.28, 1.0], opacity: 0.8, ..d },
137 "paint" => Decal { shape: DecalShape::PaintSplat, color: [0.9, 0.15, 0.4, 1.0], roughness: 0.35, ..d },
138 "moss" => Decal { shape: DecalShape::Moss, color: [0.1, 0.25, 0.05, 1.0], roughness: 0.95, normal_strength: 1.2, ..d },
139 "rust" => Decal { shape: DecalShape::Rust, color: [0.45, 0.18, 0.06, 1.0], roughness: 0.9, ..d },
140 "leaks" => Decal { shape: DecalShape::Leaks, blend: DecalBlend::Multiply, color: [0.35, 0.33, 0.3, 1.0], angle_fade: 0.0, ..d },
141 "graffiti_glow" => Decal { shape: DecalShape::Ring, color: [0.2, 0.9, 1.0, 1.0], emission: 3.0, ..d },
142 "light_spot" => Decal { shape: DecalShape::Blob, blend: DecalBlend::Lighten, color: [1.0, 0.85, 0.6, 1.0], opacity: 0.6, ..d },
143 _ => d,
144 }
145}
146
147pub const PRESETS: &[&str] = &["blood", "scorch", "bullet_hole", "cracks", "puddle", "dirt", "paint", "moss", "rust", "leaks", "graffiti_glow", "light_spot"];
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn presets_and_params_round_trip() {
156 for name in PRESETS {
157 let d = preset(name);
158 let json = serde_json::to_string(&d).unwrap();
159 let back: Decal = serde_json::from_str(&json).unwrap();
160 assert_eq!(back.shader_params(), d.shader_params(), "{name}");
161 }
162 let d = Decal { normal_map: "n.png".into(), ..Default::default() };
163 assert_eq!(d.shader_params()[6], 1.0);
164 }
165}