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}