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