use std::{f32::consts::PI, 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::*;
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) .register_type::<LookTo>();
}
}
#[derive(Debug, Event)]
pub struct LookToTrigger {
pub target_facing_direction: Dir3,
pub target_up_direction: Dir3,
pub camera: Entity,
}
impl LookToTrigger {
pub fn auto_snap_up_direction(
facing: Dir3,
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: Dir3::new_unchecked(up.normalize()),
camera: cam_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: Dir3,
initial_up_direction: Dir3,
target_facing_direction: Dir3,
target_up_direction: Dir3,
complete: bool,
}
#[derive(Resource, Reflect)]
pub struct LookTo {
pub animation_duration: Duration,
#[reflect(ignore)]
pub animation_curve: CubicSegment<Vec2>,
#[reflect(ignore)]
map: HashMap<Entity, LookToEntry>,
}
impl Default for LookTo {
fn default() -> Self {
Self {
animation_duration: Duration::from_millis(400),
animation_curve: CubicSegment::new_bezier((0.25, 0.1), (0.25, 1.0)),
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;
let animation_curve = state.animation_curve;
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 = animation_curve.ease(progress_t);
let rotate_around = |transform: &mut Transform, point: DVec3, rotation: DQuat| {
transform.translation =
(point + rotation * (transform.translation.as_dvec3() - point)).as_vec3();
transform.rotation = (rotation * transform.rotation.as_dquat())
.as_quat()
.normalize();
};
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)
.rotation;
let rot_target = Transform::default()
.looking_to(**target_facing_direction, **target_up_direction)
.rotation;
let rot_next = rot_init.slerp(rot_target, progress);
let rot_last = transform.rotation;
let rot_delta = rot_next * rot_last.inverse();
rotate_around(&mut transform, anchor_world, rot_delta.as_dquat());
if progress_t >= 1.0 {
*complete = true;
}
redraw.send(RequestRedraw);
}
state.map.retain(|_, v| !v.complete);
}
}