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}