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}