1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
use crate::util::float_ext::FloatExt;

/// Difficulty-related info about this control point.
#[derive(Clone, Debug, PartialEq)]
pub struct DifficultyPoint {
    pub time: f64,
    pub slider_velocity: f64,
    pub bpm_multiplier: f64,
    pub generate_ticks: bool,
}

impl DifficultyPoint {
    pub const DEFAULT_SLIDER_VELOCITY: f64 = 1.0;
    pub const DEFAULT_BPM_MULTIPLIER: f64 = 1.0;
    pub const DEFAULT_GENERATE_TICKS: bool = true;

    pub fn new(time: f64, beat_len: f64, speed_multiplier: f64) -> Self {
        Self {
            time,
            slider_velocity: speed_multiplier.clamp(0.1, 10.0),
            bpm_multiplier: if beat_len < 0.0 {
                f64::from((-beat_len) as f32).clamp(10.0, 10_000.0) / 100.0
            } else {
                1.0
            },
            generate_ticks: !beat_len.is_nan(),
        }
    }

    pub fn is_redundant(&self, existing: &Self) -> bool {
        self.generate_ticks == existing.generate_ticks
            && self.slider_velocity.eq(existing.slider_velocity)
    }
}

impl Default for DifficultyPoint {
    fn default() -> Self {
        Self {
            time: 0.0,
            slider_velocity: Self::DEFAULT_SLIDER_VELOCITY,
            bpm_multiplier: Self::DEFAULT_BPM_MULTIPLIER,
            generate_ticks: Self::DEFAULT_GENERATE_TICKS,
        }
    }
}

pub fn difficulty_point_at(points: &[DifficultyPoint], time: f64) -> Option<&DifficultyPoint> {
    points
        .binary_search_by(|probe| probe.time.total_cmp(&time))
        .map_or_else(|i| i.checked_sub(1), Some)
        .map(|i| &points[i])
}