smooth_bevy_cameras/
look_transform.rs

1use bevy::{
2    app::prelude::*, ecs::prelude::*, math::prelude::*, prelude::ReflectDefault, reflect::Reflect,
3    transform::components::Transform,
4};
5
6pub struct LookTransformPlugin;
7
8impl Plugin for LookTransformPlugin {
9    fn build(&self, app: &mut App) {
10        app.add_systems(Update, look_transform_system);
11    }
12}
13
14#[derive(Bundle, Clone)]
15pub struct LookTransformBundle {
16    pub transform: LookTransform,
17    pub smoother: Smoother,
18}
19
20/// An eye and the target it's looking at. As a component, this can be modified in place of bevy's `Transform`, and the two will
21/// stay in sync.
22#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[reflect(Component, Default, Debug, PartialEq)]
25pub struct LookTransform {
26    pub eye: Vec3,
27    pub target: Vec3,
28    pub up: Vec3,
29}
30
31impl From<LookTransform> for Transform {
32    fn from(t: LookTransform) -> Self {
33        eye_look_at_target_transform(t.eye, t.target, t.up)
34    }
35}
36
37impl Default for LookTransform {
38    fn default() -> Self {
39        Self {
40            eye: Vec3::default(),
41            target: Vec3::default(),
42            up: Vec3::Y,
43        }
44    }
45}
46
47impl LookTransform {
48    pub fn new(eye: Vec3, target: Vec3, up: Vec3) -> Self {
49        Self { eye, target, up }
50    }
51
52    pub fn radius(&self) -> f32 {
53        (self.target - self.eye).length()
54    }
55
56    pub fn look_direction(&self) -> Option<Vec3> {
57        (self.target - self.eye).try_normalize()
58    }
59}
60
61fn eye_look_at_target_transform(eye: Vec3, target: Vec3, up: Vec3) -> Transform {
62    // If eye and target are very close, we avoid imprecision issues by keeping the look vector a unit vector.
63    let look_vector = (target - eye).normalize();
64    let look_at = eye + look_vector;
65
66    Transform::from_translation(eye).looking_at(look_at, up)
67}
68
69/// Preforms exponential smoothing on a `LookTransform`. Set the `lag_weight` between `0.0` and `1.0`, where higher is smoother.
70#[derive(Clone, Component, Copy, Debug, Reflect)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72#[reflect(Component, Default, Debug)]
73pub struct Smoother {
74    lag_weight: f32,
75    lerp_tfm: Option<LookTransform>,
76    enabled: bool,
77}
78
79impl Default for Smoother {
80    fn default() -> Self {
81        Self {
82            lag_weight: 0.9,
83            lerp_tfm: Some(LookTransform::default()),
84            enabled: true,
85        }
86    }
87}
88
89impl Smoother {
90    pub fn new(lag_weight: f32) -> Self {
91        Self {
92            lag_weight,
93            lerp_tfm: None,
94            enabled: true,
95        }
96    }
97
98    pub(crate) fn set_enabled(&mut self, enabled: bool) {
99        self.enabled = enabled;
100        if self.enabled {
101            // To prevent camera jumping from last lerp before disabling to the current position,
102            // reset smoother state
103            self.reset();
104        }
105    }
106
107    pub fn set_lag_weight(&mut self, lag_weight: f32) {
108        self.lag_weight = lag_weight;
109    }
110
111    pub fn smooth_transform(&mut self, new_tfm: &LookTransform) -> LookTransform {
112        debug_assert!(0.0 <= self.lag_weight);
113        debug_assert!(self.lag_weight < 1.0);
114
115        let old_lerp_tfm = self.lerp_tfm.unwrap_or(*new_tfm);
116
117        let lead_weight = 1.0 - self.lag_weight;
118        let lerp_tfm = LookTransform {
119            eye: old_lerp_tfm.eye * self.lag_weight + new_tfm.eye * lead_weight,
120            target: old_lerp_tfm.target * self.lag_weight + new_tfm.target * lead_weight,
121            up: new_tfm.up,
122        };
123
124        self.lerp_tfm = Some(lerp_tfm);
125
126        lerp_tfm
127    }
128
129    pub fn reset(&mut self) {
130        self.lerp_tfm = None;
131    }
132}
133
134pub fn look_transform_system(
135    mut cameras: Query<(&LookTransform, &mut Transform, Option<&mut Smoother>)>,
136) {
137    for (look_transform, mut scene_transform, smoother) in cameras.iter_mut() {
138        match smoother {
139            Some(mut s) if s.enabled => {
140                *scene_transform = s.smooth_transform(look_transform).into()
141            }
142            _ => (),
143        };
144    }
145}