1use crate::math::{add2, len2, scale2, sub2};
2
3pub const SUBSTEP: f32 = 1.0 / 240.0;
4const PER: usize = 5;
5const MAX_WIRES: usize = 256;
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub struct Drift {
9 pub x_main: [f32; 2],
10 pub x_side: [f32; 3],
11 pub y_main: [f32; 2],
12 pub y_side: [f32; 2],
13 pub side: f32,
14}
15
16impl Default for Drift {
17 fn default() -> Self {
18 Self {
19 x_main: [0.02, 0.5],
20 x_side: [0.012, 0.3, 0.006],
21 y_main: [0.02, 0.4],
22 y_side: [0.016, 0.25],
23 side: 0.5,
24 }
25 }
26}
27
28impl Drift {
29 pub fn at(&self, p: [f32; 2], time: f32) -> [f32; 2] {
30 [
31 (p[1] * self.x_main[0] + time * self.x_main[1]).sin()
32 + self.side
33 * (p[0] * self.x_side[0] - time * self.x_side[1] + p[1] * self.x_side[2]).sin(),
34 (p[0] * self.y_main[0] - time * self.y_main[1]).cos()
35 + self.side * (p[1] * self.y_side[0] + time * self.y_side[1]).cos(),
36 ]
37 }
38}
39
40#[derive(Clone, Copy, Debug, PartialEq)]
41pub struct ChainParams {
42 pub pull: f32,
43 pub drag: f32,
44 pub flow: f32,
45 pub slack: f32,
46 pub cord: f32,
47 pub drift: Drift,
48}
49
50impl Default for ChainParams {
51 fn default() -> Self {
52 Self {
53 pull: 24.0,
54 drag: 2.5,
55 flow: 20.0,
56 slack: 1.1,
57 cord: 12.0,
58 drift: Drift::default(),
59 }
60 }
61}
62
63pub struct Chain {
64 params: ChainParams,
65 nodes: Vec<[f32; 2]>,
66 prev: Vec<[f32; 2]>,
67 cords: Vec<[f32; 2]>,
68 cords_prev: Vec<[f32; 2]>,
69 clock: f32,
70 inputs: Vec<[f32; 2]>,
71 asleep: bool,
72}
73
74impl Default for Chain {
75 fn default() -> Self {
76 Self::new(ChainParams::default())
77 }
78}
79
80impl Chain {
81 pub fn new(params: ChainParams) -> Self {
82 Self {
83 params,
84 nodes: Vec::new(),
85 prev: Vec::new(),
86 cords: Vec::new(),
87 cords_prev: Vec::new(),
88 clock: 0.0,
89 inputs: Vec::new(),
90 asleep: false,
91 }
92 }
93
94 pub fn nodes(&self) -> &[[f32; 2]] {
95 &self.nodes
96 }
97
98 pub fn cords(&self) -> &[[f32; 2]] {
99 &self.cords
100 }
101
102 pub fn asleep(&self) -> bool {
103 self.asleep
104 }
105
106 pub fn bead(&self, index: usize) -> Option<[f32; 2]> {
107 self.nodes.get((index + 1) * PER).copied()
108 }
109
110 pub fn snap(&mut self, anchor: [f32; 2], beads: &[[f32; 2]], cords: &[[f32; 2]]) {
111 let point = |k: usize| if k == 0 { anchor } else { beads[k - 1] };
112 self.nodes = vec![anchor];
113 for k in 0..beads.len() {
114 let (a, b) = (point(k), point(k + 1));
115 for j in 1..=PER {
116 let t = j as f32 / PER as f32;
117 self.nodes
118 .push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
119 }
120 }
121 self.prev = self.nodes.clone();
122 self.cords = cords.to_vec();
123 self.cords_prev = cords.to_vec();
124 }
125
126 pub fn step(
127 &mut self,
128 dt: f32,
129 time: f32,
130 anchor: [f32; 2],
131 beads: &[[f32; 2]],
132 cords: &[[f32; 2]],
133 idle: f32,
134 ) {
135 let params = self.params;
136 let wanted = if beads.is_empty() {
137 0
138 } else {
139 beads.len() * PER + 1
140 };
141 while self.nodes.len() < wanted {
142 let from = self.nodes.last().copied().unwrap_or(anchor);
143 self.nodes.push(from);
144 self.prev.push(from);
145 }
146 self.nodes.truncate(wanted);
147 self.prev.truncate(wanted);
148 while self.cords.len() < cords.len() {
149 let i = self.cords.len();
150 let from = self.nodes.get((i + 2) * PER).copied().unwrap_or(anchor);
151 self.cords.push(from);
152 self.cords_prev.push(from);
153 }
154 self.cords.truncate(cords.len());
155 self.cords_prev.truncate(cords.len());
156 if wanted == 0 {
157 return;
158 }
159 let inputs: Vec<[f32; 2]> = std::iter::once(anchor)
160 .chain(beads.iter().copied())
161 .chain(cords.iter().copied())
162 .collect();
163 let same = inputs.len() == self.inputs.len()
164 && inputs
165 .iter()
166 .zip(&self.inputs)
167 .all(|(a, b)| (a[0] - b[0]).abs() < 0.01 && (a[1] - b[1]).abs() < 0.01);
168 self.inputs = inputs;
169 if !same {
170 self.asleep = false;
171 }
172 if self.asleep {
173 self.clock = 0.0;
174 return;
175 }
176 let point = |k: usize| if k == 0 { anchor } else { beads[k - 1] };
177 let rest: Vec<f32> = (0..beads.len())
178 .map(|k| {
179 let (a, b) = (point(k), point(k + 1));
180 (b[0] - a[0]).hypot(b[1] - a[1]) / PER as f32 * params.slack
181 })
182 .collect();
183 let h = SUBSTEP;
184 self.clock = (self.clock + dt).min(h * 60.0);
185 let steps = (self.clock / h).floor() as usize;
186 self.clock -= steps as f32 * h;
187 let keep = (-params.drag * h).exp();
188 let flow = params.flow * idle;
189 for step in 0..steps {
190 let t = time + h * step as f32;
191 self.nodes[0] = anchor;
192 self.prev[0] = anchor;
193 for j in 1..self.nodes.len() {
194 let p = self.nodes[j];
195 let v = [
196 (p[0] - self.prev[j][0]) * keep,
197 (p[1] - self.prev[j][1]) * keep,
198 ];
199 let w = params.drift.at(p, t);
200 let mut f = [w[0] * flow, w[1] * flow];
201 if j % PER == 0 {
202 let goal = point(j / PER);
203 f[0] += params.pull * (goal[0] - p[0]);
204 f[1] += params.pull * (goal[1] - p[1]);
205 }
206 self.prev[j] = p;
207 self.nodes[j] = [p[0] + v[0] + f[0] * h * h, p[1] + v[1] + f[1] * h * h];
208 }
209 for (i, goal) in cords.iter().enumerate() {
210 let p = self.cords[i];
211 let v = [
212 (p[0] - self.cords_prev[i][0]) * keep,
213 (p[1] - self.cords_prev[i][1]) * keep,
214 ];
215 let w = params.drift.at(p, t + 3.0);
216 let f = [
217 w[0] * flow + params.pull * 1.6 * (goal[0] - p[0]),
218 w[1] * flow + params.pull * 1.6 * (goal[1] - p[1]),
219 ];
220 self.cords_prev[i] = p;
221 self.cords[i] = [p[0] + v[0] + f[0] * h * h, p[1] + v[1] + f[1] * h * h];
222 }
223 for _ in 0..12 {
224 for j in 0..self.nodes.len() - 1 {
225 let (a, b) = (self.nodes[j], self.nodes[j + 1]);
226 let d = [b[0] - a[0], b[1] - a[1]];
227 let len = d[0].hypot(d[1]).max(1e-4);
228 let target = rest[(j / PER).min(rest.len() - 1)];
229 let fix = (len - target) / len;
230 if j == 0 {
231 self.nodes[1] = [b[0] - d[0] * fix, b[1] - d[1] * fix];
232 } else {
233 self.nodes[j] = [a[0] + d[0] * fix * 0.5, a[1] + d[1] * fix * 0.5];
234 self.nodes[j + 1] = [b[0] - d[0] * fix * 0.5, b[1] - d[1] * fix * 0.5];
235 }
236 }
237 for i in 0..self.cords.len() {
238 let Some(a) = self.nodes.get((i + 2) * PER).copied() else {
239 continue;
240 };
241 let b = self.cords[i];
242 let d = [b[0] - a[0], b[1] - a[1]];
243 let len = d[0].hypot(d[1]).max(1e-4);
244 let fix = (len - params.cord) / len;
245 self.cords[i] = [b[0] - d[0] * fix, b[1] - d[1] * fix];
246 }
247 }
248 }
249 let moving = |now: &[[f32; 2]], was: &[[f32; 2]]| {
250 now.iter()
251 .zip(was)
252 .any(|(a, b)| (a[0] - b[0]).abs() > 0.01 || (a[1] - b[1]).abs() > 0.01)
253 };
254 if steps > 0 && !moving(&self.nodes, &self.prev) && !moving(&self.cords, &self.cords_prev) {
255 self.prev = self.nodes.clone();
256 self.cords_prev = self.cords.clone();
257 self.asleep = true;
258 }
259 }
260
261 pub fn wires(&self) -> Vec<[f32; 4]> {
262 let mut out = Vec::with_capacity(MAX_WIRES);
263 if self.nodes.is_empty() {
264 return out;
265 }
266 let at = |i: isize| self.nodes[i.clamp(0, self.nodes.len() as isize - 1) as usize];
267 for i in 0..self.nodes.len().saturating_sub(1) {
268 if out.len() == MAX_WIRES {
269 return out;
270 }
271 let (p0, p1, p2, p3) = (
272 at(i as isize - 1),
273 at(i as isize),
274 at(i as isize + 1),
275 at(i as isize + 2),
276 );
277 let mut prev = p1;
278 for k in 1..=3 {
279 if out.len() == MAX_WIRES {
280 return out;
281 }
282 let t = k as f32 / 3.0;
283 let (t2, t3) = (t * t, t * t * t);
284 let f = |a: f32, b: f32, c: f32, d: f32| {
285 0.5 * (2.0 * b
286 + (-a + c) * t
287 + (2.0 * a - 5.0 * b + 4.0 * c - d) * t2
288 + (-a + 3.0 * b - 3.0 * c + d) * t3)
289 };
290 let next = [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
291 out.push([prev[0], prev[1], next[0], next[1]]);
292 prev = next;
293 }
294 }
295 for (i, end) in self.cords.iter().enumerate() {
296 if out.len() == MAX_WIRES {
297 break;
298 }
299 if let Some(start) = self.nodes.get((i + 2) * PER) {
300 out.push([start[0], start[1], end[0], end[1]]);
301 }
302 }
303 out
304 }
305}
306
307pub struct Rope {
308 pos: Vec<[f32; 2]>,
309 prev: Vec<[f32; 2]>,
310 rest: Vec<f32>,
311 pins: Vec<Option<[f32; 2]>>,
312 drag: f32,
313 iterations: u32,
314 clock: f32,
315 drift: Drift,
316}
317
318impl Rope {
319 pub fn between(start: [f32; 2], end: [f32; 2], segments: usize, slack: f32) -> Self {
320 let segments = segments.max(1);
321 let mut pos = Vec::with_capacity(segments + 1);
322 for i in 0..=segments {
323 let t = i as f32 / segments as f32;
324 pos.push(add2(start, scale2(sub2(end, start), t)));
325 }
326 let rest_len = len2(sub2(end, start)) / segments as f32 * slack;
327 let mut pins = vec![None; pos.len()];
328 pins[0] = Some(start);
329 pins[segments] = Some(end);
330 Self {
331 prev: pos.clone(),
332 pos,
333 rest: vec![rest_len; segments],
334 pins,
335 drag: 3.0,
336 iterations: 12,
337 clock: 0.0,
338 drift: Drift::default(),
339 }
340 }
341
342 pub fn drifting(mut self, drift: Drift) -> Self {
343 self.drift = drift;
344 self
345 }
346
347 pub fn pin(&mut self, index: usize, at: [f32; 2]) {
348 if let Some(slot) = self.pins.get_mut(index) {
349 *slot = Some(at);
350 self.pos[index] = at;
351 self.prev[index] = at;
352 }
353 }
354
355 pub fn nodes(&self) -> &[[f32; 2]] {
356 &self.pos
357 }
358
359 pub fn length_error(&self) -> f32 {
360 let mut worst: f32 = 0.0;
361 for (j, rest) in self.rest.iter().enumerate() {
362 let len = len2(sub2(self.pos[j + 1], self.pos[j]));
363 worst = worst.max((len - rest).abs());
364 }
365 worst
366 }
367
368 pub fn step(&mut self, dt: f32, gravity: [f32; 2]) {
369 self.step_at(dt, 0.0, gravity, 0.0);
370 }
371
372 pub fn step_at(&mut self, dt: f32, time: f32, gravity: [f32; 2], flow: f32) {
373 if !dt.is_finite() || dt <= 0.0 {
374 return;
375 }
376 self.clock = (self.clock + dt).min(SUBSTEP * 60.0);
377 let steps = (self.clock / SUBSTEP).floor() as usize;
378 self.clock -= steps as f32 * SUBSTEP;
379 let h = SUBSTEP;
380 let keep = (-self.drag * h).exp();
381 for step in 0..steps {
382 let t = time + h * step as f32;
383 for j in 0..self.pos.len() {
384 if let Some(pin) = self.pins[j] {
385 self.pos[j] = pin;
386 self.prev[j] = pin;
387 continue;
388 }
389 let p = self.pos[j];
390 let v = scale2(sub2(p, self.prev[j]), keep);
391 let w = self.drift.at(p, t);
392 let f = add2(gravity, scale2(w, flow));
393 self.prev[j] = p;
394 self.pos[j] = add2(add2(p, v), scale2(f, h * h));
395 }
396 for _ in 0..self.iterations {
397 for j in 0..self.rest.len() {
398 let a_pin = self.pins[j].is_some();
399 let b_pin = self.pins[j + 1].is_some();
400 if a_pin && b_pin {
401 continue;
402 }
403 let a = self.pos[j];
404 let b = self.pos[j + 1];
405 let d = sub2(b, a);
406 let len = len2(d).max(1e-4);
407 let fix = (len - self.rest[j]) / len;
408 if a_pin {
409 self.pos[j + 1] = sub2(b, scale2(d, fix));
410 } else if b_pin {
411 self.pos[j] = add2(a, scale2(d, fix));
412 } else {
413 self.pos[j] = add2(a, scale2(d, fix * 0.5));
414 self.pos[j + 1] = sub2(b, scale2(d, fix * 0.5));
415 }
416 }
417 for j in 0..self.pos.len() {
418 if let Some(pin) = self.pins[j] {
419 self.pos[j] = pin;
420 }
421 }
422 }
423 }
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[derive(Default)]
432 struct ReferenceChain {
433 nodes: Vec<[f32; 2]>,
434 prev: Vec<[f32; 2]>,
435 cords: Vec<[f32; 2]>,
436 cords_prev: Vec<[f32; 2]>,
437 clock: f32,
438 inputs: Vec<[f32; 2]>,
439 asleep: bool,
440 }
441
442 const REFERENCE: ChainParams = ChainParams {
443 pull: 28.0,
444 drag: 2.5,
445 flow: 22.0,
446 slack: 1.08,
447 cord: 14.0,
448 drift: Drift {
449 x_main: [0.023, 0.43],
450 x_side: [0.011, 0.33, 0.009],
451 y_main: [0.017, 0.37],
452 y_side: [0.019, 0.29],
453 side: 0.55,
454 },
455 };
456
457 fn reference_drift(p: [f32; 2], time: f32) -> [f32; 2] {
458 [
459 (p[1] * 0.023 + time * 0.43).sin()
460 + 0.55 * (p[0] * 0.011 - time * 0.33 + p[1] * 0.009).sin(),
461 (p[0] * 0.017 - time * 0.37).cos() + 0.55 * (p[1] * 0.019 + time * 0.29).cos(),
462 ]
463 }
464
465 impl ReferenceChain {
466 fn step(
467 &mut self,
468 dt: f32,
469 time: f32,
470 anchor: [f32; 2],
471 beads: &[[f32; 2]],
472 cords: &[[f32; 2]],
473 idle: f32,
474 ) {
475 let [pull, drag, flow, slack, cord] = [28.0, 2.5, 22.0, 1.08, 14.0];
476 let flow = flow * idle;
477 let wanted = if beads.is_empty() {
478 0
479 } else {
480 beads.len() * PER + 1
481 };
482 while self.nodes.len() < wanted {
483 let from = self.nodes.last().copied().unwrap_or(anchor);
484 self.nodes.push(from);
485 self.prev.push(from);
486 }
487 self.nodes.truncate(wanted);
488 self.prev.truncate(wanted);
489 while self.cords.len() < cords.len() {
490 let i = self.cords.len();
491 let from = self.nodes.get((i + 2) * PER).copied().unwrap_or(anchor);
492 self.cords.push(from);
493 self.cords_prev.push(from);
494 }
495 self.cords.truncate(cords.len());
496 self.cords_prev.truncate(cords.len());
497 if wanted == 0 {
498 return;
499 }
500 let inputs: Vec<[f32; 2]> = std::iter::once(anchor)
501 .chain(beads.iter().copied())
502 .chain(cords.iter().copied())
503 .collect();
504 let same = inputs.len() == self.inputs.len()
505 && inputs
506 .iter()
507 .zip(&self.inputs)
508 .all(|(a, b)| (a[0] - b[0]).abs() < 0.01 && (a[1] - b[1]).abs() < 0.01);
509 self.inputs = inputs;
510 if !same {
511 self.asleep = false;
512 }
513 if self.asleep {
514 self.clock = 0.0;
515 return;
516 }
517 let point = |k: usize| if k == 0 { anchor } else { beads[k - 1] };
518 let rest: Vec<f32> = (0..beads.len())
519 .map(|k| {
520 let (a, b) = (point(k), point(k + 1));
521 (b[0] - a[0]).hypot(b[1] - a[1]) / PER as f32 * slack
522 })
523 .collect();
524 let h = 1.0 / 240.0;
525 self.clock = (self.clock + dt).min(h * 60.0);
526 let steps = (self.clock / h).floor() as usize;
527 self.clock -= steps as f32 * h;
528 let keep = (-drag * h).exp();
529 for step in 0..steps {
530 let t = time + h * step as f32;
531 self.nodes[0] = anchor;
532 self.prev[0] = anchor;
533 for j in 1..self.nodes.len() {
534 let p = self.nodes[j];
535 let v = [
536 (p[0] - self.prev[j][0]) * keep,
537 (p[1] - self.prev[j][1]) * keep,
538 ];
539 let w = reference_drift(p, t);
540 let mut f = [w[0] * flow, w[1] * flow];
541 if j % PER == 0 {
542 let goal = point(j / PER);
543 f[0] += pull * (goal[0] - p[0]);
544 f[1] += pull * (goal[1] - p[1]);
545 }
546 self.prev[j] = p;
547 self.nodes[j] = [p[0] + v[0] + f[0] * h * h, p[1] + v[1] + f[1] * h * h];
548 }
549 for (i, goal) in cords.iter().enumerate() {
550 let p = self.cords[i];
551 let v = [
552 (p[0] - self.cords_prev[i][0]) * keep,
553 (p[1] - self.cords_prev[i][1]) * keep,
554 ];
555 let w = reference_drift(p, t + 3.0);
556 let f = [
557 w[0] * flow + pull * 1.6 * (goal[0] - p[0]),
558 w[1] * flow + pull * 1.6 * (goal[1] - p[1]),
559 ];
560 self.cords_prev[i] = p;
561 self.cords[i] = [p[0] + v[0] + f[0] * h * h, p[1] + v[1] + f[1] * h * h];
562 }
563 for _ in 0..12 {
564 for j in 0..self.nodes.len() - 1 {
565 let (a, b) = (self.nodes[j], self.nodes[j + 1]);
566 let d = [b[0] - a[0], b[1] - a[1]];
567 let len = d[0].hypot(d[1]).max(1e-4);
568 let target = rest[(j / PER).min(rest.len() - 1)];
569 let fix = (len - target) / len;
570 if j == 0 {
571 self.nodes[1] = [b[0] - d[0] * fix, b[1] - d[1] * fix];
572 } else {
573 self.nodes[j] = [a[0] + d[0] * fix * 0.5, a[1] + d[1] * fix * 0.5];
574 self.nodes[j + 1] = [b[0] - d[0] * fix * 0.5, b[1] - d[1] * fix * 0.5];
575 }
576 }
577 for i in 0..self.cords.len() {
578 let Some(a) = self.nodes.get((i + 2) * PER).copied() else {
579 continue;
580 };
581 let b = self.cords[i];
582 let d = [b[0] - a[0], b[1] - a[1]];
583 let len = d[0].hypot(d[1]).max(1e-4);
584 let fix = (len - cord) / len;
585 self.cords[i] = [b[0] - d[0] * fix, b[1] - d[1] * fix];
586 }
587 }
588 }
589 let moving = |now: &[[f32; 2]], was: &[[f32; 2]]| {
590 now.iter()
591 .zip(was)
592 .any(|(a, b)| (a[0] - b[0]).abs() > 0.01 || (a[1] - b[1]).abs() > 0.01)
593 };
594 if steps > 0
595 && !moving(&self.nodes, &self.prev)
596 && !moving(&self.cords, &self.cords_prev)
597 {
598 self.prev = self.nodes.clone();
599 self.cords_prev = self.cords.clone();
600 self.asleep = true;
601 }
602 }
603
604 fn bead(&self, k: usize) -> Option<[f32; 2]> {
605 self.nodes.get((k + 1) * PER).copied()
606 }
607
608 fn snap(&mut self, anchor: [f32; 2], beads: &[[f32; 2]], cords: &[[f32; 2]]) {
609 let point = |k: usize| if k == 0 { anchor } else { beads[k - 1] };
610 self.nodes = vec![anchor];
611 for k in 0..beads.len() {
612 let (a, b) = (point(k), point(k + 1));
613 for j in 1..=PER {
614 let t = j as f32 / PER as f32;
615 self.nodes
616 .push([a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);
617 }
618 }
619 self.prev = self.nodes.clone();
620 self.cords = cords.to_vec();
621 self.cords_prev = cords.to_vec();
622 }
623
624 fn wires(&self, out: &mut Vec<[f32; 4]>) {
625 let at = |i: isize| self.nodes[i.clamp(0, self.nodes.len() as isize - 1) as usize];
626 for i in 0..self.nodes.len().saturating_sub(1) {
627 let (p0, p1, p2, p3) = (
628 at(i as isize - 1),
629 at(i as isize),
630 at(i as isize + 1),
631 at(i as isize + 2),
632 );
633 let mut prev = p1;
634 for k in 1..=3 {
635 let t = k as f32 / 3.0;
636 let (t2, t3) = (t * t, t * t * t);
637 let f = |a: f32, b: f32, c: f32, d: f32| {
638 0.5 * (2.0 * b
639 + (-a + c) * t
640 + (2.0 * a - 5.0 * b + 4.0 * c - d) * t2
641 + (-a + 3.0 * b - 3.0 * c + d) * t3)
642 };
643 let next = [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
644 out.push([prev[0], prev[1], next[0], next[1]]);
645 prev = next;
646 }
647 }
648 for (i, end) in self.cords.iter().enumerate() {
649 if let Some(start) = self.nodes.get((i + 2) * PER) {
650 out.push([start[0], start[1], end[0], end[1]]);
651 }
652 }
653 }
654 }
655
656 #[test]
657 fn pinned_rope_keeps_its_length() {
658 let start = [0.0, 0.0];
659 let end = [10.0, 0.0];
660 let mut rope = Rope::between(start, end, 10, 1.15);
661 let dt = SUBSTEP * 4.0;
662 for frame in 0..180 {
663 rope.step_at(dt, frame as f32 * dt, [0.0, -400.0], 6.0);
664 }
665 assert_eq!(rope.nodes()[0], start);
666 assert_eq!(*rope.nodes().last().unwrap(), end);
667 let err = rope.length_error();
668 assert!(err < 0.03, "length error {err}");
669 }
670
671 #[test]
672 fn chain_matches_reference_step_for_step() {
673 let mut chain = Chain::new(REFERENCE);
674 let mut reference = ReferenceChain::default();
675 let anchor = [20.0, 40.0];
676 let mut beads = vec![[55.0, 45.0], [89.0, 60.0]];
677 let mut cords = vec![[95.0, 85.0]];
678 let dt = 1.0 / 60.0;
679 let mut slept = false;
680 for frame in 0..4000 {
681 if frame == 30 {
682 beads.push([120.0, 75.0]);
683 cords.push([125.0, 100.0]);
684 }
685 if frame == 60 {
686 beads.remove(1);
687 cords.pop();
688 }
689 if frame == 90 {
690 beads[0] = [62.0, 53.0];
691 beads[1] = [117.0, 72.0];
692 cords[0] = [118.0, 95.0];
693 chain.snap(anchor, &beads, &cords);
694 reference.snap(anchor, &beads, &cords);
695 }
696 let idle = if frame < 90 { 0.7 } else { 0.0 };
697 let time = frame as f32 * dt;
698 chain.step(dt, time, anchor, &beads, &cords, idle);
699 reference.step(dt, time, anchor, &beads, &cords, idle);
700 assert_eq!(chain.nodes().len(), reference.nodes.len(), "frame {frame}");
701 assert_eq!(chain.cords().len(), reference.cords.len(), "frame {frame}");
702 for (index, (actual, expected)) in
703 chain.nodes().iter().zip(&reference.nodes).enumerate()
704 {
705 for axis in 0..2 {
706 assert!(
707 (actual[axis] - expected[axis]).abs() < 0.01,
708 "frame {frame} node {index} axis {axis}: {} != {}",
709 actual[axis],
710 expected[axis]
711 );
712 }
713 }
714 for (index, (actual, expected)) in
715 chain.cords().iter().zip(&reference.cords).enumerate()
716 {
717 for axis in 0..2 {
718 assert!(
719 (actual[axis] - expected[axis]).abs() < 0.01,
720 "frame {frame} cord {index} axis {axis}: {} != {}",
721 actual[axis],
722 expected[axis]
723 );
724 }
725 }
726 assert_eq!(chain.asleep(), reference.asleep, "frame {frame}");
727 for index in 0..beads.len() {
728 let actual = chain.bead(index).unwrap();
729 let from_node = chain.nodes()[(index + 1) * PER];
730 assert_eq!(actual, from_node);
731 assert_eq!(actual, reference.bead(index).unwrap());
732 }
733 let mut expected_wires = Vec::new();
734 reference.wires(&mut expected_wires);
735 assert_eq!(chain.wires(), expected_wires);
736 if frame >= 90 && chain.asleep() {
737 slept = true;
738 break;
739 }
740 }
741 assert!(slept);
742 beads[0][0] += 0.02;
743 chain.step(0.0, 70.0, anchor, &beads, &cords, 0.0);
744 reference.step(0.0, 70.0, anchor, &beads, &cords, 0.0);
745 assert_eq!(chain.asleep(), reference.asleep);
746 assert!(!chain.asleep());
747 }
748
749 #[test]
750 fn chain_wire_counts_and_cap() {
751 let mut chain = Chain::default();
752 let beads = [[10.0, 0.0], [20.0, 0.0]];
753 let cords = [[20.0, 16.0]];
754 assert!(chain.wires().is_empty());
755 chain.snap([0.0, 0.0], &beads, &cords);
756 assert_eq!(chain.wires().len(), 31);
757 assert_eq!(chain.wires()[0][0..2], [0.0, 0.0]);
758 assert_eq!(chain.wires()[2][2..4], [2.0, 0.0]);
759 assert_eq!(chain.wires()[30], [20.0, 0.0, 20.0, 16.0]);
760 let many: Vec<_> = (1..=18).map(|i| [i as f32 * 10.0, 0.0]).collect();
761 chain.snap([0.0, 0.0], &many, &[]);
762 assert_eq!(chain.wires().len(), 256);
763 }
764
765 #[test]
766 fn chain_is_deterministic() {
767 let params = ChainParams::default();
768 let mut a = Chain::new(params);
769 let mut b = Chain::new(params);
770 let beads = [[15.0, 9.0], [30.0, 20.0]];
771 let cords = [[32.0, 40.0]];
772 for frame in 0..120 {
773 let dt = if frame % 3 == 0 { 0.033 } else { 0.011 };
774 let time = frame as f32 * 0.019;
775 a.step(dt, time, [0.0, 0.0], &beads, &cords, 0.6);
776 b.step(dt, time, [0.0, 0.0], &beads, &cords, 0.6);
777 assert_eq!(a.nodes(), b.nodes());
778 assert_eq!(a.cords(), b.cords());
779 assert_eq!(a.asleep(), b.asleep());
780 }
781 }
782}