minecraft_assets/schemas/
models.rs

1//! Serde-(de)serializable data types for
2//! `assets/<namespace>/models/{block,item}/*.json`.
3//!
4//! Start here: [`Model`].
5//!
6//! See <https://minecraft.fandom.com/wiki/Model#Block_models>.
7
8use std::{
9    collections::HashMap,
10    hash::Hash,
11    ops::{Deref, DerefMut},
12};
13
14use serde::{Deserialize, Serialize};
15
16/// A block or item model as stored in the
17/// `assets/<namespace>/models/{block,item}/` directories.
18///
19/// See also the corresponding section of the [wiki page]
20///
21/// [wiki page]: <https://minecraft.fandom.com/wiki/Model#Block_models>
22#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)]
23pub struct Model {
24    /// Specifies that this model should inherit fields from the model at the
25    /// given [resource location]. If both `parent` and `elements` are set, the
26    /// `elements` field overrides the `elements` field from the parent model.
27    ///
28    /// For an item model, this can be set to a couple builtin values:
29    ///
30    /// * `"item/generated"`, to use a model that is created out of the item's
31    ///   icon.
32    ///
33    /// * `"builtin/entity"`, to load a model from an entity file.
34    ///   * As you cannot specify the entity, this does not work for all items
35    ///     (only for chests, ender chests, mob heads, shields, banners and
36    ///     tridents).
37    ///
38    /// [resource location]: <https://minecraft.fandom.com/wiki/Model#File_path>
39    pub parent: Option<String>,
40
41    /// Contains the different places where item models are displayed in
42    /// different views.
43    pub display: Option<Display>,
44
45    /// Contains the textures of the model.
46    pub textures: Option<Textures>,
47
48    /// Contains all the elements of the model.
49    ///
50    /// If both `parent` and `elements` are set, the `elements` tag overrides
51    /// the `elements` tag from the previous model.
52    pub elements: Option<Vec<Element>>,
53
54    /// Whether to use ambient occlusion (`true` - default), or not (`false`).
55    ///
56    /// **Applies only to block models.**
57    #[serde(rename = "ambientocclusion")]
58    pub ambient_occlusion: Option<bool>,
59
60    /// Specifies how to shade the model in the GUI.
61    ///
62    /// Can be `front` or `side`. If set to `side`, the model is rendered like a
63    /// block. If set to `front`, model is shaded like a flat item. Defaults to
64    /// `side`.
65    ///
66    /// **Applies only to item models.**[^1]
67    ///
68    /// [^1]: In versions >= 1.16.2, it appears that `block/block.json` also has
69    ///     this field set.
70    #[serde(rename = "gui_light")]
71    pub gui_light_mode: Option<GuiLightMode>,
72
73    /// Specifies cases in which a different model should be used based on item
74    /// tags.
75    ///
76    /// All cases are evaluated in order from top to bottom and last predicate
77    /// that matches overrides. However, overrides are ignored if it has been
78    /// already overridden once, for example this avoids recursion on overriding
79    /// to the same model.
80    ///
81    /// **Applies only to item models.**
82    pub overrides: Option<Vec<OverrideCase>>,
83}
84
85/// Specifies how a [`Model`] is displayed in different views.
86#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq)]
87pub struct Display {
88    /// How the model is displayed when held in the right hand in third-person
89    /// view.
90    pub thirdperson_righthand: Option<Transform>,
91
92    /// How the model is displayed when held in the left hand in third-person
93    /// view.
94    pub thirdperson_lefthand: Option<Transform>,
95
96    /// How the model is displayed when held in the right hand in first-person
97    /// view.
98    pub firstperson_righthand: Option<Transform>,
99
100    /// How the model is displayed when held in the left hand in first-person
101    /// view.
102    pub firstperson_lefthand: Option<Transform>,
103
104    /// How the model is displayed in the GUI (e.g., in the inventory).
105    pub gui: Option<Transform>,
106
107    /// How the model is displayed when worn on the player's head.
108    pub head: Option<Transform>,
109
110    /// How the model is displayed when on the ground.
111    pub ground: Option<Transform>,
112
113    /// How the model is displayed in an item frame.
114    pub fixed: Option<Transform>,
115}
116
117/// Specifies the position, rotation, and scale at which a model is displayed.
118///
119/// Note that translations are applied to the model before rotations.
120#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
121pub struct Transform {
122    /// Specifies the rotation of the model in degrees according to the scheme
123    /// `[x, y, z]`.
124    #[serde(default = "Transform::zeros")]
125    pub rotation: [f32; 3],
126
127    /// Specifies the position of the model according to the scheme `[x, y, z]`.
128    ///
129    /// The unit of distance is **1/16th of a block** (0.0625 meters).
130    ///
131    /// The values should be clamped between -80 and 80.
132    #[serde(default = "Transform::zeros")]
133    pub translation: [f32; 3],
134
135    /// Specifies the scale of the model according to the scheme `[x, y, z]`.
136    ///
137    /// If the value is greater than 4, it is displayed as 4.
138    #[serde(default = "Transform::ones")]
139    pub scale: [f32; 3],
140}
141
142impl Transform {
143    pub(crate) const fn zeros() -> [f32; 3] {
144        [0.0; 3]
145    }
146
147    pub(crate) const fn ones() -> [f32; 3] {
148        [1.0; 3]
149    }
150}
151
152impl Default for Transform {
153    fn default() -> Self {
154        Self {
155            rotation: [0.0, 0.0, 0.0],
156            translation: [0.0, 0.0, 0.0],
157            scale: [1.0, 1.0, 1.0],
158        }
159    }
160}
161
162/// Specifies the [`Texture`]s of a [`Model`].
163///
164/// ## Texture Variables
165///
166/// A model's textures are specified as a set of named **texture variables**.
167/// This allows the value of one texture variable to be set to the value of
168/// another via reference, e.g., `"top": "#bottom"`.
169///
170/// ## Builtin Texture Variables
171///
172/// * **`particle`**
173///   * What texture to load particles from.
174///   * This texture is used if you are in a nether portal.
175///   * Also used for water and lava's still textures.
176///   * Applies to block and item models.
177///
178/// * **`layerN`**
179///   * Used to specify the icon of the item used in the inventory.
180///   * There can be more than just one layer (e.g. for spawn eggs), but the
181///     amount of possible layers is hardcoded for each item.
182///   * Works only in combination with `"item/generated"`.
183///   * Applies to item models.
184///
185/// ## Example
186///
187/// `block/cross.json` is the common parent of all saplings, and specifies that
188/// the `particle` texture variable should take on the value of the `cross`
189/// texture variable:
190///
191/// ```json
192/// {
193///     "textures": {
194///         "particle": "#cross"
195///     },
196///     ...
197/// }
198/// ```
199///
200/// `block/oak_sapling.json` specifies a concrete location for the `cross`
201/// texture variable:
202///
203/// ```json
204/// {
205///     "parent": "block/cross",
206///     "textures": {
207///         "cross": "block/oak_sapling"
208///     }
209/// }
210/// ```
211///
212/// [resource location]: <https://minecraft.fandom.com/wiki/Model#File_path>
213#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Eq)]
214pub struct Textures {
215    /// The values of all texture variables by name.
216    #[serde(flatten)]
217    pub variables: HashMap<String, Texture>,
218}
219
220impl Textures {
221    /// Attempts to resolve each of the texture variables in `self` using the
222    /// values present in `other`.
223    ///
224    /// # Example
225    ///
226    /// ```
227    /// # use minecraft_assets::schemas::models::*;
228    /// use maplit::hashmap;
229    ///
230    /// let mut textures = Textures::from(hashmap! {
231    ///     "foo" => "#foobar",
232    ///     "bar" => "#barvar"
233    /// });
234    ///
235    /// textures.resolve(&Textures::from(hashmap! {
236    ///     "barvar" => "herobrine",
237    /// }));
238    ///
239    /// let expected = Textures::from(hashmap! {
240    ///     "foo" => "#foobar",
241    ///     "bar" => "herobrine",
242    /// });
243    ///
244    /// assert_eq!(textures, expected);
245    /// ```
246    pub fn resolve(&mut self, other: &Self) {
247        for texture in self.values_mut() {
248            if let Some(substitution) = texture.resolve(other) {
249                *texture = Texture::from(substitution);
250            }
251        }
252    }
253
254    /// Merges the values from `other` into `self`.
255    ///
256    /// # Example
257    ///
258    /// ```
259    /// # use minecraft_assets::schemas::models::*;
260    /// use maplit::hashmap;
261    ///
262    /// let mut textures = Textures::from(hashmap! {
263    ///     "foo" => "#foobar",
264    ///     "bar" => "#barvar"
265    /// });
266    ///
267    /// textures.merge(Textures::from(hashmap! {
268    ///     "foo" => "fooey",
269    ///     "creeper" => "aw man"
270    /// }));
271    ///
272    /// let expected = Textures::from(hashmap! {
273    ///     "foo" => "fooey",
274    ///     "creeper" => "aw man",
275    ///     "bar" => "#barvar"
276    /// });
277    ///
278    /// assert_eq!(textures, expected);
279    /// ```
280    pub fn merge(&mut self, other: Self) {
281        for (name, texture) in other.variables.into_iter() {
282            //println!("inserting: {:?}", (&name, &texture));
283            self.insert(name, texture);
284        }
285    }
286}
287
288impl<K, V> From<HashMap<K, V>> for Textures
289where
290    K: Into<String>,
291    V: Into<Texture>,
292{
293    fn from(source: HashMap<K, V>) -> Self {
294        let variables = source
295            .into_iter()
296            .map(|(k, v)| (k.into(), v.into()))
297            .collect();
298        Self { variables }
299    }
300}
301
302impl Deref for Textures {
303    type Target = HashMap<String, Texture>;
304
305    fn deref(&self) -> &Self::Target {
306        &self.variables
307    }
308}
309
310impl DerefMut for Textures {
311    fn deref_mut(&mut self) -> &mut Self::Target {
312        &mut self.variables
313    }
314}
315
316/// The value of a [texture variable] in the [`Textures`] map.
317///
318/// The string value will either specify a [`location`] to load the texture from
319/// or a [`reference`] to another texture variable to take its value from.
320///
321/// [texture variable]: Textures#texture-variables
322/// [`location`]: Self::location
323/// [`reference`]: Self::reference
324#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Eq)]
325pub struct Texture(pub String);
326
327impl Texture {
328    /// Returns the [resource location] of the texture, or `None` if the texture
329    /// should instead take on the value of another texture variable.
330    ///
331    /// [resource location]: <https://minecraft.fandom.com/wiki/Model#File_path>
332    ///
333    /// # Example
334    ///
335    /// ```
336    /// # use minecraft_assets::schemas::models::*;
337    /// let texture = Texture::from("texture/location");
338    /// assert_eq!(texture.location(), Some("texture/location"));
339    ///
340    /// let texture = Texture::from("#another_var");
341    /// assert_eq!(texture.location(), None);
342    pub fn location(&self) -> Option<&str> {
343        if self.0.starts_with('#') {
344            None
345        } else {
346            Some(&self.0[..])
347        }
348    }
349
350    /// Returns the name of the texture variable from which this texture should
351    /// get its value, or `None` if the texture should be loaded from a
352    /// resource.
353    ///
354    /// # Example
355    ///
356    /// ```
357    /// # use minecraft_assets::schemas::models::*;
358    /// let texture = Texture::from("texture/location");
359    /// assert_eq!(texture.reference(), None);
360    ///
361    /// let texture = Texture::from("#another_var");
362    /// assert_eq!(texture.reference(), Some("another_var"));
363    /// ```
364    pub fn reference(&self) -> Option<&str> {
365        if self.0.starts_with('#') {
366            Some(&self.0[1..])
367        } else {
368            None
369        }
370    }
371
372    /// Resolves this texture value using the variables present in `other`, or
373    /// returns `None` if:
374    /// * This texture value not reference another texture variable, or
375    /// * There is no variable in `other` that matches
376    ///
377    /// # Example
378    ///
379    /// ```
380    /// # use minecraft_assets::schemas::models::*;
381    /// use maplit::hashmap;
382    ///
383    /// let substitutions = Textures::from(hashmap! {
384    ///     "foo" => "textures/foo",
385    ///     "bar" => "#another_var",
386    /// });
387    ///
388    /// let texture = Texture::from("#foo");
389    /// assert_eq!(texture.resolve(&substitutions), Some("textures/foo"));
390    ///
391    /// let texture = Texture::from("#bar");
392    /// assert_eq!(texture.resolve(&substitutions), Some("#another_var"));
393    ///
394    /// let texture = Texture::from("#not_found");
395    /// assert_eq!(texture.resolve(&substitutions), None);
396    ///
397    /// let texture = Texture::from("not_a_reference");
398    /// assert_eq!(texture.resolve(&substitutions), None);
399    /// ```
400    pub fn resolve<'a>(&'a self, substitutions: &'a Textures) -> Option<&'a str> {
401        if let Some(reference) = self.reference() {
402            if let Some(substitution) = substitutions.get(reference) {
403                return Some(&substitution.0);
404            }
405        }
406        None
407    }
408}
409
410impl From<String> for Texture {
411    fn from(source: String) -> Self {
412        Self(source)
413    }
414}
415
416impl<'a> From<&'a str> for Texture {
417    fn from(source: &'a str) -> Self {
418        Self(String::from(source))
419    }
420}
421
422/// A single, cube-shaped element of a [`Model`]'s geometry.
423#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
424pub struct Element {
425    /// Start point of a cuboid according to the scheme `[x, y, z]`.
426    ///
427    /// Values must be between -16 and 32.
428    pub from: [f32; 3],
429
430    /// Stop point of a cuboid according to the scheme `[x, y, z]`.
431    ///
432    /// Values must be between -16 and 32.
433    pub to: [f32; 3],
434
435    /// Holds all the faces of the cuboid. If a face is left out, it does not
436    /// render.
437    pub faces: HashMap<BlockFace, ElementFace>,
438
439    /// The rotation of the element
440    #[serde(default)]
441    pub rotation: ElementRotation,
442
443    /// Specifies if shadows are rendered (`true` - default), or not (`false`).
444    #[serde(default = "Element::default_shade")]
445    pub shade: bool,
446}
447
448impl Element {
449    pub(crate) const fn default_shade() -> bool {
450        true
451    }
452}
453
454impl Default for Element {
455    fn default() -> Self {
456        Self {
457            from: [0.0, 0.0, 0.0],
458            to: [16.0, 16.0, 16.0],
459            faces: Default::default(),
460            rotation: Default::default(),
461            shade: Self::default_shade(),
462        }
463    }
464}
465
466/// Specifies the rotation of an [`Element`].
467#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
468pub struct ElementRotation {
469    /// Sets the center of the rotation according to the scheme `[x, y, z]`.
470    pub origin: [f32; 3],
471
472    /// Specifies the direction of rotation.
473    pub axis: Axis,
474
475    /// Specifies the angle of rotation.
476    ///
477    /// Can be 45 through -45 degrees in 22.5 degree increments.
478    pub angle: f32,
479
480    /// Specifies whether or not to scale the faces across the whole block.
481    ///
482    /// Defaults to `false`.
483    #[serde(default = "ElementRotation::default_rescale")]
484    pub rescale: bool,
485}
486
487impl ElementRotation {
488    pub(crate) const fn default_rescale() -> bool {
489        false
490    }
491}
492
493impl Default for ElementRotation {
494    fn default() -> Self {
495        Self {
496            origin: [0.0, 0.0, 0.0],
497            axis: Axis::X,
498            angle: 0.0,
499            rescale: Self::default_rescale(),
500        }
501    }
502}
503
504/// Specifies the details of a single face in a cuboid [`Element`].
505#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
506pub struct ElementFace {
507    /// Defines the area of the image that should be sampled for this texture.
508    ///
509    /// The UV coordinates are specified as `[x1, y1, x2, y2]`.
510    ///
511    /// UV is optional, and if not supplied it defaults to values equal to the
512    /// xyz position of the element.
513    ///
514    /// The texture behavior is inconsistent if UV extends below 0 or above 16.
515    /// If the numbers of `x1` and `x2` are swapped (e.g. from `0, 0, 16, 16` to
516    /// `16, 0, 0, 16`), the texture flips.
517    pub uv: Option<[f32; 4]>,
518
519    /// Specifies the texture as [texture variable] prepended with a `#`.
520    ///
521    /// [texture variable]: Textures#texture-variables
522    pub texture: Texture,
523
524    /// Specifies whether a face does not need to be rendered when there is a
525    /// block touching it in the specified position.
526    ///
527    /// The position can be: `down`, `up`, `north`, `south`, `west`, or `east`.
528    ///
529    /// It also determines the side of the block to use the light level from for
530    /// lighting the face, and if unset, defaults to the side.
531    ///
532    /// `bottom` may also be used in the latest versions instead of `down`,
533    /// despite appearing only once in the actual game assets.
534    #[serde(rename = "cullface")]
535    pub cull_face: Option<BlockFace>,
536
537    /// Rotates the texture by the specified number of degrees.
538    ///
539    /// Can be `0`, `90`, `180`, or `270`. Defaults to `0`. Rotation does not
540    /// affect which part of the texture is used. Instead, it amounts to a
541    /// permutation of the selected texture vertexes (selected implicitly, or
542    /// explicitly though `uv`).
543    #[serde(default = "ElementFace::default_rotation")]
544    pub rotation: u32,
545
546    /// Determines whether to tint the texture using a hardcoded tint index.
547    ///
548    /// The default value, `-1`, indicates not to use the tint. Any other number
549    /// is provided to BlockColors to get the tint value corresponding to that
550    /// index. However, most blocks do not have a tint value defined (in which
551    /// case white is used). Furthermore, no vanilla block currently uses
552    /// multiple tint values, and thus the tint index value is ignored (as long
553    /// as it is set to something other than `-1`); it could be used for modded
554    /// blocks that need multiple distinct tint values in the same block though.
555    #[serde(rename = "tintindex", default = "ElementFace::default_tint_index")]
556    pub tint_index: i32,
557}
558
559impl ElementFace {
560    pub(crate) const fn default_rotation() -> u32 {
561        0
562    }
563
564    pub(crate) const fn default_tint_index() -> i32 {
565        -1
566    }
567}
568
569impl Default for ElementFace {
570    fn default() -> Self {
571        Self {
572            uv: Default::default(),
573            texture: Default::default(),
574            cull_face: Default::default(),
575            rotation: Self::default_rotation(),
576            tint_index: Self::default_tint_index(),
577        }
578    }
579}
580
581/// One possible case in which an item's [`Model`] should be overridden.
582#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
583pub struct OverrideCase {
584    /// Specifies when this override should be active.
585    ///
586    /// See the [wiki page] for a list of possible item predicates.
587    ///
588    /// [wiki page]: <https://minecraft.fandom.com/wiki/Model#Item_predicates>
589    pub predicate: HashMap<String, PredicateValue>,
590
591    /// The path to the model to use if the case is met, in form of a [resource
592    /// location].
593    ///
594    /// [resource location]: <https://minecraft.fandom.com/wiki/Model#File_path>
595    pub model: String,
596}
597
598/// The value for an item tag specified in a predicate in an [`OverrideCase`].
599#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
600#[serde(untagged)]
601#[allow(missing_docs)]
602pub enum PredicateValue {
603    Int(u32),
604    Float(f32),
605}
606
607/// The two possible ways to shade a model in the UI.
608#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
609#[serde(rename_all = "lowercase")]
610pub enum GuiLightMode {
611    /// Shade the model like a block.
612    Side,
613
614    /// Shade the model like a flat item.
615    Front,
616}
617
618/// The three possible axes in 3D space.
619#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
620#[serde(rename_all = "lowercase")]
621#[allow(missing_docs)]
622pub enum Axis {
623    X,
624    Y,
625    Z,
626}
627
628/// The six possible faces of a cuboid.
629#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
630#[serde(rename_all = "lowercase")]
631#[allow(missing_docs)]
632#[repr(u8)]
633pub enum BlockFace {
634    // The format accepts two possible names for `"down"`.
635    #[serde(alias = "bottom")]
636    Down,
637    Up,
638    North,
639    South,
640    West,
641    East,
642}