Skip to main content

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}