Skip to main content

oxihuman_export/
mesh_sequence_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Mesh sequence (vertex animation cache) export.
6
7/// A single frame of mesh positions.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct MeshFrame {
11    pub time: f32,
12    pub positions: Vec<[f32; 3]>,
13}
14
15/// Mesh sequence export.
16#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct MeshSequenceExport {
19    pub frames: Vec<MeshFrame>,
20    pub vertex_count: usize,
21}
22
23/// Create new sequence.
24#[allow(dead_code)]
25pub fn new_mesh_sequence(vertex_count: usize) -> MeshSequenceExport {
26    MeshSequenceExport {
27        frames: vec![],
28        vertex_count,
29    }
30}
31
32/// Add a frame.
33#[allow(dead_code)]
34pub fn add_frame(e: &mut MeshSequenceExport, time: f32, positions: &[[f32; 3]]) {
35    if positions.len() == e.vertex_count {
36        e.frames.push(MeshFrame {
37            time,
38            positions: positions.to_vec(),
39        });
40    }
41}
42
43/// Frame count.
44#[allow(dead_code)]
45pub fn ms_frame_count(e: &MeshSequenceExport) -> usize {
46    e.frames.len()
47}
48
49/// Duration.
50#[allow(dead_code)]
51pub fn ms_duration(e: &MeshSequenceExport) -> f32 {
52    if e.frames.is_empty() {
53        return 0.0;
54    }
55    let min = e.frames.iter().map(|f| f.time).fold(f32::MAX, f32::min);
56    let max = e.frames.iter().map(|f| f.time).fold(f32::MIN, f32::max);
57    max - min
58}
59
60/// Estimated size in bytes.
61#[allow(dead_code)]
62pub fn ms_size_bytes(e: &MeshSequenceExport) -> usize {
63    e.frames.len() * e.vertex_count * 12 // 3 floats * 4 bytes
64}
65
66/// Get positions at frame.
67#[allow(dead_code)]
68pub fn get_frame_positions(e: &MeshSequenceExport, idx: usize) -> Option<&[[f32; 3]]> {
69    e.frames.get(idx).map(|f| f.positions.as_slice())
70}
71
72/// FPS estimate.
73#[allow(dead_code)]
74pub fn ms_fps(e: &MeshSequenceExport) -> f32 {
75    let dur = ms_duration(e);
76    if dur < 1e-12 || e.frames.len() < 2 {
77        return 0.0;
78    }
79    (e.frames.len() - 1) as f32 / dur
80}
81
82/// Validate.
83#[allow(dead_code)]
84pub fn ms_validate(e: &MeshSequenceExport) -> bool {
85    e.frames
86        .iter()
87        .all(|f| f.positions.len() == e.vertex_count && f.time >= 0.0)
88}
89
90/// Export to JSON.
91#[allow(dead_code)]
92pub fn mesh_sequence_to_json(e: &MeshSequenceExport) -> String {
93    format!(
94        "{{\"frames\":{},\"vertices\":{},\"duration\":{:.6}}}",
95        ms_frame_count(e),
96        e.vertex_count,
97        ms_duration(e)
98    )
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    #[test]
105    fn test_new() {
106        let e = new_mesh_sequence(3);
107        assert_eq!(ms_frame_count(&e), 0);
108    }
109    #[test]
110    fn test_add_frame() {
111        let mut e = new_mesh_sequence(2);
112        add_frame(&mut e, 0.0, &[[0.0; 3]; 2]);
113        assert_eq!(ms_frame_count(&e), 1);
114    }
115    #[test]
116    fn test_wrong_count() {
117        let mut e = new_mesh_sequence(2);
118        add_frame(&mut e, 0.0, &[[0.0; 3]; 3]);
119        assert_eq!(ms_frame_count(&e), 0);
120    }
121    #[test]
122    fn test_duration() {
123        let mut e = new_mesh_sequence(1);
124        add_frame(&mut e, 0.0, &[[0.0; 3]]);
125        add_frame(&mut e, 2.0, &[[1.0; 3]]);
126        assert!((ms_duration(&e) - 2.0).abs() < 1e-6);
127    }
128    #[test]
129    fn test_duration_empty() {
130        let e = new_mesh_sequence(1);
131        assert!((ms_duration(&e)).abs() < 1e-9);
132    }
133    #[test]
134    fn test_size() {
135        let mut e = new_mesh_sequence(10);
136        add_frame(&mut e, 0.0, &[[0.0; 3]; 10]);
137        assert_eq!(ms_size_bytes(&e), 120);
138    }
139    #[test]
140    fn test_get_frame() {
141        let mut e = new_mesh_sequence(1);
142        add_frame(&mut e, 0.0, &[[5.0, 0.0, 0.0]]);
143        let p = get_frame_positions(&e, 0).expect("should succeed");
144        assert!((p[0][0] - 5.0).abs() < 1e-6);
145    }
146    #[test]
147    fn test_fps() {
148        let mut e = new_mesh_sequence(1);
149        for i in 0..25 {
150            add_frame(&mut e, i as f32 / 24.0, &[[0.0; 3]]);
151        }
152        assert!((ms_fps(&e) - 24.0).abs() < 0.1);
153    }
154    #[test]
155    fn test_validate() {
156        let mut e = new_mesh_sequence(1);
157        add_frame(&mut e, 0.0, &[[0.0; 3]]);
158        assert!(ms_validate(&e));
159    }
160    #[test]
161    fn test_to_json() {
162        let e = new_mesh_sequence(5);
163        assert!(mesh_sequence_to_json(&e).contains("\"vertices\":5"));
164    }
165}