Skip to main content

yog_formats/content/
model.rs

1//! Models: textures, elements, display transforms, and a block's states.
2
3use yog_data::{Ordered, Reader, Schema, Shape, Value, ValueRef};
4
5/// A texture: a slot of the model (`#side`), or a texture by id —
6/// `@texture:mymod:block/ruby` (the mod's `.ytexture`) or the game's
7/// (`@texture:minecraft:block/stone`).
8#[derive(Debug, Clone, PartialEq)]
9pub enum Texture {
10    Slot(String),
11    /// A texture's id, its namespace given (the game's if none).
12    Ref(String),
13}
14
15/// What a texture slot starts with: `#side`.
16pub const SLOT_MARK: char = '#';
17
18impl Schema for Texture {
19    fn shape() -> Shape {
20        yog_data::schema::ref_shape::<yog_data::kinds::Texture>()
21    }
22    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
23        if let ValueRef::Str(s) = v
24            && let Some(slot) = s.strip_prefix(SLOT_MARK)
25        {
26            if slot.is_empty() {
27                cx.problem("`#` needs a slot name");
28                return None;
29            }
30            return Some(Texture::Slot(slot.to_string()));
31        }
32        let id = yog_data::Id::<yog_data::kinds::Texture>::read(v, cx)?;
33        Some(Texture::Ref(yog_ids::qualify(&id.id)))
34    }
35    fn write(&self) -> Value {
36        match self {
37            Texture::Slot(s) => Value::Str(format!("{SLOT_MARK}{s}")),
38            Texture::Ref(id) => yog_data::Id::<yog_data::kinds::Texture>::new(id.clone()).write(),
39        }
40    }
41}
42
43#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq)]
44pub enum Axis {
45    X,
46    Y,
47    Z,
48}
49
50#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq)]
51pub enum GuiLight {
52    Front,
53    Side,
54}
55
56/// An element's rotation about one axis.
57#[derive(Schema, Debug, Clone)]
58pub struct ElementRotation {
59    /// The point it turns about; the block's middle by default.
60    #[data(range = "-16..=32")]
61    pub origin: Option<[f64; 3]>,
62    pub axis: Axis,
63    /// -45, -22.5, 0, 22.5 or 45 degrees.
64    #[data(range = "-45..=45")]
65    pub angle: f64,
66    #[data(default)]
67    pub rescale: bool,
68}
69
70/// One face of an element.
71#[derive(Schema, Debug, Clone)]
72pub struct Face {
73    /// `#slot` — a slot of the model's textures.
74    pub texture: String,
75    #[data(range = "0..=16")]
76    pub uv: Option<[f64; 4]>,
77    /// 0, 90, 180 or 270.
78    pub rotation: Option<u32>,
79    /// Not drawn when this side touches a full block.
80    pub cullface: Option<yog_core::Face>,
81    /// The tint index (-1: none).
82    #[data(range = "-1..=255")]
83    pub tint: Option<i64>,
84}
85
86#[derive(Schema, Debug, Clone, Default)]
87pub struct Faces {
88    pub down: Option<Face>,
89    pub up: Option<Face>,
90    pub north: Option<Face>,
91    pub south: Option<Face>,
92    pub west: Option<Face>,
93    pub east: Option<Face>,
94}
95
96impl Faces {
97    pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Face)> {
98        [
99            ("down", &self.down),
100            ("up", &self.up),
101            ("north", &self.north),
102            ("south", &self.south),
103            ("west", &self.west),
104            ("east", &self.east),
105        ]
106        .into_iter()
107        .filter_map(|(d, f)| f.as_ref().map(|f| (d, f)))
108    }
109}
110
111/// A box of a model.
112#[derive(Schema, Debug, Clone)]
113pub struct Element {
114    pub name: Option<String>,
115    #[data(range = "-16..=32")]
116    pub from: [f64; 3],
117    #[data(range = "-16..=32")]
118    pub to: [f64; 3],
119    pub rotation: Option<ElementRotation>,
120    pub shade: Option<bool>,
121    pub faces: Option<Faces>,
122}
123
124/// How an item model sits in one place.
125#[derive(Schema, Debug, Clone)]
126pub struct Transform {
127    #[data(range = "-360..=360")]
128    pub rotation: Option<[f64; 3]>,
129    #[data(range = "-80..=80")]
130    pub translation: Option<[f64; 3]>,
131    #[data(range = "-4..=4")]
132    pub scale: Option<[f64; 3]>,
133}
134
135#[derive(Schema, Debug, Clone, Default)]
136pub struct Display {
137    pub thirdperson_righthand: Option<Transform>,
138    pub thirdperson_lefthand: Option<Transform>,
139    pub firstperson_righthand: Option<Transform>,
140    pub firstperson_lefthand: Option<Transform>,
141    pub gui: Option<Transform>,
142    pub head: Option<Transform>,
143    pub ground: Option<Transform>,
144    pub fixed: Option<Transform>,
145}
146
147impl Display {
148    pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Transform)> {
149        [
150            ("thirdperson_righthand", &self.thirdperson_righthand),
151            ("thirdperson_lefthand", &self.thirdperson_lefthand),
152            ("firstperson_righthand", &self.firstperson_righthand),
153            ("firstperson_lefthand", &self.firstperson_lefthand),
154            ("gui", &self.gui),
155            ("head", &self.head),
156            ("ground", &self.ground),
157            ("fixed", &self.fixed),
158        ]
159        .into_iter()
160        .filter_map(|(d, t)| t.as_ref().map(|t| (d, t)))
161    }
162}
163
164/// A model: a template (`cube_all`, `generated`, …) or a model id as
165/// `parent`, its textures, or its own elements.
166#[derive(Schema, Debug, Clone)]
167pub struct Model {
168    pub parent: Option<String>,
169    /// A `.ymaterial` (`@material:mymod:block/ruby`): its textures, under the
170    /// model's own, and — a block's — its render layer when the block gives none.
171    #[data(with = "yog_data::with::Opt<yog_data::with::RefTo<yog_data::kinds::Material>>")]
172    pub material: Option<String>,
173    /// Slot → texture.
174    #[data(default)]
175    pub textures: Ordered<Texture>,
176    pub elements: Option<Vec<Element>>,
177    pub display: Option<Display>,
178    pub ambient_occlusion: Option<bool>,
179    pub gui_light: Option<GuiLight>,
180}
181
182/// One way the block stands.
183#[derive(Schema, Debug, Clone)]
184pub struct Rotation {
185    /// 0, 90, 180 or 270.
186    pub x: Option<u32>,
187    /// 0, 90, 180 or 270.
188    pub y: Option<u32>,
189    #[data(range = "1..=1000000")]
190    pub weight: Option<f64>,
191    #[data(default)]
192    pub uvlock: bool,
193}
194
195/// Arms towards neighbours that connect (see `block.connect_groups`).
196#[derive(Schema, Debug, Clone)]
197pub struct Connect {
198    /// The model of the arm towards north.
199    pub arm: Model,
200    #[data(default)]
201    pub uvlock: bool,
202}
203
204#[derive(Schema, Debug, Clone)]
205pub struct States {
206    pub rotations: Option<Vec<Rotation>>,
207    pub connect: Option<Connect>,
208    /// The block turns when placed (a `facing` property); its model's north
209    /// side is its front.
210    pub facing: Option<Facing>,
211    /// Which way it turns when placed: its front toward the player (as a
212    /// furnace, the default) or the way they look (as an observer).
213    pub placement: Option<Placement>,
214    /// It hangs on a floor, wall or ceiling, as a button does: a `face`
215    /// property (`floor`, `wall`, `ceiling`) and a horizontal `facing`. Its
216    /// model lies on the floor.
217    #[data(default)]
218    pub attach: bool,
219    /// It can stand in water (a `waterlogged` property).
220    #[data(default)]
221    pub waterlogged: bool,
222    /// Its textures stay as they are in the world when it turns (a
223    /// blockstate's `uvlock`).
224    #[data(default)]
225    pub uvlock: bool,
226    /// Properties of its own: a name and its values, the first the
227    /// default; `["false", "true"]` is a yes/no.
228    #[data(default)]
229    pub properties: Ordered<Vec<String>>,
230    /// More models, by name, for `variants`.
231    #[data(default)]
232    pub models: Ordered<Model>,
233    /// The model a state shows: some of its properties
234    /// (`"energized=true,facing=up"`) → a name in `models`, or a list of
235    /// names to pick from at random per position. The first that matches
236    /// wins, else the block's own model; turning for `facing` and `face` is
237    /// added.
238    #[data(default)]
239    pub variants: Ordered<ModelNames>,
240}
241
242/// One model name, or several to pick from at random (a blockstate's list of variants).
243pub type ModelNames = yog_data::OneOrList<String>;
244
245pub use yog_registry::{Facing, Placement};