desk_physics/
follow.rs

1use std::f32::EPSILON;
2
3use bevy_ecs::prelude::Component;
4use bevy_math::Vec2;
5
6use crate::Velocity;
7
8#[derive(Component, Debug, Clone, PartialEq)]
9pub struct Follow<T> {
10    pub target: T,
11    pub parameters: FollowParams,
12}
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct FollowParams {
16    pub position_offset: Vec2,
17    pub ignore_area_size: f32,
18    pub velocity_coefficient: f32,
19    pub velocity_power: f32,
20    pub velocity_max: f32,
21    pub velocity_offset: f32,
22}
23
24impl Default for FollowParams {
25    fn default() -> Self {
26        Self {
27            position_offset: Default::default(),
28            ignore_area_size: 0.0,
29            velocity_coefficient: 1.0,
30            velocity_power: 1.0,
31            velocity_max: 1000.0,
32            velocity_offset: 0.0,
33        }
34    }
35}
36
37impl FollowParams {
38    pub fn follow_vector(&self, me: &Vec2, target: &Vec2) -> Velocity {
39        let vec = Vec2::new(target.x - me.x, target.y - me.y) - self.position_offset;
40        let length = vec.length();
41        if length <= EPSILON {
42            return Vec2::ZERO.into();
43        }
44        let velocity = (length + self.velocity_offset - self.ignore_area_size)
45            .max(0.0)
46            .powf(self.velocity_power)
47            * self.velocity_coefficient;
48        let vec = vec.normalize() * velocity.min(self.velocity_max);
49        vec.into()
50    }
51}
52
53#[cfg(test)]
54mod test {
55    use super::*;
56
57    #[test]
58    fn default() {
59        let params = FollowParams::default();
60        assert_nearly_eq(
61            params
62                .follow_vector(&Vec2::new(0.3, 2.), &Vec2::new(1.0, 0.5))
63                .0,
64            Vec2::new(0.7, -1.5),
65        );
66    }
67
68    #[test]
69    fn avoid_nan_zero() {
70        let params = FollowParams::default();
71        assert_eq!(
72            params
73                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(0.0, 0.0))
74                .0,
75            Vec2::ZERO,
76        );
77    }
78
79    #[test]
80    fn offset() {
81        let params = FollowParams {
82            position_offset: Vec2::new(1.0, 2.0),
83            ..Default::default()
84        };
85        assert_nearly_eq(
86            params
87                .follow_vector(&Vec2::new(0.4, 2.), &Vec2::new(2.0, 3.5))
88                .0,
89            Vec2::new(0.6, -0.5),
90        );
91    }
92
93    #[test]
94    fn power() {
95        let params = FollowParams {
96            velocity_power: 1.0,
97            ..Default::default()
98        };
99        assert_nearly_eq(
100            params
101                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
102                .0,
103            Vec2::new(3.0, 4.0),
104        );
105
106        let params = FollowParams {
107            velocity_power: 2.0,
108            ..Default::default()
109        };
110        assert_nearly_eq(
111            params
112                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
113                .0,
114            Vec2::new(15.0, 20.0),
115        );
116    }
117
118    #[test]
119    fn ignore_velocity() {
120        let params = FollowParams {
121            ignore_area_size: 5.0,
122            ..Default::default()
123        };
124        assert_nearly_eq(
125            params
126                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
127                .0,
128            Vec2::new(0.0, 0.0),
129        );
130        assert_nearly_eq(
131            params
132                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.3, 4.4))
133                .0,
134            Vec2::new(0.3, 0.4),
135        );
136    }
137
138    #[test]
139    fn ignore_velocity_avoids_negative_velocity() {
140        let params = FollowParams {
141            ignore_area_size: 10.0,
142            ..Default::default()
143        };
144        assert_nearly_eq(
145            params
146                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
147                .0,
148            Vec2::ZERO,
149        );
150    }
151
152    #[test]
153    fn max_velocity() {
154        let params = FollowParams {
155            velocity_max: 5.0,
156            ..Default::default()
157        };
158        assert_nearly_eq(
159            params
160                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
161                .0,
162            Vec2::new(3.0, 4.0),
163        );
164        assert_nearly_eq(
165            params
166                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.03, 4.04))
167                .0,
168            Vec2::new(3.0, 4.0),
169        );
170    }
171
172    #[test]
173    fn velocty_offset() {
174        let params = FollowParams {
175            velocity_offset: -1.0,
176            ..Default::default()
177        };
178        assert_nearly_eq(
179            params
180                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
181                .0,
182            Vec2::new(3.0 * 4.0 / 5.0, 4.0 * 4.0 / 5.0),
183        );
184    }
185
186    #[test]
187    fn velocity_offset_avoids_negative_velocity() {
188        let params = FollowParams {
189            velocity_offset: -10.0,
190            ..Default::default()
191        };
192        assert_nearly_eq(
193            params
194                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(3.0, 4.0))
195                .0,
196            Vec2::ZERO,
197        );
198    }
199
200    #[test]
201    fn velocty_coefficient() {
202        let params = FollowParams {
203            velocity_coefficient: 2.0,
204            ..Default::default()
205        };
206        assert_nearly_eq(
207            params
208                .follow_vector(&Vec2::new(0.0, 0.0), &Vec2::new(1.0, 1.0))
209                .0,
210            Vec2::new(2.0, 2.0),
211        );
212    }
213
214    fn assert_nearly_eq(vec1: Vec2, vec2: Vec2) {
215        let diff = vec1 - vec2;
216        if diff.x.abs() > 0.0001 || diff.y.abs() > 0.0001 {
217            assert_eq!(vec1, vec2);
218        }
219    }
220
221    #[test]
222    #[should_panic]
223    fn assert_nearly_eq_fail() {
224        assert_nearly_eq(Vec2::new(1.0, 1.0), Vec2::new(1.0, 1.0 + 0.001));
225    }
226
227    #[test]
228    #[should_panic]
229    fn assert_nearly_eq_fail2() {
230        assert_nearly_eq(Vec2::new(1.0, 1.0), Vec2::new(1.0 + 0.001, 1.0));
231    }
232}