1use std::collections::BTreeSet;
6
7use serde::Serialize;
8use serde_json::Value;
9
10use crate::pass::{Given, OptionSpec, Options, Pass, PassLog, PassSpec};
11use crate::{Component, Entity, JsonError, Name, Source, Texture, TextureFile, World};
12
13#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
25pub struct TextureForms {
26 pub detail: u8,
27 pub forms: Vec<TextureForm>,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
32pub struct TextureForm {
33 pub detail: u8,
34 pub texture: Entity,
35}
36
37impl TextureForms {
38 pub fn choose(&self, choice: TextureChoice) -> usize {
42 match choice {
43 TextureChoice::Finest => 0,
44 TextureChoice::AtMost(n) => std::iter::once(self.detail)
45 .chain(self.forms.iter().map(|f| f.detail))
46 .enumerate()
47 .filter(|&(_, d)| d <= n)
48 .fold(None, |best: Option<(usize, u8)>, (i, d)| match best {
49 Some((_, b)) if b >= d => best,
50 _ => Some((i, d)),
51 })
52 .map_or(0, |(i, _)| i),
53 }
54 }
55}
56
57fn has_texture(w: &World, e: Entity) -> bool {
58 w.has::<Texture>(e) || w.has::<TextureFile>(e)
59}
60
61impl Component for TextureForms {
62 const NAME: &'static str = "core.texture_forms";
63 const GEOMETRY: crate::Geometry = crate::Geometry::None;
64 fn to_json(&self) -> Result<Option<Value>, JsonError> {
65 crate::json::to_value(self).map(Some)
66 }
67 fn references(&self, out: &mut Vec<Entity>) {
68 out.extend(self.forms.iter().map(|f| f.texture));
69 }
70 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
73 if !has_texture(w, e) {
74 return Err("other forms of an entity without a texture".into());
75 }
76 let mut seen = BTreeSet::new();
77 for f in &self.forms {
78 if f.texture == e || !w.is_alive(f.texture) {
79 return Err(format!("form {:?} is not another alive entity", f.texture));
80 }
81 if !seen.insert(f.texture) {
82 return Err(format!("form {:?} is listed twice", f.texture));
83 }
84 if !has_texture(w, f.texture) {
85 return Err(format!("form {:?} holds no texture", f.texture));
86 }
87 if w.has::<TextureForms>(f.texture) {
88 return Err(format!("form {:?} has forms of its own", f.texture));
89 }
90 }
91 Ok(())
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub enum TextureChoice {
98 Finest,
100 AtMost(u8),
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
111pub struct SelectTextureForm {
112 pub choice: TextureChoice,
113}
114
115pub const SELECT_TEXTURE_FORM: PassSpec = PassSpec {
117 name: "select-texture-form",
118 options: &[OptionSpec::required(
119 "form",
120 "finest",
121 "finest or a level 0-255: the stored form each texture keeps (TextureChoice)",
122 )],
123 make: select_texture_form,
124};
125
126fn select_texture_form(g: &Given) -> Result<Box<dyn Pass>, String> {
127 let choice = match g.required("form") {
128 "finest" => TextureChoice::Finest,
129 n => TextureChoice::AtMost(
130 n.parse()
131 .map_err(|_| format!("form={n} is not finest or a level 0-255"))?,
132 ),
133 };
134 Ok(Box::new(SelectTextureForm { choice }))
135}
136
137impl Pass for SelectTextureForm {
138 fn name(&self) -> &'static str {
139 "select-texture-form"
140 }
141 fn options(&self) -> Options {
142 let form = match self.choice {
143 TextureChoice::Finest => "finest".to_owned(),
144 TextureChoice::AtMost(n) => n.to_string(),
145 };
146 Options::from([("form".to_owned(), form)])
147 }
148 fn removes(&self) -> &[&'static str] {
150 &[TextureForms::NAME, Texture::NAME, TextureFile::NAME]
151 }
152 fn modifies(&self) -> &[&'static str] {
153 &[Texture::NAME, TextureFile::NAME]
154 }
155 fn despawns_entities(&self) -> bool {
156 true
157 }
158 fn run(&self, w: &mut World, log: &mut PassLog) -> Result<(), String> {
159 let all: Vec<(Entity, TextureForms)> =
160 w.iter::<TextureForms>().map(|(e, f)| (e, f.clone())).collect();
161 let mut forms = BTreeSet::new();
162 for (texture, held) in &all {
163 held.verify(*texture, w)
164 .map_err(|m| format!("{texture:?}: {m}"))?;
165 for f in &held.forms {
166 if !forms.insert(f.texture) {
167 return Err(format!("form {:?} belongs to more than one texture", f.texture));
168 }
169 }
170 }
171 if let Some((name, holder, form)) = w
172 .references_to(&forms)
173 .into_iter()
174 .find(|(name, _, _)| *name != TextureForms::NAME)
175 {
176 return Err(format!("{name} on {holder:?} refers to form {form:?}"));
177 }
178 for (texture, held) in all {
179 let chosen = held.choose(self.choice);
180 match chosen.checked_sub(1).map(|k| held.forms[k]) {
181 None => log.count("textures kept in their own form", 1),
182 Some(f) => {
183 for (gone, name) in [
184 (w.remove::<Texture>(texture).is_some(), Texture::NAME),
185 (w.remove::<TextureFile>(texture).is_some(), TextureFile::NAME),
186 ] {
187 if gone {
188 log.record_removed(name);
189 }
190 }
191 for (left, name) in [
193 (w.remove::<Name>(f.texture).is_some(), Name::NAME),
194 (w.remove::<Source>(f.texture).is_some(), Source::NAME),
195 ] {
196 if left {
197 log.record_removed(name);
198 log.count(
199 &format!("{name} of the kept form left behind: the texture keeps its own"),
200 1,
201 );
202 }
203 }
204 let moved = w.move_components(f.texture, texture)?;
205 debug_assert!(moved, "checked alive above");
206 log.count("textures kept in another form", 1);
207 }
208 }
209 for (k, f) in held.forms.iter().enumerate() {
210 if k + 1 != chosen {
211 crate::ledger::despawn(w, f.texture, log, "texture forms left out");
212 log.count("texture forms left out", 1);
213 }
214 }
215 w.remove::<TextureForms>(texture);
216 }
217 Ok(())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::pass::PassRegistry;
225 use crate::pbr::{PbrMaterial, TextureRef};
226 use crate::{ColorSpace, TexFormat, TexRole};
227
228 fn texture(side: u32, value: u8) -> Texture {
229 Texture {
230 width: side,
231 height: side,
232 format: TexFormat::Rgba8Unorm,
233 levels: vec![vec![value; (side * side * 4) as usize]],
234 color_space: ColorSpace::Srgb,
235 role: TexRole::Color,
236 normal: None,
237 }
238 }
239
240 fn world() -> (World, Entity, Entity, Entity) {
242 let mut w = World::new();
243 let own = w.spawn();
244 w.insert(own, texture(8, 200));
245 w.insert(own, Name("aet000_001_a".into()));
246 let low = w.spawn();
247 w.insert(low, texture(2, 50));
248 w.insert(low, Name("aet000_001_a_l".into()));
249 w.insert(
250 own,
251 TextureForms {
252 detail: 0,
253 forms: vec![TextureForm {
254 detail: 1,
255 texture: low,
256 }],
257 },
258 );
259 let material = w.spawn();
260 w.insert(
261 material,
262 PbrMaterial {
263 base_color_texture: Some(TextureRef::new(own)),
264 ..PbrMaterial::default()
265 },
266 );
267 (w, own, low, material)
268 }
269
270 fn run(choice: TextureChoice, w: &mut World) -> PassLog {
271 let mut log = PassLog::default();
272 SelectTextureForm { choice }.run(w, &mut log).unwrap();
273 log
274 }
275
276 #[test]
279 fn a_texture_keeps_the_form_chosen_and_its_bindings() {
280 let (mut w, own, low, material) = world();
281 let log = run(TextureChoice::Finest, &mut w);
282 assert_eq!(log.counts["textures kept in their own form"], 1);
283 assert_eq!(w.get::<Texture>(own).unwrap().width, 8);
284 assert!(!w.is_alive(low) && !w.has::<TextureForms>(own));
285
286 let (mut w, own, low, material_2) = world();
287 let log = run(TextureChoice::AtMost(1), &mut w);
288 assert_eq!(log.counts["textures kept in another form"], 1);
289 let kept = w.get::<Texture>(own).unwrap();
290 assert_eq!((kept.width, kept.levels[0][0]), (2, 50));
291 assert_eq!(w.get::<Name>(own).unwrap().0, "aet000_001_a");
292 assert!(!w.is_alive(low) && !w.has::<TextureForms>(own));
293 let bound = w
294 .get::<PbrMaterial>(material_2)
295 .unwrap()
296 .base_color_texture
297 .as_ref()
298 .map(|t| t.texture);
299 assert_eq!(bound, Some(own));
300 let _ = material;
301
302 let (mut w, own, _, _) = world();
304 run(TextureChoice::AtMost(0), &mut w);
305 assert_eq!(w.get::<Texture>(own).unwrap().width, 8);
306 }
307
308 #[test]
311 fn an_invalid_selection_changes_nothing() {
312 let (mut w, own, low, material) = world();
313 w.get_mut::<PbrMaterial>(material).unwrap().base_color_texture = Some(TextureRef::new(low));
314 let err = SelectTextureForm {
315 choice: TextureChoice::Finest,
316 }
317 .run(&mut w, &mut PassLog::default())
318 .unwrap_err();
319 assert!(err.contains("refers to form"), "{err}");
320 assert!(w.is_alive(low) && w.has::<TextureForms>(own));
321
322 let (mut w, own, low, _) = world();
323 w.remove::<Texture>(low);
324 assert_eq!(
325 w.get::<TextureForms>(own).unwrap().verify(own, &w),
326 Err(format!("form {low:?} holds no texture"))
327 );
328
329 let (mut w, _, low, _) = world();
330 let other = w.spawn();
331 w.insert(other, texture(4, 1));
332 w.insert(
333 other,
334 TextureForms {
335 detail: 0,
336 forms: vec![TextureForm {
337 detail: 1,
338 texture: low,
339 }],
340 },
341 );
342 let err = SelectTextureForm {
343 choice: TextureChoice::Finest,
344 }
345 .run(&mut w, &mut PassLog::default())
346 .unwrap_err();
347 assert!(err.contains("belongs to more than one texture"), "{err}");
348 }
349
350 #[test]
352 fn the_pass_states_its_options() {
353 let mut reg = PassRegistry::new();
354 reg.add(&SELECT_TEXTURE_FORM);
355 for form in ["finest", "1"] {
356 let options = Options::from([("form".to_owned(), form.to_owned())]);
357 assert_eq!(
358 reg.make("select-texture-form", &options).unwrap().options(),
359 options
360 );
361 }
362 assert!(reg.make("select-texture-form", &Options::new()).is_err());
363 }
364}