use glam::{Quat, Vec3};
use super::Camera;
use crate::ecs::{Component, Entity};
const BEHIND: f32 = 7.0;
const ABOVE: f32 = 3.2;
const AIM_ABOVE: f32 = 1.0;
const CATCH_UP: f32 = 8.0;
#[derive(Component, Clone, Copy, Debug, PartialEq)]
pub struct Follow {
pub target: Entity,
pub yaw: f32,
pub behind: f32,
pub above: f32,
pub aim_above: f32,
pub catch_up: f32,
settled: Option<Vec3>,
}
impl Follow {
pub fn behind(target: Entity) -> Self {
Self {
target,
yaw: 0.0,
behind: BEHIND,
above: ABOVE,
aim_above: AIM_ABOVE,
catch_up: CATCH_UP,
settled: None,
}
}
pub fn yaw(mut self, radians: f32) -> Self {
self.yaw = radians;
self
}
pub fn at(mut self, behind: f32, above: f32) -> Self {
self.behind = behind;
self.above = above;
self
}
pub fn wants(&self, target: Vec3) -> Vec3 {
let back = Quat::from_rotation_y(self.yaw) * Vec3::Z;
target + back * self.behind + Vec3::Y * self.above
}
pub fn aim(&self, target: Vec3) -> Vec3 {
target + Vec3::Y * self.aim_above
}
pub fn follow(&mut self, target: Vec3, delta: f32) -> Camera {
let wants = self.wants(target);
let eye = match self.settled {
None => wants,
Some(_) if self.catch_up <= 0.0 => wants,
Some(settled) => {
let caught = 1.0 - (-self.catch_up * delta.max(0.0)).exp();
settled + (wants - settled) * caught
}
};
self.settled = Some(eye);
Camera::looking_at(eye, self.aim(target))
}
pub fn eye(&self) -> Option<Vec3> {
self.settled
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rig() -> Follow {
Follow::behind(Entity::from_raw_u32(1).unwrap())
}
#[test]
fn the_eye_sits_back_and_up_from_what_it_follows() {
let follow = rig();
let at = Vec3::new(4.0, 0.5, -2.0);
let eye = follow.wants(at);
assert!(eye.y > at.y, "above it");
assert!(
(eye - at).with_y(0.0).length() > 1.0,
"and back from it: {eye:?} against {at:?}",
);
}
#[test]
fn it_aims_over_the_target_rather_than_at_it() {
let follow = rig();
let at = Vec3::new(0.0, 0.5, 0.0);
assert!(follow.aim(at).y > at.y);
}
#[test]
fn yaw_walks_the_eye_around_at_the_same_range() {
let at = Vec3::new(1.0, 0.0, 1.0);
let straight = rig().wants(at);
let turned = rig().yaw(std::f32::consts::FRAC_PI_2).wants(at);
assert!(
(straight - turned).length() > 1.0,
"a quarter turn should move it a long way",
);
let range = |eye: Vec3| (eye - at).length();
assert!(
(range(straight) - range(turned)).abs() < 1e-4,
"but not further away: {} against {}",
range(straight),
range(turned),
);
}
#[test]
fn the_first_frame_snaps_into_place() {
let mut follow = rig();
let at = Vec3::new(10.0, 0.0, 10.0);
let camera = follow.follow(at, 1.0 / 60.0);
assert_eq!(camera.eye, follow.wants(at));
}
#[test]
fn it_trails_a_target_that_moves() {
let mut follow = rig();
follow.follow(Vec3::ZERO, 1.0 / 60.0);
let camera = follow.follow(Vec3::new(0.0, 0.0, -10.0), 1.0 / 60.0);
let wants = follow.wants(Vec3::new(0.0, 0.0, -10.0));
assert_ne!(camera.eye, wants, "it should not be there yet");
assert!(
(camera.eye - wants).length() < 10.0,
"but should have set off: {:?} against {wants:?}",
camera.eye,
);
}
#[test]
fn it_catches_up_once_the_target_stops() {
let mut follow = rig();
follow.follow(Vec3::ZERO, 1.0 / 60.0);
let at = Vec3::new(0.0, 0.0, -10.0);
for _ in 0..240 {
follow.follow(at, 1.0 / 60.0);
}
assert!(
(follow.eye().unwrap() - follow.wants(at)).length() < 0.01,
"four seconds is long enough to settle",
);
}
#[test]
fn catching_up_does_not_depend_on_the_framerate() {
let at = Vec3::new(0.0, 0.0, -10.0);
let run = |steps: u32| {
let mut follow = rig();
follow.follow(Vec3::ZERO, 0.0);
let delta = 1.0 / steps as f32;
for _ in 0..steps {
follow.follow(at, delta);
}
follow.eye().unwrap()
};
let (slow, fast) = (run(15), run(240));
assert!(
(slow - fast).length() < 0.05,
"a second of following should land in the same place: \
{slow:?} at 15fps against {fast:?} at 240",
);
}
#[test]
fn no_catch_up_welds_it_on() {
let mut follow = rig();
follow.catch_up = 0.0;
follow.follow(Vec3::ZERO, 1.0 / 60.0);
let at = Vec3::new(5.0, 0.0, 5.0);
assert_eq!(follow.follow(at, 1.0 / 60.0).eye, follow.wants(at));
}
}