Skip to content

Commit

Permalink
Merge pull request #4 from aevyrie/feat-look-to
Browse files Browse the repository at this point in the history
Add look to feature
  • Loading branch information
aevyrie authored Jun 14, 2024
2 parents 90d0080 + b8214eb commit bf5b161
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 22 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ bevy = { version = "0.13", default-features = false, features = [
"bevy_text",
"bevy_ui",
"bevy_winit",
"default_font",
"multi-threaded",
"jpeg",
"ktx2",
"tonemapping_luts",
Expand Down
94 changes: 77 additions & 17 deletions examples/cad.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ use bevy::{
utils::Instant,
window::RequestRedraw,
};
use bevy_editor_cam::{extensions::dolly_zoom::DollyZoomTrigger, prelude::*};
use bevy_core_pipeline::Skybox;
use bevy_editor_cam::{
extensions::{dolly_zoom::DollyZoomTrigger, look_to::LookToTrigger},
prelude::*,
};

fn main() {
App::new()
Expand All @@ -30,8 +34,8 @@ fn main() {
brightness: 0.0,
..default()
})
.add_systems(Startup, (setup, setup_ui))
.add_systems(Update, (toggle_projection, explode))
.add_systems(Startup, setup)
.add_systems(Update, (toggle_projection, explode, switch_direction))
.run()
}

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

commands
let camera = commands
.spawn((
Camera3dBundle {
transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y),
Expand All @@ -63,13 +67,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
last_anchor_depth: 2.0,
..Default::default()
},
bevy_editor_cam::extensions::independent_skybox::IndependentSkybox::new(
diffuse_map,
500.0,
),
Skybox {
image: diffuse_map,
brightness: 500.0,
},
))
.insert(ScreenSpaceAmbientOcclusionBundle::default())
.insert(TemporalAntiAliasBundle::default());
.insert(TemporalAntiAliasBundle::default())
.id();

setup_ui(commands, camera);
}

fn toggle_projection(
Expand All @@ -92,21 +99,73 @@ fn toggle_projection(
}
}

fn setup_ui(mut commands: Commands) {
fn switch_direction(
keys: Res<ButtonInput<KeyCode>>,
mut dolly: EventWriter<LookToTrigger>,
cam: Query<Entity, With<EditorCam>>,
) {
if keys.just_pressed(KeyCode::Digit1) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::X,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
}
if keys.just_pressed(KeyCode::Digit2) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::Z,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
}
if keys.just_pressed(KeyCode::Digit3) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::NEG_X,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
}
if keys.just_pressed(KeyCode::Digit4) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::NEG_Z,
target_up_direction: Direction3d::Y,
camera: cam.single(),
});
}
if keys.just_pressed(KeyCode::Digit5) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::Y,
target_up_direction: Direction3d::NEG_X,
camera: cam.single(),
});
}
if keys.just_pressed(KeyCode::Digit6) {
dolly.send(LookToTrigger {
target_facing_direction: Direction3d::NEG_Y,
target_up_direction: Direction3d::X,
camera: cam.single(),
});
}
}

fn setup_ui(mut commands: Commands, camera: Entity) {
let style = TextStyle {
font_size: 20.0,
..default()
};
commands
.spawn((NodeBundle {
style: Style {
width: Val::Percent(100.),
height: Val::Percent(100.),
padding: UiRect::all(Val::Px(20.)),
.spawn((
TargetCamera(camera),
NodeBundle {
style: Style {
width: Val::Percent(100.),
height: Val::Percent(100.),
padding: UiRect::all(Val::Px(20.)),
..default()
},
..default()
},
..default()
},))
))
.with_children(|parent| {
parent.spawn(
TextBundle::from_sections(vec![
Expand All @@ -115,6 +174,7 @@ fn setup_ui(mut commands: Commands) {
TextSection::new("Scroll - Zoom\n", style.clone()),
TextSection::new("P - Toggle projection\n", style.clone()),
TextSection::new("E - Toggle explode\n", style.clone()),
TextSection::new("1-6 - Switch direction\n", style.clone()),
])
.with_style(Style { ..default() }),
);
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/dolly_zoom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! because it ensures that the object the user is focusing on does not change size even as the
//! projection changes.
use std::{f32::EPSILON, time::Duration};
use std::time::Duration;

use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
Expand Down Expand Up @@ -66,7 +66,7 @@ impl DollyZoomTrigger {
}) = event.target_projection
{
// If the target and current fov are the same, there is nothing to do.
if (target_fov - perspective.fov).abs() <= EPSILON {
if (target_fov - perspective.fov).abs() <= f32::EPSILON {
continue;
}
}
Expand Down
175 changes: 175 additions & 0 deletions src/extensions/look_to.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//! 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 bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_math::{prelude::*, DQuat, DVec3};
use bevy_reflect::prelude::*;
use bevy_transform::prelude::*;
use bevy_utils::{HashMap, Instant};
use bevy_window::RequestRedraw;

use crate::prelude::*;

/// See the [module](self) docs.
pub struct LookToPlugin;

impl Plugin for LookToPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<LookTo>()
.add_event::<LookToTrigger>()
.add_systems(
PreUpdate,
LookTo::update
.before(crate::controller::component::EditorCam::update_camera_positions),
)
.add_systems(PostUpdate, LookToTrigger::receive) // In PostUpdate so we don't miss users sending this in Update. LookTo::update will catch the changes next frame.
.register_type::<LookTo>();
}
}

/// Triggers a rotation for the specified camera.
#[derive(Debug, Event)]
pub struct LookToTrigger {
/// The new direction to face.
pub target_facing_direction: Direction3d,
/// The camera's "up" direction when finished moving.
pub target_up_direction: Direction3d,
/// The camera to update.
pub camera: Entity,
}

impl LookToTrigger {
fn receive(
mut events: EventReader<Self>,
mut state: ResMut<LookTo>,
mut cameras: Query<(&mut EditorCam, &Transform)>,
mut redraw: EventWriter<RequestRedraw>,
) {
for event in events.read() {
let Ok((mut controller, transform)) = cameras.get_mut(event.camera) else {
continue;
};
redraw.send(RequestRedraw);

state
.map
.entry(event.camera)
.and_modify(|e| {
e.start = Instant::now();
e.initial_facing_direction = transform.forward();
e.initial_up_direction = transform.up();
e.target_facing_direction = event.target_facing_direction;
e.target_up_direction = event.target_up_direction;
e.complete = false;
})
.or_insert(LookToEntry {
start: Instant::now(),
initial_facing_direction: transform.forward(),
initial_up_direction: transform.up(),
target_facing_direction: event.target_facing_direction,
target_up_direction: event.target_up_direction,
complete: false,
});

controller.end_move();
controller.current_motion = motion::CurrentMotion::Stationary;
}
}
}

struct LookToEntry {
start: Instant,
initial_facing_direction: Direction3d,
initial_up_direction: Direction3d,
target_facing_direction: Direction3d,
target_up_direction: Direction3d,
complete: bool,
}

/// Stores settings and state for the dolly zoom plugin.
#[derive(Resource, Reflect)]
pub struct LookTo {
/// The duration of the "look to" transition animation.
pub animation_duration: Duration,
#[reflect(ignore)]
map: HashMap<Entity, LookToEntry>,
}

impl Default for LookTo {
fn default() -> Self {
Self {
animation_duration: Duration::from_millis(400),
map: Default::default(),
}
}
}

impl LookTo {
fn update(
mut state: ResMut<Self>,
mut cameras: Query<(&mut Transform, &EditorCam)>,
mut redraw: EventWriter<RequestRedraw>,
) {
let animation_duration = state.animation_duration;
for (
camera,
LookToEntry {
start,
initial_facing_direction,
initial_up_direction,
target_facing_direction,
target_up_direction,
complete,
},
) in state.map.iter_mut()
{
let Ok((mut transform, controller)) = cameras.get_mut(*camera) else {
*complete = true;
continue;
};
let progress_t =
(start.elapsed().as_secs_f32() / animation_duration.as_secs_f32()).clamp(0.0, 1.0);
let progress = CubicSegment::new_bezier((0.25, 0.1), (0.25, 1.0)).ease(progress_t);

let rotate_around = |transform: &mut Transform, point: DVec3, rotation: DQuat| {
// 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();
};

let anchor_world = controller.anchor_view_space().map(|anchor_view_space| {
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)
.rotation;
let rot_target = Transform::default()
.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(),
);

transform.rotation = transform.rotation.normalize();

if progress_t >= 1.0 {
*complete = true;
}
redraw.send(RequestRedraw);
}
state.map.retain(|_, v| !v.complete);
}
}
6 changes: 4 additions & 2 deletions src/extensions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Optional extensions to the base camera controller.
//! Extensions to the base camera controller.
pub mod dolly_zoom;
pub mod look_to;

#[cfg(feature = "extension_anchor_indicator")]
pub mod anchor_indicator;
pub mod dolly_zoom;
#[cfg(feature = "extension_independent_skybox")]
pub mod independent_skybox;
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ impl PluginGroup for DefaultEditorCamPlugins {
let group = PluginGroupBuilder::start::<Self>()
.add(input::DefaultInputPlugin)
.add(controller::MinimalEditorCamPlugin)
.add(extensions::dolly_zoom::DollyZoomPlugin);
.add(extensions::dolly_zoom::DollyZoomPlugin)
.add(extensions::look_to::LookToPlugin);

#[cfg(feature = "extension_anchor_indicator")]
let group = group.add(extensions::anchor_indicator::AnchorIndicatorPlugin);
Expand Down

0 comments on commit bf5b161

Please # to comment.