1use std::f32::consts::PI;
4
5use bevy::{
6 camera::Viewport,
7 feathers::{
8 controls::FeathersButton, dark_theme::create_dark_theme, display::caption, theme::UiTheme,
9 FeathersPlugins,
10 },
11 light::CascadeShadowConfigBuilder,
12 prelude::*,
13 ui_widgets::Activate,
14 window::WindowResized,
15};
16
17fn main() {
18 App::new()
19 .add_plugins((DefaultPlugins, FeathersPlugins))
20 .insert_resource(UiTheme(create_dark_theme()))
21 .add_systems(Startup, setup)
22 .add_systems(Update, set_camera_viewports)
23 .add_observer(on_activate_rotate_camera)
24 .run();
25}
26
27fn setup(
29 mut commands: Commands,
30 asset_server: Res<AssetServer>,
31 mut meshes: ResMut<Assets<Mesh>>,
32 mut materials: ResMut<Assets<StandardMaterial>>,
33) {
34 commands.spawn((
36 Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0))),
37 MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
38 ));
39
40 commands.spawn(WorldAssetRoot(
41 asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
42 ));
43
44 commands.spawn((
46 Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
47 DirectionalLight {
48 shadow_maps_enabled: true,
49 ..default()
50 },
51 CascadeShadowConfigBuilder {
52 num_cascades: if cfg!(all(
53 feature = "webgl2",
54 target_arch = "wasm32",
55 not(feature = "webgpu")
56 )) {
57 1
59 } else {
60 2
61 },
62 first_cascade_far_bound: 200.0,
63 maximum_distance: 280.0,
64 ..default()
65 }
66 .build(),
67 ));
68
69 for (index, (camera_name, camera_pos)) in [
71 ("Player 1", Vec3::new(0.0, 200.0, -150.0)),
72 ("Player 2", Vec3::new(150.0, 150., 50.0)),
73 ("Player 3", Vec3::new(100.0, 150., -150.0)),
74 ("Player 4", Vec3::new(-100.0, 80., 150.0)),
75 ]
76 .iter()
77 .enumerate()
78 {
79 let camera = commands
80 .spawn((
81 Camera3d::default(),
82 Transform::from_translation(*camera_pos).looking_at(Vec3::ZERO, Vec3::Y),
83 Camera {
84 order: index as isize,
86 ..default()
87 },
88 CameraPosition {
89 pos: UVec2::new((index % 2) as u32, (index / 2) as u32),
90 },
91 ))
92 .id();
93
94 let buttons_entity = commands.spawn_scene(buttons_panel()).id();
96 commands
97 .spawn((
98 UiTargetCamera(camera),
99 Node {
100 width: percent(100),
101 height: percent(100),
102 ..default()
103 },
104 children![(
105 Text::new(*camera_name),
106 Node {
107 margin: px(12).all(),
108 ..default()
109 },
110 ),],
111 ))
112 .add_child(buttons_entity);
113 }
114
115 fn buttons_panel() -> impl Scene {
116 bsn! {
117 Node {
118 position_type: PositionType::Absolute,
119 width: percent(100),
120 height: percent(100),
121 display: Display::Flex,
122 flex_direction: FlexDirection::Row,
123 justify_content: JustifyContent::SpaceBetween,
124 align_items: AlignItems::Center,
125 padding: UiRect::all(px(20)),
126 }
127 Children [
128 @rotate_button("<", Direction::Left)
129 --
130 @rotate_button(">", Direction::Right)
131 ]
132 }
133 }
134
135 fn rotate_button(text_caption: &'static str, direction: Direction) -> impl Scene {
136 bsn! {
137 RotateCamera(direction)
138 @FeathersButton {
139 @caption: bsn! { @caption(text_caption) },
140 }
141 Node {
142 width: px(40),
143 }
144 }
145 }
146}
147
148#[derive(Component)]
149struct CameraPosition {
150 pos: UVec2,
151}
152
153#[derive(Component, Clone, Default, PartialEq)]
154struct RotateCamera(Direction);
155
156#[derive(Clone, Default, PartialEq)]
157enum Direction {
158 #[default]
159 Left,
160 Right,
161}
162
163fn set_camera_viewports(
164 windows: Query<&Window>,
165 mut window_resized_reader: MessageReader<WindowResized>,
166 mut query: Query<(&CameraPosition, &mut Camera)>,
167) {
168 for window_resized in window_resized_reader.read() {
172 let window = windows.get(window_resized.window).unwrap();
173 let size = window.physical_size() / 2;
174
175 for (camera_position, mut camera) in &mut query {
176 camera.viewport = Some(Viewport {
177 physical_position: camera_position.pos * size,
178 physical_size: size,
179 ..default()
180 });
181 }
182 }
183}
184
185fn on_activate_rotate_camera(
186 event: On<Activate>,
187 button_query: Query<(&ComputedUiTargetCamera, &RotateCamera), With<FeathersButton>>,
188 mut camera_query: Query<&mut Transform, With<Camera>>,
189) {
190 let Ok((computed_target, RotateCamera(direction))) = button_query.get(event.entity) else {
191 return;
192 };
193
194 if let Some(mut camera_transform) = computed_target
197 .get()
198 .and_then(|camera| camera_query.get_mut(camera).ok())
199 {
200 let angle = match direction {
201 Direction::Left => -0.1,
202 Direction::Right => 0.1,
203 };
204 camera_transform.rotate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, angle));
205 }
206}