Skip to content

Commit

Permalink
Fix look to rotation anchor (#8)
Browse files Browse the repository at this point in the history
* Fix look to rotation anchor

* better init anchor depth

* update cad example

* clippy
  • Loading branch information
aevyrie authored Jun 14, 2024
1 parent aafd66e commit 873b725
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 55 deletions.
121 changes: 80 additions & 41 deletions examples/cad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use bevy::{
utils::Instant,
window::RequestRedraw,
};
use bevy_core_pipeline::Skybox;
use bevy_editor_cam::{
extensions::{dolly_zoom::DollyZoomTrigger, look_to::LookToTrigger},
prelude::*,
Expand All @@ -29,13 +28,21 @@ fn main() {
// The camera controller works with reactive rendering:
// .insert_resource(bevy::winit::WinitSettings::desktop_app())
.insert_resource(Msaa::Off)
.insert_resource(ClearColor(Color::NONE))
.insert_resource(ClearColor(Color::rgb(0.15, 0.15, 0.15)))
.insert_resource(AmbientLight {
brightness: 0.0,
..default()
})
.add_systems(Startup, setup)
.add_systems(Update, (toggle_projection, explode, switch_direction))
.add_systems(
Update,
(
toggle_projection,
toggle_constraint,
explode,
switch_direction,
),
)
.run()
}

Expand All @@ -49,10 +56,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..Default::default()
});

let cam_trans = Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y);

let camera = commands
.spawn((
Camera3dBundle {
transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y),
transform: cam_trans,
tonemapping: Tonemapping::AcesFitted,
..default()
},
Expand All @@ -64,13 +73,9 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
},
EditorCam {
orbit_constraint: OrbitConstraint::Free,
last_anchor_depth: 2.0,
last_anchor_depth: cam_trans.translation.length() as f64,
..Default::default()
},
Skybox {
image: diffuse_map,
brightness: 500.0,
},
))
.insert(ScreenSpaceAmbientOcclusionBundle::default())
.insert(TemporalAntiAliasBundle::default())
Expand Down Expand Up @@ -99,52 +104,85 @@ fn toggle_projection(
}
}

fn toggle_constraint(
keys: Res<ButtonInput<KeyCode>>,
mut cam: Query<(Entity, &Transform, &mut EditorCam)>,
mut look_to: EventWriter<LookToTrigger>,
) {
if keys.just_pressed(KeyCode::KeyC) {
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<ButtonInput<KeyCode>>,
mut dolly: EventWriter<LookToTrigger>,
cam: Query<Entity, With<EditorCam>>,
mut look_to: EventWriter<LookToTrigger>,
cam: Query<(Entity, &Transform, &EditorCam)>,
) {
let (camera, transform, editor) = cam.single();
if keys.just_pressed(KeyCode::Digit1) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::X,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
look_to.send(LookToTrigger::auto_snap_up_direction(
Direction3d::X,
camera,
transform,
editor,
));
}
if keys.just_pressed(KeyCode::Digit2) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::Z,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
look_to.send(LookToTrigger::auto_snap_up_direction(
Direction3d::Z,
camera,
transform,
editor,
));
}
if keys.just_pressed(KeyCode::Digit3) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::NEG_X,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
look_to.send(LookToTrigger::auto_snap_up_direction(
Direction3d::NEG_X,
camera,
transform,
editor,
));
}
if keys.just_pressed(KeyCode::Digit4) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::NEG_Z,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
look_to.send(LookToTrigger::auto_snap_up_direction(
Direction3d::NEG_Z,
camera,
transform,
editor,
));
}
if keys.just_pressed(KeyCode::Digit5) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::Y,
target_up_direction: Direction3d::NEG_X,
camera: cam.single(),
});
look_to.send(LookToTrigger::auto_snap_up_direction(
Direction3d::Y,
camera,
transform,
editor,
));
}
if keys.just_pressed(KeyCode::Digit6) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::NEG_Y,
target_up_direction: Direction3d::X,
camera: cam.single(),
});
look_to.send(LookToTrigger::auto_snap_up_direction(
Direction3d::NEG_Y,
camera,
transform,
editor,
));
}
}

Expand Down Expand Up @@ -173,6 +211,7 @@ fn setup_ui(mut commands: Commands, camera: Entity) {
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()),
])
Expand Down
80 changes: 66 additions & 14 deletions src/extensions/look_to.rs
Original file line number Diff line number Diff line change
@@ -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_app::prelude::*;
use bevy_ecs::prelude::*;
Expand Down Expand Up @@ -41,6 +41,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: Direction3d,
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: Direction3d::new_unchecked(up.normalize()),
camera: cam_entity,
}
}
}

impl LookToTrigger {
fn receive(
mut events: EventReader<Self>,
Expand Down Expand Up @@ -138,13 +188,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_dquat()).as_quat();
transform.rotation = (rotation * transform.rotation.as_dquat())
.as_quat()
.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_dquat() * anchor_view_space + t.as_dvec3()
});
};

let rot_init = Transform::default()
.looking_to(**initial_facing_direction, **initial_up_direction)
Expand All @@ -153,17 +211,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_dquat(),
);
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_dquat());

if progress_t >= 1.0 {
*complete = true;
Expand Down

0 comments on commit 873b725

Please sign in to comment.