ranim/app/scene.rs
1//! Scene description types.
2//!
3//! These types describe *what* to render (scene metadata, output settings)
4//! rather than *how* to animate (which lives in `ranim-core`).
5
6use std::sync::Arc;
7
8use ranim_core::{RanimScene, SealedRanimScene};
9
10/// A scene descriptor bundling a constructor, config, and outputs.
11#[derive(Clone)]
12pub struct Scene {
13 /// Scene name
14 pub name: String,
15 constructor: Arc<dyn SceneConstructor>,
16 /// Scene config
17 pub config: SceneConfig,
18 /// Scene outputs
19 pub outputs: Vec<Output>,
20}
21
22impl Scene {
23 /// Describe a scene with default rendering configuration and output.
24 ///
25 /// The constructor may capture plugin configuration. It runs only when a
26 /// selected scene is built, and may run again for another session.
27 pub fn new(name: impl Into<String>, constructor: impl SceneConstructor + 'static) -> Self {
28 #[cfg(all(feature = "subsecond", not(target_family = "wasm")))]
29 let constructor = HotSceneConstructor(constructor);
30 Self {
31 name: name.into(),
32 constructor: Arc::new(constructor),
33 config: SceneConfig::default(),
34 outputs: vec![Output::default()],
35 }
36 }
37}
38
39// Keep the concrete constructor type at the hot-call boundary, before erasing
40// it behind Arc<dyn SceneConstructor>. Like Bevy's FunctionSystem, this lets
41// Subsecond redirect the monomorphized call into the project's patched code.
42#[cfg(all(feature = "subsecond", not(target_family = "wasm")))]
43struct HotSceneConstructor<F>(F);
44
45#[cfg(all(feature = "subsecond", not(target_family = "wasm")))]
46impl<F: SceneConstructor> SceneConstructor for HotSceneConstructor<F> {
47 fn construct(&self, r: &mut RanimScene) {
48 dioxus_devtools::subsecond::call(|| self.0.construct(r));
49 }
50}
51
52impl SceneConstructor for Scene {
53 fn construct(&self, r: &mut RanimScene) {
54 self.constructor.construct(r);
55 }
56}
57
58/// Scene config
59#[derive(Debug, Clone)]
60pub struct SceneConfig {
61 /// The clear color
62 pub clear_color: String,
63}
64
65impl Default for SceneConfig {
66 fn default() -> Self {
67 Self {
68 clear_color: "#333333ff".to_string(),
69 }
70 }
71}
72
73/// The output format of a scene
74#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
75pub enum OutputFormat {
76 /// H.264 in MP4 container (default, opaque)
77 #[default]
78 Mp4,
79 /// VP9 with alpha in WebM container (transparent)
80 Webm,
81 /// ProRes 4444 in MOV container (transparent)
82 Mov,
83 /// GIF (opaque, limited palette)
84 Gif,
85}
86
87impl std::fmt::Display for OutputFormat {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 Self::Mp4 => write!(f, "mp4"),
91 Self::Webm => write!(f, "webm"),
92 Self::Mov => write!(f, "mov"),
93 Self::Gif => write!(f, "gif"),
94 }
95 }
96}
97
98/// The output of a scene
99#[derive(Debug, Clone)]
100pub struct Output {
101 /// The width of the output texture in pixels.
102 pub width: u32,
103 /// The height of the output texture in pixels.
104 pub height: u32,
105 /// The frame rate of the output video.
106 pub fps: u32,
107 /// Whether to save the frames.
108 pub save_frames: bool,
109 /// The name of the video, uses scene's name by default.
110 ///
111 /// e.g. the output of name `my_video` will be outputed as `my_video_<width>x<height>_<fps>.mp4`.
112 pub name: Option<String>,
113 /// The basename template for the rendered output.
114 ///
115 /// Supported placeholders:
116 /// - `{name}`: the scene/output name
117 /// - `{width}`: output width in pixels
118 /// - `{height}`: output height in pixels
119 /// - `{fps}`: output frame rate
120 ///
121 /// The file extension is appended automatically based on the output format.
122 /// Defaults to `{name}_{width}x{height}_{fps}`.
123 pub name_template: Option<String>,
124 /// The directory to save the output.
125 ///
126 /// Can be relative (resolved from cwd) or absolute.
127 pub dir: String,
128 /// The output video format.
129 pub format: OutputFormat,
130}
131
132impl Default for Output {
133 fn default() -> Self {
134 Self {
135 width: 1920,
136 height: 1080,
137 fps: 60,
138 save_frames: false,
139 name: None,
140 name_template: None,
141 dir: "./output".to_string(),
142 format: OutputFormat::default(),
143 }
144 }
145}
146
147// MARK: SceneConstructor
148// ANCHOR: SceneConstructor
149/// A scene constructor
150///
151/// It can be a simple fn pointer of `fn(&mut RanimScene)`,
152/// or any type implements `Fn(&mut RanimScene) + Send + Sync`.
153pub trait SceneConstructor: Send + Sync {
154 /// The construct logic
155 fn construct(&self, r: &mut RanimScene);
156
157 /// Use the constructor to build a [`SealedRanimScene`]
158 fn build_scene(&self) -> SealedRanimScene {
159 let mut scene = RanimScene::new();
160 self.construct(&mut scene);
161 scene.seal()
162 }
163}
164// ANCHOR_END: SceneConstructor
165
166impl<F: Fn(&mut RanimScene) + Send + Sync> SceneConstructor for F {
167 fn construct(&self, r: &mut RanimScene) {
168 self(r);
169 }
170}
171
172impl SceneConstructor for Arc<dyn SceneConstructor> {
173 fn construct(&self, r: &mut RanimScene) {
174 self.as_ref().construct(r)
175 }
176}