use std::{f64::consts::PI, time::Duration};
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_math::{prelude::*, DAffine3, DQuat, DVec3};
use bevy_platform::{collections::HashMap, time::Instant};
use bevy_reflect::prelude::*;
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_message::<LookToTrigger>()
.add_systems(
PreUpdate,
LookTo::update
.before(crate::controller::component::EditorCam::update_camera_positions),
)
.add_systems(PostUpdate, LookToTrigger::receive); }
}
#[derive(Debug, Message)]
pub struct LookToTrigger {
pub target_facing_direction: DVec3,
pub target_up_direction: DVec3,
pub camera: Entity,
}
impl LookToTrigger {
pub fn auto_snap_up_direction(
facing: DVec3,
cam_entity: Entity,
cam_rotation: &DQuat,
cam_editor: &EditorCam,
) -> Self {
const EPSILON: f64 = 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_rotation;
let options = [
DVec3::X,
DVec3::NEG_X,
DVec3::Y,
DVec3::NEG_Y,
DVec3::Z,
DVec3::NEG_Z,
];
*options
.iter()
.map(|d| (d, look_to(facing, *d)))
.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(&DVec3::Y)
});
LookToTrigger {
target_facing_direction: facing,
target_up_direction: up.normalize(),
camera: cam_entity,
}
}
}
impl LookToTrigger {
fn receive(
mut events: MessageReader<Self>,
mut state: ResMut<LookTo>,
mut camera_set: ParamSet<(Query<&mut EditorCam>, Query<EntityRef, With<EditorCam>>)>,
mut redraw: MessageWriter<RequestRedraw>,
transform_adapter: Res<TransformAdapter>,
) {
for event in events.read() {
let camera_refs = camera_set.p1();
let Ok(camera_ref) = camera_refs.get(event.camera) else {
continue;
};
let Some((_, camera_rotation)) = transform_adapter.read(&camera_ref) else {
continue;
};
let mut cameras = camera_set.p0();
let Ok(mut controller) = cameras.get_mut(event.camera) else {
continue;
};
redraw.write(RequestRedraw);
let camera_forward = camera_rotation * DVec3::NEG_Z;
let camera_up = camera_rotation * DVec3::Y;
state
.map
.entry(event.camera)
.and_modify(|e| {
e.start = Instant::now();
e.initial_facing_direction = camera_forward;
e.initial_up_direction = camera_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: camera_forward,
initial_up_direction: camera_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: DVec3,
initial_up_direction: DVec3,
target_facing_direction: DVec3,
target_up_direction: DVec3,
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_easing((0.25, 0.0), (0.25, 1.0)),
map: Default::default(),
}
}
}
impl LookTo {
fn update(
mut state: ResMut<Self>,
mut camera_set: ParamSet<(
Query<&mut EditorCam>,
Query<EntityRef, With<EditorCam>>,
Query<EntityMut, With<EditorCam>>,
)>,
mut redraw: MessageWriter<RequestRedraw>,
transform_adapter: Res<TransformAdapter>,
) {
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 camera_refs = camera_set.p1();
let Ok(camera_ref) = camera_refs.get(*camera) else {
continue;
};
let Some((mut camera_translation, mut camera_rotation)) =
transform_adapter.read(&camera_ref)
else {
continue;
};
let mut cameras = camera_set.p0();
let Ok(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 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) = (camera_rotation, camera_translation);
r * anchor_view_space + t
};
let rot_init = look_to(*initial_facing_direction, *initial_up_direction);
let rot_target = look_to(*target_facing_direction, *target_up_direction);
let rot_next = rot_init.slerp(rot_target, progress as f64);
let rot_last = camera_rotation;
let rot_delta = rot_next * rot_last.inverse();
let original_translation = camera_translation;
let original_rotation = camera_rotation;
rotate_around(
(&mut camera_translation, &mut camera_rotation),
anchor_world,
rot_delta,
);
let (_, delta_rotation, delta_translation) = {
let original =
DAffine3::from_rotation_translation(original_rotation, original_translation);
let new = DAffine3::from_rotation_translation(camera_rotation, camera_translation);
(original.inverse() * new).to_scale_rotation_translation()
};
let mut camera_muts = camera_set.p2();
let mut camera_mut = camera_muts.get_mut(*camera).unwrap();
transform_adapter.apply_delta(&mut camera_mut, delta_translation, delta_rotation);
if progress_t >= 1.0 {
*complete = true;
}
redraw.write(RequestRedraw);
}
state.map.retain(|_, v| !v.complete);
}
}