Skip to main content

yog_body/
format.rs

1//! The source formats: `.ymodel` (a skeleton of bones with cubes) and
2//! `.yanim` (clips of keyframes). Java model space — pixels, y down, as
3//! `CubeListBuilder` and `PartPose` (what Blockbench's "Modded Entity"
4//! makes); angles in degrees.
5
6use yog_data::{Ordered, Schema};
7
8/// A `.ymodel`.
9#[derive(Schema, Debug, Clone)]
10pub struct ModelFile {
11    /// The texture's id, written `@texture:<ns>:<path>` — a `.ytexture`
12    /// of the mod (`@texture:mymod:entity/wings`) or the game's
13    /// (`@texture:minecraft:entity/elytra`).
14    #[data(with = "yog_data::with::RefTo<yog_data::kinds::Texture>")]
15    pub texture: String,
16    /// Shown instead of the texture: a video of the mod
17    /// (`@video:mymod:screen`), playing while the model is drawn — its UVs
18    /// over the video's frame as over the texture.
19    #[data(with = "yog_data::with::Opt<yog_data::with::RefTo<yog_data::kinds::Video>>")]
20    pub video: Option<String>,
21    /// The texture's size in pixels, as its UVs count them.
22    #[data(default = "default_texture_size")]
23    pub texture_size: [u32; 2],
24    /// The bones, parents before their children.
25    pub bones: Vec<BoneDef>,
26}
27
28fn default_texture_size() -> [u32; 2] {
29    [64, 32]
30}
31
32/// A bone: a pivot, a rest turn, cubes, and points to follow (locators).
33#[derive(Schema, Debug, Clone)]
34pub struct BoneDef {
35    pub name: String,
36    /// The bone it hangs on — one of this model's, or nothing (the anchor).
37    pub parent: Option<String>,
38    /// Where it turns about, in its parent's space (`PartPose` offset).
39    #[data(default)]
40    pub pivot: [f32; 3],
41    /// Its rest turn, degrees.
42    #[data(default)]
43    pub rotation: [f32; 3],
44    #[data(default)]
45    pub cubes: Vec<CubeDef>,
46    /// Named points in the bone's space (a camera's eye, a hand's grip).
47    #[data(default)]
48    pub locators: Ordered<[f32; 3]>,
49}
50
51/// A box (`addBox`), textured box-UV from `uv`.
52#[derive(Schema, Debug, Clone)]
53pub struct CubeDef {
54    /// Its corner, in the bone's space.
55    pub origin: [f32; 3],
56    pub size: [f32; 3],
57    /// Where its box-UV starts on the texture (`texOffs`).
58    #[data(default)]
59    pub uv: [f32; 2],
60    /// Grown on every side by this much (`CubeDeformation`), without
61    /// changing the UVs.
62    #[data(default)]
63    pub inflate: f32,
64    /// Its UVs mirrored left to right (`mirror()`).
65    #[data(default)]
66    pub mirror: bool,
67}
68
69/// A `.yanim`: clips by name.
70#[derive(Schema, Debug, Clone, Default)]
71pub struct AnimFile(pub Ordered<ClipDef>);
72
73/// What a clip does when it runs past its end.
74#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
75pub enum Loop {
76    /// Stops: back to rest.
77    #[default]
78    Once,
79    /// Stays at its last frame.
80    Hold,
81    /// Starts over.
82    Loop,
83}
84
85/// One animation.
86#[derive(Schema, Debug, Clone)]
87pub struct ClipDef {
88    /// Seconds.
89    pub length: f32,
90    #[data(rename = "loop", default)]
91    pub repeat: Loop,
92    /// Each bone's tracks — this model's bones, or the vanilla model's
93    /// parts (`head`, `body`, `right_arm`…).
94    pub bones: Ordered<TracksDef>,
95}
96
97/// A bone's keyframes: time in seconds (`"0.25"`) → value.
98#[derive(Schema, Debug, Clone, Default)]
99pub struct TracksDef {
100    /// Turn, degrees, on top of the rest turn.
101    #[data(default)]
102    pub rotation: Ordered<KeyDef>,
103    /// Move, pixels.
104    #[data(default)]
105    pub position: Ordered<KeyDef>,
106    /// Scale, a factor.
107    #[data(default)]
108    pub scale: Ordered<KeyDef>,
109}
110
111/// How a keyframe goes to the next one.
112#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
113pub enum Ease {
114    #[default]
115    Linear,
116    /// Smooth through its neighbours (Catmull-Rom).
117    Smooth,
118    /// Jumps at the next keyframe.
119    Step,
120}
121
122/// A keyframe: `[x, y, z]`, or `{ "value": [x, y, z], "ease": "smooth" }`.
123#[derive(Debug, Clone, Copy, PartialEq)]
124pub struct KeyDef {
125    pub value: [f32; 3],
126    pub ease: Ease,
127}
128
129#[derive(Schema)]
130struct KeyObject {
131    value: [f32; 3],
132    #[data(default)]
133    ease: Ease,
134}
135
136impl Schema for KeyDef {
137    fn shape() -> yog_data::Shape {
138        yog_data::Shape::Any
139    }
140
141    fn read(v: &yog_data::ValueRef<'_>, cx: &mut yog_data::Reader) -> Option<Self> {
142        if v.as_list().is_some() {
143            return <[f32; 3]>::read(v, cx).map(|value| KeyDef {
144                value,
145                ease: Ease::Linear,
146            });
147        }
148        KeyObject::read(v, cx).map(|k| KeyDef {
149            value: k.value,
150            ease: k.ease,
151        })
152    }
153
154    fn write(&self) -> yog_data::Value {
155        if self.ease == Ease::Linear {
156            return self.value.write();
157        }
158        KeyObject {
159            value: self.value,
160            ease: self.ease,
161        }
162        .write()
163    }
164}