minecraft_assets/api/
resolve.rs

1use crate::schemas::models::{Display, Element, GuiLightMode, Model, Texture, Textures};
2
3/// Methods for resolving the properties of a [`Model`] with respect to its
4/// parents.
5pub struct ModelResolver;
6
7impl ModelResolver {
8    /// Iterates through a [`Model`] and all of its parents to resolve all of
9    /// the model's properties in a way that reflects the intended inheritance
10    /// and/or override behavior of the Minecraft model format.
11    ///
12    /// The method takes in an iterator of [`Model`]s where the first element is
13    /// the model being resolved, and the subsequent elements (if any) are the
14    /// chain of parents of that model.
15    ///
16    /// # Example
17    ///
18    /// ```
19    /// # use minecraft_assets::api::{ModelResolver};
20    /// use maplit::hashmap;
21    ///
22    /// use minecraft_assets::schemas::models::*;
23    ///
24    /// let parent = Model {
25    ///     textures: Some(Textures::from(hashmap! {
26    ///         "up" => "#side",
27    ///         "down" => "#side"
28    ///     })),
29    ///     elements: Some(vec![
30    ///         Element {
31    ///             faces: hashmap! {
32    ///                 BlockFace::Up => ElementFace {
33    ///                     texture: Texture::from("#up"),
34    ///                     ..Default::default()
35    ///                 },
36    ///                 BlockFace::Down => ElementFace {
37    ///                     texture: Texture::from("#down"),
38    ///                     ..Default::default()
39    ///                 },
40    ///                 BlockFace::East => ElementFace {
41    ///                     texture: Texture::from("#side"),
42    ///                     ..Default::default()
43    ///                 },
44    ///                 BlockFace::West => ElementFace {
45    ///                     texture: Texture::from("#side"),
46    ///                     ..Default::default()
47    ///                 }
48    ///             },
49    ///             ..Default::default()
50    ///         }
51    ///     ]),
52    ///     ..Default::default()
53    /// };
54    ///
55    /// let child = Model {
56    ///     textures: Some(Textures::from(hashmap! {
57    ///         "up" => "textures/up",
58    ///         "side" => "textures/side"
59    ///     })),
60    ///     ..Default::default()
61    /// };
62    ///
63    /// let expected = Model {
64    ///     textures: Some(Textures::from(hashmap! {
65    ///         "up" => "textures/up",
66    ///         "down" => "textures/side",
67    ///         "side" => "textures/side"
68    ///     })),
69    ///     elements: Some(vec![
70    ///         Element {
71    ///             faces: hashmap! {
72    ///                 BlockFace::Up => ElementFace {
73    ///                     texture: Texture::from("textures/up"),
74    ///                     ..Default::default()
75    ///                 },
76    ///                 BlockFace::Down => ElementFace {
77    ///                     texture: Texture::from("textures/side"),
78    ///                     ..Default::default()
79    ///                 },
80    ///                 BlockFace::East => ElementFace {
81    ///                     texture: Texture::from("textures/side"),
82    ///                     ..Default::default()
83    ///                 },
84    ///                 BlockFace::West => ElementFace {
85    ///                     texture: Texture::from("textures/side"),
86    ///                     ..Default::default()
87    ///                 }
88    ///             },
89    ///             ..Default::default()
90    ///         }
91    ///     ]),
92    ///     ..Default::default()
93    /// };
94    ///
95    /// let resolved = ModelResolver::resolve_model([&child, &parent].into_iter());
96    ///
97    /// assert_eq!(resolved, expected);
98    /// ```
99    pub fn resolve_model<'a>(models: impl IntoIterator<Item = &'a Model> + Clone) -> Model {
100        let textures = Self::resolve_textures(models.clone());
101        let mut elements = Self::resolve_elements(models.clone());
102
103        if let Some(ref mut elements) = elements {
104            Self::resolve_element_textures(elements, &textures);
105        }
106
107        let display = Self::resolve_display(models.clone());
108        let ambient_occlusion = Self::resolve_ambient_occlusion(models.clone());
109        let gui_light_mode = Self::resolve_gui_light_mode(models.clone());
110        let overrides = models.into_iter().next().unwrap().overrides.clone();
111
112        Model {
113            parent: None,
114            display,
115            textures: Some(textures),
116            elements,
117            ambient_occlusion,
118            gui_light_mode,
119            overrides,
120        }
121    }
122
123    /// Iterates through a [`Model`] and all of its parents to resolve all of
124    /// the model's [texture variables].
125    ///
126    /// This works by merging together the [`Textures`] maps from all models in
127    /// the parent-child chain, and then substituting texture variables with
128    /// concrete values where possible.
129    ///
130    /// [texture variables]: Textures#texture-variables
131    ///
132    /// # Example
133    ///
134    /// ```
135    /// # use minecraft_assets::api::{ModelResolver};
136    /// use maplit::hashmap;
137    ///
138    /// use minecraft_assets::schemas::models::{Model, Textures};
139    ///
140    /// let child = Model {
141    ///     textures: Some(Textures::from(hashmap! {
142    ///         "child_texture" => "textures/child",
143    ///         "bar" => "#parent_texture"
144    ///     })),
145    ///     ..Default::default()
146    /// };
147    ///
148    /// let parent = Model {
149    ///     textures: Some(Textures::from(hashmap! {
150    ///         "parent_texture" => "textures/parent",
151    ///         "foo" => "#child_texture"
152    ///     })),
153    ///     ..Default::default()
154    /// };
155    ///
156    /// // Provide models in increasing level of parenthood.
157    /// let models = [child, parent];
158    /// let resolved = ModelResolver::resolve_textures(models.iter());
159    ///
160    /// let expected = Textures::from(hashmap! {
161    ///     "parent_texture" => "textures/parent",
162    ///     "foo" => "textures/child",              // <------- resolved
163    ///     "child_texture" => "textures/child",
164    ///     "bar" => "textures/parent"              // <------- resolved    
165    /// });
166    ///
167    /// assert_eq!(resolved, expected);
168    /// ```
169    pub fn resolve_textures<'a>(models: impl IntoIterator<Item = &'a Model>) -> Textures {
170        let mut textures = Textures::default();
171
172        for model in models.into_iter() {
173            if let Some(mut parent_textures) = model.textures.clone() {
174                // Resolve variables in the parent using the child textures first.
175                parent_textures.resolve(&textures);
176
177                // Then resolve variables in the child using the parent textures.
178                textures.resolve(&parent_textures);
179
180                // Merge the **child** into the parent.
181                std::mem::swap(&mut textures, &mut parent_textures);
182                textures.merge(parent_textures.clone());
183            }
184        }
185
186        textures
187    }
188
189    /// Iterates through a [`Model`] and all of its parents to resolve the
190    /// model's cuboid [`Element`]s.
191    ///
192    /// This works by taking the first set of elements present in the chain of
193    /// parents. Unlike textures, child definitions for model elements
194    /// completely override elements from the parent(s).
195    ///
196    /// # Example
197    ///
198    /// ```
199    /// # use minecraft_assets::api::{ModelResolver};
200    /// use minecraft_assets::schemas::models::{Model, Element};
201    ///
202    /// let element1 = Element {
203    ///     from: [0.0, 0.0, 0.0],
204    ///     to: [1.0, 1.0, 1.0],
205    ///     ..Default::default()
206    /// };
207    ///
208    /// let element2 = Element {
209    ///     from: [5.0, 6.0, 7.0],
210    ///     to: [4.0, 3.0, 2.0],
211    ///     ..Default::default()
212    /// };
213    ///
214    /// let model1 = Model {
215    ///     elements: Some(vec![element1.clone()]),
216    ///     ..Default::default()
217    /// };
218    ///
219    /// let model2 = Model {
220    ///     elements: Some(vec![element2.clone()]),
221    ///     ..Default::default()
222    /// };
223    ///
224    /// let empty = Model::default();
225    ///
226    /// let resolved = ModelResolver::resolve_elements([&empty, &model1].into_iter());
227    /// assert_eq!(resolved, Some(vec![element1.clone()]));
228    ///
229    /// let resolved = ModelResolver::resolve_elements([&empty, &model2].into_iter());
230    /// assert_eq!(resolved, Some(vec![element2.clone()]));
231    ///
232    /// let resolved = ModelResolver::resolve_elements([&model1, &model2].into_iter());
233    /// assert_eq!(resolved, Some(vec![element1.clone()]));
234    ///
235    /// let resolved = ModelResolver::resolve_elements([&model2, &model1].into_iter());
236    /// assert_eq!(resolved, Some(vec![element2.clone()]));
237    ///
238    /// let resolved = ModelResolver::resolve_elements([&empty, &empty].into_iter());
239    /// assert_eq!(resolved, None);
240    /// ```
241    pub fn resolve_elements<'a>(
242        models: impl IntoIterator<Item = &'a Model>,
243    ) -> Option<Vec<Element>> {
244        Self::first_model_where_some(models, |model| model.elements.as_ref()).cloned()
245    }
246
247    /// Iterates through each [`ElementFace`] in each [`Element`] and resolves
248    /// any texture variables using the provided map.
249    ///
250    /// [`ElementFace`]: crate::schemas::models::ElementFace
251    pub fn resolve_element_textures<'a>(
252        elements: impl IntoIterator<Item = &'a mut Element>,
253        textures: &Textures,
254    ) {
255        for element in elements.into_iter() {
256            for face in element.faces.values_mut() {
257                if let Some(substitution) = face.texture.resolve(textures) {
258                    face.texture = Texture::from(substitution);
259                }
260            }
261        }
262    }
263
264    /// Iterates through a [`Model`] and all of its parents to resolve the
265    /// model's [`Display`] properties.
266    ///
267    /// Similar to [`elements`] works by taking the first set of properties
268    /// present in the chain of parents.
269    ///
270    /// [`elements`]: Self::resolve_elements
271    pub fn resolve_display<'a>(models: impl IntoIterator<Item = &'a Model>) -> Option<Display> {
272        Self::first_model_where_some(models, |model| model.display.as_ref()).cloned()
273    }
274
275    /// Iterates through a [`Model`] and all of its parents to resolve the
276    /// model's ambient occlusion setting.
277    ///
278    /// Similar to [`elements`] works by taking the first property value present
279    /// in the chain of parents.
280    ///
281    /// [`elements`]: Self::resolve_elements
282    pub fn resolve_ambient_occlusion<'a>(
283        models: impl IntoIterator<Item = &'a Model>,
284    ) -> Option<bool> {
285        Self::first_model_where_some(models, |model| model.ambient_occlusion.as_ref()).copied()
286    }
287
288    /// Iterates through a [`Model`] and all of its parents to resolve the
289    /// model's GUI light mode setting.
290    ///
291    /// Similar to [`elements`] works by taking the first property value present
292    /// in the chain of parents.
293    ///
294    /// [`elements`]: Self::resolve_elements
295    pub fn resolve_gui_light_mode<'a>(
296        models: impl IntoIterator<Item = &'a Model>,
297    ) -> Option<GuiLightMode> {
298        Self::first_model_where_some(models, |model| model.gui_light_mode.as_ref()).copied()
299    }
300
301    fn first_model_where_some<'a, F, T>(
302        models: impl IntoIterator<Item = &'a Model>,
303        mut op: F,
304    ) -> Option<&'a T>
305    where
306        F: FnMut(&'a Model) -> Option<&'a T>,
307    {
308        for model in models.into_iter() {
309            if let Some(item) = op(model) {
310                return Some(item);
311            }
312        }
313
314        None
315    }
316}