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 committed Jun 14, 2024
1 parent 23279e5 commit e9c618e
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 83 deletions.
195 changes: 126 additions & 69 deletions examples/cad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
}

Expand All @@ -45,24 +56,28 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..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(
Expand All @@ -85,52 +100,85 @@ fn toggle_projection(
}
}

fn toggle_constraint(
keys: Res<Input<KeyCode>>,
mut cam: Query<(Entity, &Transform, &mut EditorCam)>,
mut look_to: EventWriter<LookToTrigger>,
) {
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<Input<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::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,
));
}
}

Expand All @@ -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)]
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::{
math::{DQuat, DVec3},
Expand Down Expand Up @@ -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<Self>,
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down

0 comments on commit e9c618e

Please sign in to comment.