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 audio;
16pub mod color;
18pub mod components;
20pub mod frame;
22pub mod scene_evaluator;
24pub mod time;
26pub mod traits;
28pub mod transformed;
30pub use scene_evaluator::SceneEvaluator;
31pub mod utils;
33
34pub use glam;
35use std::fmt::Debug;
36use std::sync::Arc;
37
38use animation::{
39 build::IntoAnimNode,
40 compose::stack::AnimStack,
41 eval::OutputContext,
42 node::{AnimNode, AnimationInfo, bake_audio},
43};
44use frame::EvaluatedFrame;
45
46pub mod prelude {
48 pub use crate::color::prelude::*;
49 pub use crate::traits::*;
50
51 pub use crate::animation::build::{IntoAnimNode, PlaybackExt, Unplaced};
52 pub use crate::animation::compose::{
53 AnimIterExt,
54 lagged::{AnimLagged, LaggedFill},
55 sequence::AnimSequence,
56 stack::AnimStack,
57 };
58 pub use crate::animation::eval::{
59 Eval, EvalExt, Static, StaticAnim,
60 iterative::{Iterative, IterativeEval, IterativeFn},
61 pure::Pure,
62 };
63 pub use crate::animation::sound::Sound;
64 pub use crate::audio::{AudioClip, AudioTrack};
65 pub use crate::transformed::{Transformed, TransformedExt};
66 pub use crate::{RanimScene, TimeMark};
67}
68
69#[derive(Debug, Clone)]
71pub enum TimeMark {
72 Capture(String),
74}
75
76#[derive(Default)]
82pub struct RanimScene {
83 pub root: AnimStack,
89 time_marks: Vec<(f64, TimeMark)>,
90}
91
92impl RanimScene {
93 pub fn new() -> Self {
95 Self::default()
96 }
97
98 pub fn play<A: IntoAnimNode + 'static>(&mut self, animation: A) -> &mut Self {
100 self.root.push(animation);
101 self
102 }
103
104 pub fn insert_time_mark(&mut self, sec: f64, time_mark: TimeMark) {
106 self.time_marks.push((sec, time_mark));
107 }
108
109 pub fn seal(self) -> SealedRanimScene {
116 let total_secs = self.root.duration_secs();
117 let animations = self.root.into_built_animations();
118 let sample_rate = crate::audio::MASTER_SAMPLE_RATE as f64;
119 let audio: Arc<[f32]> = bake_audio(&animations, total_secs, sample_rate).into();
120 SealedRanimScene {
121 total_secs,
122 animations,
123 audio,
124 time_marks: self.time_marks,
125 }
126 }
127}
128
129impl Debug for RanimScene {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("RanimScene")
132 .field("animations", &self.root.built_animations().len())
133 .field("duration_secs", &self.root.duration_secs())
134 .finish()
135 }
136}
137
138pub struct SealedRanimScene {
140 total_secs: f64,
141 animations: Vec<AnimNode>,
142 audio: Arc<[f32]>,
146 time_marks: Vec<(f64, TimeMark)>,
147}
148
149impl SealedRanimScene {
150 pub fn into_evaluator(self, logic_fps: f64) -> SceneEvaluator {
155 SceneEvaluator::new(self, logic_fps)
156 }
157
158 pub fn audio(&self) -> &Arc<[f32]> {
162 &self.audio
163 }
164
165 pub fn total_secs(&self) -> f64 {
167 self.total_secs
168 }
169
170 pub fn time_marks(&self) -> &[(f64, TimeMark)] {
172 &self.time_marks
173 }
174
175 pub fn get_animation_infos(&self) -> Vec<AnimationInfo> {
177 self.animations
178 .iter()
179 .map(AnimNode::animation_info)
180 .collect()
181 }
182
183 pub fn eval_at_sec(&self, target_sec: f64, out: &mut EvaluatedFrame) {
188 out.clear();
189 for (source_id, animation) in self.animations.iter().enumerate() {
190 animation.eval_at(target_sec, &mut OutputContext::new(out, source_id));
191 }
192 }
193
194 pub fn eval_at_alpha(&self, alpha: f64, out: &mut EvaluatedFrame) {
196 self.eval_at_sec(self.total_secs * alpha, out);
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::animation::{
204 build::{PlaybackExt, Unplaced},
205 compose::sequence::AnimSequence,
206 eval::Static,
207 };
208
209 fn leaf(duration: f64) -> impl Unplaced {
210 Static(0_u8).with_duration(duration)
211 }
212
213 #[test]
214 fn scene_play_places_sequences_on_the_root_timeline() {
215 let mut reusable = AnimSequence::new();
218 reusable.push(leaf(2.0)).forward(1.0).push(leaf(1.0));
219 let mut scene = RanimScene::new();
220 scene.play(reusable);
221 scene.root.push(leaf(5.0));
222 let sealed = scene.seal();
223 assert_eq!(sealed.total_secs(), 5.0);
224 let infos = sealed.get_animation_infos();
225 assert_eq!(infos[0].range, 0.0..4.0);
226 assert_eq!(infos[0].children[1].range, 3.0..4.0);
227 assert_eq!(infos[1].range, 0.0..5.0);
228 }
229
230 #[test]
231 fn leaf_outputs_keep_containers_whole_while_children_share_source_parts() {
232 let mut scene = RanimScene::new();
233 scene.play(crate::stack![
234 Static(vec![1_u8, 2]).with_duration(1.0),
235 Static(3_u8).with_duration(1.0),
236 Static((42_u32, String::from("tuple"))).with_duration(1.0),
237 ]);
238 let sealed = scene.seal();
239 let mut frame = EvaluatedFrame::new();
240 sealed.eval_at_sec(0.5, &mut frame);
241
242 assert_eq!(frame.len(), 3);
243 let vector = &frame.items::<Vec<u8>>()[0];
244 assert_eq!(vector.value, [1, 2]);
245 assert_eq!((vector.id, vector.order), ((0, 0), 0));
246 let item = &frame.items::<u8>()[0];
247 assert_eq!(item.value, 3);
248 assert_eq!((item.id, item.order), ((0, 1), 1));
249 let tuple = &frame.items::<(u32, String)>()[0];
250 assert_eq!(tuple.value, (42, String::from("tuple")));
251 assert_eq!((tuple.id, tuple.order), ((0, 2), 2));
252 assert!(frame.items::<u32>().is_empty());
253 }
254}