Skip to main content

yog_body/
wire.rs

1//! What the host and a mod say each frame about one entity (see
2//! `docs/design/yog-body.md`): the [`Frame`] the host sends, the [`Ops`]
3//! the mod answers with. Little-endian; strings are a u16 length and UTF-8.
4
5use crate::math::{Mat4, Vec3};
6use crate::model::Quad;
7
8/// One entity, this frame.
9#[derive(Debug, Clone, Default, PartialEq)]
10pub struct Frame {
11    pub uuid: String,
12    /// `minecraft:player`, `minecraft:zombie`…
13    pub entity_type: String,
14    /// Between the last tick and the next (0–1).
15    pub partial: f32,
16    /// Ticks it has lived, with `partial`.
17    pub age: f32,
18    /// How far its legs have walked, and how fast (vanilla's limb swing).
19    pub limb_swing: f32,
20    pub limb_amount: f32,
21    /// The head's turn from the body, and its pitch (degrees).
22    pub head_yaw: f32,
23    pub head_pitch: f32,
24    /// Degrees.
25    pub body_yaw: f32,
26    pub pitch: f32,
27    pub pos: [f64; 3],
28    /// Blocks per tick.
29    pub velocity: Vec3,
30    /// `yog_core::query::entity_flags` bits.
31    pub flags: u32,
32    /// What was captured last frame (see [`Ops::capture`]).
33    pub captures: Vec<Capture>,
34}
35
36/// The posed geometry of an entity (entity space, see the design doc).
37#[derive(Debug, Clone, Default, PartialEq)]
38pub struct Capture {
39    /// `self`, or the entity type captured.
40    pub source: String,
41    pub meshes: Vec<Mesh>,
42}
43
44/// Quads with one texture.
45#[derive(Debug, Clone, Default, PartialEq)]
46pub struct Mesh {
47    pub texture: String,
48    pub quads: Vec<Quad>,
49}
50
51/// Where a mesh's vertices are.
52#[derive(Debug, Clone, PartialEq)]
53pub enum Anchor {
54    /// Blocks, y up, feet at the origin, after the body's yaw.
55    Entity,
56    /// The vanilla model's space: pixels, y down.
57    Model,
58    /// A vanilla part's space (pixels), wherever the part is posed.
59    Bone(String),
60    /// A block's space: pixels 0–16, y up, as block models (block bodies).
61    Block,
62}
63
64/// How a mesh is drawn.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
66pub enum Mode {
67    /// Opaque, transparent pixels cut out, both sides.
68    #[default]
69    Cutout,
70    /// Blended by its alpha.
71    Translucent,
72    /// Glows: full bright, added.
73    Emissive,
74}
75
76/// What to change about a vanilla part.
77#[derive(Debug, Clone, Copy, PartialEq, Default)]
78pub struct BoneOp {
79    /// Radians; replaces vanilla's turn if `set_rotation`, else adds.
80    pub rotation: Vec3,
81    pub set_rotation: bool,
82    /// Pixels; replaces vanilla's offset if `set_position`, else adds.
83    pub position: Vec3,
84    pub set_position: bool,
85    pub scale: Option<Vec3>,
86    /// `Some(false)` hides it, `Some(true)` shows it.
87    pub visible: Option<bool>,
88}
89
90/// A mod's answer: operations, written in order.
91#[derive(Debug, Clone, Default, PartialEq)]
92pub struct Ops(pub Vec<u8>);
93
94const ROOT: u8 = 1;
95const BONE: u8 = 2;
96const MESH: u8 = 3;
97const DISGUISE: u8 = 4;
98const HIDE: u8 = 5;
99const CAPTURE: u8 = 6;
100
101impl Ops {
102    pub fn is_empty(&self) -> bool {
103        self.0.is_empty()
104    }
105
106    /// Turns the whole body by `m` in entity space (after the body's yaw).
107    pub fn root(&mut self, m: &Mat4) {
108        self.0.push(ROOT);
109        for f in m.0 {
110            put_f32(&mut self.0, f);
111        }
112    }
113
114    /// Poses the vanilla part `bone` (after vanilla animated it).
115    pub fn bone(&mut self, bone: &str, op: &BoneOp) {
116        let b = &mut self.0;
117        b.push(BONE);
118        put_str(b, bone);
119        let mut flags = 0u8;
120        if op.set_rotation {
121            flags |= 1;
122        }
123        if op.set_position {
124            flags |= 2;
125        }
126        if op.scale.is_some() {
127            flags |= 4;
128        }
129        match op.visible {
130            Some(false) => flags |= 8,
131            Some(true) => flags |= 16,
132            None => {}
133        }
134        b.push(flags);
135        for v in [op.rotation, op.position, op.scale.unwrap_or(Vec3::ONE)] {
136            put_vec(b, v);
137        }
138    }
139
140    /// Draws `quads` with `texture` at `anchor`. `argb` tints it (alpha
141    /// counts in [`Mode::Translucent`]); `glint` adds the enchantment shine.
142    pub fn mesh(&mut self, anchor: &Anchor, texture: &str, mode: Mode, glint: bool, argb: u32, quads: &[Quad]) {
143        if quads.is_empty() {
144            return;
145        }
146        let b = &mut self.0;
147        b.push(MESH);
148        let (a, bone) = match anchor {
149            Anchor::Entity => (0u8, ""),
150            Anchor::Model => (1, ""),
151            Anchor::Bone(n) => (2, n.as_str()),
152            Anchor::Block => (3, ""),
153        };
154        b.push(a);
155        put_str(b, bone);
156        put_str(b, texture);
157        b.push(match mode {
158            Mode::Cutout => 0,
159            Mode::Translucent => 1,
160            Mode::Emissive => 2,
161        });
162        b.push(glint as u8);
163        b.extend_from_slice(&argb.to_le_bytes());
164        put_quads(b, quads);
165    }
166
167    /// Draws the entity as one of `entity_type`, animated like it.
168    pub fn disguise(&mut self, entity_type: &str) {
169        self.0.push(DISGUISE);
170        put_str(&mut self.0, entity_type);
171    }
172
173    /// Doesn't draw the entity itself (its model, layers and name).
174    pub fn hide(&mut self) {
175        self.0.push(HIDE);
176    }
177
178    /// Asks for the posed geometry of the entity (`"self"`) or of a type
179    /// posed like it; it comes in the next frame's [`Frame::captures`].
180    pub fn capture(&mut self, source: &str) {
181        self.0.push(CAPTURE);
182        put_str(&mut self.0, source);
183    }
184}
185
186// ── Encoding ────────────────────────────────────────────────────────────────
187
188fn put_f32(b: &mut Vec<u8>, f: f32) {
189    b.extend_from_slice(&f.to_le_bytes());
190}
191
192fn put_vec(b: &mut Vec<u8>, v: Vec3) {
193    put_f32(b, v.x);
194    put_f32(b, v.y);
195    put_f32(b, v.z);
196}
197
198fn put_str(b: &mut Vec<u8>, s: &str) {
199    let s = &s.as_bytes()[..s.len().min(u16::MAX as usize)];
200    b.extend_from_slice(&(s.len() as u16).to_le_bytes());
201    b.extend_from_slice(s);
202}
203
204/// Vertex count, then x y z u v nx ny nz per vertex.
205fn put_quads(b: &mut Vec<u8>, quads: &[Quad]) {
206    b.extend_from_slice(&((quads.len() * 4) as u32).to_le_bytes());
207    for q in quads {
208        for i in 0..4 {
209            put_vec(b, q.pos[i]);
210            put_f32(b, q.uv[i][0]);
211            put_f32(b, q.uv[i][1]);
212            put_vec(b, q.normal);
213        }
214    }
215}
216
217struct Reader<'a> {
218    b: &'a [u8],
219}
220
221impl Reader<'_> {
222    fn take(&mut self, n: usize) -> Option<&[u8]> {
223        if self.b.len() < n {
224            return None;
225        }
226        let (h, t) = self.b.split_at(n);
227        self.b = t;
228        Some(h)
229    }
230    fn u8(&mut self) -> Option<u8> {
231        self.take(1).map(|b| b[0])
232    }
233    fn u16(&mut self) -> Option<u16> {
234        self.take(2).map(|b| u16::from_le_bytes([b[0], b[1]]))
235    }
236    fn u32(&mut self) -> Option<u32> {
237        self.take(4).map(|b| u32::from_le_bytes(b.try_into().unwrap()))
238    }
239    fn f32(&mut self) -> Option<f32> {
240        self.take(4).map(|b| f32::from_le_bytes(b.try_into().unwrap()))
241    }
242    fn f64(&mut self) -> Option<f64> {
243        self.take(8).map(|b| f64::from_le_bytes(b.try_into().unwrap()))
244    }
245    fn vec(&mut self) -> Option<Vec3> {
246        Some(Vec3::new(self.f32()?, self.f32()?, self.f32()?))
247    }
248    fn str(&mut self) -> Option<String> {
249        let n = self.u16()? as usize;
250        String::from_utf8(self.take(n)?.to_vec()).ok()
251    }
252    fn quads(&mut self) -> Option<Vec<Quad>> {
253        let n = self.u32()? as usize / 4;
254        let mut out = Vec::with_capacity(n.min(1 << 16));
255        for _ in 0..n {
256            let mut q = Quad { pos: [Vec3::ZERO; 4], uv: [[0.0; 2]; 4], normal: Vec3::ZERO };
257            for i in 0..4 {
258                q.pos[i] = self.vec()?;
259                q.uv[i] = [self.f32()?, self.f32()?];
260                q.normal = self.vec()?;
261            }
262            out.push(q);
263        }
264        Some(out)
265    }
266}
267
268impl Frame {
269    /// Reads what the host sent.
270    pub fn decode(bytes: &[u8]) -> Option<Frame> {
271        let mut r = Reader { b: bytes };
272        if r.u8()? != 1 {
273            return None;
274        }
275        let mut f = Frame {
276            uuid: r.str()?,
277            entity_type: r.str()?,
278            partial: r.f32()?,
279            age: r.f32()?,
280            limb_swing: r.f32()?,
281            limb_amount: r.f32()?,
282            head_yaw: r.f32()?,
283            head_pitch: r.f32()?,
284            body_yaw: r.f32()?,
285            pitch: r.f32()?,
286            pos: [r.f64()?, r.f64()?, r.f64()?],
287            velocity: r.vec()?,
288            flags: r.u32()?,
289            captures: Vec::new(),
290        };
291        for _ in 0..r.u16()? {
292            let source = r.str()?;
293            let mut meshes = Vec::new();
294            for _ in 0..r.u16()? {
295                meshes.push(Mesh { texture: r.str()?, quads: r.quads()? });
296            }
297            f.captures.push(Capture { source, meshes });
298        }
299        Some(f)
300    }
301
302    /// As the host writes it (for tests and tools).
303    pub fn encode(&self) -> Vec<u8> {
304        let mut b = vec![1u8];
305        put_str(&mut b, &self.uuid);
306        put_str(&mut b, &self.entity_type);
307        for f in [self.partial, self.age, self.limb_swing, self.limb_amount, self.head_yaw, self.head_pitch, self.body_yaw, self.pitch] {
308            put_f32(&mut b, f);
309        }
310        for p in self.pos {
311            b.extend_from_slice(&p.to_le_bytes());
312        }
313        put_vec(&mut b, self.velocity);
314        b.extend_from_slice(&self.flags.to_le_bytes());
315        b.extend_from_slice(&(self.captures.len() as u16).to_le_bytes());
316        for c in &self.captures {
317            put_str(&mut b, &c.source);
318            b.extend_from_slice(&(c.meshes.len() as u16).to_le_bytes());
319            for m in &c.meshes {
320                put_str(&mut b, &m.texture);
321                put_quads(&mut b, &m.quads);
322            }
323        }
324        b
325    }
326
327    pub fn has(&self, flag: u32) -> bool {
328        self.flags & flag != 0
329    }
330
331    /// What was captured from `source` last frame.
332    pub fn capture(&self, source: &str) -> Option<&Capture> {
333        self.captures.iter().find(|c| c.source == source)
334    }
335
336    /// The entity's age in seconds (for clips).
337    pub fn seconds(&self) -> f32 {
338        self.age / 20.0
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345
346    #[test]
347    fn a_frame_reads_back() {
348        let q = Quad { pos: [Vec3::new(1.0, 2.0, 3.0); 4], uv: [[0.5, 0.25]; 4], normal: Vec3::new(0.0, 1.0, 0.0) };
349        let f = Frame {
350            uuid: "u".into(),
351            entity_type: "minecraft:player".into(),
352            age: 12.5,
353            pos: [1.0, 2.0, 3.0],
354            flags: 5,
355            captures: vec![Capture { source: "self".into(), meshes: vec![Mesh { texture: "t".into(), quads: vec![q] }] }],
356            ..Default::default()
357        };
358        assert_eq!(Frame::decode(&f.encode()), Some(f));
359    }
360
361    #[test]
362    fn ops_are_records() {
363        let mut o = Ops::default();
364        o.hide();
365        o.disguise("minecraft:zombie");
366        assert_eq!(o.0[0], HIDE);
367        assert_eq!(o.0[1], DISGUISE);
368        assert_eq!(&o.0[2..4], &16u16.to_le_bytes());
369    }
370}