diff --git a/examples/cad.rs b/examples/cad.rs index d955af7..e32313a 100644 --- a/examples/cad.rs +++ b/examples/cad.rs @@ -2,8 +2,11 @@ use std::time::Duration; use bevy::{ core_pipeline::{ - bloom::BloomSettings, experimental::taa::TemporalAntiAliasPlugin, tonemapping::Tonemapping, + bloom::BloomSettings, + experimental::taa::{TemporalAntiAliasBundle, TemporalAntiAliasPlugin}, + tonemapping::Tonemapping, }, + pbr::ScreenSpaceAmbientOcclusionBundle, prelude::*, render::primitives::Aabb, utils::Instant, @@ -23,15 +26,23 @@ fn main() { TemporalAntiAliasPlugin, )) // The camera controller works with reactive rendering: - .insert_resource(bevy::winit::WinitSettings::desktop_app()) - .insert_resource(Msaa::Sample4) - .insert_resource(ClearColor(Color::WHITE)) + // .insert_resource(bevy::winit::WinitSettings::desktop_app()) + .insert_resource(Msaa::Off) + .insert_resource(ClearColor(Color::rgb(0.15, 0.15, 0.15))) .insert_resource(AmbientLight { brightness: 0.0, ..default() }) .add_systems(Startup, (setup, setup_ui)) - .add_systems(Update, (toggle_projection, explode, switch_direction)) + .add_systems( + Update, + ( + toggle_projection, + toggle_constraint, + explode, + switch_direction, + ), + ) .run() } @@ -45,24 +56,28 @@ fn setup(mut commands: Commands, asset_server: Res) { ..Default::default() }); - commands.spawn(( - Camera3dBundle { - transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y), - tonemapping: Tonemapping::AcesFitted, - ..default() - }, - BloomSettings::default(), - EnvironmentMapLight { - diffuse_map: diffuse_map.clone(), - specular_map: specular_map.clone(), - }, - EditorCam { - orbit_constraint: OrbitConstraint::Free, - last_anchor_depth: 2.0, - ..Default::default() - }, - bevy_editor_cam::extensions::independent_skybox::IndependentSkybox::new(diffuse_map), - )); + let cam_trans = Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y); + + commands + .spawn(( + Camera3dBundle { + transform: cam_trans, + tonemapping: Tonemapping::AcesFitted, + ..default() + }, + BloomSettings::default(), + EnvironmentMapLight { + diffuse_map: diffuse_map.clone(), + specular_map: specular_map.clone(), + }, + EditorCam { + orbit_constraint: OrbitConstraint::Free, + last_anchor_depth: cam_trans.translation.length() as f64, + ..Default::default() + }, + )) + .insert(ScreenSpaceAmbientOcclusionBundle::default()) + .insert(TemporalAntiAliasBundle::default()); } fn toggle_projection( @@ -85,52 +100,85 @@ fn toggle_projection( } } +fn toggle_constraint( + keys: Res>, + mut cam: Query<(Entity, &Transform, &mut EditorCam)>, + mut look_to: EventWriter, +) { + if keys.just_pressed(KeyCode::C) { + let (entity, transform, mut editor) = cam.single_mut(); + match editor.orbit_constraint { + OrbitConstraint::Fixed { .. } => editor.orbit_constraint = OrbitConstraint::Free, + OrbitConstraint::Free => { + editor.orbit_constraint = OrbitConstraint::Fixed { + up: Vec3::Y, + can_pass_tdc: false, + }; + + look_to.send(LookToTrigger::auto_snap_up_direction( + transform.forward(), + entity, + transform, + editor.as_ref(), + )); + } + }; + } +} + fn switch_direction( keys: Res>, - mut dolly: EventWriter, - cam: Query>, + mut look_to: EventWriter, + cam: Query<(Entity, &Transform, &EditorCam)>, ) { + let (camera, transform, editor) = cam.single(); if keys.just_pressed(KeyCode::Key1) { - dolly.send(LookToTrigger { - target_facing_direction: Vec3::X, - target_up_direction: Vec3::Y, - camera: cam.single(), - }); + look_to.send(LookToTrigger::auto_snap_up_direction( + Vec3::X, + camera, + transform, + editor, + )); } if keys.just_pressed(KeyCode::Key2) { - dolly.send(LookToTrigger { - target_facing_direction: Vec3::Z, - target_up_direction: Vec3::Y, - camera: cam.single(), - }); + look_to.send(LookToTrigger::auto_snap_up_direction( + Vec3::Z, + camera, + transform, + editor, + )); } if keys.just_pressed(KeyCode::Key3) { - dolly.send(LookToTrigger { - target_facing_direction: Vec3::NEG_X, - target_up_direction: Vec3::Y, - camera: cam.single(), - }); + look_to.send(LookToTrigger::auto_snap_up_direction( + Vec3::NEG_X, + camera, + transform, + editor, + )); } if keys.just_pressed(KeyCode::Key4) { - dolly.send(LookToTrigger { - target_facing_direction: Vec3::NEG_Z, - target_up_direction: Vec3::Y, - camera: cam.single(), - }); + look_to.send(LookToTrigger::auto_snap_up_direction( + Vec3::NEG_Z, + camera, + transform, + editor, + )); } if keys.just_pressed(KeyCode::Key5) { - dolly.send(LookToTrigger { - target_facing_direction: Vec3::Y, - target_up_direction: Vec3::NEG_X, - camera: cam.single(), - }); + look_to.send(LookToTrigger::auto_snap_up_direction( + Vec3::Y, + camera, + transform, + editor, + )); } if keys.just_pressed(KeyCode::Key6) { - dolly.send(LookToTrigger { - target_facing_direction: Vec3::NEG_Y, - target_up_direction: Vec3::X, - camera: cam.single(), - }); + look_to.send(LookToTrigger::auto_snap_up_direction( + Vec3::NEG_Y, + camera, + transform, + editor, + )); } } @@ -139,21 +187,30 @@ fn setup_ui(mut commands: Commands) { font_size: 20.0, ..default() }; - commands.spawn( - TextBundle::from_sections(vec![ - TextSection::new("Left Mouse - Pan\n", style.clone()), - TextSection::new("Right Mouse - Orbit\n", style.clone()), - TextSection::new("Scroll - Zoom\n", style.clone()), - TextSection::new("P - Toggle projection\n", style.clone()), - TextSection::new("E - Toggle explode\n", style.clone()), - ]) - .with_style(Style { - position_type: PositionType::Absolute, - top: Val::Px(12.0), - left: Val::Px(12.0), + commands + .spawn((NodeBundle { + style: Style { + width: Val::Percent(100.), + height: Val::Percent(100.), + padding: UiRect::all(Val::Px(20.)), + ..default() + }, ..default() - }), - ); + },)) + .with_children(|parent| { + parent.spawn( + TextBundle::from_sections(vec![ + TextSection::new("Left Mouse - Pan\n", style.clone()), + TextSection::new("Right Mouse - Orbit\n", style.clone()), + TextSection::new("Scroll - Zoom\n", style.clone()), + TextSection::new("P - Toggle projection\n", style.clone()), + TextSection::new("C - Toggle orbit constraint\n", style.clone()), + TextSection::new("E - Toggle explode\n", style.clone()), + TextSection::new("1-6 - Switch direction\n", style.clone()), + ]) + .with_style(Style { ..default() }), + ); + }); } #[derive(Component)] diff --git a/src/extensions/look_to.rs b/src/extensions/look_to.rs index c8ecf42..305c7a1 100644 --- a/src/extensions/look_to.rs +++ b/src/extensions/look_to.rs @@ -1,7 +1,7 @@ //! A `bevy_editor_cam` extension that adds the ability to smoothly rotate the camera about its //! anchor point until it is looking in the specified direction. -use std::time::Duration; +use std::{f32::consts::PI, time::Duration}; use bevy::{ math::{DQuat, DVec3}, @@ -40,6 +40,56 @@ pub struct LookToTrigger { pub camera: Entity, } +impl LookToTrigger { + /// Constructs a [`LookToTrigger`] with the up direction automatically selected. + /// + /// If the camera is set to [`OrbitConstraint::Fixed`], the fixed up direction will be used, as + /// long as it is not parallel to the facing direction. If set to [`OrbitConstraint::Free`] or + /// the facing direction is parallel to the fixed up direction, the up direction will be + /// automatically selected by choosing the axis that results in the least amount of rotation. + pub fn auto_snap_up_direction( + facing: Vec3, + cam_entity: Entity, + cam_transform: &Transform, + cam_editor: &EditorCam, + ) -> Self { + const EPSILON: f32 = 0.01; + let constraint = match cam_editor.orbit_constraint { + OrbitConstraint::Fixed { up, .. } => Some(up), + OrbitConstraint::Free => None, + } + .filter(|up| { + let angle = facing.angle_between(*up).abs(); + angle > EPSILON && angle < PI - EPSILON + }); + + let up = constraint.unwrap_or_else(|| { + let current = cam_transform.rotation; + let options = [ + Vec3::X, + Vec3::NEG_X, + Vec3::Y, + Vec3::NEG_Y, + Vec3::Z, + Vec3::NEG_Z, + ]; + *options + .iter() + .map(|d| (d, Transform::default().looking_to(facing, *d).rotation)) + .map(|(d, rot)| (d, rot.angle_between(current).abs())) + .reduce(|acc, this| if this.1 < acc.1 { this } else { acc }) + .map(|nearest| nearest.0) + .unwrap_or(&Vec3::Y) + }); + + LookToTrigger { + target_facing_direction: facing, + target_up_direction: up.normalize(), + camera: cam_entity, + } + } +} + impl LookToTrigger { fn receive( mut events: EventReader, @@ -137,13 +187,21 @@ impl LookTo { // Following lines are f64 versions of Transform::rotate_around transform.translation = (point + rotation * (transform.translation.as_dvec3() - point)).as_vec3(); - transform.rotation = (rotation * transform.rotation.as_f64()).as_f32(); + transform.rotation = (rotation * transform.rotation.as_f64()) + .as_f32() + .normalize(); }; - let anchor_world = controller.anchor_view_space().map(|anchor_view_space| { + let anchor_view_space = controller.anchor_view_space().unwrap_or(DVec3::new( + 0.0, + 0.0, + controller.last_anchor_depth(), + )); + + let anchor_world = { let (r, t) = (transform.rotation, transform.translation); r.as_f64() * anchor_view_space + t.as_dvec3() - }); + }; let rot_init = Transform::default() .looking_to(*initial_facing_direction, *initial_up_direction) @@ -152,17 +210,11 @@ impl LookTo { .looking_to(*target_facing_direction, *target_up_direction) .rotation; - let rot_next = rot_init.slerp(rot_target, progress).normalize(); - let rot_last = transform.rotation.normalize(); - let rot_delta = (rot_next * rot_last.inverse()).normalize(); - - rotate_around( - &mut transform, - anchor_world.unwrap_or_default(), - rot_delta.as_f64(), - ); + let rot_next = rot_init.slerp(rot_target, progress); + let rot_last = transform.rotation; + let rot_delta = rot_next * rot_last.inverse(); - transform.rotation = transform.rotation.normalize(); + rotate_around(&mut transform, anchor_world, rot_delta.as_f64()); if progress_t >= 1.0 { *complete = true;