Skip to main content

ranim_items/camera/
render.rs

1use bevy_app::{App, Plugin};
2use bevy_ecs::prelude::*;
3use ranim_core::{glam::Vec2, traits::ApplyTransform};
4use ranim_render::{
5    extract::{Extract, ExtractContext, ExtractPlugin},
6    primitives::viewport::{DEPTH_ORDER_SPAN, ViewportGpuPacket, ViewportUniform},
7    schedule::{RenderDimensions, RenderSchedule},
8    utils::WgpuContext,
9    world::SceneOrder,
10};
11
12use super::CameraFrame;
13
14/// Installs camera extraction and uploads the active view before rendering.
15///
16/// Each rendered frame must contain exactly one extracted [`CameraFrame`].
17/// Composite frame payloads require an [`ExtractPlugin`] for their exact type.
18pub struct CameraFramePlugin;
19
20impl Plugin for CameraFramePlugin {
21    fn build(&self, app: &mut App) {
22        app.add_plugins(ExtractPlugin::<CameraFrame>::default())
23            .add_systems(RenderSchedule::Prepare, prepare_camera);
24    }
25}
26
27impl Extract for CameraFrame {
28    fn extract(&self, ctx: &mut ExtractContext<'_>) {
29        let mut value = self.clone();
30        value.apply(ctx.transform());
31        let mut entity = ctx.entity::<CameraFrame>();
32        if entity.get::<CameraFrame>() != Some(&value) {
33            entity.insert(value);
34        }
35    }
36}
37
38fn prepare_camera(
39    ctx: Res<WgpuContext>,
40    dimensions: Res<RenderDimensions>,
41    cameras: Query<(&SceneOrder, &CameraFrame)>,
42    orders: Query<&SceneOrder>,
43    mut viewport: ResMut<ViewportGpuPacket>,
44) {
45    let camera = single_camera(cameras.iter().map(|(_, camera)| camera));
46    let aspect_ratio = dimensions.width as f64 / dimensions.height as f64;
47    let order_span = orders.iter().map(|order| order.0).max().unwrap_or(0);
48    let uniform = ViewportUniform::new(
49        camera.projection_matrix(aspect_ratio).as_mat4(),
50        camera.view_matrix().as_mat4(),
51        Vec2::new(
52            (camera.frame_height * aspect_ratio) as f32 / 2.0,
53            camera.frame_height as f32 / 2.0,
54        ),
55        DEPTH_ORDER_SPAN / order_span.max(1) as f32,
56    );
57    viewport.update(&ctx, &uniform);
58}
59
60fn single_camera<'a>(mut cameras: impl Iterator<Item = &'a CameraFrame>) -> &'a CameraFrame {
61    let camera = cameras
62        .next()
63        .expect("D0002 requires exactly one active CameraFrame per rendered frame");
64    assert!(
65        cameras.next().is_none(),
66        "D0002 requires exactly one active CameraFrame per rendered frame"
67    );
68    camera
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn single_camera_is_accepted() {
77        let camera = CameraFrame::default();
78        assert_eq!(single_camera([&camera].into_iter()), &camera);
79    }
80
81    #[test]
82    #[should_panic(expected = "D0002 requires exactly one active CameraFrame")]
83    fn missing_camera_is_rejected() {
84        single_camera(std::iter::empty());
85    }
86
87    #[test]
88    #[should_panic(expected = "D0002 requires exactly one active CameraFrame")]
89    fn multiple_cameras_are_rejected() {
90        let camera = CameraFrame::default();
91        single_camera([&camera, &camera].into_iter());
92    }
93}