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