Skip to main content

oxihuman_export/
anim_event_export.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Export animation event markers (e.g. footstep, sound trigger) to JSON-like records.
6
7/// A single animation event.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct AnimEvent {
11    pub name: String,
12    pub time: f32,
13    pub payload: String,
14}
15
16/// A collection of animation events.
17#[allow(dead_code)]
18#[derive(Debug, Clone, Default)]
19pub struct AnimEventTrack {
20    pub events: Vec<AnimEvent>,
21}
22
23/// Create a new event track.
24#[allow(dead_code)]
25pub fn new_event_track() -> AnimEventTrack {
26    AnimEventTrack::default()
27}
28
29/// Add an event to the track.
30#[allow(dead_code)]
31pub fn add_event(track: &mut AnimEventTrack, name: &str, time: f32, payload: &str) {
32    track.events.push(AnimEvent {
33        name: name.to_string(),
34        time,
35        payload: payload.to_string(),
36    });
37}
38
39/// Sort events by time.
40#[allow(dead_code)]
41pub fn sort_events(track: &mut AnimEventTrack) {
42    track.events.sort_by(|a, b| {
43        a.time
44            .partial_cmp(&b.time)
45            .unwrap_or(std::cmp::Ordering::Equal)
46    });
47}
48
49/// Count events in the track.
50#[allow(dead_code)]
51pub fn event_count(track: &AnimEventTrack) -> usize {
52    track.events.len()
53}
54
55/// Find all events at or after `time`.
56#[allow(dead_code)]
57pub fn events_from(track: &AnimEventTrack, time: f32) -> Vec<&AnimEvent> {
58    track.events.iter().filter(|e| e.time >= time).collect()
59}
60
61/// Duration from first to last event (0 if fewer than 2 events).
62#[allow(dead_code)]
63pub fn track_duration(track: &AnimEventTrack) -> f32 {
64    if track.events.len() < 2 {
65        return 0.0;
66    }
67    let first = track.events.iter().map(|e| e.time).fold(f32::MAX, f32::min);
68    let last = track.events.iter().map(|e| e.time).fold(f32::MIN, f32::max);
69    last - first
70}
71
72/// Serialize events to a simple newline-separated string.
73#[allow(dead_code)]
74pub fn serialize_events(track: &AnimEventTrack) -> String {
75    track
76        .events
77        .iter()
78        .map(|e| format!("{},{},{}", e.name, e.time, e.payload))
79        .collect::<Vec<_>>()
80        .join("\n")
81}
82
83/// Check whether the track has any events with the given name.
84#[allow(dead_code)]
85pub fn has_event_named(track: &AnimEventTrack, name: &str) -> bool {
86    track.events.iter().any(|e| e.name == name)
87}
88
89/// Remove all events with time less than `cutoff`.
90#[allow(dead_code)]
91pub fn trim_before(track: &mut AnimEventTrack, cutoff: f32) {
92    track.events.retain(|e| e.time >= cutoff);
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_add_event() {
101        let mut t = new_event_track();
102        add_event(&mut t, "step", 1.0, "left");
103        assert_eq!(event_count(&t), 1);
104    }
105
106    #[test]
107    fn test_sort_events() {
108        let mut t = new_event_track();
109        add_event(&mut t, "b", 2.0, "");
110        add_event(&mut t, "a", 0.5, "");
111        sort_events(&mut t);
112        assert!((t.events[0].time - 0.5).abs() < 1e-6);
113    }
114
115    #[test]
116    fn test_events_from() {
117        let mut t = new_event_track();
118        add_event(&mut t, "x", 0.0, "");
119        add_event(&mut t, "y", 1.5, "");
120        let r = events_from(&t, 1.0);
121        assert_eq!(r.len(), 1);
122    }
123
124    #[test]
125    fn test_track_duration_empty() {
126        let t = new_event_track();
127        assert_eq!(track_duration(&t), 0.0);
128    }
129
130    #[test]
131    fn test_track_duration() {
132        let mut t = new_event_track();
133        add_event(&mut t, "a", 1.0, "");
134        add_event(&mut t, "b", 3.0, "");
135        assert!((track_duration(&t) - 2.0).abs() < 1e-6);
136    }
137
138    #[test]
139    fn test_serialize_events() {
140        let mut t = new_event_track();
141        add_event(&mut t, "step", 1.0, "L");
142        let s = serialize_events(&t);
143        assert!(s.contains("step"));
144    }
145
146    #[test]
147    fn test_has_event_named() {
148        let mut t = new_event_track();
149        add_event(&mut t, "jump", 0.5, "");
150        assert!(has_event_named(&t, "jump"));
151        assert!(!has_event_named(&t, "fall"));
152    }
153
154    #[test]
155    fn test_trim_before() {
156        let mut t = new_event_track();
157        add_event(&mut t, "a", 0.2, "");
158        add_event(&mut t, "b", 1.5, "");
159        trim_before(&mut t, 1.0);
160        assert_eq!(event_count(&t), 1);
161    }
162
163    #[test]
164    fn test_empty_serialize() {
165        let t = new_event_track();
166        assert!(serialize_events(&t).is_empty());
167    }
168
169    #[test]
170    fn test_event_fields() {
171        let e = AnimEvent {
172            name: "n".to_string(),
173            time: 3.0,
174            payload: "p".to_string(),
175        };
176        assert!((e.time - 3.0).abs() < 1e-6);
177    }
178}