primase_core/
water_body.rs1use serde::Serialize;
4use serde_json::Value;
5
6use crate::pbr::TextureRef;
7use crate::{Component, Entity, Geometry, JsonError, World};
8
9#[derive(Clone, Debug, PartialEq, Serialize)]
22pub struct WaterBody {
23 pub extinction: [f32; 3],
26 pub scatter: [f32; 3],
31 pub ior: f32,
35 pub waves: Vec<WaveLayer>,
38 pub shore_fade: f32,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
45pub struct WaveLayer {
46 pub normal: TextureRef,
49 pub velocity: [f32; 2],
52 pub strength: f32,
54}
55
56impl Component for WaterBody {
57 const NAME: &'static str = "core.water_body";
58 const GEOMETRY: Geometry = Geometry::None;
59 fn to_json(&self) -> Result<Option<Value>, JsonError> {
60 crate::json::to_value(self).map(Some)
61 }
62 fn verify(&self, _e: Entity, _w: &World) -> Result<(), String> {
64 if !self.extinction.iter().all(|x| x.is_finite() && *x >= 0.) {
65 return Err(format!("extinction {:?} is not finite and >= 0", self.extinction));
66 }
67 if !self.scatter.iter().all(|x| (0.0..=1.0).contains(x)) {
68 return Err(format!("scatter {:?} is not within 0-1", self.scatter));
69 }
70 if !(self.ior.is_finite() && self.ior > 1.) {
71 return Err(format!("ior {} is not finite and above 1", self.ior));
72 }
73 if !(self.shore_fade.is_finite() && self.shore_fade >= 0.) {
74 return Err(format!("shore fade {} is not finite and >= 0", self.shore_fade));
75 }
76 for (i, w) in self.waves.iter().enumerate() {
77 if !w.velocity.iter().chain([&w.strength]).all(|x| x.is_finite()) {
78 return Err(format!(
79 "wave {i}: velocity {:?} or strength {} is not finite",
80 w.velocity, w.strength
81 ));
82 }
83 if let Some(t) = w.normal.transform
84 && !t
85 .offset
86 .iter()
87 .chain(&t.scale)
88 .chain([&t.rotation])
89 .all(|x| x.is_finite())
90 {
91 return Err(format!("wave {i}: texture transform {t:?} is not finite"));
92 }
93 }
94 Ok(())
95 }
96 fn references(&self, out: &mut Vec<Entity>) {
97 out.extend(self.waves.iter().map(|w| w.normal.texture));
98 }
99}
100
101impl WaterBody {
102 pub fn f0(&self) -> f32 {
104 ((self.ior - 1.) / (self.ior + 1.)).powi(2)
105 }
106
107 pub fn ior_of_f0(f0: f32) -> f32 {
109 let r = f0.sqrt();
110 (1. + r) / (1. - r)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::pbr::{UvTransform, Wrap};
118 use iridia_interface::Filters;
119
120 fn lake(w: &mut World) -> WaterBody {
121 let normal = w.spawn();
122 WaterBody {
123 extinction: [0.215, 0.115, 0.09],
124 scatter: [0.; 3],
125 ior: 1.5,
126 waves: vec![WaveLayer {
127 normal: TextureRef {
128 texture: normal,
129 texcoord: 0,
130 wrap: [Wrap::Repeat; 2],
131 transform: Some(UvTransform {
132 offset: [0.; 2],
133 scale: [4.; 2],
134 rotation: 0.,
135 }),
136 filters: Filters::default(),
137 },
138 velocity: [-0.0003, 0.0015],
139 strength: 1.,
140 }],
141 shore_fade: 1. / 3.,
142 }
143 }
144
145 #[test]
146 fn a_water_body_shows_its_fields_and_refers_to_its_wave_maps() {
147 let mut w = World::new();
148 let body = lake(&mut w);
149 let json = body.to_json().unwrap().unwrap();
150 assert_eq!(
151 json["extinction"],
152 serde_json::json!([0.215f32, 0.115f32, 0.09f32])
153 );
154 assert_eq!(json["ior"], serde_json::json!(1.5));
155 assert_eq!(
156 json["waves"][0]["velocity"],
157 serde_json::json!([-0.0003f32, 0.0015f32])
158 );
159 let mut refs = Vec::new();
160 body.references(&mut refs);
161 assert_eq!(refs, vec![body.waves[0].normal.texture]);
162 let e = w.spawn();
163 assert_eq!(body.verify(e, &w), Ok(()));
164 }
165
166 #[test]
167 fn ior_and_normal_reflectance_convert_both_ways() {
168 let mut w = World::new();
169 let body = lake(&mut w);
170 assert!((body.f0() - 0.04).abs() < 1e-6);
171 assert!((WaterBody::ior_of_f0(0.04) - 1.5).abs() < 1e-5);
172 assert!((WaterBody::ior_of_f0(0.02) - 1.3294).abs() < 1e-4);
173 }
174
175 #[test]
176 fn a_value_outside_its_range_is_refused() {
177 let mut w = World::new();
178 let e = w.spawn();
179 let base = lake(&mut w);
180 let refused = |b: WaterBody| b.verify(e, &w).is_err();
181 assert!(refused(WaterBody {
182 extinction: [-0.1, 0., 0.],
183 ..base.clone()
184 }));
185 assert!(refused(WaterBody {
186 scatter: [1.5, 0., 0.],
187 ..base.clone()
188 }));
189 assert!(refused(WaterBody {
190 ior: 1.,
191 ..base.clone()
192 }));
193 assert!(refused(WaterBody {
194 shore_fade: f32::NAN,
195 ..base.clone()
196 }));
197 let mut fast = base.clone();
198 fast.waves[0].velocity = [f32::INFINITY, 0.];
199 assert!(refused(fast));
200 }
201}