1use std::{collections::HashMap, io::Read};
2
3use crate::json_extra::JsonExt;
4
5#[inline]
6fn read_n<R: Read, const N: usize>(data: &mut R) -> std::io::Result<[u8; N]> {
7 let mut buf = [0_u8; N];
8 data.read_exact(&mut buf)?;
9 Ok(buf)
10}
11
12#[inline]
13fn read_u8<R: Read>(data: &mut R) -> std::io::Result<u8> {
14 let buf = read_n::<_, 1>(data)?;
15 Ok(u8::from_ne_bytes(buf))
16}
17
18#[inline]
19fn read_be_u32<R: Read>(data: &mut R) -> std::io::Result<u32> {
20 let buf = read_n::<_, 4>(data)?;
21 Ok(u32::from_be_bytes(buf))
22}
23
24#[inline]
27fn read_vec<R: Read>(data: &mut R, n: usize) -> std::io::Result<Vec<u8>> {
28 let mut buf = Vec::new();
29 data.take(n as u64).read_to_end(&mut buf)?;
30 if buf.len() != n {
31 return Err(std::io::Error::new(
32 std::io::ErrorKind::UnexpectedEof,
33 format!("expected {} bytes, got {}", n, buf.len()),
34 ));
35 }
36 Ok(buf)
37}
38
39#[derive(Clone, Debug)]
42pub struct Puppet {
43 pub meta: Meta,
45
46 pub physics: Physics,
48
49 pub nodes: Node,
51
52 pub params: HashMap<u32, Param>,
54
55 pub automation: Automation,
57
58 pub animations: HashMap<String, Animation>,
60
61 pub groups: Vec<Group>,
64
65 pub vendors: Vec<VendorData>,
67
68 pub textures: Vec<Texture>,
70}
71
72impl Puppet {
73 pub fn open<P>(path: P) -> std::io::Result<Self>
75 where
76 P: AsRef<std::path::Path>,
77 {
78 let mut file = std::fs::File::open(path)?;
79 Self::from_reader(&mut file)
80 }
81
82 pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
84 let mut cursor = std::io::Cursor::new(bytes);
85 Self::from_reader(&mut cursor)
86 }
87
88 fn from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
90 let magic = read_n::<_, 8>(reader)?;
91
92 if !magic.starts_with(b"TRNSRTS\0") {
93 return Err(std::io::Error::new(
94 std::io::ErrorKind::InvalidData,
95 "Invalid magic: expected TRNSRTS",
96 ));
97 }
98
99 let size = read_be_u32(reader)?;
100 let json_buffer = read_vec(reader, size as usize)?;
101
102 let json_data = std::str::from_utf8(&json_buffer)
103 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
104
105 let values = json::parse(json_data)
106 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
107
108 let mut puppet = Puppet::from_json(&values)?;
109
110 let tex_magic = read_n::<_, 8>(reader)?;
112 if !tex_magic.starts_with(b"TEX_SECT") {
113 return Err(std::io::Error::new(
114 std::io::ErrorKind::InvalidData,
115 "Invalid magic: expected TEX_SECT",
116 ));
117 }
118
119 let texture_count = read_be_u32(reader)?;
120
121 for id in 0..texture_count {
122 let tex_len = read_be_u32(reader)?;
123 let format_byte = read_u8(reader)?;
124 let format = TextureFormat::from_byte(format_byte).ok_or_else(|| {
125 std::io::Error::new(
126 std::io::ErrorKind::InvalidData,
127 format!("Invalid texture format: {}", format_byte),
128 )
129 })?;
130
131 let tex_data = read_vec(reader, tex_len as usize)?;
132 let (width, height) = format.get_img_dim(&tex_data)?;
133
134 puppet
135 .textures
136 .push(Texture::new(id, width, height, format, tex_data));
137 }
138
139 let ext_magic = match read_n::<_, 8>(reader) {
141 Ok(m) => m,
142 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
143 return Ok(puppet);
144 }
145 Err(e) => return Err(e),
146 };
147
148 if !ext_magic.starts_with(b"EXT_SECT") {
149 return Err(std::io::Error::new(
150 std::io::ErrorKind::InvalidData,
151 "Invalid magic: expected EXT_SECT",
152 ));
153 }
154
155 let ext_count = read_be_u32(reader)?;
156
157 for _ in 0..ext_count {
158 let name_len = read_be_u32(reader)?;
159 let name_bytes = read_vec(reader, name_len as usize)?;
160 let name = String::from_utf8_lossy(&name_bytes).into_owned();
161
162 let payload_len = read_be_u32(reader)?;
163 let payload_bytes = read_vec(reader, payload_len as usize)?;
164 let data = json::parse(&String::from_utf8_lossy(&payload_bytes))
165 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
166
167 puppet.vendors.push(VendorData { name, data });
168 }
169
170 Ok(puppet)
171 }
172
173 fn from_json(root: &json::JsonValue) -> std::io::Result<Self> {
174 let missing = |field: &str| {
175 std::io::Error::new(
176 std::io::ErrorKind::InvalidData,
177 format!("missing '{}' in puppet JSON", field),
178 )
179 };
180 let meta_v = root.get("meta").ok_or_else(|| missing("meta"))?;
181 let physics_v = root.get("physics").ok_or_else(|| missing("physics"))?;
182 let nodes_v = root.get("nodes").ok_or_else(|| missing("nodes"))?;
183
184 Ok(Self {
185 meta: Meta::from_json(meta_v),
186 physics: Physics::from_json(physics_v),
187 nodes: Node::from_json(nodes_v),
188 params: parse_params(root),
189 automation: Automation {},
190 animations: parse_animations(root),
191 groups: parse_groups(root),
192 textures: Vec::new(),
193 vendors: Vec::new(),
194 })
195 }
196}
197
198#[derive(Clone, Debug)]
200pub struct Meta {
201 pub name: Option<String>,
203
204 pub version: String,
206
207 pub rigger: Option<String>,
209
210 pub artist: Option<String>,
212
213 pub rights: Option<String>,
215
216 pub copyright: Option<String>,
218
219 pub license_url: Option<String>,
221
222 pub contact: Option<String>,
224
225 pub reference: Option<String>,
227
228 pub thumbnail_id: u32,
230
231 pub preserve_pixels: bool,
233}
234
235impl Meta {
236 fn from_json(v: &json::JsonValue) -> Self {
237 Self {
238 name: v.get_str("name").map(str::to_owned),
239 version: v.get_str("version").unwrap_or("1.0").to_owned(),
240 rigger: v.get_str("rigger").map(str::to_owned),
241 artist: v.get_str("artist").map(str::to_owned),
242 rights: v.get_str("rights").map(str::to_owned),
243 copyright: v.get_str("copyright").map(str::to_owned),
244 license_url: v.get_str("licenseURL").map(str::to_owned),
245 contact: v.get_str("contact").map(str::to_owned),
246 reference: v.get_str("reference").map(str::to_owned),
247 thumbnail_id: v.get_u32("thumbnailId").unwrap_or(u32::MAX),
248 preserve_pixels: v.get_bool("preservePixels", false),
249 }
250 }
251}
252
253#[derive(Clone, Debug)]
254pub struct Physics {
255 pub pixels_per_meter: f32,
256 pub gravity: f32,
257}
258
259impl Physics {
260 fn from_json(v: &json::JsonValue) -> Self {
261 Self {
262 pixels_per_meter: v.get_f32("pixelsPerMeter", 1000.0),
263 gravity: v.get_f32("gravity", 9.8),
264 }
265 }
266}
267
268#[derive(Clone, Debug, Default)]
271pub struct Node {
272 pub uuid: u32,
274
275 pub name: String,
277
278 pub type_node: NodeDataType,
280
281 pub enabled: bool,
283
284 pub zsort: f32,
287
288 pub transform: Transform,
290
291 pub lock_to_root: bool,
294
295 pub children: Vec<Node>,
297}
298
299impl Node {
300 pub fn iter(&self) -> NodeIter<'_> {
302 NodeIter { stack: vec![self] }
303 }
304
305 pub fn find_by_uuid(&self, uuid: u32) -> Option<&Node> {
307 self.iter().find(|n| n.uuid == uuid)
308 }
309
310 fn from_json(v: &json::JsonValue) -> Self {
311 let type_str = v.get_str("type").unwrap_or("generic");
312 Self {
313 uuid: v.get_u32("uuid").unwrap_or(u32::MAX),
314 name: v.get_str("name").unwrap_or("").to_owned(),
315 type_node: NodeDataType::from_json(type_str, v),
316 enabled: v.get_bool("enabled", true),
317 zsort: v.get_f32("zsort", 0.0),
318 transform: Transform::from_json(v),
319 lock_to_root: v.get_bool("lockToRoot", false),
320 children: v
321 .get_array("children")
322 .unwrap_or(&[])
323 .iter()
324 .map(Node::from_json)
325 .collect(),
326 }
327 }
328}
329
330pub struct NodeIter<'a> {
332 stack: Vec<&'a Node>,
333}
334
335impl<'a> Iterator for NodeIter<'a> {
336 type Item = &'a Node;
337
338 fn next(&mut self) -> Option<Self::Item> {
339 let node = self.stack.pop()?;
340 self.stack.extend(node.children.iter().rev());
341 Some(node)
342 }
343}
344
345#[derive(Debug, Clone, Default, Copy)]
348pub struct Transform {
349 pub translation: [f32; 3],
352
353 pub rotation: [f32; 3],
356
357 pub scale: [f32; 2],
360}
361
362impl Transform {
363 fn from_json(v: &json::JsonValue) -> Self {
364 match v.get("transform") {
365 Some(t) => Self {
366 translation: t.get_vec3("trans").unwrap_or_default(),
367 rotation: t.get_vec3("rot").unwrap_or_default(),
368 scale: t.get_vec2("scale").unwrap_or([1.0, 1.0]),
369 },
370 None => Self::default(),
371 }
372 }
373}
374
375#[derive(Clone, Debug, Default)]
377pub enum NodeDataType {
378 Part(PartData),
380
381 Camera(CameraData),
383
384 SimplePhysics(SimplePhysicsData),
386
387 Composite(CompositeData),
389
390 Mask(MaskData),
392
393 MeshGroup(MeshGroupData),
395
396 #[default]
398 Generic,
399}
400
401impl NodeDataType {
402 fn from_json(type_str: &str, v: &json::JsonValue) -> Self {
403 match type_str.to_ascii_lowercase().as_str() {
404 "part" => Self::Part(PartData::from_json(v)),
405 "camera" => Self::Camera(CameraData::from_json(v)),
406 "simplephysics" => Self::SimplePhysics(SimplePhysicsData::from_json(v)),
407 "composite" => Self::Composite(CompositeData::from_json(v)),
408 "mask" => Self::Mask(MaskData::from_json(v)),
409 "meshgroup" => Self::MeshGroup(MeshGroupData::from_json(v)),
410 _ => Self::Generic,
411 }
412 }
413
414 pub fn as_part(&self) -> Option<&PartData> {
415 match self {
416 Self::Part(d) => Some(d),
417 _ => None,
418 }
419 }
420 pub fn as_composite(&self) -> Option<&CompositeData> {
421 match self {
422 Self::Composite(d) => Some(d),
423 _ => None,
424 }
425 }
426 pub fn as_mask(&self) -> Option<&MaskData> {
427 match self {
428 Self::Mask(d) => Some(d),
429 _ => None,
430 }
431 }
432 pub fn as_mesh_group(&self) -> Option<&MeshGroupData> {
433 match self {
434 Self::MeshGroup(d) => Some(d),
435 _ => None,
436 }
437 }
438 pub fn as_camera(&self) -> Option<&CameraData> {
439 match self {
440 Self::Camera(d) => Some(d),
441 _ => None,
442 }
443 }
444 pub fn as_simple_physics(&self) -> Option<&SimplePhysicsData> {
445 match self {
446 Self::SimplePhysics(d) => Some(d),
447 _ => None,
448 }
449 }
450 pub fn is_generic(&self) -> bool {
451 matches!(self, Self::Generic)
452 }
453}
454
455#[derive(Debug, Default, Clone)]
458pub struct Mesh {
459 pub vertices: Vec<f32>,
462
463 pub indices: Vec<u32>,
466
467 pub uvs: Vec<f32>,
470
471 pub origin: [f32; 2],
474}
475
476fn parse_mesh(v: &json::JsonValue) -> Option<Mesh> {
477 let mesh = v.get("mesh")?;
478 let verts = mesh.get_array("verts")?;
479 if verts.is_empty() {
480 return None;
481 }
482 let indices = mesh.get_array("indices")?;
483 let uvs = mesh.get_array("uvs")?;
484 let origin = mesh.get_vec2("origin")?;
485
486 Some(Mesh {
487 vertices: verts.iter().map(|v| v.as_f32().unwrap_or(0.0)).collect(),
488 indices: indices.iter().map(|i| i.as_u32().unwrap_or(0)).collect(),
489 uvs: uvs.iter().map(|v| v.as_f32().unwrap_or(0.0)).collect(),
490 origin,
491 })
492}
493
494#[derive(Clone, Debug, Default)]
496pub struct MaskData {
497 pub mesh: Option<Mesh>,
499}
500
501impl MaskData {
502 fn from_json(v: &json::JsonValue) -> Self {
503 Self {
504 mesh: parse_mesh(v),
505 }
506 }
507}
508
509#[derive(Debug, Clone, Default)]
510pub struct Mask {
511 pub source: u32,
512 pub mode: MaskMode,
513}
514
515#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
517pub enum MaskMode {
518 #[default]
520 Mask,
521
522 Dodge,
524}
525
526fn parse_masks(v: &json::JsonValue) -> Vec<Mask> {
527 let Some(masks_val) = v.get("masks") else {
528 return Vec::new();
529 };
530 let arr = masks_val.as_array().unwrap_or(&[]);
531 let mut result = Vec::with_capacity(arr.len());
532 for m in arr {
533 let Some(obj) = m.as_object() else { continue };
534 let (Some(source), Some(mode)) = (obj.get("source"), obj.get("mode")) else {
535 continue;
536 };
537 result.push(Mask {
538 source: source.as_u32().unwrap_or(u32::MAX),
539 mode: match mode.as_str().unwrap_or("mask") {
540 v if v.eq_ignore_ascii_case("dodgemask") => MaskMode::Dodge,
541 _ => MaskMode::Mask,
542 },
543 });
544 }
545 result
546}
547
548#[derive(Clone, Debug, Default)]
550pub struct PartData {
551 pub mesh: Option<Mesh>,
553
554 pub textures: [u32; 3],
560
561 pub blend_mode: BlendMode,
563
564 pub tint: [f32; 3],
566
567 pub screen_tint: [f32; 3],
569
570 pub emission_strength: f32,
572
573 pub mask: Vec<Mask>,
575
576 pub mask_threshold: f32,
578
579 pub opacity: f32,
581
582 pub psd_layer_path: Option<String>,
584}
585
586impl PartData {
587 fn from_json(v: &json::JsonValue) -> Self {
588 let textures_arr = v.get_array("textures").unwrap_or(&[]);
589 Self {
590 mesh: parse_mesh(v),
591 textures: [
592 textures_arr
593 .first()
594 .and_then(|t| t.as_u32())
595 .unwrap_or(u32::MAX),
596 textures_arr
597 .get(1)
598 .and_then(|t| t.as_u32())
599 .unwrap_or(u32::MAX),
600 textures_arr
601 .get(2)
602 .and_then(|t| t.as_u32())
603 .unwrap_or(u32::MAX),
604 ],
605 blend_mode: BlendMode::from_str(v.get_str("blend_mode").unwrap_or("normal"))
606 .unwrap_or_default(),
607 tint: v.get_vec3("tint").unwrap_or([1.0, 1.0, 1.0]),
608 screen_tint: v.get_vec3("screenTint").unwrap_or([0.0, 0.0, 0.0]),
609 emission_strength: v.get_f32("emissionStrength", 0.0),
610 mask: parse_masks(v),
611 mask_threshold: v.get_f32("mask_threshold", 0.5),
612 opacity: v.get_f32("opacity", 1.0),
613 psd_layer_path: v.get_str("psdLayerPath").map(str::to_owned),
614 }
615 }
616}
617
618#[derive(Clone, Debug, Default)]
620pub struct CameraData {
621 pub viewport: [f32; 2],
624}
625
626impl CameraData {
627 fn from_json(v: &json::JsonValue) -> Self {
628 Self {
629 viewport: v.get_vec2("viewport").unwrap_or([1280.0, 720.0]),
630 }
631 }
632}
633
634#[derive(Clone, Debug, Default)]
637pub struct SimplePhysicsData {
638 pub param: u32,
640
641 pub model_type: PhysicsModelType,
643
644 pub map_mode: PhysicsMapMode,
646
647 pub gravity: f32,
649
650 pub length: f32,
652
653 pub frequency: f32,
655
656 pub angle_damping: f32,
658
659 pub length_damping: f32,
661
662 pub output_scale: [f32; 2],
664
665 pub local_only: Option<bool>,
667}
668
669impl SimplePhysicsData {
670 fn from_json(v: &json::JsonValue) -> Self {
671 Self {
672 param: v.get_u32("param").unwrap_or(u32::MAX),
673 model_type: match v.get_str("model_type") {
674 Some(s) if s.eq_ignore_ascii_case("springpendulum") => {
675 PhysicsModelType::SpringPendulum
676 }
677 _ => PhysicsModelType::Pendulum,
678 },
679 map_mode: match v.get_str("map_mode") {
680 Some(s) if s.eq_ignore_ascii_case("xy") => PhysicsMapMode::XY,
681 Some(s) if s.eq_ignore_ascii_case("lengthangle") => PhysicsMapMode::LengthAngle,
682 Some(s) if s.eq_ignore_ascii_case("yx") => PhysicsMapMode::YX,
683 _ => PhysicsMapMode::AngleLength,
684 },
685 gravity: v.get_f32("gravity", 1.0),
686 length: v.get_f32("length", 100.0),
687 frequency: v.get_f32("frequency", 1.0),
688 angle_damping: v.get_f32("angle_damping", 0.5),
689 length_damping: v.get_f32("length_damping", 0.5),
690 output_scale: v.get_vec2("output_scale").unwrap_or([1.0, 1.0]),
691 local_only: v.get_as_bool("local_only"),
692 }
693 }
694}
695
696#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
698pub enum PhysicsModelType {
699 #[default]
701 Pendulum,
702
703 SpringPendulum,
705}
706
707#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
709pub enum PhysicsMapMode {
710 #[default]
712 AngleLength,
713
714 XY,
716
717 LengthAngle,
719
720 YX,
722}
723
724#[derive(Clone, Debug, Default)]
726pub struct CompositeData {
727 pub blend_mode: BlendMode,
729
730 pub tint: [f32; 3],
732
733 pub screen_tint: [f32; 3],
735
736 pub opacity: f32,
738
739 pub mask: Vec<Mask>,
741
742 pub mask_threshold: f32,
744
745 pub propagate_meshgroup: Option<bool>,
747}
748
749impl CompositeData {
750 fn from_json(v: &json::JsonValue) -> Self {
751 Self {
752 blend_mode: BlendMode::from_str(v.get_str("blend_mode").unwrap_or("normal"))
753 .unwrap_or_default(),
754 tint: v.get_vec3("tint").unwrap_or([1.0, 1.0, 1.0]),
755 screen_tint: v.get_vec3("screenTint").unwrap_or([0.0, 0.0, 0.0]),
756 opacity: v.get_f32("opacity", 1.0),
757 mask: parse_masks(v),
758 mask_threshold: v.get_f32("mask_threshold", 0.5),
759 propagate_meshgroup: v.get_as_bool("propagate_meshgroup"),
760 }
761 }
762}
763
764#[derive(Clone, Debug, Default)]
766pub struct MeshGroupData {
767 pub mesh: Option<Mesh>,
769
770 pub dynamic_deformation: bool,
772
773 pub translate_children: bool,
775}
776
777impl MeshGroupData {
778 fn from_json(v: &json::JsonValue) -> Self {
779 Self {
780 mesh: parse_mesh(v),
781 dynamic_deformation: v.get_bool("dynamic_deformation", false),
782 translate_children: v.get_bool("translate_children", true),
783 }
784 }
785}
786
787#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
790pub enum BlendMode {
791 #[default]
793 Normal,
794
795 Multiply,
797
798 Screen,
800
801 Overlay,
803
804 Darken,
806
807 Lighten,
809
810 ColorDodge,
812
813 LinearDodge,
815
816 Add,
818
819 ColorBurn,
821
822 HardLight,
824
825 SoftLight,
827
828 Subtract,
830
831 Difference,
833
834 Exclusion,
836
837 Inverse,
839
840 DestinationIn,
842
843 ClipToLower,
845
846 SliceFromLower,
848}
849
850impl BlendMode {
851 #[allow(clippy::should_implement_trait)]
852 pub fn from_str(s: &str) -> Option<Self> {
853 match s {
854 s if s.eq_ignore_ascii_case("normal") => Some(Self::Normal),
855 s if s.eq_ignore_ascii_case("multiply") => Some(Self::Multiply),
856 s if s.eq_ignore_ascii_case("screen") => Some(Self::Screen),
857 s if s.eq_ignore_ascii_case("overlay") => Some(Self::Overlay),
858 s if s.eq_ignore_ascii_case("darken") => Some(Self::Darken),
859 s if s.eq_ignore_ascii_case("lighten") => Some(Self::Lighten),
860 s if s.eq_ignore_ascii_case("colordodge") => Some(Self::ColorDodge),
861 s if s.eq_ignore_ascii_case("colorburn") => Some(Self::ColorBurn),
862 s if s.eq_ignore_ascii_case("hardlight") => Some(Self::HardLight),
863 s if s.eq_ignore_ascii_case("softlight") => Some(Self::SoftLight),
864 s if s.eq_ignore_ascii_case("lineardodge") => Some(Self::LinearDodge),
865 s if s.eq_ignore_ascii_case("difference") => Some(Self::Difference),
866 s if s.eq_ignore_ascii_case("exclusion") => Some(Self::Exclusion),
867 s if s.eq_ignore_ascii_case("add") => Some(Self::Add),
868 s if s.eq_ignore_ascii_case("subtract") => Some(Self::Subtract),
869 s if s.eq_ignore_ascii_case("cliptolower") => Some(Self::ClipToLower),
870 s if s.eq_ignore_ascii_case("slicefromlower") => Some(Self::SliceFromLower),
871 s if s.eq_ignore_ascii_case("inverse") => Some(Self::Inverse),
872 s if s.eq_ignore_ascii_case("destinationin") => Some(Self::DestinationIn),
873 _ => None,
874 }
875 }
876}
877
878#[derive(Clone, Debug)]
879pub struct Param {
880 pub parent_uuid: Option<u32>,
882
883 pub uuid: u32,
885
886 pub name: String,
888
889 pub is_vec2: bool,
891
892 pub min: [f32; 2],
894
895 pub max: [f32; 2],
897
898 pub defaults: [f32; 2],
900
901 pub axis_points: [Vec<f32>; 2],
904
905 pub merge_mode: MergeMode,
907
908 pub bindings: Vec<ParamBinding>,
910}
911
912fn parse_params(root: &json::JsonValue) -> HashMap<u32, Param> {
913 let params = root.get_array("param").unwrap_or(&[]);
914 let mut map = HashMap::default();
915 for p in params {
916 let uuid = p.get_u32("uuid").unwrap_or(u32::MAX);
917 map.insert(uuid, Param::from_json(p));
918 }
919 map
920}
921
922impl Param {
923 fn from_json(v: &json::JsonValue) -> Self {
924 Self {
925 parent_uuid: v.get_u32("parentUUID"),
926 uuid: v.get_u32("uuid").unwrap_or(u32::MAX),
927 name: v.get_str("name").unwrap_or("").to_owned(),
928 is_vec2: v.get_bool("is_vec2", false),
929 min: v.get_vec2("min").unwrap_or([0.0, 0.0]),
930 max: v.get_vec2("max").unwrap_or([1.0, 1.0]),
931 defaults: v.get_vec2("defaults").unwrap_or([0.0, 1.0]),
932 axis_points: parse_axis_points(v),
933 merge_mode: parse_merge_mode(v.get_str("merge_mode")),
934 bindings: parse_bindings(v),
935 }
936 }
937}
938
939fn parse_axis_points(v: &json::JsonValue) -> [Vec<f32>; 2] {
940 let axis = v.get_array("axis_points").unwrap_or(&[]);
941 if axis.len() != 2 {
942 return [Vec::new(), Vec::new()];
943 }
944 let parse_axis = |a: &json::JsonValue| -> Vec<f32> {
945 a.as_array()
946 .unwrap_or(&[])
947 .iter()
948 .map(|v| v.as_f32().unwrap_or(0.0))
949 .collect()
950 };
951 [parse_axis(&axis[0]), parse_axis(&axis[1])]
952}
953
954#[derive(Debug, Clone)]
957pub struct ParamBinding {
958 pub node: u32,
960
961 pub param_name: ParamName,
963
964 pub values: BindingValues,
967
968 pub is_set: Vec<Vec<bool>>,
971
972 pub interpolate_mode: Interpolation,
974}
975
976fn parse_bindings(v: &json::JsonValue) -> Vec<ParamBinding> {
977 let bindings = v.get_array("bindings").unwrap_or(&[]);
978 bindings.iter().map(ParamBinding::from_json).collect()
979}
980
981impl ParamBinding {
982 fn from_json(v: &json::JsonValue) -> Self {
983 let param_name = parse_param_name(v.get_str("param_name"));
984 let values_v = v.get("values");
985 Self {
986 node: v.get_u32("node").unwrap_or(u32::MAX),
987 values: match values_v {
988 Some(vals) => match ¶m_name {
989 ParamName::Deform => BindingValues::Deform(FlatDeformValues::new(vals)),
990 ParamName::Other(_) => BindingValues::Other(vals.clone()),
991 _ => BindingValues::Transform(FlatTransformValues::new(vals)),
992 },
993 None => BindingValues::Transform(FlatTransformValues {
994 data: Vec::new(),
995 frames: 0,
996 values_per_frame: 0,
997 }),
998 },
999 param_name,
1000 is_set: parse_is_set(v),
1001 interpolate_mode: parse_interpolation(v.get_str("interpolate_mode")),
1002 }
1003 }
1004}
1005
1006fn parse_is_set(v: &json::JsonValue) -> Vec<Vec<bool>> {
1007 let is_set = v.get_array("isSet").unwrap_or(&[]);
1008 is_set
1009 .iter()
1010 .map(|row| {
1011 row.as_array()
1012 .unwrap_or(&[])
1013 .iter()
1014 .map(|b| b.as_bool().unwrap_or(false))
1015 .collect()
1016 })
1017 .collect()
1018}
1019
1020#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
1022pub enum ParamName {
1023 TransformTX,
1025
1026 TransformTY,
1028
1029 TransformTZ,
1031
1032 TransformSX,
1034
1035 TransformSY,
1037
1038 TransformRX,
1040
1041 TransformRY,
1043
1044 TransformRZ,
1046
1047 Deform,
1049
1050 #[default]
1051 Opacity,
1053
1054 Other(String),
1056}
1057
1058fn parse_param_name(s: Option<&str>) -> ParamName {
1059 match s {
1060 Some("transform.t.x") => ParamName::TransformTX,
1061 Some("transform.t.y") => ParamName::TransformTY,
1062 Some("transform.t.z") => ParamName::TransformTZ,
1063 Some("transform.s.x") => ParamName::TransformSX,
1064 Some("transform.s.y") => ParamName::TransformSY,
1065 Some("transform.r.x") => ParamName::TransformRX,
1066 Some("transform.r.y") => ParamName::TransformRY,
1067 Some("transform.r.z") => ParamName::TransformRZ,
1068 Some("deform") => ParamName::Deform,
1069 Some("opacity") => ParamName::Opacity,
1070 Some(other) => ParamName::Other(other.to_owned()),
1071 None => ParamName::Other(String::new()),
1072 }
1073}
1074
1075#[derive(Debug, Clone)]
1078pub enum BindingValues {
1079 Transform(FlatTransformValues),
1081
1082 Deform(FlatDeformValues),
1084
1085 Other(json::JsonValue),
1087}
1088
1089#[derive(Debug, Clone)]
1094pub struct FlatTransformValues {
1095 pub data: Vec<f32>,
1097
1098 pub frames: usize,
1100
1101 pub values_per_frame: usize,
1103}
1104
1105impl FlatTransformValues {
1106 pub fn new(values: &json::JsonValue) -> Self {
1107 let parsed: Vec<Vec<f32>> = values
1108 .as_array()
1109 .unwrap_or(&[])
1110 .iter()
1111 .filter_map(|frame| {
1112 frame
1113 .as_array()
1114 .map(|f| f.iter().filter_map(|v| v.as_f32()).collect())
1115 })
1116 .collect();
1117
1118 if parsed.is_empty() {
1119 return Self {
1120 data: Vec::new(),
1121 frames: 0,
1122 values_per_frame: 0,
1123 };
1124 }
1125
1126 let values_per_frame = parsed[0].len();
1127
1128 debug_assert!(
1129 parsed.iter().all(|v| v.len() == values_per_frame),
1130 "Inconsistent values per frame"
1131 );
1132
1133 let frames = parsed.len();
1134 let data = parsed.into_iter().flatten().collect();
1135 Self {
1136 data,
1137 frames,
1138 values_per_frame,
1139 }
1140 }
1141 pub fn get(&self, frame: usize, index: usize) -> Option<f32> {
1144 if frame >= self.frames || index >= self.values_per_frame {
1145 return None;
1146 }
1147 self.data
1148 .get(frame * self.values_per_frame + index)
1149 .copied()
1150 }
1151
1152 pub fn frames(&self) -> usize {
1154 self.frames
1155 }
1156
1157 pub fn values_per_frame(&self) -> usize {
1159 self.values_per_frame
1160 }
1161}
1162
1163#[derive(Debug, Clone)]
1168pub struct FlatDeformValues {
1169 pub data: Vec<[f32; 2]>,
1171
1172 pub frames: usize,
1174
1175 pub vertices_per_frame: usize,
1178}
1179
1180impl FlatDeformValues {
1181 pub fn new(values: &json::JsonValue) -> Self {
1182 let frames_data: Vec<Vec<[f32; 2]>> = values
1183 .as_array()
1184 .unwrap_or(&[])
1185 .iter()
1186 .map(|frame| {
1187 frame
1188 .as_array()
1189 .unwrap_or(&[])
1190 .iter()
1191 .flat_map(|vertex| {
1192 vertex
1193 .as_array()
1194 .unwrap_or(&[])
1195 .iter()
1196 .filter_map(|coords| {
1197 let pair = coords.as_array()?;
1198 Some([pair.first()?.as_f32()?, pair.get(1)?.as_f32()?])
1199 })
1200 })
1201 .collect()
1202 })
1203 .collect();
1204
1205 if frames_data.is_empty() {
1206 return Self {
1207 data: Vec::new(),
1208 frames: 0,
1209 vertices_per_frame: 0,
1210 };
1211 }
1212
1213 let frames = frames_data.len();
1214 let vertices_per_frame = frames_data[0].len();
1215
1216 debug_assert!(
1217 frames_data.iter().all(|f| f.len() == vertices_per_frame),
1218 "Inconsistent vertices per frame"
1219 );
1220
1221 let data = frames_data.into_iter().flatten().collect();
1222 Self {
1223 data,
1224 frames,
1225 vertices_per_frame,
1226 }
1227 }
1228 pub fn get(&self, frame: usize, vertex: usize) -> Option<[f32; 2]> {
1231 if frame >= self.frames || vertex >= self.vertices_per_frame {
1232 return None;
1233 }
1234 let idx = frame * self.vertices_per_frame + vertex;
1235 self.data.get(idx).copied()
1236 }
1237
1238 pub fn frames(&self) -> usize {
1240 self.frames
1241 }
1242
1243 pub fn vertices_per_frame(&self) -> usize {
1245 self.vertices_per_frame
1246 }
1247
1248 pub fn get_frame(&self, frame: usize) -> Option<&[[f32; 2]]> {
1251 if frame >= self.frames {
1252 return None;
1253 }
1254 let start = frame * self.vertices_per_frame;
1255 self.data.get(start..start + self.vertices_per_frame)
1256 }
1257}
1258
1259#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1260pub enum MergeMode {
1261 Additive,
1263
1264 Multiplicative,
1266
1267 Override,
1269
1270 Forced,
1272}
1273
1274fn parse_merge_mode(s: Option<&str>) -> MergeMode {
1275 match s {
1276 Some(s) if s.eq_ignore_ascii_case("multiply") => MergeMode::Multiplicative,
1277 Some(s) if s.eq_ignore_ascii_case("override") => MergeMode::Override,
1278 Some(s) if s.eq_ignore_ascii_case("forced") => MergeMode::Forced,
1279 _ => MergeMode::Additive,
1280 }
1281}
1282
1283#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1285pub enum Interpolation {
1286 #[default]
1288 Linear,
1289
1290 Stepped,
1292
1293 Nearest,
1295
1296 Cubic,
1298}
1299
1300fn parse_interpolation(s: Option<&str>) -> Interpolation {
1301 match s {
1302 Some(s) if s.eq_ignore_ascii_case("stepped") => Interpolation::Stepped,
1303 Some(s) if s.eq_ignore_ascii_case("nearest") => Interpolation::Nearest,
1304 Some(s) if s.eq_ignore_ascii_case("cubic") => Interpolation::Cubic,
1305 Some(s) if s.eq_ignore_ascii_case("linear") => Interpolation::Linear,
1306 _ => Interpolation::Linear,
1307 }
1308}
1309
1310#[derive(Clone, Debug)]
1313pub struct Automation {}
1314
1315#[derive(Debug, Clone)]
1318pub struct Animation {
1319 pub name: String,
1321
1322 pub timestep: f32,
1324
1325 pub additive: bool,
1327
1328 pub length: u32,
1330
1331 pub lead_in: u32,
1333
1334 pub lead_out: u32,
1336
1337 pub weight: f32,
1339
1340 pub lanes: Vec<AnimationLane>,
1342}
1343
1344impl Animation {
1345 #[inline]
1347 pub fn duration(&self) -> f32 {
1348 self.length as f32 * self.timestep
1349 }
1350
1351 #[inline]
1353 pub fn time_to_frame(&self, time: f32) -> f32 {
1354 time / self.timestep
1355 }
1356
1357 #[inline]
1359 pub fn frame_to_time(&self, frame: f32) -> f32 {
1360 frame * self.timestep
1361 }
1362}
1363
1364fn parse_animations(root: &json::JsonValue) -> HashMap<String, Animation> {
1365 let Some(anims) = root.get("animations") else {
1366 return HashMap::default();
1367 };
1368 let Some(obj) = anims.as_object() else {
1369 return HashMap::default();
1370 };
1371
1372 let mut map = HashMap::default();
1373 for (name, data) in obj.iter() {
1374 map.insert(
1375 name.to_owned(),
1376 Animation {
1377 name: name.to_owned(),
1378 timestep: data.get_f32("timestep", 0.016666668),
1379 additive: data.get_bool("additive", false),
1380 length: data.get_u32("length").unwrap_or(0),
1381 lead_in: data.get_u32("leadIn").unwrap_or(0),
1382 lead_out: data.get_u32("leadOut").unwrap_or(0),
1383 weight: data.get_f32("animationWeight", 1.0),
1384 lanes: parse_lanes(data),
1385 },
1386 );
1387 }
1388 map
1389}
1390
1391#[derive(Debug, Clone)]
1393pub struct AnimationLane {
1394 pub interpolation: Interpolation,
1396
1397 pub param_uuid: u32,
1399
1400 pub target: u8,
1402
1403 pub merge_mode: MergeMode,
1405
1406 pub keyframes: Vec<Keyframe>,
1408}
1409
1410impl AnimationLane {
1411 pub fn evaluate(&self, frame: f32) -> f32 {
1413 if self.keyframes.is_empty() {
1414 return 0.0;
1415 }
1416
1417 if frame <= self.keyframes[0].frame as f32 {
1419 return self.keyframes[0].value;
1420 }
1421
1422 let last = &self.keyframes[self.keyframes.len() - 1];
1424 if frame >= last.frame as f32 {
1425 return last.value;
1426 }
1427
1428 let mut prev_idx = 0;
1430 for (i, kf) in self.keyframes.iter().enumerate() {
1431 if kf.frame as f32 > frame {
1432 break;
1433 }
1434 prev_idx = i;
1435 }
1436
1437 let prev = &self.keyframes[prev_idx];
1438 let next = &self.keyframes[prev_idx + 1];
1439
1440 let t = (frame - prev.frame as f32) / (next.frame as f32 - prev.frame as f32);
1441
1442 match self.interpolation {
1443 Interpolation::Stepped => prev.value,
1444 Interpolation::Nearest => {
1445 if t < 0.5 {
1446 prev.value
1447 } else {
1448 next.value
1449 }
1450 }
1451 Interpolation::Linear => lerp(prev.value, next.value, t),
1452 Interpolation::Cubic => {
1453 let tension = (prev.tension + next.tension) * 0.5;
1455 cubic_interpolate(prev.value, next.value, t, tension)
1456 }
1457 }
1458 }
1459}
1460
1461fn parse_lanes(v: &json::JsonValue) -> Vec<AnimationLane> {
1462 let lanes = v.get_array("lanes").unwrap_or(&[]);
1463 lanes
1464 .iter()
1465 .map(|lane| AnimationLane {
1466 interpolation: parse_interpolation(lane.get_str("interpolation")),
1467 param_uuid: lane.get_u32("uuid").unwrap_or(u32::MAX),
1468 target: lane.get_u32("target").unwrap_or(0) as u8,
1469 merge_mode: parse_merge_mode(lane.get_str("merge_mode")),
1470 keyframes: parse_keyframes(lane),
1471 })
1472 .collect()
1473}
1474
1475#[derive(Debug, Clone, Copy)]
1477pub struct Keyframe {
1478 pub frame: u32,
1480
1481 pub value: f32,
1483
1484 pub tension: f32,
1486}
1487
1488fn parse_keyframes(v: &json::JsonValue) -> Vec<Keyframe> {
1489 let kfs = v.get_array("keyframes").unwrap_or(&[]);
1490 kfs.iter()
1491 .map(|kf| Keyframe {
1492 frame: kf.get_u32("frame").unwrap_or(0),
1493 value: kf.get_f32("value", 0.0),
1494 tension: kf.get_f32("tension", 0.5),
1495 })
1496 .collect()
1497}
1498
1499#[derive(Clone, Debug)]
1502pub struct Group {
1503 pub group_uuid: u32,
1505
1506 pub name: String,
1508
1509 pub color: [f32; 3],
1511}
1512
1513fn parse_groups(root: &json::JsonValue) -> Vec<Group> {
1514 let groups = root.get_array("groups").unwrap_or(&[]);
1515 groups
1516 .iter()
1517 .map(|g| Group {
1518 group_uuid: g.get_u32("groupUUID").unwrap_or(u32::MAX),
1519 name: g.get_str("name").unwrap_or("").to_owned(),
1520 color: g.get_vec3("color").unwrap_or([0.0, 0.0, 0.0]),
1521 })
1522 .collect()
1523}
1524
1525#[derive(Debug, Clone)]
1527pub struct VendorData {
1528 pub name: String,
1530 pub data: json::JsonValue,
1532}
1533
1534#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
1536#[repr(u8)]
1537pub enum TextureFormat {
1538 #[default]
1540 Png = 0,
1541 Tga = 1,
1543 Bc7 = 2,
1545}
1546
1547impl TextureFormat {
1548 pub fn from_byte(b: u8) -> Option<Self> {
1550 match b {
1551 0 => Some(Self::Png),
1552 1 => Some(Self::Tga),
1553 2 => Some(Self::Bc7),
1554 _ => None,
1555 }
1556 }
1557
1558 pub fn extension(&self) -> &'static str {
1560 match self {
1561 Self::Png => "png",
1562 Self::Tga => "tga",
1563 Self::Bc7 => "bc7",
1564 }
1565 }
1566
1567 pub fn get_img_dim(&self, data: &[u8]) -> std::io::Result<(u32, u32)> {
1570 match self {
1571 TextureFormat::Png => {
1572 if data.len() < 24 {
1575 return Err(std::io::Error::new(
1576 std::io::ErrorKind::InvalidData,
1577 "Invalid PNG data (too short)",
1578 ));
1579 }
1580
1581 let width = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
1582 let height = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
1583
1584 Ok((width, height))
1585 }
1586
1587 TextureFormat::Tga => {
1588 if data.len() < 18 {
1591 return Err(std::io::Error::new(
1592 std::io::ErrorKind::InvalidData,
1593 "Invalid TGA data (too short)",
1594 ));
1595 }
1596
1597 let width = u16::from_le_bytes([data[12], data[13]]) as u32;
1598 let height = u16::from_le_bytes([data[14], data[15]]) as u32;
1599
1600 Ok((width, height))
1601 }
1602
1603 TextureFormat::Bc7 => {
1604 Ok((0, 0))
1608 }
1609 }
1610 }
1611}
1612
1613#[derive(Debug, Clone)]
1615pub enum TextureData {
1616 Encoded(Vec<u8>),
1618 Rgba(Vec<u8>),
1620}
1621
1622#[derive(Debug, Clone)]
1624pub struct Texture {
1625 pub id: u32,
1627 pub width: u32,
1629 pub height: u32,
1631 pub format: TextureFormat,
1633 pub data: TextureData,
1635}
1636
1637impl Texture {
1638 pub fn new(id: u32, width: u32, height: u32, format: TextureFormat, data: Vec<u8>) -> Self {
1640 Self {
1641 id,
1642 width,
1643 height,
1644 format,
1645 data: TextureData::Encoded(data),
1646 }
1647 }
1648
1649 pub fn dimensions_from_data(&self) -> std::io::Result<(u32, u32)> {
1652 match &self.data {
1653 TextureData::Encoded(bytes) => self.format.get_img_dim(bytes),
1654 TextureData::Rgba(_) => {
1655 Ok((self.width, self.height))
1658 }
1659 }
1660 }
1661}
1662
1663#[inline]
1664fn lerp(a: f32, b: f32, t: f32) -> f32 {
1665 a + (b - a) * t
1666}
1667
1668#[inline]
1669fn cubic_interpolate(a: f32, b: f32, t: f32, tension: f32) -> f32 {
1670 let t2 = t * t;
1672 let t3 = t2 * t;
1673
1674 let m = (1.0 - tension) * (b - a);
1676
1677 let h1 = 2.0 * t3 - 3.0 * t2 + 1.0;
1678 let h2 = t3 - 2.0 * t2 + t;
1679 let h3 = -2.0 * t3 + 3.0 * t2;
1680 let h4 = t3 - t2;
1681
1682 h1 * a + h2 * m + h3 * b + h4 * m
1683}