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]
25fn read_vec<R: Read>(data: &mut R, n: usize) -> std::io::Result<Vec<u8>> {
26 let mut buf = vec![0_u8; n];
27 data.read_exact(&mut buf)?;
28 Ok(buf)
29}
30
31#[derive(Debug)]
34pub struct Puppet {
35 pub meta: Meta,
37
38 pub physics: Physics,
40
41 pub nodes: Node,
43
44 pub params: HashMap<u32, Param>,
46
47 pub automation: Automation,
49 pub animations: HashMap<String, Animation>,
52
53 pub groups: Vec<Group>,
56
57 pub vendors: Vec<VendorData>,
59
60 pub textures: Vec<Texture>,
62}
63
64impl Puppet {
65 pub fn open<P>(path: P) -> std::io::Result<Self>
67 where
68 P: AsRef<std::path::Path>,
69 {
70 let mut file = std::fs::File::open(path)?;
71 Self::from_reader(&mut file)
72 }
73
74 pub fn from_bytes(bytes: &[u8]) -> std::io::Result<Self> {
76 let mut cursor = std::io::Cursor::new(bytes);
77 Self::from_reader(&mut cursor)
78 }
79
80 fn from_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
82 let magic = read_n::<_, 8>(reader)?;
83
84 if !magic.starts_with(b"TRNSRTS\0") {
85 return Err(std::io::Error::new(
86 std::io::ErrorKind::InvalidData,
87 "Invalid magic: expected TRNSRTS",
88 ));
89 }
90
91 let size = read_be_u32(reader)?;
92 let json_buffer = read_vec(reader, size as usize)?;
93
94 let json_data = std::str::from_utf8(&json_buffer)
95 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
96
97 let values = json::parse(json_data)
98 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
99
100 let mut puppet = Puppet::from_json(&values);
101
102 let tex_magic = read_n::<_, 8>(reader)?;
104 if !tex_magic.starts_with(b"TEX_SECT") {
105 return Err(std::io::Error::new(
106 std::io::ErrorKind::InvalidData,
107 "Invalid magic: expected TEX_SECT",
108 ));
109 }
110
111 let texture_count = read_be_u32(reader)?;
112
113 for id in 0..texture_count {
114 let tex_len = read_be_u32(reader)?;
115 let format_byte = read_u8(reader)?;
116 let format = TextureFormat::from_byte(format_byte).ok_or_else(|| {
117 std::io::Error::new(
118 std::io::ErrorKind::InvalidData,
119 format!("Invalid texture format: {}", format_byte),
120 )
121 })?;
122
123 let tex_data = read_vec(reader, tex_len as usize)?;
124 let (width, height) = format.get_img_dim(&tex_data)?;
125
126 puppet
127 .textures
128 .push(Texture::new(id, width, height, format, tex_data));
129 }
130
131 let ext_magic = match read_n::<_, 8>(reader) {
133 Ok(m) => m,
134 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
135 return Ok(puppet);
136 }
137 Err(e) => return Err(e),
138 };
139
140 if !ext_magic.starts_with(b"EXT_SECT") {
141 return Err(std::io::Error::new(
142 std::io::ErrorKind::InvalidData,
143 "Invalid magic: expected EXT_SECT",
144 ));
145 }
146
147 let ext_count = read_be_u32(reader)?;
148
149 for _ in 0..ext_count {
150 let name_len = read_be_u32(reader)?;
151 let name_bytes = read_vec(reader, name_len as usize)?;
152 let name = String::from_utf8_lossy(&name_bytes).into_owned();
153
154 let payload_len = read_be_u32(reader)?;
155 let payload_bytes = read_vec(reader, payload_len as usize)?;
156 let data = json::parse(&String::from_utf8_lossy(&payload_bytes))
157 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
158
159 puppet.vendors.push(VendorData { name, data });
160 }
161
162 Ok(puppet)
163 }
164
165 fn from_json(root: &json::JsonValue) -> Self {
166 let meta_v = root.get("meta").expect("missing 'meta'");
167 let physics_v = root.get("physics").expect("missing 'physics'");
168 let nodes_v = root.get("nodes").expect("missing 'nodes'");
169
170 Self {
171 meta: Meta::from_json(meta_v),
172 physics: Physics::from_json(physics_v),
173 nodes: Node::from_json(nodes_v),
174 params: parse_params(root),
175 automation: Automation {},
176 animations: parse_animations(root),
177 groups: parse_groups(root),
178 textures: Vec::new(),
179 vendors: Vec::new(),
180 }
181 }
182}
183
184#[derive(Debug)]
186pub struct Meta {
187 pub name: Option<String>,
189
190 pub version: String,
192
193 pub rigger: Option<String>,
195
196 pub artist: Option<String>,
198
199 pub rights: Option<String>,
201
202 pub copyright: Option<String>,
204
205 pub license_url: Option<String>,
207
208 pub contact: Option<String>,
210
211 pub reference: Option<String>,
213
214 pub thumbnail_id: u32,
216
217 pub preserve_pixels: bool,
219}
220
221impl Meta {
222 fn from_json(v: &json::JsonValue) -> Self {
223 Self {
224 name: v.get_str("name").map(str::to_owned),
225 version: v.get_str("version").unwrap_or("1.0").to_owned(),
226 rigger: v.get_str("rigger").map(str::to_owned),
227 artist: v.get_str("artist").map(str::to_owned),
228 rights: v.get_str("rights").map(str::to_owned),
229 copyright: v.get_str("copyright").map(str::to_owned),
230 license_url: v.get_str("licenseURL").map(str::to_owned),
231 contact: v.get_str("contact").map(str::to_owned),
232 reference: v.get_str("reference").map(str::to_owned),
233 thumbnail_id: v.get_u32("thumbnailId").unwrap_or(u32::MAX),
234 preserve_pixels: v.get_bool("preservePixels", false),
235 }
236 }
237}
238
239#[derive(Debug)]
240pub struct Physics {
241 pub pixels_per_meter: f32,
242 pub gravity: f32,
243}
244
245impl Physics {
246 fn from_json(v: &json::JsonValue) -> Self {
247 Self {
248 pixels_per_meter: v.get_f32("pixelsPerMeter", 1000.0),
249 gravity: v.get_f32("gravity", 9.8),
250 }
251 }
252}
253
254#[derive(Debug, Default)]
257pub struct Node {
258 pub uuid: u32,
260
261 pub name: String,
263
264 pub type_node: NodeDataType,
266
267 pub enabled: bool,
269
270 pub zsort: f32,
273
274 pub transform: Transform,
276
277 pub lock_to_root: bool,
280
281 pub children: Vec<Node>,
283}
284
285impl Node {
286 fn from_json(v: &json::JsonValue) -> Self {
287 let type_str = v.get_str("type").unwrap_or("generic");
288 Self {
289 uuid: v.get_u32("uuid").unwrap_or(u32::MAX),
290 name: v.get_str("name").unwrap_or("").to_owned(),
291 type_node: NodeDataType::from_json(type_str, v),
292 enabled: v.get_bool("enabled", true),
293 zsort: v.get_f32("zsort", 0.0),
294 transform: Transform::from_json(v),
295 lock_to_root: v.get_bool("lockToRoot", false),
296 children: v
297 .get_array("children")
298 .unwrap_or(&[])
299 .iter()
300 .map(Node::from_json)
301 .collect(),
302 }
303 }
304}
305
306#[derive(Debug, Clone, Default, Copy)]
309pub struct Transform {
310 pub translation: [f32; 3],
313
314 pub rotation: [f32; 3],
317
318 pub scale: [f32; 2],
321}
322
323impl Transform {
324 fn from_json(v: &json::JsonValue) -> Self {
325 match v.get("transform") {
326 Some(t) => Self {
327 translation: t.get_vec3("trans").unwrap_or_default(),
328 rotation: t.get_vec3("rot").unwrap_or_default(),
329 scale: t.get_vec2("scale").unwrap_or([1.0, 1.0]),
330 },
331 None => Self::default(),
332 }
333 }
334}
335
336#[derive(Debug, Default)]
338pub enum NodeDataType {
339 Part(PartData),
341
342 Camera(CameraData),
344
345 SimplePhysics(SimplePhysicsData),
347
348 Composite(CompositeData),
350
351 Mask(MaskData),
353
354 MeshGroup(MeshGroupData),
356
357 #[default]
359 Generic,
360}
361
362impl NodeDataType {
363 fn from_json(type_str: &str, v: &json::JsonValue) -> Self {
364 match type_str.to_ascii_lowercase().as_str() {
365 "part" => Self::Part(PartData::from_json(v)),
366 "camera" => Self::Camera(CameraData::from_json(v)),
367 "simplephysics" => Self::SimplePhysics(SimplePhysicsData::from_json(v)),
368 "composite" => Self::Composite(CompositeData::from_json(v)),
369 "mask" => Self::Mask(MaskData::from_json(v)),
370 "meshgroup" => Self::MeshGroup(MeshGroupData::from_json(v)),
371 _ => Self::Generic,
372 }
373 }
374
375 pub fn as_part(&self) -> Option<&PartData> {
376 match self {
377 Self::Part(d) => Some(d),
378 _ => None,
379 }
380 }
381 pub fn as_composite(&self) -> Option<&CompositeData> {
382 match self {
383 Self::Composite(d) => Some(d),
384 _ => None,
385 }
386 }
387 pub fn as_mask(&self) -> Option<&MaskData> {
388 match self {
389 Self::Mask(d) => Some(d),
390 _ => None,
391 }
392 }
393 pub fn as_mesh_group(&self) -> Option<&MeshGroupData> {
394 match self {
395 Self::MeshGroup(d) => Some(d),
396 _ => None,
397 }
398 }
399 pub fn as_camera(&self) -> Option<&CameraData> {
400 match self {
401 Self::Camera(d) => Some(d),
402 _ => None,
403 }
404 }
405 pub fn as_simple_physics(&self) -> Option<&SimplePhysicsData> {
406 match self {
407 Self::SimplePhysics(d) => Some(d),
408 _ => None,
409 }
410 }
411 pub fn is_generic(&self) -> bool {
412 matches!(self, Self::Generic)
413 }
414}
415
416#[derive(Debug, Default, Clone)]
419pub struct Mesh {
420 pub vertices: Vec<f32>,
423
424 pub indices: Vec<u32>,
427
428 pub uvs: Vec<f32>,
431
432 pub origin: [f32; 2],
435}
436
437fn parse_mesh(v: &json::JsonValue) -> Option<Mesh> {
438 let mesh = v.get("mesh")?;
439 let verts = mesh.get_array("verts")?;
440 if verts.is_empty() {
441 return None;
442 }
443 let indices = mesh.get_array("indices")?;
444 let uvs = mesh.get_array("uvs")?;
445 let origin = mesh.get_vec2("origin")?;
446
447 Some(Mesh {
448 vertices: verts.iter().map(|v| v.as_f32().unwrap_or(0.0)).collect(),
449 indices: indices.iter().map(|i| i.as_u32().unwrap_or(0)).collect(),
450 uvs: uvs.iter().map(|v| v.as_f32().unwrap_or(0.0)).collect(),
451 origin,
452 })
453}
454
455#[derive(Debug, Default)]
457pub struct MaskData {
458 pub mesh: Option<Mesh>,
460 }
462
463impl MaskData {
464 fn from_json(v: &json::JsonValue) -> Self {
465 Self {
466 mesh: parse_mesh(v),
467 }
468 }
469}
470
471#[derive(Debug, Clone, Default)]
472pub struct Mask {
473 pub source: u32,
474 pub mode: MaskMode,
475}
476
477#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
479pub enum MaskMode {
480 #[default]
482 Mask,
483
484 Dodge,
486}
487
488fn parse_masks(v: &json::JsonValue) -> Vec<Mask> {
489 let Some(masks_val) = v.get("masks") else {
490 return Vec::new();
491 };
492 let arr = masks_val.as_array().unwrap_or(&[]);
493 let mut result = Vec::with_capacity(arr.len());
494 for m in arr {
495 let Some(obj) = m.as_object() else { continue };
496 let (Some(source), Some(mode)) = (obj.get("source"), obj.get("mode")) else {
497 continue;
498 };
499 result.push(Mask {
500 source: source.as_u32().unwrap_or(u32::MAX),
501 mode: match mode.as_str().unwrap_or("mask") {
502 v if v.eq_ignore_ascii_case("dodgemask") => MaskMode::Dodge,
503 _ => MaskMode::Mask,
504 },
505 });
506 }
507 result
508}
509
510#[derive(Debug, Default)]
512pub struct PartData {
513 pub mesh: Option<Mesh>,
515
516 pub textures: [u32; 3],
523
524 pub blend_mode: BlendMode,
526
527 pub tint: [f32; 3],
529
530 pub screen_tint: [f32; 3],
532
533 pub emission_strength: f32,
535
536 pub mask: Vec<Mask>,
538
539 pub mask_threshold: f32,
541
542 pub opacity: f32,
544
545 pub psd_layer_path: Option<String>,
547}
548
549impl PartData {
550 fn from_json(v: &json::JsonValue) -> Self {
551 let textures_arr = v.get_array("textures").unwrap_or(&[]);
552 Self {
553 mesh: parse_mesh(v),
554 textures: [
555 textures_arr
556 .get(0)
557 .and_then(|t| t.as_u32())
558 .unwrap_or(u32::MAX),
559 textures_arr
560 .get(1)
561 .and_then(|t| t.as_u32())
562 .unwrap_or(u32::MAX),
563 textures_arr
564 .get(2)
565 .and_then(|t| t.as_u32())
566 .unwrap_or(u32::MAX),
567 ],
568 blend_mode: BlendMode::from_str(v.get_str("blend_mode").unwrap_or("normal"))
569 .unwrap_or_default(),
570 tint: v.get_vec3("tint").unwrap_or([1.0, 1.0, 1.0]),
571 screen_tint: v.get_vec3("screenTint").unwrap_or([0.0, 0.0, 0.0]),
572 emission_strength: v.get_f32("emissionStrength", 0.0),
573 mask: parse_masks(v),
574 mask_threshold: v.get_f32("mask_threshold", 0.5),
575 opacity: v.get_f32("opacity", 1.0),
576 psd_layer_path: v.get_str("psdLayerPath").map(str::to_owned),
577 }
578 }
579}
580
581#[derive(Debug, Default)]
583pub struct CameraData {
584 pub viewport: [f32; 2],
587}
588
589impl CameraData {
590 fn from_json(v: &json::JsonValue) -> Self {
591 Self {
592 viewport: v.get_vec2("viewport").unwrap_or([1280.0, 720.0]),
593 }
594 }
595}
596
597#[derive(Debug, Default)]
600pub struct SimplePhysicsData {
601 pub param: u32,
603
604 pub model_type: PhysicsModelType,
606
607 pub map_mode: PhysicsMapMode,
609
610 pub gravity: f32,
612
613 pub length: f32,
615
616 pub frequency: f32,
618
619 pub angle_damping: f32,
621
622 pub length_damping: f32,
624
625 pub output_scale: [f32; 2],
627
628 pub local_only: Option<bool>,
630}
631
632impl SimplePhysicsData {
633 fn from_json(v: &json::JsonValue) -> Self {
634 Self {
635 param: v.get_u32("param").unwrap_or(u32::MAX),
636 model_type: match v.get_str("model_type") {
637 Some(s) if s.eq_ignore_ascii_case("springpendulum") => {
638 PhysicsModelType::SpringPendulum
639 }
640 _ => PhysicsModelType::Pendulum,
641 },
642 map_mode: match v.get_str("map_mode") {
643 Some(s) if s.eq_ignore_ascii_case("xy") => PhysicsMapMode::XY,
644 Some(s) if s.eq_ignore_ascii_case("lengthangle") => PhysicsMapMode::LengthAngle,
645 Some(s) if s.eq_ignore_ascii_case("yx") => PhysicsMapMode::YX,
646 _ => PhysicsMapMode::AngleLength,
647 },
648 gravity: v.get_f32("gravity", 1.0),
649 length: v.get_f32("length", 100.0),
650 frequency: v.get_f32("frequency", 1.0),
651 angle_damping: v.get_f32("angle_damping", 0.5),
652 length_damping: v.get_f32("length_damping", 0.5),
653 output_scale: v.get_vec2("output_scale").unwrap_or([1.0, 1.0]),
654 local_only: v.get_as_bool("local_only"),
655 }
656 }
657}
658
659#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
661pub enum PhysicsModelType {
662 #[default]
664 Pendulum,
665
666 SpringPendulum,
668}
669
670#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
672pub enum PhysicsMapMode {
673 #[default]
675 AngleLength,
676
677 XY,
679
680 LengthAngle,
682
683 YX,
685}
686
687#[derive(Debug, Default)]
689pub struct CompositeData {
690 pub blend_mode: BlendMode,
692
693 pub tint: [f32; 3],
695
696 pub screen_tint: [f32; 3],
698
699 pub opacity: f32,
701
702 pub mask: Vec<Mask>,
704
705 pub mask_threshold: f32,
707
708 pub propagate_meshgroup: Option<bool>,
710}
711
712impl CompositeData {
713 fn from_json(v: &json::JsonValue) -> Self {
714 Self {
715 blend_mode: BlendMode::from_str(v.get_str("blend_mode").unwrap_or("normal"))
716 .unwrap_or_default(),
717 tint: v.get_vec3("tint").unwrap_or([1.0, 1.0, 1.0]),
718 screen_tint: v.get_vec3("screenTint").unwrap_or([0.0, 0.0, 0.0]),
719 opacity: v.get_f32("opacity", 1.0),
720 mask: parse_masks(v),
721 mask_threshold: v.get_f32("mask_threshold", 0.5),
722 propagate_meshgroup: v.get_as_bool("propagate_meshgroup"),
723 }
724 }
725}
726
727#[derive(Debug, Default)]
729pub struct MeshGroupData {
730 pub mesh: Option<Mesh>,
732
733 pub dynamic_deformation: bool,
735
736 pub translate_children: bool,
738}
739
740impl MeshGroupData {
741 fn from_json(v: &json::JsonValue) -> Self {
742 Self {
743 mesh: parse_mesh(v),
744 dynamic_deformation: v.get_bool("dynamic_deformation", false),
745 translate_children: v.get_bool("translate_children", true),
746 }
747 }
748}
749
750#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
753pub enum BlendMode {
754 #[default]
756 Normal,
757
758 Multiply,
760
761 Screen,
763
764 Overlay,
766
767 Darken,
769
770 Lighten,
772
773 ColorDodge,
775
776 LinearDodge,
778
779 Add,
781
782 ColorBurn,
784
785 HardLight,
787
788 SoftLight,
790
791 Subtract,
793
794 Difference,
796
797 Exclusion,
799
800 Inverse,
802
803 DestinationIn,
805
806 ClipToLower,
808
809 SliceFromLower,
811}
812
813impl BlendMode {
814 pub fn from_str(s: &str) -> Option<Self> {
815 match s {
816 s if s.eq_ignore_ascii_case("normal") => Some(Self::Normal),
817 s if s.eq_ignore_ascii_case("multiply") => Some(Self::Multiply),
818 s if s.eq_ignore_ascii_case("screen") => Some(Self::Screen),
819 s if s.eq_ignore_ascii_case("overlay") => Some(Self::Overlay),
820 s if s.eq_ignore_ascii_case("darken") => Some(Self::Darken),
821 s if s.eq_ignore_ascii_case("lighten") => Some(Self::Lighten),
822 s if s.eq_ignore_ascii_case("colordodge") => Some(Self::ColorDodge),
823 s if s.eq_ignore_ascii_case("colorburn") => Some(Self::ColorBurn),
824 s if s.eq_ignore_ascii_case("hardlight") => Some(Self::HardLight),
825 s if s.eq_ignore_ascii_case("softlight") => Some(Self::SoftLight),
826 s if s.eq_ignore_ascii_case("lineardodge") => Some(Self::LinearDodge),
827 s if s.eq_ignore_ascii_case("difference") => Some(Self::Difference),
828 s if s.eq_ignore_ascii_case("exclusion") => Some(Self::Exclusion),
829 s if s.eq_ignore_ascii_case("add") => Some(Self::Add),
830 s if s.eq_ignore_ascii_case("subtract") => Some(Self::Subtract),
831 s if s.eq_ignore_ascii_case("cliptolower") => Some(Self::ClipToLower),
832 s if s.eq_ignore_ascii_case("slicefromlower") => Some(Self::SliceFromLower),
833 s if s.eq_ignore_ascii_case("inverse") => Some(Self::Inverse),
834 s if s.eq_ignore_ascii_case("destinationin") => Some(Self::DestinationIn),
835 _ => None,
836 }
837 }
838}
839
840#[derive(Debug)]
841pub struct Param {
842 pub parent_uuid: Option<u32>,
844
845 pub uuid: u32,
847
848 pub name: String,
850
851 pub is_vec2: bool,
853
854 pub min: [f32; 2],
856
857 pub max: [f32; 2],
859
860 pub defaults: [f32; 2],
862
863 pub axis_points: [Vec<f32>; 2],
866
867 pub merge_mode: MergeMode,
869
870 pub bindings: Vec<ParamBinding>,
872}
873
874fn parse_params(root: &json::JsonValue) -> HashMap<u32, Param> {
875 let params = root.get_array("param").unwrap_or(&[]);
876 let mut map = HashMap::default();
877 for p in params {
878 let uuid = p.get_u32("uuid").unwrap_or(u32::MAX);
879 map.insert(uuid, Param::from_json(p));
880 }
881 map
882}
883
884impl Param {
885 fn from_json(v: &json::JsonValue) -> Self {
886 Self {
887 parent_uuid: v.get_u32("parentUUID"),
888 uuid: v.get_u32("uuid").unwrap_or(u32::MAX),
889 name: v.get_str("name").unwrap_or("").to_owned(),
890 is_vec2: v.get_bool("is_vec2", false),
891 min: v.get_vec2("min").unwrap_or([0.0, 0.0]),
892 max: v.get_vec2("max").unwrap_or([1.0, 1.0]),
893 defaults: v.get_vec2("defaults").unwrap_or([0.0, 1.0]),
894 axis_points: parse_axis_points(v),
895 merge_mode: parse_merge_mode(v.get_str("merge_mode")),
896 bindings: parse_bindings(v),
897 }
898 }
899}
900
901fn parse_axis_points(v: &json::JsonValue) -> [Vec<f32>; 2] {
902 let axis = v.get_array("axis_points").unwrap_or(&[]);
903 if axis.len() != 2 {
904 return [Vec::new(), Vec::new()];
905 }
906 let parse_axis = |a: &json::JsonValue| -> Vec<f32> {
907 a.as_array()
908 .unwrap_or(&[])
909 .iter()
910 .map(|v| v.as_f32().unwrap_or(0.0))
911 .collect()
912 };
913 [parse_axis(&axis[0]), parse_axis(&axis[1])]
914}
915
916#[derive(Debug, Clone)]
919pub struct ParamBinding {
920 pub node: u32,
922
923 pub param_name: ParamName,
925
926 pub values: BindingValues,
929
930 pub is_set: Vec<Vec<bool>>,
933
934 pub interpolate_mode: Interpolation,
936}
937
938fn parse_bindings(v: &json::JsonValue) -> Vec<ParamBinding> {
939 let bindings = v.get_array("bindings").unwrap_or(&[]);
940 bindings.iter().map(ParamBinding::from_json).collect()
941}
942
943impl ParamBinding {
944 fn from_json(v: &json::JsonValue) -> Self {
945 let param_name = parse_param_name(v.get_str("param_name"));
946 let values_v = v.get("values");
947 Self {
948 node: v.get_u32("node").unwrap_or(u32::MAX),
949 values: match values_v {
950 Some(vals) => match ¶m_name {
951 ParamName::Deform => BindingValues::Deform(FlatDeformValues::new(vals)),
952 ParamName::Other(_) => BindingValues::Other(vals.clone()),
953 _ => BindingValues::Transform(FlatTransformValues::new(vals)),
954 },
955 None => BindingValues::Transform(FlatTransformValues {
956 data: Vec::new(),
957 frames: 0,
958 values_per_frame: 0,
959 }),
960 },
961 param_name,
962 is_set: parse_is_set(v),
963 interpolate_mode: parse_interpolation(v.get_str("interpolate_mode")),
964 }
965 }
966}
967
968fn parse_is_set(v: &json::JsonValue) -> Vec<Vec<bool>> {
969 let is_set = v.get_array("isSet").unwrap_or(&[]);
970 is_set
971 .iter()
972 .map(|row| {
973 row.as_array()
974 .unwrap_or(&[])
975 .iter()
976 .map(|b| b.as_bool().unwrap_or(false))
977 .collect()
978 })
979 .collect()
980}
981
982#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
984pub enum ParamName {
985 TransformTX,
987
988 TransformTY,
990
991 TransformTZ,
993
994 TransformSX,
996
997 TransformSY,
999
1000 TransformRX,
1002
1003 TransformRY,
1005
1006 TransformRZ,
1008
1009 Deform,
1011
1012 #[default]
1013 Opacity,
1015
1016 Other(String),
1018}
1019
1020fn parse_param_name(s: Option<&str>) -> ParamName {
1021 match s {
1022 Some("transform.t.x") => ParamName::TransformTX,
1023 Some("transform.t.y") => ParamName::TransformTY,
1024 Some("transform.t.z") => ParamName::TransformTZ,
1025 Some("transform.s.x") => ParamName::TransformSX,
1026 Some("transform.s.y") => ParamName::TransformSY,
1027 Some("transform.r.x") => ParamName::TransformRX,
1028 Some("transform.r.y") => ParamName::TransformRY,
1029 Some("transform.r.z") => ParamName::TransformRZ,
1030 Some("deform") => ParamName::Deform,
1031 Some("opacity") => ParamName::Opacity,
1032 Some(other) => ParamName::Other(other.to_owned()),
1033 None => ParamName::Other(String::new()),
1034 }
1035}
1036
1037#[derive(Debug, Clone)]
1040pub enum BindingValues {
1041 Transform(FlatTransformValues),
1043
1044 Deform(FlatDeformValues),
1046
1047 Other(json::JsonValue),
1049}
1050
1051#[derive(Debug, Clone)]
1054pub struct FlatTransformValues {
1055 pub data: Vec<f32>,
1057
1058 pub frames: usize,
1060
1061 pub values_per_frame: usize,
1063}
1064
1065impl FlatTransformValues {
1066 pub fn new(values: &json::JsonValue) -> Self {
1067 let parsed: Vec<Vec<f32>> = values
1068 .as_array()
1069 .unwrap_or(&[])
1070 .iter()
1071 .filter_map(|frame| {
1072 frame
1073 .as_array()
1074 .map(|f| f.iter().filter_map(|v| v.as_f32()).collect())
1075 })
1076 .collect();
1077
1078 if parsed.is_empty() {
1079 return Self {
1080 data: Vec::new(),
1081 frames: 0,
1082 values_per_frame: 0,
1083 };
1084 }
1085
1086 let values_per_frame = parsed[0].len();
1087
1088 debug_assert!(
1089 parsed.iter().all(|v| v.len() == values_per_frame),
1090 "Inconsistent values per frame"
1091 );
1092
1093 let frames = parsed.len();
1094 let data = parsed.into_iter().flatten().collect();
1095 Self {
1096 data,
1097 frames,
1098 values_per_frame,
1099 }
1100 }
1101 pub fn get(&self, frame: usize, index: usize) -> Option<f32> {
1104 if frame >= self.frames || index >= self.values_per_frame {
1105 return None;
1106 }
1107 self.data
1108 .get(frame * self.values_per_frame + index)
1109 .copied()
1110 }
1111
1112 pub fn frames(&self) -> usize {
1114 self.frames
1115 }
1116
1117 pub fn values_per_frame(&self) -> usize {
1119 self.values_per_frame
1120 }
1121}
1122
1123#[derive(Debug, Clone)]
1126pub struct FlatDeformValues {
1127 pub data: Vec<[f32; 2]>,
1129
1130 pub frames: usize,
1132
1133 pub vertices_per_frame: usize,
1135}
1136
1137impl FlatDeformValues {
1138 pub fn new(values: &json::JsonValue) -> Self {
1139 let frames_data: Vec<Vec<[f32; 2]>> = values
1140 .as_array()
1141 .unwrap_or(&[])
1142 .iter()
1143 .map(|frame| {
1144 frame
1145 .as_array()
1146 .unwrap_or(&[])
1147 .iter()
1148 .flat_map(|vertex| {
1149 vertex
1150 .as_array()
1151 .unwrap_or(&[])
1152 .iter()
1153 .filter_map(|coords| {
1154 let pair = coords.as_array()?;
1155 Some([pair.get(0)?.as_f32()?, pair.get(1)?.as_f32()?])
1156 })
1157 })
1158 .collect()
1159 })
1160 .collect();
1161
1162 if frames_data.is_empty() {
1163 return Self {
1164 data: Vec::new(),
1165 frames: 0,
1166 vertices_per_frame: 0,
1167 };
1168 }
1169
1170 let frames = frames_data.len();
1171 let vertices_per_frame = frames_data[0].len();
1172
1173 debug_assert!(
1174 frames_data.iter().all(|f| f.len() == vertices_per_frame),
1175 "Inconsistent vertices per frame"
1176 );
1177
1178 let data = frames_data.into_iter().flatten().collect();
1179 Self {
1180 data,
1181 frames,
1182 vertices_per_frame,
1183 }
1184 }
1185 pub fn get(&self, frame: usize, vertex: usize) -> Option<[f32; 2]> {
1188 if frame >= self.frames || vertex >= self.vertices_per_frame {
1189 return None;
1190 }
1191 let idx = frame * self.vertices_per_frame + vertex;
1192 self.data.get(idx).copied()
1193 }
1194
1195 pub fn frames(&self) -> usize {
1197 self.frames
1198 }
1199
1200 pub fn vertices_per_frame(&self) -> usize {
1202 self.vertices_per_frame
1203 }
1204
1205 pub fn get_frame(&self, frame: usize) -> Option<&[[f32; 2]]> {
1208 if frame >= self.frames {
1209 return None;
1210 }
1211 let start = frame * self.vertices_per_frame;
1212 self.data.get(start..start + self.vertices_per_frame)
1213 }
1214}
1215
1216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1217pub enum MergeMode {
1218 Additive,
1220
1221 Multiplicative,
1223
1224 Override,
1226
1227 Forced,
1229}
1230
1231fn parse_merge_mode(s: Option<&str>) -> MergeMode {
1232 match s {
1233 Some(s) if s.eq_ignore_ascii_case("multiply") => MergeMode::Multiplicative,
1234 Some(s) if s.eq_ignore_ascii_case("override") => MergeMode::Override,
1235 Some(s) if s.eq_ignore_ascii_case("forced") => MergeMode::Forced,
1236 _ => MergeMode::Additive,
1237 }
1238}
1239
1240#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1242pub enum Interpolation {
1243 #[default]
1245 Linear,
1246
1247 Stepped,
1249
1250 Nearest,
1253
1254 Cubic,
1256}
1257
1258fn parse_interpolation(s: Option<&str>) -> Interpolation {
1259 match s {
1260 Some(s) if s.eq_ignore_ascii_case("stepped") => Interpolation::Stepped,
1261 Some(s) if s.eq_ignore_ascii_case("nearest") => Interpolation::Nearest,
1262 Some(s) if s.eq_ignore_ascii_case("cubic") => Interpolation::Cubic,
1263 Some(s) if s.eq_ignore_ascii_case("linear") => Interpolation::Linear,
1264 _ => Interpolation::Nearest,
1265 }
1266}
1267
1268#[derive(Debug)]
1271pub struct Automation {}
1272
1273#[derive(Debug, Clone)]
1276pub struct Animation {
1277 pub name: String,
1279
1280 pub timestep: f32,
1282
1283 pub additive: bool,
1285
1286 pub length: u32,
1288
1289 pub lead_in: u32,
1291
1292 pub lead_out: u32,
1294
1295 pub weight: f32,
1297
1298 pub lanes: Vec<AnimationLane>,
1300}
1301
1302impl Animation {
1303 #[inline]
1305 pub fn duration(&self) -> f32 {
1306 self.length as f32 * self.timestep
1307 }
1308
1309 #[inline]
1311 pub fn time_to_frame(&self, time: f32) -> f32 {
1312 time / self.timestep
1313 }
1314
1315 #[inline]
1317 pub fn frame_to_time(&self, frame: f32) -> f32 {
1318 frame * self.timestep
1319 }
1320}
1321
1322fn parse_animations(root: &json::JsonValue) -> HashMap<String, Animation> {
1323 let Some(anims) = root.get("animations") else {
1324 return HashMap::default();
1325 };
1326 let Some(obj) = anims.as_object() else {
1327 return HashMap::default();
1328 };
1329
1330 let mut map = HashMap::default();
1331 for (name, data) in obj.iter() {
1332 map.insert(
1333 name.to_owned(),
1334 Animation {
1335 name: name.to_owned(),
1336 timestep: data.get_f32("timestep", 0.016666668),
1337 additive: data.get_bool("additive", false),
1338 length: data.get_u32("length").unwrap_or(0),
1339 lead_in: data.get_u32("leadIn").unwrap_or(0),
1340 lead_out: data.get_u32("leadOut").unwrap_or(0),
1341 weight: data.get_f32("animationWeight", 1.0),
1342 lanes: parse_lanes(data),
1343 },
1344 );
1345 }
1346 map
1347}
1348
1349#[derive(Debug, Clone)]
1351pub struct AnimationLane {
1352 pub interpolation: Interpolation,
1354
1355 pub param_uuid: u32,
1357
1358 pub target: u8,
1360
1361 pub merge_mode: MergeMode,
1363
1364 pub keyframes: Vec<Keyframe>,
1366}
1367
1368impl AnimationLane {
1369 pub fn evaluate(&self, frame: f32) -> f32 {
1371 if self.keyframes.is_empty() {
1372 return 0.0;
1373 }
1374
1375 if frame <= self.keyframes[0].frame as f32 {
1377 return self.keyframes[0].value;
1378 }
1379
1380 let last = &self.keyframes[self.keyframes.len() - 1];
1382 if frame >= last.frame as f32 {
1383 return last.value;
1384 }
1385
1386 let mut prev_idx = 0;
1388 for (i, kf) in self.keyframes.iter().enumerate() {
1389 if kf.frame as f32 > frame {
1390 break;
1391 }
1392 prev_idx = i;
1393 }
1394
1395 let prev = &self.keyframes[prev_idx];
1396 let next = &self.keyframes[prev_idx + 1];
1397
1398 let t = (frame - prev.frame as f32) / (next.frame as f32 - prev.frame as f32);
1399
1400 match self.interpolation {
1401 Interpolation::Stepped | Interpolation::Nearest => prev.value,
1402 Interpolation::Linear => lerp(prev.value, next.value, t),
1403 Interpolation::Cubic => {
1404 let tension = (prev.tension + next.tension) * 0.5;
1406 cubic_interpolate(prev.value, next.value, t, tension)
1407 }
1408 }
1409 }
1410}
1411
1412fn parse_lanes(v: &json::JsonValue) -> Vec<AnimationLane> {
1413 let lanes = v.get_array("lanes").unwrap_or(&[]);
1414 lanes
1415 .iter()
1416 .map(|lane| AnimationLane {
1417 interpolation: parse_interpolation(lane.get_str("interpolation")),
1418 param_uuid: lane.get_u32("uuid").unwrap_or(u32::MAX),
1419 target: lane.get_u32("target").unwrap_or(0) as u8,
1420 merge_mode: parse_merge_mode(lane.get_str("merge_mode")),
1421 keyframes: parse_keyframes(lane),
1422 })
1423 .collect()
1424}
1425
1426#[derive(Debug, Clone, Copy)]
1428pub struct Keyframe {
1429 pub frame: u32,
1431
1432 pub value: f32,
1434
1435 pub tension: f32,
1437}
1438
1439fn parse_keyframes(v: &json::JsonValue) -> Vec<Keyframe> {
1440 let kfs = v.get_array("keyframes").unwrap_or(&[]);
1441 kfs.iter()
1442 .map(|kf| Keyframe {
1443 frame: kf.get_u32("frame").unwrap_or(0),
1444 value: kf.get_f32("value", 0.0),
1445 tension: kf.get_f32("tension", 0.5),
1446 })
1447 .collect()
1448}
1449
1450#[derive(Debug)]
1453pub struct Group {
1454 pub group_uuid: u32,
1456
1457 pub name: String,
1459
1460 pub color: [f32; 3],
1462}
1463
1464fn parse_groups(root: &json::JsonValue) -> Vec<Group> {
1465 let groups = root.get_array("groups").unwrap_or(&[]);
1466 groups
1467 .iter()
1468 .map(|g| Group {
1469 group_uuid: g.get_u32("groupUUID").unwrap_or(u32::MAX),
1470 name: g.get_str("name").unwrap_or("").to_owned(),
1471 color: g.get_vec3("color").unwrap_or([0.0, 0.0, 0.0]),
1472 })
1473 .collect()
1474}
1475
1476#[derive(Debug, Clone)]
1478pub struct VendorData {
1479 pub name: String,
1481 pub data: json::JsonValue,
1483}
1484
1485#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1487#[repr(u8)]
1488pub enum TextureFormat {
1489 Png = 0,
1491 Tga = 1,
1493 Bc7 = 2,
1495}
1496
1497impl TextureFormat {
1498 pub fn from_byte(b: u8) -> Option<Self> {
1500 match b {
1501 0 => Some(Self::Png),
1502 1 => Some(Self::Tga),
1503 2 => Some(Self::Bc7),
1504 _ => None,
1505 }
1506 }
1507
1508 pub fn extension(&self) -> &'static str {
1510 match self {
1511 Self::Png => "png",
1512 Self::Tga => "tga",
1513 Self::Bc7 => "bc7",
1514 }
1515 }
1516
1517 pub fn get_img_dim(&self, data: &[u8]) -> std::io::Result<(u32, u32)> {
1520 match self {
1521 TextureFormat::Png => {
1522 if data.len() < 24 {
1525 return Err(std::io::Error::new(
1526 std::io::ErrorKind::InvalidData,
1527 "Invalid PNG data (too short)",
1528 ));
1529 }
1530
1531 let width = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
1532 let height = u32::from_be_bytes([data[20], data[21], data[22], data[23]]);
1533
1534 Ok((width, height))
1535 }
1536
1537 TextureFormat::Tga => {
1538 if data.len() < 18 {
1541 return Err(std::io::Error::new(
1542 std::io::ErrorKind::InvalidData,
1543 "Invalid TGA data (too short)",
1544 ));
1545 }
1546
1547 let width = u16::from_le_bytes([data[12], data[13]]) as u32;
1548 let height = u16::from_le_bytes([data[14], data[15]]) as u32;
1549
1550 Ok((width, height))
1551 }
1552
1553 TextureFormat::Bc7 => {
1554 Ok((0, 0))
1558 }
1559 }
1560 }
1561}
1562
1563impl Default for TextureFormat {
1564 fn default() -> Self {
1565 Self::Png
1566 }
1567}
1568
1569#[derive(Debug, Clone)]
1571pub enum TextureData {
1572 Encoded(Vec<u8>),
1574 Rgba(Vec<u8>),
1576}
1577
1578#[derive(Debug, Clone)]
1580pub struct Texture {
1581 pub id: u32,
1583 pub width: u32,
1585 pub height: u32,
1587 pub format: TextureFormat,
1589 pub data: TextureData,
1591}
1592
1593impl Texture {
1594 pub fn new(id: u32, width: u32, height: u32, format: TextureFormat, data: Vec<u8>) -> Self {
1596 Self {
1597 id,
1598 width,
1599 height,
1600 format,
1601 data: TextureData::Encoded(data),
1602 }
1603 }
1604
1605 pub fn dimensions_from_data(&self) -> std::io::Result<(u32, u32)> {
1608 match &self.data {
1609 TextureData::Encoded(bytes) => self.format.get_img_dim(bytes),
1610 TextureData::Rgba(_) => {
1611 Ok((self.width, self.height))
1614 }
1615 }
1616 }
1617}
1618
1619#[inline]
1620fn lerp(a: f32, b: f32, t: f32) -> f32 {
1621 a + (b - a) * t
1622}
1623
1624#[inline]
1625fn cubic_interpolate(a: f32, b: f32, t: f32, tension: f32) -> f32 {
1626 let t2 = t * t;
1628 let t3 = t2 * t;
1629
1630 let m = (1.0 - tension) * (b - a);
1632
1633 let h1 = 2.0 * t3 - 3.0 * t2 + 1.0;
1634 let h2 = t3 - 2.0 * t2 + t;
1635 let h3 = -2.0 * t3 + 3.0 * t2;
1636 let h4 = t3 - t2;
1637
1638 h1 * a + h2 * m + h3 * b + h4 * m
1639}