1use std::{borrow::Cow, cmp::Ordering, convert::identity, f64::consts::PI, iter, mem};
2
3use crate::{section::general::GameMode, util::Pos};
4
5use super::{path_type::SplineType, PathControlPoint};
6
7const BEZIER_TOLERANCE: f32 = 0.25;
8const CATMULL_DETAIL: usize = 50;
9const CIRCULAR_ARC_TOLERANCE: f32 = 0.1;
10
11#[derive(Clone, Debug, Default)]
13pub struct CurveBuffers {
14 path: Vec<Pos>,
15 lengths: Vec<f64>,
16 vertices: Vec<Pos>,
17 bezier: BezierBuffers,
18}
19
20#[derive(Clone, Debug, Default)]
21struct BezierBuffers {
22 left: Vec<Pos>,
23 right: Vec<Pos>,
24 midpoints: Vec<Pos>,
25 left_child: Vec<Pos>,
26}
27
28impl BezierBuffers {
29 fn extend_exact(&mut self, len: usize) {
33 if len <= self.left.len() {
34 return;
35 }
36
37 let additional = len - self.left.len();
38
39 self.left
40 .extend(iter::repeat(Pos::default()).take(additional));
41 self.right
42 .extend(iter::repeat(Pos::default()).take(additional));
43 self.midpoints
44 .extend(iter::repeat(Pos::default()).take(additional));
45 self.left_child
46 .extend(iter::repeat(Pos::default()).take(additional));
47 }
48}
49
50struct CircularArcProperties {
51 theta_start: f64,
52 theta_range: f64,
53 direction: f64,
54 radius: f32,
55 centre: Pos,
56}
57
58#[derive(Clone, Debug, PartialEq)]
60pub struct Curve {
61 path: Vec<Pos>,
62 lengths: Vec<f64>,
63}
64
65impl Curve {
66 pub fn new(
68 mode: GameMode,
69 points: &[PathControlPoint],
70 expected_len: Option<f64>,
71 bufs: &mut CurveBuffers,
72 ) -> Self {
73 let mut optimized_len = 0.0;
74 calculate_path(mode, points, bufs, &mut optimized_len);
75 calculate_length(bufs, expected_len, optimized_len);
76
77 Self {
78 path: mem::take(&mut bufs.path),
79 lengths: mem::take(&mut bufs.lengths),
80 }
81 }
82
83 pub fn path(&self) -> &[Pos] {
85 &self.path
86 }
87
88 pub fn lengths(&self) -> &[f64] {
90 &self.lengths
91 }
92
93 pub fn position_at(&self, progress: f64) -> Pos {
95 position_at(&self.path, &self.lengths, progress)
96 }
97
98 pub fn progress_to_dist(&self, progress: f64) -> f64 {
101 progress_to_dist(&self.lengths, progress)
102 }
103
104 pub fn dist(&self) -> f64 {
106 dist(&self.lengths)
107 }
108
109 pub fn idx_of_dist(&self, d: f64) -> usize {
111 idx_of_dist(&self.lengths, d)
112 }
113
114 pub fn interpolate_vertices(&self, i: usize, d: f64) -> Pos {
117 interpolate_vertices(&self.path, &self.lengths, i, d)
118 }
119
120 pub fn as_borrowed_curve(&self) -> BorrowedCurve<'_> {
122 BorrowedCurve {
123 path: &self.path,
124 lengths: &self.lengths,
125 }
126 }
127}
128
129#[derive(Clone, Debug, PartialEq)]
131pub struct BorrowedCurve<'bufs> {
132 path: &'bufs [Pos],
133 lengths: &'bufs [f64],
134}
135
136impl<'bufs> BorrowedCurve<'bufs> {
137 pub fn new(
139 mode: GameMode,
140 points: &[PathControlPoint],
141 expected_len: Option<f64>,
142 bufs: &'bufs mut CurveBuffers,
143 ) -> Self {
144 let mut optimized_len = 0.0;
145 calculate_path(mode, points, bufs, &mut optimized_len);
146 calculate_length(bufs, expected_len, optimized_len);
147
148 Self {
149 path: &bufs.path,
150 lengths: &bufs.lengths,
151 }
152 }
153
154 pub const fn path(&self) -> &[Pos] {
156 self.path
157 }
158
159 pub const fn lengths(&self) -> &[f64] {
161 self.lengths
162 }
163
164 pub fn position_at(&self, progress: f64) -> Pos {
166 position_at(self.path, self.lengths, progress)
167 }
168
169 pub fn progress_to_dist(&self, progress: f64) -> f64 {
172 progress_to_dist(self.lengths, progress)
173 }
174
175 pub fn dist(&self) -> f64 {
177 dist(self.lengths)
178 }
179
180 pub fn idx_of_dist(&self, d: f64) -> usize {
182 idx_of_dist(self.lengths, d)
183 }
184
185 pub fn interpolate_vertices(&self, i: usize, d: f64) -> Pos {
188 interpolate_vertices(self.path, self.lengths, i, d)
189 }
190
191 pub fn to_owned_curve(&self) -> Curve {
193 Curve {
194 path: self.path.to_owned(),
195 lengths: self.lengths.to_owned(),
196 }
197 }
198}
199
200fn position_at(path: &[Pos], lengths: &[f64], progress: f64) -> Pos {
201 let d = progress_to_dist(lengths, progress);
202 let i = idx_of_dist(lengths, d);
203
204 interpolate_vertices(path, lengths, i, d)
205}
206
207fn progress_to_dist(lengths: &[f64], progress: f64) -> f64 {
208 progress.clamp(0.0, 1.0) * dist(lengths)
209}
210
211fn dist(lengths: &[f64]) -> f64 {
212 lengths.last().copied().unwrap_or(0.0)
213}
214
215fn idx_of_dist(lengths: &[f64], d: f64) -> usize {
216 lengths
217 .binary_search_by(|len| len.partial_cmp(&d).unwrap_or(Ordering::Equal))
218 .map_or_else(identity, identity)
219}
220
221fn interpolate_vertices(path: &[Pos], lengths: &[f64], i: usize, d: f64) -> Pos {
222 if path.is_empty() {
223 return Pos::default();
224 }
225
226 let p1 = if i == 0 {
227 return path[0];
228 } else if let Some(p) = path.get(i) {
229 *p
230 } else {
231 return path[path.len() - 1];
232 };
233
234 let p0 = path[i - 1];
235
236 let d0 = lengths[i - 1];
237 let d1 = lengths[i];
238
239 if (d0 - d1).abs() <= f64::EPSILON {
242 return p0;
243 }
244
245 let w = (d - d0) / (d1 - d0);
246
247 p0 + (p1 - p0) * w as f32
248}
249
250fn calculate_path(
251 mode: GameMode,
252 points: &[PathControlPoint],
253 bufs: &mut CurveBuffers,
254 optimized_len: &mut f64,
255) {
256 if points.is_empty() {
257 return;
258 }
259
260 let CurveBuffers {
261 vertices,
262 bezier,
263 path,
264 ..
265 } = bufs;
266
267 path.clear();
268 *optimized_len = 0.0;
269
270 vertices.clear();
271 vertices.extend(points.iter().map(|p| p.pos));
272
273 let mut start = 0;
274
275 for i in 0..points.len() {
276 if points[i].path_type.is_none() && i < points.len() - 1 {
277 continue;
278 }
279
280 let segment_vertices = &vertices[start..=i];
282
283 match segment_vertices.len() {
284 0 => {
285 unreachable!();
288 }
289 1 => {
290 path.push(segment_vertices[0]);
292 }
293 _ => {
294 let segment_kind = points[start]
295 .path_type
296 .map_or(SplineType::Linear, |path_type| path_type.kind);
297
298 let path_len = path.len();
299
300 calculate_subpath(
301 mode,
302 path,
303 segment_vertices,
304 segment_kind,
305 optimized_len,
306 bezier,
307 );
308
309 let skip_first = path_len
311 .checked_sub(1)
312 .zip(path.get(path_len))
313 .map_or(false, |(idx, first)| &path[idx] == first);
314
315 if skip_first {
316 path[path_len..].rotate_left(1);
317 path.pop();
318 }
319 }
320 }
321
322 start = i;
326 }
327}
328
329fn calculate_length(bufs: &mut CurveBuffers, expected_len: Option<f64>, optimized_len: f64) {
330 let CurveBuffers {
331 path,
332 lengths: cumulative_len,
333 ..
334 } = bufs;
335
336 cumulative_len.clear();
337 let mut calculated_len = optimized_len;
338 cumulative_len.reserve(path.len());
339 cumulative_len.push(0.0);
340
341 let length_iter = path.iter().zip(path.iter().skip(1)).map(|(&curr, &next)| {
342 calculated_len += f64::from((next - curr).length());
343
344 calculated_len
345 });
346
347 cumulative_len.extend(length_iter);
348
349 if let Some(expected_len) =
350 expected_len.filter(|&len| (calculated_len - len).abs() >= f64::EPSILON)
351 {
352 if matches!(path.as_slice() , [.., a, b] if a == b && expected_len > calculated_len) {
354 cumulative_len.push(calculated_len);
355 return;
356 }
357
358 if cumulative_len.len() == 1 {
360 return;
361 }
362
363 cumulative_len.pop();
365
366 let last_valid = cumulative_len
367 .iter()
368 .rev()
369 .position(|l| *l < expected_len)
370 .map_or(0, |idx| cumulative_len.len() - idx);
371
372 if last_valid < cumulative_len.len() {
375 cumulative_len.truncate(last_valid);
376 path.truncate(last_valid + 1);
377
378 if cumulative_len.is_empty() {
379 cumulative_len.push(0.0);
382
383 return;
384 }
385 }
386
387 let end_idx = cumulative_len.len();
388 let prev_idx = end_idx - 1;
389
390 let dir = (path[end_idx] - path[prev_idx]).normalize();
392
393 path[end_idx] = path[prev_idx] + dir * (expected_len - cumulative_len[prev_idx]) as f32;
394 cumulative_len.push(expected_len);
395 }
396}
397
398fn calculate_subpath(
399 mode: GameMode,
400 path: &mut Vec<Pos>,
401 sub_points: &[Pos],
402 path_type: SplineType,
403 optimized_len: &mut f64,
404 bufs: &mut BezierBuffers,
405) {
406 match path_type {
407 SplineType::Linear => approximate_linear(path, sub_points),
408 SplineType::PerfectCurve => {
409 if let [a, b, c] = sub_points {
410 if approximate_circular_arc(path, *a, *b, *c) {
411 return;
412 }
413 }
414
415 approximate_bezier(path, sub_points, bufs);
416 }
417 SplineType::Catmull => {
418 let start_len = path.len();
419 approximate_catmull(path, sub_points);
420
421 if !matches!(mode, GameMode::Osu) {
422 return;
423 }
424
425 let sub_path = path.split_off(start_len);
426
427 let mut last_start = None;
428 let mut len_removed_since_start = 0.0;
429
430 for (i, curr) in sub_path.iter().copied().enumerate() {
431 let Some(ref last_start_) = last_start else {
432 path.push(curr);
433 last_start = Some(curr);
434
435 continue;
436 };
437
438 let dist_from_start = f64::from(last_start_.distance(curr));
439 len_removed_since_start += f64::from(sub_path[i - 1].distance(curr));
440
441 #[allow(clippy::items_after_statements)]
443 const CATMULL_SEGMENT_LEN: usize = CATMULL_DETAIL * 2;
444
445 if dist_from_start > 6.0
447 || ((i + 1) % CATMULL_SEGMENT_LEN) == 0
448 || i == sub_path.len() - 1
449 {
450 path.push(curr);
451 *optimized_len += len_removed_since_start - dist_from_start;
452
453 last_start = None;
454 len_removed_since_start = 0.0;
455 }
456 }
457 }
458 SplineType::BSpline => approximate_bezier(path, sub_points, bufs),
459 }
460}
461
462fn approximate_bezier(path: &mut Vec<Pos>, points: &[Pos], bufs: &mut BezierBuffers) {
463 bufs.extend_exact(points.len());
464
465 approximate_bspline(path, points, bufs);
466}
467
468fn approximate_catmull(path: &mut Vec<Pos>, points: &[Pos]) {
469 if points.len() == 1 {
470 return;
471 }
472
473 path.reserve((points.len() - 1) * CATMULL_DETAIL * 2);
474
475 let v1 = points[0];
477 let v2 = points[0];
478 let v3 = points.get(1).copied().unwrap_or(v2);
479 let v4 = points.get(2).copied().unwrap_or_else(|| v3 * 2.0 - v2);
480
481 catmull_subpath(path, v1, v2, v3, v4);
482
483 for (i, (&v1, &v2)) in (2..points.len()).zip(points.iter().zip(points.iter().skip(1))) {
485 let v3 = points.get(i).copied().unwrap_or_else(|| v2 * 2.0 - v1);
486 let v4 = points.get(i + 1).copied().unwrap_or_else(|| v3 * 2.0 - v2);
487
488 catmull_subpath(path, v1, v2, v3, v4);
489 }
490}
491
492fn approximate_linear(path: &mut Vec<Pos>, points: &[Pos]) {
493 path.extend(points);
494}
495
496fn approximate_circular_arc(path: &mut Vec<Pos>, a: Pos, b: Pos, c: Pos) -> bool {
497 let Some(pr) = circular_arc_properties(a, b, c) else {
498 return false;
499 };
500
501 let sub_points = if 2.0 * pr.radius <= CIRCULAR_ARC_TOLERANCE {
507 2
508 } else {
509 let divisor = 2.0 * (1.0 - (CIRCULAR_ARC_TOLERANCE / pr.radius)).acos();
510
511 if divisor.abs() <= f32::EPSILON {
514 2
515 } else {
516 ((pr.theta_range / f64::from(divisor)).ceil() as usize).max(2)
517 }
518 };
519
520 if sub_points >= 1000 {
523 return false;
524 }
525
526 path.reserve(sub_points);
527 let divisor = (sub_points - 1) as f64;
528 let directed_range = pr.direction * pr.theta_range;
529
530 let subpath = (0..sub_points).map(|i| {
531 let fract = i as f64 / divisor;
532 let theta = pr.theta_start + fract * directed_range;
533 let (sin, cos) = theta.sin_cos();
534
535 let origin = Pos {
536 x: cos as f32,
537 y: sin as f32,
538 };
539
540 pr.centre + origin * pr.radius
541 });
542
543 path.extend(subpath);
544
545 true
546}
547
548fn approximate_bspline(path: &mut Vec<Pos>, points: &[Pos], bufs: &mut BezierBuffers) {
549 let p = points.len();
550
551 let mut to_flatten = Vec::new();
552 let mut free_bufs = Vec::new();
553
554 to_flatten.push(Cow::Borrowed(points));
557
558 let BezierBuffers {
565 left,
566 right,
567 midpoints,
568 left_child,
569 } = bufs;
570
571 while let Some(mut parent) = to_flatten.pop() {
572 if bezier_is_flat_enough(&parent) {
573 bezier_approximate(&parent, path, left, right, midpoints);
578 free_bufs.push(parent);
579
580 continue;
581 }
582
583 let mut right_child = free_bufs
586 .pop()
587 .unwrap_or_else(|| Cow::Owned(vec![Pos::default(); p]));
588
589 bezier_subdivide(&parent, left_child, right_child.to_mut(), midpoints);
590
591 parent.to_mut().copy_from_slice(&left_child[..p]);
593
594 to_flatten.push(right_child);
595 to_flatten.push(parent);
596 }
597
598 path.push(points[p - 1]);
599}
600
601fn bezier_is_flat_enough(points: &[Pos]) -> bool {
602 let limit = BEZIER_TOLERANCE * BEZIER_TOLERANCE * 4.0;
603
604 !points
605 .iter()
606 .zip(points.iter().skip(1))
607 .zip(points.iter().skip(2))
608 .any(|((&prev, &curr), &next)| (prev - curr * 2.0 + next).length_squared() > limit)
609}
610
611fn bezier_subdivide(points: &[Pos], l: &mut [Pos], r: &mut [Pos], midpoints: &mut [Pos]) {
612 let count = points.len();
613 midpoints[..count].copy_from_slice(&points[..count]);
614
615 for i in (1..count).rev() {
616 l[count - i - 1] = midpoints[0];
617 r[i] = midpoints[i];
618
619 for j in 0..i {
620 midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2.0;
621 }
622 }
623
624 l[count - 1] = midpoints[0];
625 r[0] = midpoints[0];
626}
627
628fn bezier_approximate(
630 points: &[Pos],
631 path: &mut Vec<Pos>,
632 l: &mut [Pos],
633 r: &mut [Pos],
634 midpoints: &mut [Pos],
635) {
636 let count = points.len();
637
638 bezier_subdivide(points, l, r, midpoints);
639 path.push(points[0]);
640
641 let l = &l[..count];
642 let r = &r[1..count];
643
644 let subpath = l
645 .iter()
646 .chain(r)
647 .skip(1)
648 .zip(l.iter().chain(r).skip(2))
649 .zip(l.iter().chain(r).skip(3))
650 .step_by(2)
651 .map(|((&prev, &curr), &next)| (prev + curr * 2.0 + next) * 0.25);
652
653 path.extend(subpath);
654}
655
656fn catmull_subpath(path: &mut Vec<Pos>, v1: Pos, v2: Pos, v3: Pos, v4: Pos) {
657 let x1 = 2.0 * v2.x;
658 let x2 = -v1.x + v3.x;
659 let x3 = 2.0 * v1.x - 5.0 * v2.x + 4.0 * v3.x - v4.x;
660 let x4 = -v1.x + 3.0 * (v2.x - v3.x) + v4.x;
661
662 let y1 = 2.0 * v2.y;
663 let y2 = -v1.y + v3.y;
664 let y3 = 2.0 * v1.y - 5.0 * v2.y + 4.0 * v3.y - v4.y;
665 let y4 = -v1.y + 3.0 * (v2.y - v3.y) + v4.y;
666
667 let catmull_detail = CATMULL_DETAIL as f32;
668
669 let subpath = (0..CATMULL_DETAIL).flat_map(|c| {
670 let c = c as f32;
671 let t1 = c / catmull_detail;
672 let t2 = t1 * t1;
673 let t3 = t2 * t1;
674
675 let pos1 = Pos {
676 x: 0.5 * (x1 + x2 * t1 + x3 * t2 + x4 * t3),
677 y: 0.5 * (y1 + y2 * t1 + y3 * t2 + y4 * t3),
678 };
679
680 let t1 = (c + 1.0) / catmull_detail;
681 let t2 = t1 * t1;
682 let t3 = t2 * t1;
683
684 let pos2 = Pos {
685 x: 0.5 * (x1 + x2 * t1 + x3 * t2 + x4 * t3),
686 y: 0.5 * (y1 + y2 * t1 + y3 * t2 + y4 * t3),
687 };
688
689 iter::once(pos1).chain(iter::once(pos2))
690 });
691
692 path.extend(subpath);
693}
694
695fn circular_arc_properties(a: Pos, b: Pos, c: Pos) -> Option<CircularArcProperties> {
696 if ((b.y - a.y) * (c.x - a.x) - (b.x - a.x) * (c.y - a.y)).abs() <= f32::EPSILON {
699 return None;
700 }
701
702 let d = 2.0 * (a.x * (b - c).y + b.x * (c - a).y + c.x * (a - b).y);
704 let a_sq = a.length_squared();
705 let b_sq = b.length_squared();
706 let c_sq = c.length_squared();
707
708 let centre = Pos {
709 x: (a_sq * (b - c).y + b_sq * (c - a).y + c_sq * (a - b).y) / d,
710 y: (a_sq * (c - b).x + b_sq * (a - c).x + c_sq * (b - a).x) / d,
711 };
712
713 let d_a = a - centre;
714 let d_c = c - centre;
715
716 let radius = d_a.length();
717
718 let theta_start = f64::from(d_a.y).atan2(f64::from(d_a.x));
719 let mut theta_end = f64::from(d_c.y).atan2(f64::from(d_c.x));
720
721 while theta_end < theta_start {
722 theta_end += 2.0 * PI;
723 }
724
725 let mut direction = 1.0;
726 let mut theta_range = theta_end - theta_start;
727
728 let mut ortho_a_to_c = c - a;
731
732 ortho_a_to_c = Pos {
733 x: ortho_a_to_c.y,
734 y: -ortho_a_to_c.x,
735 };
736
737 if ortho_a_to_c.dot(b - a) < 0.0 {
738 direction = -direction;
739 theta_range = 2.0 * PI - theta_range;
740 }
741
742 Some(CircularArcProperties {
743 theta_start,
744 theta_range,
745 direction,
746 radius,
747 centre,
748 })
749}