Skip to main content

pfx_physics/
shape.rs

1use crate::math::{add2, dot2, len2, mix, mix2, norm2, scale2, sub2};
2
3pub const SPACING: f32 = 2.4;
4pub const KERNEL: f32 = 6.0;
5pub const SUBSTEP: f32 = 1.0 / 240.0;
6pub const SLOTS: u32 = 32;
7pub const MAX_PARTICLES: u32 = 65_536;
8pub const MAX_TARGETS: usize = 128;
9pub const DRAIN_TIME: f32 = 1.5;
10pub const RIM: f32 = 5.0;
11const CARRY: [f32; 2] = [40.0, 160.0];
12const DROP_PULL: f32 = 1.0;
13const SHARPNESS: f32 = 6.0;
14
15#[derive(Clone, Copy, Debug)]
16pub struct Form {
17    pub tilt: f32,
18    pub irregular: f32,
19    pub seed: f32,
20    pub rim: f32,
21    pub weight: [f32; 2],
22}
23
24#[derive(Clone, Copy, Debug)]
25pub struct Look {
26    pub tall: f32,
27    pub active: f32,
28    pub weight_b: f32,
29    pub weight_a: f32,
30}
31
32#[repr(C)]
33#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Default, Debug, PartialEq)]
34pub struct Target {
35    pub a: [f32; 4],
36    pub b: [f32; 4],
37    pub c: [f32; 4],
38    pub look: [f32; 4],
39    pub flow: [f32; 4],
40    pub form: [f32; 4],
41    pub carry: [f32; 4],
42}
43
44#[repr(C)]
45#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Default, Debug)]
46pub struct Particle {
47    pub pos: [f32; 2],
48    pub vel: [f32; 2],
49    pub owner: u32,
50    pub rho: f32,
51    pub life: f32,
52    pub born: f32,
53}
54
55#[derive(Clone, Copy, Debug)]
56pub struct Params {
57    pub pressure: f32,
58    pub contain: f32,
59    pub cohesion: f32,
60    pub damping: f32,
61    pub viscosity: f32,
62    pub gather: f32,
63    pub tension: [f32; 4],
64    pub apart: [f32; 4],
65    pub life_rate: f32,
66}
67
68impl Default for Params {
69    fn default() -> Self {
70        Self {
71            pressure: 60_000.0,
72            contain: 4_000.0,
73            cohesion: 30.0,
74            damping: 8.0,
75            viscosity: 100.0,
76            gather: 1.5,
77            tension: [16.0, 3.0, 0.5, 0.0],
78            apart: [40_000.0, 3.0, 0.0, 0.0],
79            life_rate: 3.0,
80        }
81    }
82}
83
84#[derive(Clone, Debug)]
85pub struct Pour {
86    pub key: String,
87    pub target: Target,
88    pub from: Option<[f32; 2]>,
89    pub fill: Option<Target>,
90}
91
92impl Target {
93    fn with(
94        kind: f32,
95        a: [f32; 4],
96        b: [f32; 4],
97        c: [f32; 4],
98        form: [f32; 4],
99        look: &Look,
100        pull: f32,
101    ) -> Self {
102        Self {
103            a,
104            b,
105            c,
106            look: [look.tall, look.active, look.weight_b, look.weight_a],
107            flow: [1.0, pull, kind, 1.0],
108            form,
109            carry: [0.0; 4],
110        }
111    }
112
113    pub fn drop(center: [f32; 2], radii: [f32; 2], form: &Form, look: &Look) -> Self {
114        let r = radii[0].min(radii[1]).max(4.0);
115        Self::with(
116            0.0,
117            [center[0], center[1], radii[0], radii[1]],
118            [form.weight[1], SHARPNESS, 0.0, 0.0],
119            [0.0; 4],
120            [form.tilt, form.irregular, form.seed, form.weight[0]],
121            look,
122            (100.0 / r.max(30.0)).powi(2) * DROP_PULL,
123        )
124    }
125
126    pub fn organic(mut self, bend: f32, wobble: f32, seed: f32) -> Self {
127        self.form = [bend, wobble, seed, 0.0];
128        self
129    }
130
131    pub fn outline(center: [f32; 2], radius: f32, waves: [[f32; 2]; 5], look: &Look) -> Self {
132        let [w2, w3, w4, w6, w7] = waves;
133        Self::with(
134            4.0,
135            [center[0], center[1], radius, 0.0],
136            [w2[0], w3[0], w4[0], w6[0]],
137            [w7[0], w7[1], 0.0, 0.0],
138            [w2[1], w3[1], w4[1], w6[1]],
139            look,
140            (100.0 / radius.max(4.0)).powi(2),
141        )
142    }
143
144    pub fn squash(mut self, height: f32) -> Self {
145        self.a[3] = height;
146        self
147    }
148
149    pub fn outline_radius(&self, p: [f32; 2]) -> f32 {
150        let squash = if self.a[3] > 0.0 { self.a[3] } else { 1.0 };
151        let t = ((p[1] - self.a[1]) / squash).atan2(p[0] - self.a[0]);
152        let wave = |n: f32, amp: f32, phase: f32| amp * (n * t - phase).cos();
153        (t.cos().powi(2) + (squash * t.sin()).powi(2)).sqrt()
154            * self.a[2]
155            * (1.0
156                + wave(2.0, self.b[0], self.form[0])
157                + wave(3.0, self.b[1], self.form[1])
158                + wave(4.0, self.b[2], self.form[2])
159                + wave(6.0, self.b[3], self.form[3])
160                + wave(7.0, self.c[0], self.c[1]))
161    }
162
163    pub fn capsule(from: [f32; 2], to: [f32; 2], radii: [f32; 2], look: &Look) -> Self {
164        let r = radii[0].max(radii[1]).max(2.0);
165        Self::with(
166            1.0,
167            [from[0], from[1], to[0], to[1]],
168            [radii[0], radii[1], 0.0, 0.0],
169            [0.0; 4],
170            [0.0; 4],
171            look,
172            (100.0 / r).powi(2) * 0.6,
173        )
174    }
175
176    pub fn curve(points: [[f32; 2]; 4], radii: [f32; 3], look: &Look) -> Self {
177        let r = radii[0].max(radii[1]).max(2.0);
178        Self::with(
179            2.0,
180            [points[0][0], points[0][1], points[1][0], points[1][1]],
181            [points[2][0], points[2][1], points[3][0], points[3][1]],
182            [radii[0], radii[1], radii[2], 0.0],
183            [0.0; 4],
184            look,
185            (100.0 / r).powi(2) * 0.6,
186        )
187    }
188
189    pub fn bead(center: [f32; 2], radius: f32, look: &Look) -> Self {
190        Self::with(
191            3.0,
192            [center[0], center[1], radius, 0.0],
193            [0.0; 4],
194            [0.0; 4],
195            [0.0; 4],
196            look,
197            (100.0 / radius.max(2.0)).powi(2) * 0.8,
198        )
199    }
200
201    pub fn drifting(mut self, speed: f32) -> Self {
202        self.form[3] = speed;
203        self
204    }
205
206    pub fn flare(mut self, power: f32) -> Self {
207        self.form[0] = power;
208        self
209    }
210
211    pub fn drain(mut self, amount: f32) -> Self {
212        self.form[1] = amount;
213        self
214    }
215
216    pub fn pull(mut self, factor: f32) -> Self {
217        self.flow[1] *= factor;
218        self
219    }
220
221    pub fn mass(mut self, mass: f32) -> Self {
222        self.flow[0] = mass;
223        self
224    }
225
226    pub fn gathered(mut self, to: [f32; 2], pull: f32, scale: f32) -> Self {
227        let toward = |x: f32, y: f32| {
228            [
229                to[0] + (x - to[0]) * (1.0 - pull),
230                to[1] + (y - to[1]) * (1.0 - pull),
231            ]
232        };
233        match self.kind() {
234            1 => {
235                let girth = 0.5 + 0.5 * scale;
236                let a = toward(self.a[0], self.a[1]);
237                let b = toward(
238                    self.a[0] + (self.a[2] - self.a[0]) * scale,
239                    self.a[1] + (self.a[3] - self.a[1]) * scale,
240                );
241                self.a = [a[0], a[1], b[0], b[1]];
242                self.b[0] *= girth;
243                self.b[1] *= girth;
244                self.form[0] *= scale;
245                self.flow[0] *= scale * girth;
246            }
247            2 => {
248                let p0 = toward(self.a[0], self.a[1]);
249                let p1 = toward(self.a[2], self.a[3]);
250                let p2 = toward(self.b[0], self.b[1]);
251                let p3 = toward(self.b[2], self.b[3]);
252                self.a = [p0[0], p0[1], p1[0], p1[1]];
253                self.b = [p2[0], p2[1], p3[0], p3[1]];
254                self.c[0] *= scale;
255                self.c[1] *= scale;
256                self.c[2] *= scale;
257                self.flow[0] *= scale * scale;
258            }
259            _ => {
260                let c = toward(self.a[0], self.a[1]);
261                self.a[0] = c[0];
262                self.a[1] = c[1];
263                self.a[2] *= scale;
264                if self.kind() != 4 {
265                    self.a[3] *= scale;
266                }
267                self.flow[0] *= scale * scale;
268            }
269        }
270        self
271    }
272
273    pub fn shifted(mut self, by: [f32; 2]) -> Self {
274        let move_by = |x: &mut f32, y: &mut f32| {
275            *x += by[0];
276            *y += by[1];
277        };
278        match self.kind() {
279            1 => {
280                let [a0, a1, a2, a3] = &mut self.a;
281                move_by(a0, a1);
282                move_by(a2, a3);
283            }
284            2 => {
285                let [a0, a1, a2, a3] = &mut self.a;
286                move_by(a0, a1);
287                move_by(a2, a3);
288                let [b0, b1, b2, b3] = &mut self.b;
289                move_by(b0, b1);
290                move_by(b2, b3);
291            }
292            _ => {
293                let [a0, a1, _, _] = &mut self.a;
294                move_by(a0, a1);
295            }
296        }
297        self
298    }
299
300    pub fn origin(&self) -> [f32; 2] {
301        [self.a[0], self.a[1]]
302    }
303
304    pub fn revealed(mut self, to: f32) -> Self {
305        self.carry[2] = to;
306        self.carry[3] = 1.0;
307        self
308    }
309
310    pub fn kind(&self) -> u32 {
311        self.flow[2] as u32
312    }
313
314    pub(crate) fn carried(mut self, before: &Target, dt: f32) -> Self {
315        self.carry = [0.0, 0.0, self.carry[2], self.carry[3]];
316        if let (Some(now), Some(was), true) = (
317            self.anchor(),
318            before.anchor(),
319            dt > 1e-4 && self.kind() == before.kind(),
320        ) {
321            let v = [(now[0] - was[0]) / dt, (now[1] - was[1]) / dt];
322            let speed = v[0].hypot(v[1]);
323            let [slow, fast] = CARRY;
324            let k = ((speed - slow) / (fast - slow)).clamp(0.0, 1.0);
325            let k = k * k * (3.0 - 2.0 * k);
326            if speed < 4000.0 {
327                self.carry = [v[0] * k, v[1] * k, self.carry[2], self.carry[3]];
328            }
329        }
330        self
331    }
332
333    fn anchor(&self) -> Option<[f32; 2]> {
334        match self.kind() {
335            1 => Some([(self.a[0] + self.a[2]) * 0.5, (self.a[1] + self.a[3]) * 0.5]),
336            2 => None,
337            _ => Some([self.a[0], self.a[1]]),
338        }
339    }
340
341    pub(crate) fn spawns(&self, p: [f32; 2]) -> bool {
342        if !self.inside(p) {
343            return false;
344        }
345        if self.c[3] < 0.5 {
346            return true;
347        }
348        if self.kind() == 2 {
349            let (distance, radius, u) = self.curve_near(p);
350            return distance > radius - self.form[3].max(SPACING)
351                && u > self.form[2]
352                && u < 1.0 - self.form[2];
353        }
354        if self.kind() == 0 && self.b[3] > 0.0 {
355            let angle = (p[1] - self.a[1]).atan2(p[0] - self.a[0]);
356            let off = (angle - self.b[2] + std::f32::consts::PI).rem_euclid(std::f32::consts::TAU)
357                - std::f32::consts::PI;
358            if off.abs() < self.b[3] {
359                return false;
360            }
361        }
362        let band = if self.kind() == 0 {
363            RIM
364        } else {
365            self.form[3].max(SPACING)
366        };
367        match self.kind() {
368            1 => {
369                let ba = [self.a[2] - self.a[0], self.a[3] - self.a[1]];
370                let pa = [p[0] - self.a[0], p[1] - self.a[1]];
371                let h = ((pa[0] * ba[0] + pa[1] * ba[1])
372                    / (ba[0] * ba[0] + ba[1] * ba[1]).max(1e-4))
373                .clamp(0.0, 1.0);
374                let length = ba[0].hypot(ba[1]).max(1e-3);
375                let normal = [-ba[1] / length, ba[0] / length];
376                let lift = self.form[0] * (std::f32::consts::PI * h).sin();
377                let axis = [
378                    self.a[0] + ba[0] * h + normal[0] * lift,
379                    self.a[1] + ba[1] * h + normal[1] * lift,
380                ];
381                let wobble = 0.6 * (std::f32::consts::TAU * h * 1.3 + self.form[2]).sin()
382                    + 0.4 * (std::f32::consts::TAU * h * 2.7 + self.form[2] * 1.7).sin();
383                let r = (self.b[0] + (self.b[1] - self.b[0]) * (1.0 - (1.0 - h) * (1.0 - h)))
384                    * (1.0 + self.form[1] * wobble);
385                (p[0] - axis[0]).hypot(p[1] - axis[1]) < r
386            }
387            3 => (p[0] - self.a[0]).hypot(p[1] - self.a[1]) > self.a[2] - band,
388            _ => {
389                let (s, c) = (-self.form[0]).sin_cos();
390                let x = p[0] - self.a[0];
391                let y = p[1] - self.a[1];
392                let local = [c * x - s * y, s * x + c * y];
393                let k = (local[0] / (self.a[2] * 0.92)).hypot(local[1] / (self.a[3] * 0.92));
394                k > 1.0 - band / (self.a[2].min(self.a[3]) * 0.92)
395            }
396        }
397    }
398
399    fn curve_near(&self, p: [f32; 2]) -> (f32, f32, f32) {
400        let mut best = (f32::MAX, 0.0, 0.0);
401        let mut prev = bezier(self, 0.0);
402        for i in 1..=16 {
403            let next = bezier(self, i as f32 / 16.0);
404            let ba = sub2(next, prev);
405            let pa = sub2(p, prev);
406            let h = (dot2(pa, ba) / dot2(ba, ba).max(1e-4)).clamp(0.0, 1.0);
407            let d = len2(sub2(pa, scale2(ba, h)));
408            if d < best.0 {
409                let u = (i as f32 - 1.0 + h) / 16.0;
410                let e = (2.0 * u - 1.0).abs();
411                let power = if self.form[0] > 0.0 {
412                    self.form[0]
413                } else {
414                    2.0
415                };
416                let r = self.c[2]
417                    + (self.c[0] + (self.c[1] - self.c[0]) * u - self.c[2]) * e.powf(power);
418                best = (d, r, u);
419            }
420            prev = next;
421        }
422        best
423    }
424
425    pub(crate) fn inside(&self, p: [f32; 2]) -> bool {
426        let segment = |a: [f32; 2], b: [f32; 2]| {
427            let ba = sub2(b, a);
428            let pa = sub2(p, a);
429            let h = (dot2(pa, ba) / dot2(ba, ba).max(1e-4)).clamp(0.0, 1.0);
430            (len2(sub2(pa, scale2(ba, h))), h)
431        };
432        match self.kind() {
433            1 => {
434                let (d, h) = segment([self.a[0], self.a[1]], [self.a[2], self.a[3]]);
435                d < self.b[0] + (self.b[1] - self.b[0]) * (1.0 - (1.0 - h) * (1.0 - h))
436            }
437            2 => {
438                let mut prev = bezier(self, 0.0);
439                for i in 1..=16 {
440                    let next = bezier(self, i as f32 / 16.0);
441                    let (d, h) = segment(prev, next);
442                    let u = (i as f32 - 1.0 + h) / 16.0;
443                    let e = (2.0 * u - 1.0).abs();
444                    let power = if self.form[0] > 0.0 {
445                        self.form[0]
446                    } else {
447                        2.0
448                    };
449                    let r = self.c[2]
450                        + (self.c[0] + (self.c[1] - self.c[0]) * u - self.c[2]) * e.powf(power);
451                    if d < r.max(SPACING * 0.75) {
452                        return true;
453                    }
454                    prev = next;
455                }
456                false
457            }
458            3 => (p[0] - self.a[0]).hypot(p[1] - self.a[1]) < self.a[2],
459            4 => (p[0] - self.a[0]).hypot(p[1] - self.a[1]) < self.outline_radius(p) * 0.97,
460            _ => {
461                let (s, c) = (-self.form[0]).sin_cos();
462                let x = p[0] - self.a[0];
463                let y = p[1] - self.a[1];
464                let local = [c * x - s * y, s * x + c * y];
465                (local[0] / (self.a[2] * 0.92)).hypot(local[1] / (self.a[3] * 0.92)) < 1.0
466            }
467        }
468    }
469
470    pub(crate) fn bounds(&self) -> ([f32; 2], [f32; 2]) {
471        match self.kind() {
472            1 => {
473                let r = self.b[0].max(self.b[1]);
474                (
475                    [self.a[0].min(self.a[2]) - r, self.a[1].min(self.a[3]) - r],
476                    [self.a[0].max(self.a[2]) + r, self.a[1].max(self.a[3]) + r],
477                )
478            }
479            2 => {
480                let r = self.c[0].max(self.c[1]).max(self.c[2]);
481                let xs = [self.a[0], self.a[2], self.b[0], self.b[2]];
482                let ys = [self.a[1], self.a[3], self.b[1], self.b[3]];
483                (
484                    [
485                        xs.into_iter().fold(f32::MAX, f32::min) - r,
486                        ys.into_iter().fold(f32::MAX, f32::min) - r,
487                    ],
488                    [
489                        xs.into_iter().fold(f32::MIN, f32::max) + r,
490                        ys.into_iter().fold(f32::MIN, f32::max) + r,
491                    ],
492                )
493            }
494            3 => (
495                [self.a[0] - self.a[2], self.a[1] - self.a[2]],
496                [self.a[0] + self.a[2], self.a[1] + self.a[2]],
497            ),
498            4 => {
499                let r = self.a[2] * 1.4;
500                (
501                    [self.a[0] - r, self.a[1] - r],
502                    [self.a[0] + r, self.a[1] + r],
503                )
504            }
505            _ => {
506                let r = self.a[2].max(self.a[3]) * 1.2;
507                (
508                    [self.a[0] - r, self.a[1] - r],
509                    [self.a[0] + r, self.a[1] + r],
510                )
511            }
512        }
513    }
514}
515
516pub fn rest_density() -> f32 {
517    let h = KERNEL;
518    let reach = (h / SPACING).ceil() as i32;
519    let mut sum = 0.0;
520    for y in -reach..=reach {
521        for x in -reach..=reach {
522            let r2 = ((x * x + y * y) as f32) * SPACING * SPACING;
523            if r2 < h * h {
524                let d = h * h - r2;
525                sum += 4.0 / (std::f32::consts::PI * h.powi(8)) * d * d * d;
526            }
527        }
528    }
529    sum
530}
531
532pub(crate) fn spawn(
533    target: &Target,
534    slot: usize,
535    from: Option<[f32; 2]>,
536    seed: u32,
537) -> Vec<Particle> {
538    let (lo, hi) = target.bounds();
539    let mut out = Vec::new();
540    let mut rng = seed.wrapping_mul(2_654_435_761) | 1;
541    let mut jitter = || {
542        rng ^= rng << 13;
543        rng ^= rng >> 17;
544        rng ^= rng << 5;
545        ((rng >> 8) as f32 / (1u32 << 24) as f32 - 0.5) * SPACING * 0.3
546    };
547    let mut y = lo[1];
548    while y <= hi[1] {
549        let mut x = lo[0];
550        while x <= hi[0] {
551            if target.spawns([x, y]) {
552                let mut at = [x + jitter(), y + jitter()];
553                if let Some(from) = from {
554                    at = add2(from, scale2(sub2(at, from), 0.3));
555                }
556                out.push(Particle {
557                    pos: at,
558                    vel: [0.0, 0.0],
559                    owner: slot as u32,
560                    rho: 1.0,
561                    life: 0.0,
562                    born: 1.0,
563                });
564            }
565            x += SPACING;
566        }
567        y += SPACING;
568    }
569    out
570}
571
572pub(crate) fn cells_for(table: [f32; 2]) -> [usize; 2] {
573    [
574        (table[0] / KERNEL).ceil().max(1.0) as usize,
575        (table[1] / KERNEL).ceil().max(1.0) as usize,
576    ]
577}
578
579pub(crate) fn poly6(r2: f32) -> f32 {
580    let h2 = KERNEL * KERNEL;
581    if r2 >= h2 {
582        return 0.0;
583    }
584    let d = h2 - r2;
585    4.0 / (std::f32::consts::PI * KERNEL.powi(8)) * d * d * d
586}
587
588pub(crate) fn spiky(r: f32) -> f32 {
589    if r >= KERNEL || r <= 1e-5 {
590        return 0.0;
591    }
592    let d = KERNEL - r;
593    -30.0 / (std::f32::consts::PI * KERNEL.powi(5)) * d * d
594}
595
596pub(crate) fn region(target: &Target, p: [f32; 2], time: f32, calm: f32) -> [f32; 3] {
597    match target.kind() {
598        1 => {
599            let a = [target.a[0], target.a[1]];
600            let b = [target.a[2], target.a[3]];
601            let s = segment(p, a, b);
602            let span = sub2(b, a);
603            let normal = scale2([-span[1], span[0]], 1.0 / len2(span).max(1e-3));
604            let u = s[1];
605            let axis = add2(
606                mix2(a, b, u),
607                scale2(normal, target.form[0] * (std::f32::consts::PI * u).sin()),
608            );
609            let drift = time * calm * target.form[3];
610            let wobble = 0.6 * (std::f32::consts::TAU * u * 1.3 + target.form[2] + drift).sin()
611                + 0.4
612                    * (std::f32::consts::TAU * u * 2.7 + target.form[2] * 1.7 - drift * 0.7).sin();
613            let radius = mix(target.b[0], target.b[1], 1.0 - (1.0 - u) * (1.0 - u))
614                * (1.0 + target.form[1] * wobble);
615            [len2(sub2(p, axis)) - radius, axis[0], axis[1]]
616        }
617        2 => {
618            let mut best = [1e5, p[0], p[1]];
619            let mut prev = bezier(target, 0.0);
620            for i in 1..=16 {
621                let u1 = i as f32 / 16.0;
622                let next = bezier(target, u1);
623                let s = segment(p, prev, next);
624                let u = (i as f32 - 1.0 + s[1]) / 16.0;
625                let e = (2.0 * u - 1.0).abs();
626                let power = if target.form[0] > 0.0 {
627                    target.form[0]
628                } else {
629                    2.0
630                };
631                let radius = mix(target.c[2], mix(target.c[0], target.c[1], u), e.powf(power));
632                if s[0] - radius < best[0] {
633                    let at = mix2(prev, next, s[1]);
634                    best = [s[0] - radius, at[0], at[1]];
635                }
636                prev = next;
637            }
638            best
639        }
640        3 => {
641            let centre = [target.a[0], target.a[1]];
642            [len2(sub2(p, centre)) - target.a[2], centre[0], centre[1]]
643        }
644        4 => {
645            let squash = if target.a[3] > 0.0 { target.a[3] } else { 1.0 };
646            let d = [p[0] - target.a[0], (p[1] - target.a[1]) / squash];
647            let th = d[1].atan2(d[0]);
648            let wave = target.b[0] * (2.0 * th - target.form[0]).cos()
649                + target.b[1] * (3.0 * th - target.form[1]).cos()
650                + target.b[2] * (4.0 * th - target.form[2]).cos()
651                + target.b[3] * (6.0 * th - target.form[3]).cos()
652                + target.c[0] * (7.0 * th - target.c[1]).cos();
653            [
654                (len2(d) - target.a[2] * (1.0 + wave)) * squash.min(1.0),
655                target.a[0],
656                target.a[1],
657            ]
658        }
659        _ => {
660            let local = turn(sub2(p, [target.a[0], target.a[1]]), -target.form[0]);
661            let r = [target.a[2].max(1.0), target.a[3].max(1.0)];
662            let theta = (local[1] / r[1]).atan2(local[0] / r[0]);
663            let c = (theta + target.form[0] - target.form[3]).cos().max(0.0);
664            let s = 1.0
665                + target.form[1] * harmonic(theta, target.form[2])
666                + target.b[0] * c.powf(target.b[1].max(2.0));
667            let shaped = [r[0] * s, r[1] * s];
668            let k0 = len2([local[0] / shaped[0], local[1] / shaped[1]]);
669            let k1 = len2([
670                local[0] / (shaped[0] * shaped[0]),
671                local[1] / (shaped[1] * shaped[1]),
672            ]);
673            [k0 * (k0 - 1.0) / k1.max(1e-5), target.a[0], target.a[1]]
674        }
675    }
676}
677
678pub(crate) fn along(target: &Target, p: [f32; 2]) -> [f32; 4] {
679    let mut total = 0.0;
680    let mut prev = bezier(target, 0.0);
681    for i in 1..=16 {
682        let next = bezier(target, i as f32 / 16.0);
683        total += len2(sub2(next, prev));
684        prev = next;
685    }
686    let mut best = 1e9;
687    let mut out = [0.5, 1.0, 0.0, total];
688    prev = bezier(target, 0.0);
689    for i in 1..=16 {
690        let next = bezier(target, i as f32 / 16.0);
691        let s = segment(p, prev, next);
692        if s[0] < best {
693            best = s[0];
694            let dir = norm2(add2(sub2(next, prev), [1e-5, 0.0]));
695            out = [(i as f32 - 1.0 + s[1]) / 16.0, dir[0], dir[1], total];
696        }
697        prev = next;
698    }
699    out
700}
701
702fn bezier(target: &Target, u: f32) -> [f32; 2] {
703    let v = 1.0 - u;
704    let w = [v * v * v, 3.0 * v * v * u, 3.0 * v * u * u, u * u * u];
705    [
706        w[0] * target.a[0] + w[1] * target.a[2] + w[2] * target.b[0] + w[3] * target.b[2],
707        w[0] * target.a[1] + w[1] * target.a[3] + w[2] * target.b[1] + w[3] * target.b[3],
708    ]
709}
710
711fn segment(p: [f32; 2], a: [f32; 2], b: [f32; 2]) -> [f32; 2] {
712    let ba = sub2(b, a);
713    let h = (dot2(sub2(p, a), ba) / dot2(ba, ba).max(1e-4)).clamp(0.0, 1.0);
714    [len2(sub2(p, add2(a, scale2(ba, h)))), h]
715}
716
717fn turn(p: [f32; 2], angle: f32) -> [f32; 2] {
718    let (s, c) = angle.sin_cos();
719    [c * p[0] - s * p[1], s * p[0] + c * p[1]]
720}
721
722fn harmonic(theta: f32, seed: f32) -> f32 {
723    let square = (seed * 0.618).fract() * 0.9;
724    0.46 * (2.0 * theta + seed * 1.7).sin()
725        + 0.36 * (3.0 * theta + seed * 2.9 + 1.0).sin()
726        + square * 0.40 * (4.0 * theta + seed * 0.7).cos()
727        + 0.12 * (5.0 * theta + seed * 4.3 + 2.0).sin()
728}