ranim_items/camera/
render.rs1use 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
14pub 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}