| 1 |
|
| 2 |
|
| 3 |
use std::f32::consts::FRAC_PI_2; |
| 4 |
|
| 5 |
use crate::radio::{feathers_option_buttons, main_ui_node_scene, RadioButtonOptionValue}; |
| 6 |
use bevy::camera::RenderTarget; |
| 7 |
use bevy::{ |
| 8 |
color::palettes::css::GREEN, |
| 9 |
feathers::{ |
| 10 |
controls::FeathersRadio, dark_theme::create_dark_theme, theme::UiTheme, FeathersPlugins, |
| 11 |
}, |
| 12 |
input::mouse::AccumulatedMouseMotion, |
| 13 |
math::{reflection_matrix, uvec2, vec3}, |
| 14 |
pbr::{ExtendedMaterial, MaterialExtension}, |
| 15 |
picking::hover::Hovered, |
| 16 |
prelude::*, |
| 17 |
render::render_resource::{AsBindGroup, TextureFormat}, |
| 18 |
shader::ShaderRef, |
| 19 |
ui_widgets::{radio_self_update, ValueChange}, |
| 20 |
window::{PrimaryWindow, WindowResized}, |
| 21 |
}; |
| 22 |
|
| 23 |
#[path = "../helpers/radio.rs"] |
| 24 |
mod radio; |
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
#[derive(Resource)] |
| 29 |
struct MirrorImage(Handle<Image>); |
| 30 |
|
| 31 |
|
| 32 |
#[derive(Component)] |
| 33 |
struct MirrorCamera; |
| 34 |
|
| 35 |
|
| 36 |
#[derive(Component)] |
| 37 |
struct Mirror; |
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
#[derive(Clone, AsBindGroup, Asset, Reflect)] |
| 52 |
struct ScreenSpaceTextureExtension { |
| 53 |
|
| 54 |
|
| 55 |
#[uniform(100)] |
| 56 |
dummy: f32, |
| 57 |
} |
| 58 |
|
| 59 |
impl MaterialExtension for ScreenSpaceTextureExtension { |
| 60 |
fn fragment_shader() -> ShaderRef { |
| 61 |
"shaders/screen_space_texture_material.wesl".into() |
| 62 |
} |
| 63 |
} |
| 64 |
|
| 65 |
|
| 66 |
|
| 67 |
#[derive(Clone, Copy, PartialEq, Default)] |
| 68 |
enum DragAction { |
| 69 |
|
| 70 |
#[default] |
| 71 |
MoveCamera, |
| 72 |
|
| 73 |
MoveFox, |
| 74 |
} |
| 75 |
|
| 76 |
|
| 77 |
|
| 78 |
|
| 79 |
#[derive(Resource, Default)] |
| 80 |
struct AppStatus { |
| 81 |
|
| 82 |
|
| 83 |
drag_action: DragAction, |
| 84 |
} |
| 85 |
|
| 86 |
|
| 87 |
#[derive(Clone, Copy, Component)] |
| 88 |
struct HelpText; |
| 89 |
|
| 90 |
|
| 91 |
const CAMERA_TARGET: Vec3 = vec3(-25.0, 20.0, 0.0); |
| 92 |
|
| 93 |
const CAMERA_ORBIT_DISTANCE: f32 = 500.0; |
| 94 |
|
| 95 |
|
| 96 |
const CAMERA_PITCH_SPEED: f32 = 0.003; |
| 97 |
|
| 98 |
|
| 99 |
const CAMERA_YAW_SPEED: f32 = 0.004; |
| 100 |
|
| 101 |
const CAMERA_PITCH_LIMIT: f32 = FRAC_PI_2 - 0.01; |
| 102 |
|
| 103 |
|
| 104 |
|
| 105 |
|
| 106 |
const MIRROR_ROTATION_ANGLE: f32 = -FRAC_PI_2; |
| 107 |
const MIRROR_POSITION: Vec3 = vec3(-25.0, 75.0, 0.0); |
| 108 |
|
| 109 |
|
| 110 |
static FOX_ASSET_PATH: &str = "models/animated/Fox.glb"; |
| 111 |
|
| 112 |
|
| 113 |
fn main() { |
| 114 |
App::new() |
| 115 |
.add_plugins(DefaultPlugins.set(WindowPlugin { |
| 116 |
primary_window: Some(Window { |
| 117 |
title: "Bevy Mirror Example".into(), |
| 118 |
..default() |
| 119 |
}), |
| 120 |
..default() |
| 121 |
})) |
| 122 |
.add_plugins(MaterialPlugin::< |
| 123 |
ExtendedMaterial<StandardMaterial, ScreenSpaceTextureExtension>, |
| 124 |
>::default()) |
| 125 |
.add_plugins(FeathersPlugins) |
| 126 |
.insert_resource(UiTheme(create_dark_theme())) |
| 127 |
.init_resource::<AppStatus>() |
| 128 |
.add_systems(Startup, setup) |
| 129 |
.add_systems(Update, handle_window_resize_messages) |
| 130 |
.add_systems(Update, (move_camera_on_mouse_down, move_fox_on_mouse_down)) |
| 131 |
.add_observer(handle_drag_action_change) |
| 132 |
.add_observer(radio_self_update) |
| 133 |
.add_systems( |
| 134 |
Update, |
| 135 |
update_mirror_camera_on_main_camera_transform_change.after(move_camera_on_mouse_down), |
| 136 |
) |
| 137 |
.add_systems(Update, play_fox_animation) |
| 138 |
.add_systems(Update, update_help_text) |
| 139 |
.run(); |
| 140 |
} |
| 141 |
|
| 142 |
|
| 143 |
fn setup( |
| 144 |
mut commands: Commands, |
| 145 |
windows_query: Query<&Window>, |
| 146 |
asset_server: Res<AssetServer>, |
| 147 |
mut meshes: ResMut<Assets<Mesh>>, |
| 148 |
mut standard_materials: ResMut<Assets<StandardMaterial>>, |
| 149 |
mut screen_space_texture_materials: ResMut< |
| 150 |
Assets<ExtendedMaterial<StandardMaterial, ScreenSpaceTextureExtension>>, |
| 151 |
>, |
| 152 |
mut images: ResMut<Assets<Image>>, |
| 153 |
app_status: Res<AppStatus>, |
| 154 |
) { |
| 155 |
|
| 156 |
let camera_projection = PerspectiveProjection::default(); |
| 157 |
let camera_transform = spawn_main_camera(&mut commands, &camera_projection); |
| 158 |
|
| 159 |
|
| 160 |
spawn_light(&mut commands); |
| 161 |
|
| 162 |
|
| 163 |
spawn_ground_plane(&mut commands, &mut meshes, &mut standard_materials); |
| 164 |
spawn_fox(&mut commands, &asset_server); |
| 165 |
|
| 166 |
|
| 167 |
let mirror_render_target_image = |
| 168 |
create_mirror_texture_resource(&mut commands, &windows_query, &mut images); |
| 169 |
let mirror_transform = spawn_mirror( |
| 170 |
&mut commands, |
| 171 |
&mut meshes, |
| 172 |
&mut screen_space_texture_materials, |
| 173 |
mirror_render_target_image.clone(), |
| 174 |
); |
| 175 |
spawn_mirror_camera( |
| 176 |
&mut commands, |
| 177 |
&camera_transform, |
| 178 |
&camera_projection, |
| 179 |
&mirror_transform, |
| 180 |
mirror_render_target_image, |
| 181 |
); |
| 182 |
|
| 183 |
|
| 184 |
spawn_buttons(&mut commands); |
| 185 |
spawn_help_text(&mut commands, &app_status); |
| 186 |
} |
| 187 |
|
| 188 |
|
| 189 |
fn spawn_main_camera( |
| 190 |
commands: &mut Commands, |
| 191 |
camera_projection: &PerspectiveProjection, |
| 192 |
) -> Transform { |
| 193 |
let camera_transform = Transform::from_translation( |
| 194 |
vec3(-2.0, 1.0, -2.0).normalize_or_zero() * CAMERA_ORBIT_DISTANCE, |
| 195 |
) |
| 196 |
.looking_at(CAMERA_TARGET, Vec3::Y); |
| 197 |
|
| 198 |
commands.spawn(( |
| 199 |
Camera3d::default(), |
| 200 |
camera_transform, |
| 201 |
Projection::Perspective(camera_projection.clone()), |
| 202 |
)); |
| 203 |
|
| 204 |
camera_transform |
| 205 |
} |
| 206 |
|
| 207 |
|
| 208 |
fn spawn_light(commands: &mut Commands) { |
| 209 |
commands.spawn(( |
| 210 |
DirectionalLight { |
| 211 |
illuminance: 5000.0, |
| 212 |
..default() |
| 213 |
}, |
| 214 |
Transform::from_xyz(-85.0, 16.0, -200.0).looking_at(vec3(-50.0, 0.0, 100.0), Vec3::Y), |
| 215 |
)); |
| 216 |
} |
| 217 |
|
| 218 |
|
| 219 |
fn spawn_ground_plane( |
| 220 |
commands: &mut Commands, |
| 221 |
meshes: &mut Assets<Mesh>, |
| 222 |
standard_materials: &mut Assets<StandardMaterial>, |
| 223 |
) { |
| 224 |
commands.spawn(( |
| 225 |
Mesh3d(meshes.add(Circle::new(200.0))), |
| 226 |
MeshMaterial3d(standard_materials.add(Color::from(GREEN))), |
| 227 |
Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2)) |
| 228 |
.with_translation(vec3(-25.0, 0.0, 0.0)), |
| 229 |
)); |
| 230 |
} |
| 231 |
|
| 232 |
|
| 233 |
|
| 234 |
fn create_mirror_texture_resource( |
| 235 |
commands: &mut Commands, |
| 236 |
windows_query: &Query<&Window>, |
| 237 |
images: &mut Assets<Image>, |
| 238 |
) -> Handle<Image> { |
| 239 |
let window = windows_query.iter().next().expect("No window found"); |
| 240 |
let window_size = uvec2(window.physical_width(), window.physical_height()); |
| 241 |
let image = create_mirror_texture_image(images, window_size); |
| 242 |
commands.insert_resource(MirrorImage(image.clone())); |
| 243 |
image |
| 244 |
} |
| 245 |
|
| 246 |
|
| 247 |
fn spawn_mirror_camera( |
| 248 |
commands: &mut Commands, |
| 249 |
camera_transform: &Transform, |
| 250 |
camera_projection: &PerspectiveProjection, |
| 251 |
mirror_transform: &Transform, |
| 252 |
mirror_render_target: Handle<Image>, |
| 253 |
) { |
| 254 |
let (mirror_camera_transform, mirror_camera_projection) = |
| 255 |
calculate_mirror_camera_transform_and_projection( |
| 256 |
camera_transform, |
| 257 |
camera_projection, |
| 258 |
mirror_transform, |
| 259 |
); |
| 260 |
|
| 261 |
commands.spawn(( |
| 262 |
Camera3d::default(), |
| 263 |
Camera { |
| 264 |
order: -1, |
| 265 |
|
| 266 |
|
| 267 |
|
| 268 |
invert_culling: true, |
| 269 |
..default() |
| 270 |
}, |
| 271 |
RenderTarget::Image(mirror_render_target.clone().into()), |
| 272 |
mirror_camera_transform, |
| 273 |
Projection::Perspective(mirror_camera_projection), |
| 274 |
MirrorCamera, |
| 275 |
)); |
| 276 |
} |
| 277 |
|
| 278 |
|
| 279 |
|
| 280 |
|
| 281 |
|
| 282 |
fn spawn_fox(commands: &mut Commands, asset_server: &AssetServer) { |
| 283 |
commands.spawn(( |
| 284 |
WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_ASSET_PATH))), |
| 285 |
Transform::from_xyz(-50.0, 0.0, -100.0), |
| 286 |
)); |
| 287 |
} |
| 288 |
|
| 289 |
|
| 290 |
fn spawn_mirror( |
| 291 |
commands: &mut Commands, |
| 292 |
meshes: &mut Assets<Mesh>, |
| 293 |
screen_space_texture_materials: &mut Assets< |
| 294 |
ExtendedMaterial<StandardMaterial, ScreenSpaceTextureExtension>, |
| 295 |
>, |
| 296 |
mirror_render_target: Handle<Image>, |
| 297 |
) -> Transform { |
| 298 |
let mirror_transform = Transform::from_scale(vec3(300.0, 1.0, 150.0)) |
| 299 |
.with_rotation(Quat::from_rotation_x(MIRROR_ROTATION_ANGLE)) |
| 300 |
.with_translation(MIRROR_POSITION); |
| 301 |
|
| 302 |
commands.spawn(( |
| 303 |
Mesh3d(meshes.add(Plane3d::default().mesh().size(1.0, 1.0))), |
| 304 |
MeshMaterial3d(screen_space_texture_materials.add(ExtendedMaterial { |
| 305 |
base: StandardMaterial { |
| 306 |
base_color: Color::BLACK, |
| 307 |
emissive: Color::WHITE.into(), |
| 308 |
emissive_texture: Some(mirror_render_target), |
| 309 |
perceptual_roughness: 0.0, |
| 310 |
metallic: 1.0, |
| 311 |
..default() |
| 312 |
}, |
| 313 |
extension: ScreenSpaceTextureExtension { dummy: 0.0 }, |
| 314 |
})), |
| 315 |
mirror_transform, |
| 316 |
Mirror, |
| 317 |
)); |
| 318 |
|
| 319 |
mirror_transform |
| 320 |
} |
| 321 |
|
| 322 |
|
| 323 |
fn spawn_buttons(commands: &mut Commands) { |
| 324 |
|
| 325 |
|
| 326 |
commands.spawn_scene(bsn! { |
| 327 |
@main_ui_node_scene() |
| 328 |
Children [@feathers_option_buttons( |
| 329 |
"Drag Action", |
| 330 |
&[ |
| 331 |
(DragAction::MoveCamera, "Move Camera"), |
| 332 |
(DragAction::MoveFox, "Move Fox"), |
| 333 |
], |
| 334 |
0, |
| 335 |
)] |
| 336 |
}); |
| 337 |
} |
| 338 |
|
| 339 |
|
| 340 |
|
| 341 |
fn calculate_mirror_camera_transform_and_projection( |
| 342 |
main_camera_transform: &Transform, |
| 343 |
main_camera_projection: &PerspectiveProjection, |
| 344 |
mirror_transform: &Transform, |
| 345 |
) -> (Transform, PerspectiveProjection) { |
| 346 |
|
| 347 |
|
| 348 |
|
| 349 |
|
| 350 |
|
| 351 |
|
| 352 |
|
| 353 |
|
| 354 |
let mirror_camera_transform = Transform::from_matrix( |
| 355 |
Mat4::from_mat3a(reflection_matrix(Vec3::NEG_Z)) * main_camera_transform.to_matrix(), |
| 356 |
); |
| 357 |
|
| 358 |
|
| 359 |
|
| 360 |
|
| 361 |
let distance_from_camera_to_mirror = InfinitePlane3d::new(mirror_transform.rotation * Vec3::Y) |
| 362 |
.signed_distance( |
| 363 |
Isometry3d::IDENTITY, |
| 364 |
mirror_transform.translation - main_camera_transform.translation, |
| 365 |
); |
| 366 |
|
| 367 |
|
| 368 |
let view_from_world = main_camera_transform.compute_affine().matrix3.inverse(); |
| 369 |
let mirror_projection_plane_normal = |
| 370 |
(view_from_world * (mirror_transform.rotation * Vec3::NEG_Y)).normalize(); |
| 371 |
|
| 372 |
|
| 373 |
|
| 374 |
|
| 375 |
let mirror_camera_projection = PerspectiveProjection { |
| 376 |
near_clip_plane: mirror_projection_plane_normal.extend(distance_from_camera_to_mirror), |
| 377 |
..*main_camera_projection |
| 378 |
}; |
| 379 |
|
| 380 |
(mirror_camera_transform, mirror_camera_projection) |
| 381 |
} |
| 382 |
|
| 383 |
|
| 384 |
|
| 385 |
|
| 386 |
|
| 387 |
|
| 388 |
fn handle_window_resize_messages( |
| 389 |
windows_query: Query<&Window>, |
| 390 |
mut mirror_cameras_query: Query<&mut RenderTarget, With<MirrorCamera>>, |
| 391 |
mut images: ResMut<Assets<Image>>, |
| 392 |
mut mirror_image: ResMut<MirrorImage>, |
| 393 |
mut screen_space_texture_materials: ResMut< |
| 394 |
Assets<ExtendedMaterial<StandardMaterial, ScreenSpaceTextureExtension>>, |
| 395 |
>, |
| 396 |
mut resize_messages: MessageReader<WindowResized>, |
| 397 |
) { |
| 398 |
|
| 399 |
|
| 400 |
let Some(resize_message) = resize_messages.read().next() else { |
| 401 |
return; |
| 402 |
}; |
| 403 |
let Ok(window) = windows_query.get(resize_message.window) else { |
| 404 |
return; |
| 405 |
}; |
| 406 |
|
| 407 |
let window_size = uvec2(window.physical_width(), window.physical_height()); |
| 408 |
let image = create_mirror_texture_image(&mut images, window_size); |
| 409 |
images.remove(mirror_image.0.id()); |
| 410 |
|
| 411 |
mirror_image.0 = image.clone(); |
| 412 |
|
| 413 |
for mut target in mirror_cameras_query.iter_mut() { |
| 414 |
*target = image.clone().into(); |
| 415 |
} |
| 416 |
|
| 417 |
for (_, material) in screen_space_texture_materials.iter_mut() { |
| 418 |
material.base.emissive_texture = Some(image.clone()); |
| 419 |
} |
| 420 |
} |
| 421 |
|
| 422 |
|
| 423 |
fn create_mirror_texture_image(images: &mut Assets<Image>, window_size: UVec2) -> Handle<Image> { |
| 424 |
images.add(Image::new_target_texture( |
| 425 |
window_size.x, |
| 426 |
window_size.y, |
| 427 |
TextureFormat::Bgra8UnormSrgb, |
| 428 |
None, |
| 429 |
)) |
| 430 |
} |
| 431 |
|
| 432 |
|
| 433 |
fn move_fox_on_mouse_down( |
| 434 |
mut scene_roots_query: Query<&mut Transform, With<WorldAssetRoot>>, |
| 435 |
windows_query: Query<&Window, With<PrimaryWindow>>, |
| 436 |
cameras_query: Query<(&Camera, &GlobalTransform)>, |
| 437 |
radio_hovered_query: Query<&Hovered, With<FeathersRadio>>, |
| 438 |
buttons: Res<ButtonInput<MouseButton>>, |
| 439 |
app_status: Res<AppStatus>, |
| 440 |
) { |
| 441 |
|
| 442 |
|
| 443 |
|
| 444 |
if app_status.drag_action != DragAction::MoveFox |
| 445 |
|| !buttons.pressed(MouseButton::Left) |
| 446 |
|| radio_hovered_query.iter().any(Hovered::get) |
| 447 |
{ |
| 448 |
return; |
| 449 |
} |
| 450 |
|
| 451 |
|
| 452 |
let Some(mouse_position) = windows_query |
| 453 |
.iter() |
| 454 |
.next() |
| 455 |
.and_then(Window::cursor_position) |
| 456 |
else { |
| 457 |
return; |
| 458 |
}; |
| 459 |
|
| 460 |
|
| 461 |
let Some((camera, camera_transform)) = cameras_query.iter().next() else { |
| 462 |
return; |
| 463 |
}; |
| 464 |
|
| 465 |
|
| 466 |
let Ok(ray) = camera.viewport_to_world(camera_transform, mouse_position) else { |
| 467 |
return; |
| 468 |
}; |
| 469 |
let Some(ray_distance) = ray.intersect_plane(Vec3::ZERO, InfinitePlane3d::new(Vec3::Y)) else { |
| 470 |
return; |
| 471 |
}; |
| 472 |
let plane_intersection = ray.origin + ray.direction.normalize() * ray_distance; |
| 473 |
|
| 474 |
|
| 475 |
for mut transform in scene_roots_query.iter_mut() { |
| 476 |
transform.translation = transform.translation.with_xz(plane_intersection.xz()); |
| 477 |
} |
| 478 |
} |
| 479 |
|
| 480 |
|
| 481 |
|
| 482 |
fn handle_drag_action_change( |
| 483 |
event: On<ValueChange<Entity>>, |
| 484 |
new_value_query: Query<&RadioButtonOptionValue<DragAction>>, |
| 485 |
mut app_status: ResMut<AppStatus>, |
| 486 |
) { |
| 487 |
let Ok(RadioButtonOptionValue(drag_action)) = new_value_query.get(event.value) else { |
| 488 |
return; |
| 489 |
}; |
| 490 |
|
| 491 |
app_status.drag_action = *drag_action; |
| 492 |
} |
| 493 |
|
| 494 |
|
| 495 |
|
| 496 |
|
| 497 |
fn move_camera_on_mouse_down( |
| 498 |
mut main_cameras_query: Query<&mut Transform, (With<Camera>, Without<MirrorCamera>)>, |
| 499 |
radio_hovered_query: Query<&Hovered, With<FeathersRadio>>, |
| 500 |
mouse_buttons: Res<ButtonInput<MouseButton>>, |
| 501 |
mouse_motion: Res<AccumulatedMouseMotion>, |
| 502 |
app_status: Res<AppStatus>, |
| 503 |
) { |
| 504 |
|
| 505 |
|
| 506 |
|
| 507 |
if app_status.drag_action != DragAction::MoveCamera |
| 508 |
|| !mouse_buttons.pressed(MouseButton::Left) |
| 509 |
|| radio_hovered_query.iter().any(Hovered::get) |
| 510 |
{ |
| 511 |
return; |
| 512 |
} |
| 513 |
|
| 514 |
let delta = mouse_motion.delta; |
| 515 |
|
| 516 |
|
| 517 |
|
| 518 |
|
| 519 |
let delta_pitch = delta.y * CAMERA_PITCH_SPEED; |
| 520 |
let delta_yaw = delta.x * CAMERA_YAW_SPEED; |
| 521 |
|
| 522 |
for mut main_camera_transform in &mut main_cameras_query { |
| 523 |
|
| 524 |
let (yaw, pitch, _) = main_camera_transform.rotation.to_euler(EulerRot::YXZ); |
| 525 |
|
| 526 |
|
| 527 |
let pitch = (pitch + delta_pitch).clamp(-CAMERA_PITCH_LIMIT, CAMERA_PITCH_LIMIT); |
| 528 |
let yaw = yaw + delta_yaw; |
| 529 |
main_camera_transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.0); |
| 530 |
|
| 531 |
|
| 532 |
|
| 533 |
let target = Vec3::ZERO; |
| 534 |
main_camera_transform.translation = |
| 535 |
target - main_camera_transform.forward() * CAMERA_ORBIT_DISTANCE; |
| 536 |
} |
| 537 |
} |
| 538 |
|
| 539 |
|
| 540 |
|
| 541 |
|
| 542 |
|
| 543 |
|
| 544 |
|
| 545 |
|
| 546 |
fn update_mirror_camera_on_main_camera_transform_change( |
| 547 |
main_cameras_query: Query< |
| 548 |
(&Transform, &Projection), |
| 549 |
(Changed<Transform>, With<Camera>, Without<MirrorCamera>), |
| 550 |
>, |
| 551 |
mut mirror_cameras_query: Query< |
| 552 |
(&mut Transform, &mut Projection), |
| 553 |
(With<Camera>, With<MirrorCamera>, Without<Mirror>), |
| 554 |
>, |
| 555 |
mirrors_query: Query<&Transform, (Without<MirrorCamera>, With<Mirror>)>, |
| 556 |
) { |
| 557 |
let Some((main_camera_transform, Projection::Perspective(main_camera_projection))) = |
| 558 |
main_cameras_query.iter().next() |
| 559 |
else { |
| 560 |
return; |
| 561 |
}; |
| 562 |
|
| 563 |
let Some(mirror_transform) = mirrors_query.iter().next() else { |
| 564 |
return; |
| 565 |
}; |
| 566 |
|
| 567 |
|
| 568 |
|
| 569 |
let (new_mirror_camera_transform, new_mirror_camera_projection) = |
| 570 |
calculate_mirror_camera_transform_and_projection( |
| 571 |
main_camera_transform, |
| 572 |
main_camera_projection, |
| 573 |
mirror_transform, |
| 574 |
); |
| 575 |
|
| 576 |
for (mut mirror_camera_transform, mut mirror_camera_projection) in &mut mirror_cameras_query { |
| 577 |
*mirror_camera_transform = new_mirror_camera_transform; |
| 578 |
*mirror_camera_projection = Projection::Perspective(new_mirror_camera_projection.clone()); |
| 579 |
} |
| 580 |
} |
| 581 |
|
| 582 |
|
| 583 |
fn play_fox_animation( |
| 584 |
mut commands: Commands, |
| 585 |
mut animation_players_query: Query< |
| 586 |
(Entity, &mut AnimationPlayer), |
| 587 |
Without<AnimationGraphHandle>, |
| 588 |
>, |
| 589 |
asset_server: Res<AssetServer>, |
| 590 |
mut animation_graphs: ResMut<Assets<AnimationGraph>>, |
| 591 |
) { |
| 592 |
|
| 593 |
|
| 594 |
|
| 595 |
if animation_players_query.is_empty() { |
| 596 |
return; |
| 597 |
} |
| 598 |
|
| 599 |
let fox_animation = asset_server.load(GltfAssetLabel::Animation(0).from_asset(FOX_ASSET_PATH)); |
| 600 |
let (fox_animation_graph, fox_animation_node) = |
| 601 |
AnimationGraph::from_clip(fox_animation.clone()); |
| 602 |
let fox_animation_graph = animation_graphs.add(fox_animation_graph); |
| 603 |
|
| 604 |
for (entity, mut animation_player) in animation_players_query.iter_mut() { |
| 605 |
commands |
| 606 |
.entity(entity) |
| 607 |
.insert(AnimationGraphHandle(fox_animation_graph.clone())); |
| 608 |
animation_player.play(fox_animation_node).repeat(); |
| 609 |
} |
| 610 |
} |
| 611 |
|
| 612 |
|
| 613 |
fn spawn_help_text(commands: &mut Commands, app_status: &AppStatus) { |
| 614 |
commands.spawn(( |
| 615 |
Text::new(create_help_string(app_status)), |
| 616 |
Node { |
| 617 |
position_type: PositionType::Absolute, |
| 618 |
top: px(12), |
| 619 |
left: px(12), |
| 620 |
..default() |
| 621 |
}, |
| 622 |
HelpText, |
| 623 |
)); |
| 624 |
} |
| 625 |
|
| 626 |
|
| 627 |
fn create_help_string(app_status: &AppStatus) -> String { |
| 628 |
format!( |
| 629 |
"Click and drag to move the {}", |
| 630 |
match app_status.drag_action { |
| 631 |
DragAction::MoveCamera => "camera", |
| 632 |
DragAction::MoveFox => "fox", |
| 633 |
} |
| 634 |
) |
| 635 |
} |
| 636 |
|
| 637 |
|
| 638 |
|
| 639 |
fn update_help_text(mut help_text: Query<&mut Text, With<HelpText>>, app_status: Res<AppStatus>) { |
| 640 |
for mut text in &mut help_text { |
| 641 |
text.0 = create_help_string(&app_status); |
| 642 |
} |
| 643 |
} |