1use super::{NodeMotion, Value, unit};
5
6#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct MotionState {
11 pub travelled: [f32; 3],
13 pub gained: f32,
15 pub lifted: f32,
17 pub turned: [f32; 4],
19 pub facing: [f32; 4],
22 pub turns: u32,
24 pub next_turn: f32,
25}
26
27impl Default for MotionState {
28 fn default() -> MotionState {
30 MotionState {
31 travelled: [0.0; 3],
32 gained: 0.0,
33 lifted: 0.0,
34 turned: IDENTITY,
35 facing: IDENTITY,
36 turns: 0,
37 next_turn: 0.0,
38 }
39 }
40}
41
42const TURN_STREAM: u32 = 0x4000_0000 + 20;
45const TURN_SCOPE: u64 = u64::MAX - 1;
47const TURN_TOLERANCE: f32 = 1e-4;
50
51const IDENTITY: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
52
53#[derive(Clone, Copy)]
56pub struct Mover<'a> {
57 pub seed: u64,
58 pub emitter: u32,
59 pub input: &'a dyn Fn(u32) -> f32,
60}
61
62impl std::fmt::Debug for Mover<'_> {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("Mover")
65 .field("seed", &self.seed)
66 .field("emitter", &self.emitter)
67 .finish_non_exhaustive()
68 }
69}
70
71impl NodeMotion {
72 pub fn advance(
85 &self,
86 state: &mut MotionState,
87 mover: Mover<'_>,
88 active: f32,
89 dt: f32,
90 placement: [f32; 4],
91 ) {
92 let Mover { seed, emitter, input } = mover;
93 let ev = |v: &Value| v.eval(active, seed, emitter, 0, input);
94 let speed = ev(&self.speed)[0] * ev(&self.speed_multiplier)[0];
95 let acceleration = ev(&self.acceleration)[0] * ev(&self.acceleration_multiplier)[0];
96 let limited = self.limit(speed + state.gained + acceleration * dt);
97 state.gained = limited - speed;
98 let along = rotate(mul(placement, state.turned), self.forward);
99 state.lifted += ev(&self.lift)[0] * dt;
100 let up = if self.lift_in_world {
101 [0.0, 1.0, 0.0]
102 } else {
103 rotate(placement, [0.0, 1.0, 0.0])
104 };
105 let velocity: [f32; 3] = std::array::from_fn(|k| along[k] * limited + up[k] * state.lifted);
106 for (t, v) in state.travelled.iter_mut().zip(velocity) {
107 *t += v * dt;
108 }
109 if let Some(d) = &self.displacement {
110 let v = ev(d);
111 state.travelled = [v[0], v[1], v[2]];
112 }
113 let spin: [f32; 3] = std::array::from_fn(|k| ev(&self.spin[k])[0] * ev(&self.spin_multiplier[k])[0]);
114 if spin != [0.0; 3] {
115 let step = euler_yxz(
116 (spin[1] * dt).to_radians(),
117 (spin[0] * dt).to_radians(),
118 (spin[2] * dt).to_radians(),
119 );
120 state.turned = normalize(mul(state.turned, step));
121 }
122 if let Some(t) = &self.turns
123 && t.interval > 0.0
124 && active + TURN_TOLERANCE >= state.next_turn
125 {
126 let back = [0.0, 0.0, -1.0];
127 let turned = random_turn(back, ev(&t.max_angle)[0], seed, emitter, state.turns);
128 state.turned = normalize(mul(state.turned, arc(back, turned)));
129 state.turns += 1;
130 state.next_turn = active + t.interval;
131 }
132 if self.align {
133 let length = velocity.iter().map(|v| v * v).sum::<f32>().sqrt();
134 if length > 1e-6 {
135 let heading = velocity.map(|v| v / length);
136 state.facing = arc(along, heading);
137 }
138 }
139 }
140
141 fn limit(&self, speed: f32) -> f32 {
143 let speed = self.min_speed.map_or(speed, |min| speed.max(min));
144 self.max_speed.map_or(speed, |max| speed.min(max))
145 }
146}
147
148fn random_turn(v: [f32; 3], max_degrees: f32, seed: u64, emitter: u32, n: u32) -> [f32; 3] {
150 let draw = |component| unit(seed, emitter, TURN_SCOPE, TURN_STREAM + n % 64, component);
151 let (t1, t2) = basis(v);
152 let alpha = max_degrees.to_radians() * draw(1);
153 let phi = std::f32::consts::TAU * draw(2);
154 let turned: [f32; 3] =
155 std::array::from_fn(|k| v[k] * alpha.cos() + (t1[k] * phi.cos() + t2[k] * phi.sin()) * alpha.sin());
156 let length = turned.iter().map(|x| x * x).sum::<f32>().sqrt();
157 if length.is_finite() && length > 0.0 {
158 turned.map(|x| x / length)
159 } else {
160 v
161 }
162}
163
164fn basis(dir: [f32; 3]) -> ([f32; 3], [f32; 3]) {
166 let dir = unit_or_zero(dir);
167 let helper = if dir[1].abs() < 0.9 {
168 [0.0, 1.0, 0.0]
169 } else {
170 [1.0, 0.0, 0.0]
171 };
172 let t1 = unit_or_zero(cross(dir, helper));
173 let t2 = unit_or_zero(cross(dir, t1));
174 (t1, t2)
175}
176
177fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
178 [
179 a[1] * b[2] - a[2] * b[1],
180 a[2] * b[0] - a[0] * b[2],
181 a[0] * b[1] - a[1] * b[0],
182 ]
183}
184
185fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
186 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
187}
188
189fn unit_or_zero(v: [f32; 3]) -> [f32; 3] {
190 let length = dot(v, v).sqrt();
191 if length.is_finite() && length > 0.0 {
192 v.map(|x| x / length)
193 } else {
194 [0.0; 3]
195 }
196}
197
198fn mul(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
200 let [ax, ay, az, aw] = a;
201 let [bx, by, bz, bw] = b;
202 [
203 aw * bx + ax * bw + ay * bz - az * by,
204 aw * by - ax * bz + ay * bw + az * bx,
205 aw * bz + ax * by - ay * bx + az * bw,
206 aw * bw - ax * bx - ay * by - az * bz,
207 ]
208}
209
210fn normalize(q: [f32; 4]) -> [f32; 4] {
211 let length = q.iter().map(|x| x * x).sum::<f32>().sqrt();
212 if length.is_finite() && length > 0.0 {
213 q.map(|x| x / length)
214 } else {
215 IDENTITY
216 }
217}
218
219fn rotate(q: [f32; 4], v: [f32; 3]) -> [f32; 3] {
221 let u = [q[0], q[1], q[2]];
222 let w = q[3];
223 let uv = cross(u, v);
224 let uuv = cross(u, uv);
225 std::array::from_fn(|k| v[k] + 2.0 * (w * uv[k] + uuv[k]))
226}
227
228fn euler_yxz(y: f32, x: f32, z: f32) -> [f32; 4] {
230 let about = |axis: usize, angle: f32| {
231 let mut q = [0.0, 0.0, 0.0, (angle / 2.0).cos()];
232 q[axis] = (angle / 2.0).sin();
233 q
234 };
235 mul(mul(about(1, y), about(0, x)), about(2, z))
236}
237
238fn arc(from: [f32; 3], to: [f32; 3]) -> [f32; 4] {
241 let d = dot(from, to);
242 if d < -1.0 + 1e-6 {
243 let (axis, _) = basis(from);
244 return [axis[0], axis[1], axis[2], 0.0];
245 }
246 let c = cross(from, to);
247 let s = (2.0 * (1.0 + d)).sqrt();
248 [c[0] / s, c[1] / s, c[2] / s, s / 2.0]
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::particles::{Turns, Value};
255
256 fn v(x: f32) -> Value {
257 Value::constant(&[x])
258 }
259
260 fn motion() -> NodeMotion {
261 NodeMotion {
262 forward: [0.0, 0.0, -1.0],
263 speed: v(0.0),
264 speed_multiplier: v(1.0),
265 acceleration: v(0.0),
266 acceleration_multiplier: v(1.0),
267 lift: v(0.0),
268 lift_in_world: false,
269 displacement: None,
270 spin: [v(0.0), v(0.0), v(0.0)],
271 spin_multiplier: [v(1.0), v(1.0), v(1.0)],
272 align: false,
273 turns: None,
274 drag: None,
275 min_speed: None,
276 max_speed: None,
277 }
278 }
279
280 fn play(m: &NodeMotion, seconds: f32) -> MotionState {
282 let mut s = MotionState::default();
283 let mover = Mover {
284 seed: 7,
285 emitter: 0,
286 input: &|_| 0.0,
287 };
288 let dt = 1.0 / 120.0;
289 let steps = (seconds / dt).round() as u32;
290 for i in 0..steps {
291 m.advance(&mut s, mover, i as f32 * dt, dt, IDENTITY);
292 }
293 s
294 }
295
296 fn close(a: [f32; 3], b: [f32; 3], tolerance: f32) -> bool {
297 a.iter().zip(b).all(|(x, y)| (x - y).abs() <= tolerance)
298 }
299
300 #[test]
301 fn acceleration_is_held_within_the_speed_limits() {
302 let mut m = motion();
304 m.acceleration = v(40.0);
305 m.min_speed = Some(6.0);
306 m.max_speed = Some(20.0);
307 let s = play(&m, 1.0);
308 assert!((s.gained - 20.0).abs() < 1e-3, "{}", s.gained);
309 assert!(close(s.travelled, [0.0, 0.0, -17.55], 0.2), "{:?}", s.travelled);
311 let mut m = motion();
313 m.speed = v(30.0);
314 m.max_speed = Some(10.0);
315 let s = play(&m, 1.0);
316 assert!(close(s.travelled, [0.0, 0.0, -10.0], 1e-3), "{:?}", s.travelled);
317 }
318
319 #[test]
320 fn an_aligned_emitter_faces_its_curving_path_and_one_not_aligned_does_not() {
321 let mut m = motion();
322 m.speed = v(10.0);
323 m.lift = v(-10.0);
324 m.lift_in_world = true;
325 let s = play(&m, 1.0);
326 assert_eq!(s.facing, IDENTITY);
327 m.align = true;
328 let s = play(&m, 1.0);
329 let forward = rotate(s.facing, [0.0, 0.0, -1.0]);
331 let half = std::f32::consts::FRAC_1_SQRT_2;
332 assert!(close(forward, [0.0, -half, -half], 1e-2), "{forward:?}");
333 }
334
335 #[test]
336 fn random_turns_come_every_interval_and_are_the_same_each_play() {
337 let mut m = motion();
338 m.speed = v(1.0);
339 m.turns = Some(Turns {
340 interval: 0.1,
341 max_angle: v(30.0),
342 });
343 let a = play(&m, 1.0);
344 assert_eq!(a.turns, 10);
346 assert_eq!(a, play(&m, 1.0));
347 assert_ne!(a.turned, IDENTITY);
348 }
349
350 #[test]
351 fn a_displacement_places_the_emitter_where_it_says() {
352 let mut m = motion();
353 m.speed = v(5.0);
354 m.displacement = Some(Value::constant(&[1.0, 2.0, 3.0]));
355 assert_eq!(play(&m, 0.5).travelled, [1.0, 2.0, 3.0]);
356 }
357
358 #[test]
359 fn quaternion_helpers_agree_with_a_quarter_turn() {
360 let q = euler_yxz(std::f32::consts::FRAC_PI_2, 0.0, 0.0);
361 assert!(close(rotate(q, [0.0, 0.0, -1.0]), [-1.0, 0.0, 0.0], 1e-6));
362 let a = arc([0.0, 0.0, -1.0], [1.0, 0.0, 0.0]);
363 assert!(close(rotate(a, [0.0, 0.0, -1.0]), [1.0, 0.0, 0.0], 1e-6));
364 let back = arc([0.0, 0.0, -1.0], [0.0, 0.0, 1.0]);
365 assert!(close(rotate(back, [0.0, 0.0, -1.0]), [0.0, 0.0, 1.0], 1e-6));
366 }
367}