Skip to main content

inochi2d_parser/
serialize.rs

1use std::io::Write;
2
3use crate::owned::*;
4
5//  Binary layout:
6//    TRNSRTS\0           8 bytes magic
7//    u32 BE              JSON blob length
8//    [JSON UTF-8]        model data
9//    TEX_SECT            8 bytes magic
10//    u32 BE              texture count
11//    per texture:
12//      u32 BE            texture data length
13//      u8                format byte (0=PNG, 1=TGA, 2=BC7)
14//      [bytes]           raw texture data
15//    EXT_SECT            8 bytes (optional)
16//    u32 BE              vendor count
17//    per vendor:
18//      u32 BE            name length + name bytes
19//      u32 BE            payload length + payload bytes
20
21impl Puppet {
22    pub fn to_bytes(&self) -> Vec<u8> {
23        let mut buf = Vec::new();
24        self.write_to(&mut buf).expect("write to Vec never fails");
25        buf
26    }
27
28    pub fn write_to<W: Write>(&self, w: &mut W) -> std::io::Result<()> {
29        // Build a JSON where everything is written, preserving float format
30        let mut json_buf = Vec::new();
31        {
32            let mut jw = JsonWriter::new(&mut json_buf);
33            self.write_json(&mut jw);
34        }
35
36        // Header
37        w.write_all(b"TRNSRTS\0")?;
38        w.write_all(&(json_buf.len() as u32).to_be_bytes())?;
39        w.write_all(&json_buf)?;
40
41        // TEX_SECT
42        w.write_all(b"TEX_SECT")?;
43        w.write_all(&(self.textures.len() as u32).to_be_bytes())?;
44
45        for tex in &self.textures {
46            let data = match &tex.data {
47                TextureData::Encoded(d) => d.as_slice(),
48                TextureData::Rgba(d) => d.as_slice(),
49            };
50            w.write_all(&(data.len() as u32).to_be_bytes())?;
51            w.write_all(&[tex.format as u8])?;
52            w.write_all(data)?;
53        }
54
55        // EXT_SECT
56        if !self.vendors.is_empty() {
57            w.write_all(b"EXT_SECT")?;
58            w.write_all(&(self.vendors.len() as u32).to_be_bytes())?;
59
60            for vendor in &self.vendors {
61                let name_bytes = vendor.name.as_bytes();
62                w.write_all(&(name_bytes.len() as u32).to_be_bytes())?;
63                w.write_all(name_bytes)?;
64
65                let payload = json::stringify(vendor.data.clone()).into_bytes();
66                w.write_all(&(payload.len() as u32).to_be_bytes())?;
67                w.write_all(&payload)?;
68            }
69        }
70
71        Ok(())
72    }
73
74    pub fn save<P: AsRef<std::path::Path>>(&self, path: P) -> std::io::Result<()> {
75        let mut file = std::fs::File::create(path)?;
76        self.write_to(&mut file)
77    }
78}
79
80struct JsonWriter<'w, W: Write> {
81    w: &'w mut W,
82}
83
84impl<'w, W: Write> JsonWriter<'w, W> {
85    fn new(w: &'w mut W) -> Self {
86        Self { w }
87    }
88
89    fn raw(&mut self, s: &str) {
90        let _ = self.w.write_all(s.as_bytes());
91    }
92
93    fn comma(&mut self) {
94        self.raw(",");
95    }
96
97    fn key(&mut self, k: &str) {
98        self.raw("\"");
99        self.raw(k);
100        self.raw("\":");
101    }
102
103    fn str_val(&mut self, s: &str) {
104        self.raw("\"");
105        // Escape special characters
106        for c in s.chars() {
107            match c {
108                '"' => self.raw("\\\""),
109                '\\' => self.raw("\\\\"),
110                '\n' => self.raw("\\n"),
111                '\r' => self.raw("\\r"),
112                '\t' => self.raw("\\t"),
113                c if (c as u32) < 0x20 => {
114                    let _ = write!(self.w, "\\u{:04x}", c as u32);
115                }
116                c => {
117                    let mut buf = [0u8; 4];
118                    let s = c.encode_utf8(&mut buf);
119                    let _ = self.w.write_all(s.as_bytes());
120                }
121            }
122        }
123        self.raw("\"");
124    }
125
126    fn null(&mut self) {
127        self.raw("null");
128    }
129
130    fn bool_val(&mut self, b: bool) {
131        self.raw(if b { "true" } else { "false" });
132    }
133
134    fn u32_val(&mut self, v: u32) {
135        let _ = write!(self.w, "{}", v);
136    }
137
138    fn _i32_val(&mut self, v: i32) {
139        let _ = write!(self.w, "{}", v);
140    }
141
142    /// Format f32 keeping the .0 for integer values and limiting precision.
143    /// This is the closest we get to keeping 55.0 as 55.0, 0.15 as 0.15.
144    fn f32_val(&mut self, v: f32) {
145        if v.is_nan() {
146            self.raw("0.0");
147            return;
148        }
149        if v.is_infinite() {
150            if v > 0.0 {
151                self.raw("1e38");
152            } else {
153                self.raw("-1e38");
154            }
155            return;
156        }
157
158        // Format as f32 (not f64) to get natural representation
159        let s = format!("{}", v);
160        if s.contains('.') || s.contains('e') || s.contains('E') {
161            let _ = self.w.write_all(s.as_bytes());
162        } else {
163            // Append .0 to any integer number
164            let _ = self.w.write_all(s.as_bytes());
165            self.raw(".0");
166        }
167    }
168
169    fn f32_arr(&mut self, vals: &[f32]) {
170        self.raw("[");
171        for (i, &v) in vals.iter().enumerate() {
172            if i > 0 {
173                self.comma();
174            }
175            self.f32_val(v);
176        }
177        self.raw("]");
178    }
179
180    fn vec2(&mut self, v: [f32; 2]) {
181        self.f32_arr(&v);
182    }
183
184    fn vec3(&mut self, v: [f32; 3]) {
185        self.f32_arr(&v);
186    }
187
188    fn opt_str(&mut self, s: Option<&str>) {
189        match s {
190            Some(s) => self.str_val(s),
191            None => self.null(),
192        }
193    }
194
195    fn begin_obj(&mut self) {
196        self.raw("{");
197    }
198
199    fn end_obj(&mut self) {
200        self.raw("}");
201    }
202
203    fn begin_arr(&mut self) {
204        self.raw("[");
205    }
206
207    fn end_arr(&mut self) {
208        self.raw("]");
209    }
210
211    /// Writes as a literal JSON string (for BindingValues::Other and VendorData)
212    fn json_value(&mut self, v: &json::JsonValue) {
213        let s = json::stringify(v.clone());
214        let _ = self.w.write_all(s.as_bytes());
215    }
216}
217
218impl Puppet {
219    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
220        j.begin_obj();
221        // meta
222        j.key("meta");
223        self.meta.write_json(j);
224        // physics
225        j.comma();
226        j.key("physics");
227        self.physics.write_json(j);
228        // nodes
229        j.comma();
230        j.key("nodes");
231        self.nodes.write_json(j);
232        // params
233        j.comma();
234        j.key("param");
235
236        j.begin_arr();
237        for (i, p) in self.params.values().enumerate() {
238            if i > 0 {
239                j.comma();
240            }
241            p.write_json(j);
242        }
243        j.end_arr();
244
245        // automation
246        j.comma();
247        j.key("automation");
248        j.null();
249        // animations
250        j.comma();
251        j.key("animations");
252        self.write_animations(j);
253        // groups
254        j.comma();
255        j.key("groups");
256        j.begin_arr();
257        for (i, g) in self.groups.iter().enumerate() {
258            if i > 0 {
259                j.comma();
260            }
261            g.write_json(j);
262        }
263        j.end_arr();
264
265        j.end_obj();
266    }
267
268    fn write_animations<W: Write>(&self, j: &mut JsonWriter<W>) {
269        j.begin_obj();
270        let mut first = true;
271        for (name, anim) in &self.animations {
272            if !first {
273                j.comma();
274            }
275            first = false;
276            j.key(name);
277            anim.write_json(j);
278        }
279        j.end_obj();
280    }
281}
282
283impl Meta {
284    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
285        j.begin_obj();
286        j.key("name");
287        j.opt_str(self.name.as_deref());
288        j.comma();
289        j.key("version");
290        j.str_val(&self.version);
291        j.comma();
292        j.key("rigger");
293        j.opt_str(self.rigger.as_deref());
294        j.comma();
295        j.key("artist");
296        j.opt_str(self.artist.as_deref());
297        j.comma();
298        j.key("rights");
299        j.opt_str(self.rights.as_deref());
300        j.comma();
301        j.key("copyright");
302        j.opt_str(self.copyright.as_deref());
303        j.comma();
304        j.key("licenseURL");
305        j.opt_str(self.license_url.as_deref());
306        j.comma();
307        j.key("contact");
308        j.opt_str(self.contact.as_deref());
309        j.comma();
310        j.key("reference");
311        j.opt_str(self.reference.as_deref());
312        j.comma();
313        j.key("thumbnailId");
314        j.u32_val(self.thumbnail_id);
315        j.comma();
316        j.key("preservePixels");
317        j.bool_val(self.preserve_pixels);
318        j.end_obj();
319    }
320}
321
322impl Physics {
323    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
324        j.begin_obj();
325        j.key("pixelsPerMeter");
326        j.f32_val(self.pixels_per_meter);
327        j.comma();
328        j.key("gravity");
329        j.f32_val(self.gravity);
330        j.end_obj();
331    }
332}
333
334impl Node {
335    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
336        j.begin_obj();
337        j.key("uuid");
338        j.u32_val(self.uuid);
339        j.comma();
340        j.key("name");
341        j.str_val(&self.name);
342        j.comma();
343        j.key("type");
344        j.str_val(self.type_node.type_str());
345        j.comma();
346        j.key("enabled");
347        j.bool_val(self.enabled);
348        j.comma();
349        j.key("zsort");
350        j.f32_val(self.zsort);
351        j.comma();
352        j.key("transform");
353        self.transform.write_json(j);
354        j.comma();
355        j.key("lockToRoot");
356        j.bool_val(self.lock_to_root);
357
358        self.type_node.write_fields(j);
359
360        if !self.children.is_empty() {
361            j.comma();
362            j.key("children");
363            j.begin_arr();
364            for (i, child) in self.children.iter().enumerate() {
365                if i > 0 {
366                    j.comma();
367                }
368                child.write_json(j);
369            }
370            j.end_arr();
371        }
372
373        j.end_obj();
374    }
375}
376
377impl Transform {
378    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
379        j.begin_obj();
380        j.key("trans");
381        j.vec3(self.translation);
382        j.comma();
383        j.key("rot");
384        j.vec3(self.rotation);
385        j.comma();
386        j.key("scale");
387        j.vec2(self.scale);
388        j.end_obj();
389    }
390}
391
392impl NodeDataType {
393    fn type_str(&self) -> &'static str {
394        match self {
395            Self::Part(_) => "Part",
396            Self::Camera(_) => "Camera",
397            Self::SimplePhysics(_) => "SimplePhysics",
398            Self::Composite(_) => "Composite",
399            Self::Mask(_) => "Mask",
400            Self::MeshGroup(_) => "MeshGroup",
401            Self::Generic => "Node",
402        }
403    }
404
405    fn write_fields<W: Write>(&self, j: &mut JsonWriter<W>) {
406        match self {
407            Self::Part(d) => d.write_fields(j),
408            Self::Camera(d) => d.write_fields(j),
409            Self::SimplePhysics(d) => d.write_fields(j),
410            Self::Composite(d) => d.write_fields(j),
411            Self::Mask(d) => d.write_fields(j),
412            Self::MeshGroup(d) => d.write_fields(j),
413            Self::Generic => {}
414        }
415    }
416}
417
418impl PartData {
419    fn write_fields<W: Write>(&self, j: &mut JsonWriter<W>) {
420        j.comma();
421        j.key("mesh");
422        write_mesh(j, self.mesh.as_ref());
423        j.comma();
424        j.key("textures");
425        j.begin_arr();
426        j.u32_val(self.textures[0]);
427        j.comma();
428        j.u32_val(self.textures[1]);
429        j.comma();
430        j.u32_val(self.textures[2]);
431        j.end_arr();
432        j.comma();
433        j.key("blend_mode");
434        j.str_val(blend_mode_str(self.blend_mode));
435        j.comma();
436        j.key("tint");
437        j.vec3(self.tint);
438        j.comma();
439        j.key("screenTint");
440        j.vec3(self.screen_tint);
441        j.comma();
442        j.key("emissionStrength");
443        j.f32_val(self.emission_strength);
444        if !self.mask.is_empty() {
445            j.comma();
446            j.key("masks");
447            j.begin_arr();
448            for (i, m) in self.mask.iter().enumerate() {
449                if i > 0 {
450                    j.comma();
451                }
452                m.write_json(j);
453            }
454            j.end_arr();
455        }
456        j.comma();
457        j.key("mask_threshold");
458        j.f32_val(self.mask_threshold);
459        j.comma();
460        j.key("opacity");
461        j.f32_val(self.opacity);
462        if let Some(ref path) = self.psd_layer_path {
463            j.comma();
464            j.key("psdLayerPath");
465            j.str_val(path);
466        }
467    }
468}
469
470impl CameraData {
471    fn write_fields<W: Write>(&self, j: &mut JsonWriter<W>) {
472        j.comma();
473        j.key("viewport");
474        j.vec2(self.viewport);
475    }
476}
477
478impl SimplePhysicsData {
479    fn write_fields<W: Write>(&self, j: &mut JsonWriter<W>) {
480        j.comma();
481        j.key("param");
482        j.u32_val(self.param);
483        j.comma();
484        j.key("model_type");
485        j.str_val(match self.model_type {
486            PhysicsModelType::Pendulum => "Pendulum",
487            PhysicsModelType::SpringPendulum => "SpringPendulum",
488        });
489        j.comma();
490        j.key("map_mode");
491        j.str_val(match self.map_mode {
492            PhysicsMapMode::AngleLength => "AngleLength",
493            PhysicsMapMode::XY => "XY",
494            PhysicsMapMode::LengthAngle => "LengthAngle",
495            PhysicsMapMode::YX => "YX",
496        });
497        j.comma();
498        j.key("gravity");
499        j.f32_val(self.gravity);
500        j.comma();
501        j.key("length");
502        j.f32_val(self.length);
503        j.comma();
504        j.key("frequency");
505        j.f32_val(self.frequency);
506        j.comma();
507        j.key("angle_damping");
508        j.f32_val(self.angle_damping);
509        j.comma();
510        j.key("length_damping");
511        j.f32_val(self.length_damping);
512        j.comma();
513        j.key("output_scale");
514        j.vec2(self.output_scale);
515        if let Some(lo) = self.local_only {
516            j.comma();
517            j.key("local_only");
518            j.bool_val(lo);
519        }
520    }
521}
522
523impl CompositeData {
524    fn write_fields<W: Write>(&self, j: &mut JsonWriter<W>) {
525        j.comma();
526        j.key("blend_mode");
527        j.str_val(blend_mode_str(self.blend_mode));
528        j.comma();
529        j.key("tint");
530        j.vec3(self.tint);
531        j.comma();
532        j.key("screenTint");
533        j.vec3(self.screen_tint);
534        j.comma();
535        j.key("opacity");
536        j.f32_val(self.opacity);
537        if !self.mask.is_empty() {
538            j.comma();
539            j.key("masks");
540            j.begin_arr();
541            for (i, m) in self.mask.iter().enumerate() {
542                if i > 0 {
543                    j.comma();
544                }
545                m.write_json(j);
546            }
547            j.end_arr();
548        }
549        j.comma();
550        j.key("mask_threshold");
551        j.f32_val(self.mask_threshold);
552        if let Some(p) = self.propagate_meshgroup {
553            j.comma();
554            j.key("propagate_meshgroup");
555            j.bool_val(p);
556        }
557    }
558}
559
560impl MaskData {
561    fn write_fields<W: Write>(&self, j: &mut JsonWriter<W>) {
562        j.comma();
563        j.key("mesh");
564        write_mesh(j, self.mesh.as_ref());
565    }
566}
567
568impl MeshGroupData {
569    fn write_fields<W: Write>(&self, j: &mut JsonWriter<W>) {
570        j.comma();
571        j.key("mesh");
572        write_mesh(j, self.mesh.as_ref());
573        j.comma();
574        j.key("dynamic_deformation");
575        j.bool_val(self.dynamic_deformation);
576        j.comma();
577        j.key("translate_children");
578        j.bool_val(self.translate_children);
579    }
580}
581
582impl Mask {
583    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
584        j.begin_obj();
585        j.key("source");
586        j.u32_val(self.source);
587        j.comma();
588        j.key("mode");
589        j.str_val(match self.mode {
590            MaskMode::Mask => "Mask",
591            MaskMode::Dodge => "DodgeMask",
592        });
593        j.end_obj();
594    }
595}
596
597impl Param {
598    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
599        j.begin_obj();
600        if let Some(parent) = self.parent_uuid {
601            j.key("parentUUID");
602            j.u32_val(parent);
603            j.comma();
604        }
605        j.key("uuid");
606        j.u32_val(self.uuid);
607        j.comma();
608        j.key("name");
609        j.str_val(&self.name);
610        j.comma();
611        j.key("is_vec2");
612        j.bool_val(self.is_vec2);
613        j.comma();
614        j.key("min");
615        j.vec2(self.min);
616        j.comma();
617        j.key("max");
618        j.vec2(self.max);
619        j.comma();
620        j.key("defaults");
621        j.vec2(self.defaults);
622        j.comma();
623        j.key("axis_points");
624        j.begin_arr();
625        j.f32_arr(&self.axis_points[0]);
626        j.comma();
627        j.f32_arr(&self.axis_points[1]);
628        j.end_arr();
629        j.comma();
630        j.key("merge_mode");
631        j.str_val(merge_mode_str(self.merge_mode));
632        j.comma();
633        j.key("bindings");
634        j.begin_arr();
635        for (i, b) in self.bindings.iter().enumerate() {
636            if i > 0 {
637                j.comma();
638            }
639            b.write_json(j);
640        }
641        j.end_arr();
642        j.end_obj();
643    }
644}
645
646impl ParamBinding {
647    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
648        j.begin_obj();
649        j.key("node");
650        j.u32_val(self.node);
651        j.comma();
652        j.key("param_name");
653        j.str_val(param_name_str(&self.param_name));
654        j.comma();
655        j.key("interpolate_mode");
656        j.str_val(interpolation_str(self.interpolate_mode));
657        j.comma();
658        j.key("isSet");
659        j.begin_arr();
660        for (i, row) in self.is_set.iter().enumerate() {
661            if i > 0 {
662                j.comma();
663            }
664            j.begin_arr();
665            for (k, &b) in row.iter().enumerate() {
666                if k > 0 {
667                    j.comma();
668                }
669                j.bool_val(b);
670            }
671            j.end_arr();
672        }
673        j.end_arr();
674        j.comma();
675        j.key("values");
676        self.write_values(j);
677        j.end_obj();
678    }
679
680    fn write_values<W: Write>(&self, j: &mut JsonWriter<W>) {
681        match &self.values {
682            BindingValues::Transform(flat) => flat.write_json(j),
683            BindingValues::Deform(flat) => {
684                let x_count = self.is_set.len().max(1);
685                let y_count = self.is_set.first().map(|r| r.len()).unwrap_or(1).max(1);
686                flat.write_json_shaped(j, x_count, y_count);
687            }
688            BindingValues::Other(v) => j.json_value(v),
689        }
690    }
691}
692
693impl FlatTransformValues {
694    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
695        j.begin_arr();
696        for f in 0..self.frames {
697            if f > 0 {
698                j.comma();
699            }
700            j.begin_arr();
701            let start = f * self.values_per_frame;
702            for k in 0..self.values_per_frame {
703                if k > 0 {
704                    j.comma();
705                }
706                j.f32_val(self.data[start + k]);
707            }
708            j.end_arr();
709        }
710        j.end_arr();
711    }
712}
713
714impl FlatDeformValues {
715    fn write_json_shaped<W: Write>(&self, j: &mut JsonWriter<W>, x_count: usize, y_count: usize) {
716        // FlatDeformValues data layout:
717        //   frames = x_count
718        //   vertices_per_frame = y_count * actual_vertex_count
719        //   data[x * vpf + y * actual_vtx + v] = [dx, dy]
720        //
721        // Output JSON: values[x][y][v] = [dx, dy]
722        let actual_vtx = if y_count > 0 {
723            self.vertices_per_frame / y_count
724        } else {
725            self.vertices_per_frame
726        };
727
728        j.begin_arr();
729        for x in 0..x_count {
730            if x > 0 {
731                j.comma();
732            }
733            j.begin_arr();
734            for y in 0..y_count {
735                if y > 0 {
736                    j.comma();
737                }
738                j.begin_arr();
739                for v in 0..actual_vtx {
740                    if v > 0 {
741                        j.comma();
742                    }
743                    let idx = x * self.vertices_per_frame + y * actual_vtx + v;
744                    let [dx, dy] = self.data.get(idx).copied().unwrap_or([0.0, 0.0]);
745                    j.begin_arr();
746                    j.f32_val(dx);
747                    j.comma();
748                    j.f32_val(dy);
749                    j.end_arr();
750                }
751                j.end_arr();
752            }
753            j.end_arr();
754        }
755        j.end_arr();
756    }
757}
758
759impl Animation {
760    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
761        j.begin_obj();
762        j.key("timestep");
763        j.f32_val(self.timestep);
764        j.comma();
765        j.key("additive");
766        j.bool_val(self.additive);
767        j.comma();
768        j.key("length");
769        j.u32_val(self.length);
770        j.comma();
771        j.key("leadIn");
772        j.u32_val(self.lead_in);
773        j.comma();
774        j.key("leadOut");
775        j.u32_val(self.lead_out);
776        j.comma();
777        j.key("animationWeight");
778        j.f32_val(self.weight);
779        j.comma();
780        j.key("lanes");
781        j.begin_arr();
782        for (i, lane) in self.lanes.iter().enumerate() {
783            if i > 0 {
784                j.comma();
785            }
786            lane.write_json(j);
787        }
788        j.end_arr();
789        j.end_obj();
790    }
791}
792
793impl AnimationLane {
794    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
795        j.begin_obj();
796        j.key("interpolation");
797        j.str_val(interpolation_str(self.interpolation));
798        j.comma();
799        j.key("uuid");
800        j.u32_val(self.param_uuid);
801        j.comma();
802        j.key("target");
803        j.u32_val(self.target as u32);
804        j.comma();
805        j.key("merge_mode");
806        j.str_val(merge_mode_str(self.merge_mode));
807        j.comma();
808        j.key("keyframes");
809        j.begin_arr();
810        for (i, kf) in self.keyframes.iter().enumerate() {
811            if i > 0 {
812                j.comma();
813            }
814            kf.write_json(j);
815        }
816        j.end_arr();
817        j.end_obj();
818    }
819}
820
821impl Keyframe {
822    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
823        j.begin_obj();
824        j.key("frame");
825        j.u32_val(self.frame);
826        j.comma();
827        j.key("value");
828        j.f32_val(self.value);
829        j.comma();
830        j.key("tension");
831        j.f32_val(self.tension);
832        j.end_obj();
833    }
834}
835
836impl Group {
837    fn write_json<W: Write>(&self, j: &mut JsonWriter<W>) {
838        j.begin_obj();
839        j.key("groupUUID");
840        j.u32_val(self.group_uuid);
841        j.comma();
842        j.key("name");
843        j.str_val(&self.name);
844        j.comma();
845        j.key("color");
846        j.vec3(self.color);
847        j.end_obj();
848    }
849}
850
851fn write_mesh<W: Write>(j: &mut JsonWriter<W>, mesh: Option<&Mesh>) {
852    match mesh {
853        Some(m) => {
854            j.begin_obj();
855            j.key("verts");
856            j.f32_arr(&m.vertices);
857            j.comma();
858            j.key("indices");
859            j.begin_arr();
860            for (i, &idx) in m.indices.iter().enumerate() {
861                if i > 0 {
862                    j.comma();
863                }
864                j.u32_val(idx);
865            }
866            j.end_arr();
867            j.comma();
868            j.key("uvs");
869            j.f32_arr(&m.uvs);
870            j.comma();
871            j.key("origin");
872            j.vec2(m.origin);
873            j.end_obj();
874        }
875        None => {
876            // Matches original format: {"verts":[],"indices":null,"origin":[0.0,0.0]}
877            j.begin_obj();
878            j.key("verts");
879            j.begin_arr();
880            j.end_arr();
881            j.comma();
882            j.key("indices");
883            j.null();
884            j.comma();
885            j.key("origin");
886            j.vec2([0.0, 0.0]);
887            j.end_obj();
888        }
889    }
890}
891
892fn blend_mode_str(bm: BlendMode) -> &'static str {
893    match bm {
894        BlendMode::Normal => "Normal",
895        BlendMode::Multiply => "Multiply",
896        BlendMode::Screen => "Screen",
897        BlendMode::Overlay => "Overlay",
898        BlendMode::Darken => "Darken",
899        BlendMode::Lighten => "Lighten",
900        BlendMode::ColorDodge => "ColorDodge",
901        BlendMode::LinearDodge => "LinearDodge",
902        BlendMode::Add => "Add",
903        BlendMode::ColorBurn => "ColorBurn",
904        BlendMode::HardLight => "HardLight",
905        BlendMode::SoftLight => "SoftLight",
906        BlendMode::Subtract => "Subtract",
907        BlendMode::Difference => "Difference",
908        BlendMode::Exclusion => "Exclusion",
909        BlendMode::Inverse => "Inverse",
910        BlendMode::DestinationIn => "DestinationIn",
911        BlendMode::ClipToLower => "ClipToLower",
912        BlendMode::SliceFromLower => "SliceFromLower",
913    }
914}
915
916fn merge_mode_str(mm: MergeMode) -> &'static str {
917    match mm {
918        MergeMode::Additive => "Additive",
919        MergeMode::Multiplicative => "Multiply",
920        MergeMode::Override => "Override",
921        MergeMode::Forced => "Forced",
922    }
923}
924
925fn interpolation_str(i: Interpolation) -> &'static str {
926    match i {
927        Interpolation::Linear => "Linear",
928        Interpolation::Stepped => "Stepped",
929        Interpolation::Nearest => "Nearest",
930        Interpolation::Cubic => "Cubic",
931    }
932}
933
934fn param_name_str(pn: &ParamName) -> &str {
935    match pn {
936        ParamName::TransformTX => "transform.t.x",
937        ParamName::TransformTY => "transform.t.y",
938        ParamName::TransformTZ => "transform.t.z",
939        ParamName::TransformSX => "transform.s.x",
940        ParamName::TransformSY => "transform.s.y",
941        ParamName::TransformRX => "transform.r.x",
942        ParamName::TransformRY => "transform.r.y",
943        ParamName::TransformRZ => "transform.r.z",
944        ParamName::Deform => "deform",
945        ParamName::Opacity => "opacity",
946        ParamName::Other(s) => s.as_str(),
947    }
948}