1#![warn(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![allow(rustdoc::private_intra_doc_links)]
6#![doc(
7 html_logo_url = "https://raw.githubusercontent.com/AzurIce/ranim/refs/heads/main/assets/ranim.svg",
8 html_favicon_url = "https://raw.githubusercontent.com/AzurIce/ranim/refs/heads/main/assets/ranim.svg"
9)]
10
11pub mod anchor;
13pub mod animation;
14pub mod color;
16pub mod components;
18pub mod core_item;
20pub mod scene_evaluator;
22pub mod traits;
24pub use scene_evaluator::SceneEvaluator;
25pub mod utils;
27
28pub use glam;
29pub use num;
30use std::fmt::Debug;
31
32use animation::{AnimStack, Animation, AnimationCell};
33pub use animation::{AnimationInfo, AnimationInfoKind};
34use core_item::CoreItem;
35
36pub mod prelude {
38 pub use crate::color::prelude::*;
39 pub use crate::traits::*;
40
41 pub use crate::animation::{
42 AnimSequence, AnimStack, Animation, AnimationExt, Eval, Placeable, StaticAnim,
43 };
44 pub use crate::core_item::camera_frame::CameraFrame;
45 pub use crate::{RanimScene, TimeMark};
46}
47
48pub trait Extract {
50 type Target: Clone;
52 fn extract_into(&self, buf: &mut Vec<Self::Target>);
54 fn extract(&self) -> Vec<Self::Target> {
56 let mut buf = Vec::new();
57 self.extract_into(&mut buf);
58 buf
59 }
60}
61
62impl<E: Extract, I> Extract for I
63where
64 for<'a> &'a I: IntoIterator<Item = &'a E>,
65{
66 type Target = E::Target;
67
68 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
69 for element in self {
70 element.extract_into(buf);
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
77pub enum TimeMark {
78 Capture(String),
80}
81
82#[derive(Default)]
88pub struct RanimScene {
89 pub root: AnimStack,
91 time_marks: Vec<(f64, TimeMark)>,
92}
93
94impl RanimScene {
95 pub fn new() -> Self {
97 Self::default()
98 }
99
100 pub fn play<A: Animation + 'static>(&mut self, animation: A) -> &mut Self {
102 self.root.push(animation);
103 self
104 }
105
106 pub fn insert_time_mark(&mut self, sec: f64, time_mark: TimeMark) {
108 self.time_marks.push((sec, time_mark));
109 }
110
111 pub fn seal(self) -> SealedRanimScene {
113 let total_secs = self.root.duration_secs();
114 SealedRanimScene {
115 total_secs,
116 animations: self.root.into_built_animations(),
117 time_marks: self.time_marks,
118 }
119 }
120}
121
122impl Debug for RanimScene {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.debug_struct("RanimScene")
125 .field("animations", &self.root.built_animations().len())
126 .field("duration_secs", &self.root.duration_secs())
127 .finish()
128 }
129}
130
131pub struct SealedRanimScene {
133 total_secs: f64,
134 animations: Vec<AnimationCell>,
135 time_marks: Vec<(f64, TimeMark)>,
136}
137
138impl SealedRanimScene {
139 pub fn into_evaluator(self, logic_fps: f64) -> SceneEvaluator {
145 SceneEvaluator::new(self, logic_fps)
146 }
147
148 pub fn total_secs(&self) -> f64 {
150 self.total_secs
151 }
152
153 pub fn time_marks(&self) -> &[(f64, TimeMark)] {
155 &self.time_marks
156 }
157
158 pub fn get_animation_infos(&self) -> Vec<AnimationInfo> {
160 self.animations
161 .iter()
162 .map(AnimationCell::animation_info)
163 .collect()
164 }
165
166 pub fn eval_at_sec(&self, target_sec: f64) -> impl Iterator<Item = ((usize, usize), CoreItem)> {
168 self.animations
169 .iter()
170 .enumerate()
171 .filter_map(move |(animation_id, animation)| {
172 if !animation.enabled() {
173 return None;
174 }
175
176 let range = animation.time_range();
177 let active = range.contains(&target_sec)
178 || (target_sec == self.total_secs && target_sec == range.end);
179 active
180 .then(|| animation.eval_at_sec(target_sec))
181 .flatten()
182 .map(move |items| (animation_id, items))
183 })
184 .flat_map(|(animation_id, items)| {
185 items
186 .into_iter()
187 .flat_map(|item| item.extract())
188 .enumerate()
189 .map(move |(part, item)| ((animation_id, part), item))
190 })
191 }
192
193 pub fn eval_at_alpha(&self, alpha: f64) -> impl Iterator<Item = ((usize, usize), CoreItem)> {
195 self.eval_at_sec(self.total_secs * alpha)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::{
203 animation::{AnimSequence, AnimationExt, Placeable, Static},
204 core_item::vitem::VItem,
205 };
206
207 fn leaf(duration: f64) -> impl Placeable {
208 Static(VItem::default()).with_duration(duration)
209 }
210
211 #[test]
212 fn scene_play_pushes_into_the_root_stack() {
213 let mut scene = RanimScene::new();
214 scene.play(seq![leaf(2.0), leaf(3.0)]);
215 let sealed = scene.seal();
216
217 assert_eq!(sealed.total_secs(), 5.0);
218 let infos = sealed.get_animation_infos();
219 assert_eq!(infos[0].range, 0.0..5.0);
220 assert_eq!(infos[0].children[0].range, 0.0..2.0);
221 assert_eq!(infos[0].children[1].range, 2.0..5.0);
222 }
223
224 #[test]
225 fn extracted_items_have_unique_semantic_part_ids() {
226 let mut scene = RanimScene::new();
227 scene.play(Static(vec![VItem::default(), VItem::default()]).with_duration(1.0));
228 let sealed = scene.seal();
229
230 let ids = sealed
231 .eval_at_sec(0.5)
232 .map(|(id, _)| id)
233 .collect::<Vec<_>>();
234
235 assert_eq!(ids, [(0, 0), (0, 1)]);
236 }
237
238 #[test]
239 fn scene_modules_share_the_root_origin() {
240 let mut reusable = AnimSequence::new();
241 reusable.push(leaf(2.0)).forward(1.0).push(leaf(1.0));
242
243 let mut scene = RanimScene::new();
244 scene.play(reusable);
245 scene.root.push(leaf(5.0));
246 let sealed = scene.seal();
247
248 assert_eq!(sealed.total_secs(), 5.0);
249 let infos = sealed.get_animation_infos();
250 assert_eq!(infos[0].range, 0.0..4.0);
251 assert_eq!(infos[0].children[0].range, 0.0..2.0);
252 assert_eq!(infos[0].children[1].range, 3.0..4.0);
253 assert_eq!(infos[1].range, 0.0..5.0);
254 }
255}