1use yog_data::Schema;
8use yog_data::with::Raw;
9use yog_graphics::Color;
10use yog_ids::time::TICKS_PER_SECOND as TICKS;
11
12use crate::math::{Mat4, Vec3};
13use crate::model::Quad;
14
15const VERTEX_FLOATS: usize = 8;
17const QUAD_VERTICES: usize = 4;
19
20#[derive(Schema, Debug, Clone, Default, PartialEq)]
22#[data(rename = "BodyFrame")]
23pub struct Frame {
24 pub uuid: String,
25 pub entity_type: String,
27 pub partial: f32,
29 pub age: f32,
31 pub limb_swing: f32,
33 pub limb_amount: f32,
34 pub head_yaw: f32,
36 pub head_pitch: f32,
37 pub body_yaw: f32,
39 pub pitch: f32,
40 pub pos: [f64; 3],
41 pub velocity: Vec3,
43 pub flags: u32,
45 pub game_time: f64,
48 #[data(default)]
50 pub captures: Vec<Capture>,
51 #[data(default)]
53 pub data: Vec<Kept>,
54}
55
56#[derive(Schema, Debug, Clone, Default, PartialEq)]
58#[data(rename = "BodyKept")]
59pub struct Kept {
60 pub key: String,
61 #[data(with = "Raw")]
62 pub value: Vec<u8>,
63}
64
65#[derive(Schema, Debug, Clone, Default, PartialEq)]
67#[data(rename = "BodyCapture")]
68pub struct Capture {
69 pub source: String,
71 pub meshes: Vec<Mesh>,
72}
73
74#[derive(Schema, Debug, Clone, Default, PartialEq)]
76#[data(rename = "BodyMesh")]
77pub struct Mesh {
78 pub texture: String,
79 pub verts: Vec<f32>,
80}
81
82impl Mesh {
83 pub fn new(texture: &str, quads: &[Quad]) -> Mesh {
84 Mesh {
85 texture: texture.to_string(),
86 verts: verts(quads),
87 }
88 }
89
90 pub fn quads(&self) -> Vec<Quad> {
92 self.verts
93 .as_chunks::<{ VERTEX_FLOATS * QUAD_VERTICES }>()
94 .0
95 .iter()
96 .map(|q| {
97 let vertex = |i: usize| &q[i * VERTEX_FLOATS..(i + 1) * VERTEX_FLOATS];
98 Quad {
99 pos: std::array::from_fn(|i| {
100 Vec3::new(vertex(i)[0], vertex(i)[1], vertex(i)[2])
101 }),
102 uv: std::array::from_fn(|i| [vertex(i)[3], vertex(i)[4]]),
103 normal: Vec3::new(vertex(0)[5], vertex(0)[6], vertex(0)[7]),
104 }
105 })
106 .collect()
107 }
108}
109
110fn verts(quads: &[Quad]) -> Vec<f32> {
112 let mut out = Vec::with_capacity(quads.len() * QUAD_VERTICES * VERTEX_FLOATS);
113 for q in quads {
114 for i in 0..QUAD_VERTICES {
115 out.extend([q.pos[i].x, q.pos[i].y, q.pos[i].z, q.uv[i][0], q.uv[i][1]]);
116 out.extend([q.normal.x, q.normal.y, q.normal.z]);
117 }
118 }
119 out
120}
121
122#[derive(Debug, Clone, PartialEq)]
124pub enum Anchor {
125 Entity,
127 Model,
129 Bone(String),
131 Block,
133}
134
135#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
137#[data(rename = "BodyAnchor")]
138pub enum AnchorKind {
139 #[default]
140 Entity,
141 Model,
142 Bone,
143 Block,
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct Look {
150 pub mode: Mode,
151 pub glint: bool,
153 pub tint: Color,
154}
155
156impl Default for Look {
157 fn default() -> Self {
158 Look {
159 mode: Mode::Cutout,
160 glint: false,
161 tint: Color::WHITE,
162 }
163 }
164}
165
166#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
168#[data(rename = "BodyMode")]
169pub enum Mode {
170 #[default]
172 Cutout,
173 Translucent,
175 Emissive,
177}
178
179#[derive(Schema, Debug, Clone, Copy, PartialEq, Default)]
181#[data(rename = "BodyBoneOp")]
182pub struct BoneOp {
183 pub rotation: Vec3,
185 pub set_rotation: bool,
186 pub position: Vec3,
188 pub set_position: bool,
189 pub scale: Option<Vec3>,
190 pub visible: Option<bool>,
192}
193
194#[derive(Schema, Debug, Clone, PartialEq, Default)]
196#[data(rename = "BodyBone")]
197pub struct Bone {
198 pub name: String,
199 pub op: BoneOp,
200}
201
202#[derive(Schema, Debug, Clone, PartialEq, Default)]
205#[data(rename = "BodyDraw")]
206pub struct Draw {
207 pub anchor: AnchorKind,
208 pub bone: String,
210 pub mesh: Mesh,
211 pub mode: Mode,
212 pub glint: bool,
213 pub tint: u32,
214}
215
216#[derive(Schema, Debug, Clone, Default, PartialEq)]
218#[data(rename = "BodyOps")]
219pub struct Ops {
220 pub root: Option<Vec<f32>>,
222 #[data(default)]
223 pub bones: Vec<Bone>,
224 #[data(default)]
225 pub draws: Vec<Draw>,
226 pub disguise: Option<String>,
228 #[data(default)]
230 pub hide: bool,
231 #[data(default)]
233 pub captures: Vec<String>,
234}
235
236impl Ops {
237 pub fn is_empty(&self) -> bool {
238 *self == Ops::default()
239 }
240
241 pub fn root(&mut self, m: &Mat4) {
243 let turned = match self
244 .root
245 .as_deref()
246 .and_then(|r| <[f32; 16]>::try_from(r).ok())
247 {
248 Some(r) => Mat4(r) * *m,
249 None => *m,
250 };
251 self.root = Some(turned.0.to_vec());
252 }
253
254 pub fn bone(&mut self, bone: &str, op: &BoneOp) {
256 self.bones.push(Bone {
257 name: bone.to_string(),
258 op: *op,
259 });
260 }
261
262 pub fn mesh(&mut self, anchor: &Anchor, texture: &str, look: Look, quads: &[Quad]) {
264 if quads.is_empty() {
265 return;
266 }
267 let (anchor, bone) = match anchor {
268 Anchor::Entity => (AnchorKind::Entity, ""),
269 Anchor::Model => (AnchorKind::Model, ""),
270 Anchor::Bone(n) => (AnchorKind::Bone, n.as_str()),
271 Anchor::Block => (AnchorKind::Block, ""),
272 };
273 let mesh = Mesh::new(texture, quads);
274 self.draws.push(Draw {
275 anchor,
276 bone: bone.to_string(),
277 mesh,
278 mode: look.mode,
279 glint: look.glint,
280 tint: look.tint.0,
281 });
282 }
283
284 pub fn disguise(&mut self, entity_type: &str) {
286 self.disguise = Some(entity_type.to_string());
287 }
288
289 pub fn hide(&mut self) {
291 self.hide = true;
292 }
293
294 pub fn capture(&mut self, source: &str) {
297 self.captures.push(source.to_string());
298 }
299
300 pub fn add(&mut self, other: Ops) {
303 if let Some(r) = other
304 .root
305 .as_deref()
306 .and_then(|r| <[f32; 16]>::try_from(r).ok())
307 {
308 self.root(&Mat4(r));
309 }
310 self.bones.extend(other.bones);
311 self.draws.extend(other.draws);
312 self.disguise = other.disguise.or(self.disguise.take());
313 self.hide |= other.hide;
314 self.captures.extend(other.captures);
315 }
316}
317
318impl Frame {
319 pub fn has(&self, flag: u32) -> bool {
320 self.flags & flag != 0
321 }
322
323 pub fn capture(&self, source: &str) -> Option<&Capture> {
325 self.captures.iter().find(|c| c.source == source)
326 }
327
328 pub fn seconds(&self) -> f32 {
331 self.age / TICKS as f32
332 }
333
334 pub fn world_seconds(&self) -> f32 {
336 (self.game_time / f64::from(TICKS)) as f32
337 }
338
339 pub fn data(&self, key: &str) -> Option<&[u8]> {
341 self.data
342 .iter()
343 .find(|k| k.key == key)
344 .map(|k| k.value.as_slice())
345 }
346}
347
348#[cfg(test)]
349mod tests;