Skip to main content

pfx_physics/
rope.rs

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}