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