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}