1use bevy::{
32 camera::{
33 visibility::{VisibilitySystems, VisibleEntities},
34 Viewport,
35 },
36 camera_controller::free_camera::{FreeCamera, FreeCameraPlugin, FreeCameraState},
37 gizmos::aabb::ShowAabbGizmo,
38 input::common_conditions::input_just_pressed,
39 prelude::*,
40};
41use std::f32::consts::PI;
42
43fn main() {
44 App::new()
45 .add_plugins((
46 DefaultPlugins.set(WindowPlugin {
47 primary_window: Some(Window {
48 resizable: false,
49 ..default()
50 }),
51 ..default()
52 }),
53 FreeCameraPlugin,
54 ))
55 .add_systems(Startup, setup)
56 .add_systems(
57 Update,
58 (
59 move_shapes,
60 move_free_camera_to_my_camera.run_if(input_just_pressed(KeyCode::Digit1)),
61 move_free_camera_to_original_position.run_if(input_just_pressed(KeyCode::Digit2)),
62 ),
63 )
64 .add_systems(
65 PostUpdate,
69 update_shape_aabb_colors.after(VisibilitySystems::CheckVisibility),
70 )
71 .run();
72}
73
74#[derive(Component)]
76struct ShapeRing;
77
78#[derive(Component, Default)]
82#[require(ShowAabbGizmo)]
83struct MyShape;
84
85#[derive(Component)]
87#[require(MyShape)]
88struct WallShape;
89
90#[derive(Component)]
94#[require(ShowFrustumGizmo)]
95struct MyCamera;
96
97const SHAPE_RING_RADIUS: f32 = 10.0;
98const WALL_SHAPE_TIMER_DURATION_SECS: f32 = 8.0;
99const FREE_CAMERA_START_TRANSFORM: Transform = Transform::from_xyz(-20., 10., 22.);
100const FREE_CAMERA_START_TARGET: Vec3 = Vec3::new(7., 1.5, 0.);
101
102fn setup(
103 mut commands: Commands,
104 windows: Query<&Window>,
105 mut config_store: ResMut<GizmoConfigStore>,
106 mut meshes: ResMut<Assets<Mesh>>,
107 mut materials: ResMut<Assets<StandardMaterial>>,
108) -> Result {
109 let window = windows.single()?;
110 let free_camera = commands
112 .spawn((
113 Camera3d::default(),
114 FREE_CAMERA_START_TRANSFORM.looking_at(FREE_CAMERA_START_TARGET, Vec3::Y),
115 FreeCamera::default(),
116 ))
117 .id();
118
119 let my_camera = commands
122 .spawn((
123 Camera3d::default(),
124 Transform::from_xyz(0., 1.5, 0.).looking_at(Vec3::new(1.0, 1.5, 0.), Vec3::Y),
125 Camera {
126 order: 1,
127 viewport: Some(Viewport {
129 physical_position: window.physical_size() * 2 / 3,
130 physical_size: window.physical_size() / 3,
131 ..default()
132 }),
133 msaa_writeback: MsaaWriteback::Off,
135 ..default()
136 },
137 MyCamera,
138 ))
139 .id();
140
141 commands.spawn((
143 UiTargetCamera(free_camera),
144 Node {
145 width: percent(100),
146 height: percent(100),
147 ..default()
148 },
149 children![(
150 Text::new(
151 "This example utilizes free camera controls i.e. move with WASD and mouse grab to change orientation.\n\
152 Press '1' to move the free camera to where MyCamera is, matching its view frustum.\n\
153 Press '2' to move the free camera to its initial position in the example.",
154 ),
155 Node {
156 position_type: PositionType::Absolute,
157 top: px(12),
158 left: px(12),
159 ..default()
160 },
161 )]
162 ));
163 commands.spawn((
165 UiTargetCamera(my_camera),
166 Node {
167 width: percent(100),
168 height: percent(100),
169 ..default()
170 },
171 children![(
172 Text::new("View of MyCamera"),
173 Node {
174 position_type: PositionType::Absolute,
175 bottom: px(12),
176 right: px(100),
177 ..default()
178 },
179 )],
180 ));
181
182 commands.spawn((
184 Mesh3d(
185 meshes.add(
186 Plane3d::default()
187 .mesh()
188 .size(SHAPE_RING_RADIUS * 4., SHAPE_RING_RADIUS * 4.),
189 ),
190 ),
191 MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
192 ));
193 commands.spawn((
195 Mesh3d(meshes.add(Plane3d::default().mesh().size(5., 5.))),
196 MeshMaterial3d(materials.add(Color::srgb(0.3, 0.3, 0.5))),
197 Transform::from_xyz(20., 2.5, 10.).with_rotation(Quat::from_rotation_z(PI / 2.)),
198 ));
199 commands.spawn((
201 PointLight {
202 shadow_maps_enabled: true,
203 ..default()
204 },
205 Transform::from_xyz(0.0, 10.0, 0.0),
206 ));
207
208 let (_, aabb_gizmo_config) = config_store.config_mut::<AabbGizmoConfigGroup>();
210 aabb_gizmo_config.default_color = Some(Color::LinearRgba(LinearRgba::RED));
211
212 let white_matl = materials.add(Color::WHITE);
214 let shapes = [
215 meshes.add(Cuboid {
216 half_size: Vec3::new(2., 0.5, 1.),
217 }),
218 meshes.add(Tetrahedron {
219 vertices: [
220 Vec3::new(3., 4., 3.),
221 Vec3::new(-0.5, 4., -0.5),
222 Vec3::new(-0.5, -0.5, 3.),
223 Vec3::new(3., -0.5, -0.5),
224 ],
225 }),
226 meshes.add(Cylinder {
227 radius: 0.1,
228 half_height: 1.5,
229 }),
230 meshes.add(Cuboid {
231 half_size: Vec3::new(1., 0.1, 2.),
232 }),
233 meshes.add(Sphere::default().mesh().ico(5).unwrap()),
234 ];
235 let shapes_len = shapes.len() as f32;
236 let mut shape_ring = commands.spawn((Transform::default(), Visibility::default(), ShapeRing));
237 for (i, shape) in shapes.into_iter().enumerate() {
238 let shape_angle = i as f32 * 2. * PI / shapes_len;
240 let (s, c) = ops::sin_cos(shape_angle);
241 let (x, z) = (SHAPE_RING_RADIUS * c, SHAPE_RING_RADIUS * s);
242 shape_ring.with_child((
243 Mesh3d(shape),
244 MeshMaterial3d(white_matl.clone()),
245 Transform::from_xyz(x, 1.5, z).with_rotation(Quat::from_rotation_x(-PI / 4.)),
246 MyShape,
247 ));
248 }
249
250 let wall_shape = meshes.add(Torus::default());
252 commands.spawn((
253 Mesh3d(wall_shape),
254 MeshMaterial3d(white_matl.clone()),
255 Transform::from_xyz(25., 1.5, 12.5).with_rotation(Quat::from_rotation_x(-PI / 4.)),
256 WallShape,
257 ));
258
259 Ok(())
260}
261
262fn move_shapes(
267 time: Res<Time>,
268 mut timer: Local<Timer>,
269 mut ring_query: Query<&mut Transform, (With<ShapeRing>, Without<MyShape>)>,
270 mut shape_query: Query<(&mut Transform, Has<WallShape>), (With<MyShape>, Without<ShapeRing>)>,
271) -> Result {
272 if timer.duration().is_zero() {
274 *timer = Timer::from_seconds(WALL_SHAPE_TIMER_DURATION_SECS, TimerMode::Repeating);
275 }
276 timer.tick(time.delta());
277 let dt = time.delta_secs();
278
279 for (mut transform, has_wall_shape) in &mut shape_query {
281 transform.rotate_y(dt / 2.);
282 if has_wall_shape {
283 transform.translation.y = if timer.elapsed_secs() < WALL_SHAPE_TIMER_DURATION_SECS / 2.0
286 {
287 1.5 + 15.0 * timer.elapsed_secs() / (WALL_SHAPE_TIMER_DURATION_SECS / 2.0)
288 } else {
289 1.5 + 15.0 * (WALL_SHAPE_TIMER_DURATION_SECS - timer.elapsed_secs())
290 / (WALL_SHAPE_TIMER_DURATION_SECS / 2.0)
291 }
292 }
293 }
294
295 let transform = &mut ring_query.single_mut()?;
297 transform.rotate_y(dt / 3.);
298
299 Ok(())
300}
301
302fn update_shape_aabb_colors(
305 view_query: Query<&VisibleEntities, With<MyCamera>>,
306 mut gizmo_query: Query<&mut ShowAabbGizmo, With<MyShape>>,
307) -> Result {
308 for mut shape_gizmo in &mut gizmo_query {
310 shape_gizmo.color = None;
311 }
312
313 let visible_entities = view_query.single()?;
316 for entity in visible_entities.entities.values().flatten() {
317 if let Ok(mut shape_gizmo) = gizmo_query.get_mut(*entity) {
318 shape_gizmo.color = Some(Color::LinearRgba(LinearRgba::GREEN));
319 }
320 }
321 Ok(())
322}
323
324fn move_free_camera_to_my_camera(
328 view_query: Query<&Transform, With<MyCamera>>,
329 free_camera_query: Query<
330 (&mut Transform, &mut FreeCameraState),
331 (With<Camera3d>, Without<MyCamera>),
332 >,
333) -> Result {
334 let my_camera_transform = view_query.single()?;
335 move_free_camera(*my_camera_transform, free_camera_query)
336}
337
338fn move_free_camera_to_original_position(
340 free_camera_query: Query<
341 (&mut Transform, &mut FreeCameraState),
342 (With<Camera3d>, Without<MyCamera>),
343 >,
344) -> Result {
345 move_free_camera(
346 FREE_CAMERA_START_TRANSFORM.looking_at(FREE_CAMERA_START_TARGET, Vec3::Y),
347 free_camera_query,
348 )
349}
350
351fn move_free_camera(
352 new_transform: Transform,
353 mut free_camera_query: Query<
354 (&mut Transform, &mut FreeCameraState),
355 (With<Camera3d>, Without<MyCamera>),
356 >,
357) -> Result {
358 let (mut transform, mut state) = free_camera_query.single_mut()?;
359 *transform = new_transform;
360
361 let (yaw, pitch, _roll) = transform.rotation.to_euler(EulerRot::YXZ);
363 state.yaw = yaw;
364 state.pitch = pitch;
365
366 Ok(())
367}