use crate::{
osu::osu_object::{NestedObjectKind, OsuObjectKind},
parse::Pos2,
};
use super::{osu_object::OsuSlider, OsuObject, ScalingFactor};
#[derive(Clone, Debug)]
pub(crate) struct OsuDifficultyObject<'h> {
pub(crate) start_time: f64,
pub(crate) delta_time: f64,
pub(crate) base: &'h OsuObject,
pub(crate) strain_time: f64,
pub(crate) dists: Distances,
pub(crate) idx: usize,
}
impl<'h> OsuDifficultyObject<'h> {
pub(crate) const MIN_DELTA_TIME: u32 = 25;
pub(crate) fn new(
base: &'h OsuObject,
last: &'h OsuObject,
clock_rate: f64,
idx: usize,
dists: Distances,
) -> Self {
let start_time = base.start_time / clock_rate;
let delta_time = (base.start_time - last.start_time) / clock_rate;
let strain_time = delta_time.max(Self::MIN_DELTA_TIME as f64);
Self {
start_time,
delta_time,
base,
strain_time,
dists,
idx,
}
}
pub(crate) fn opacity_at(
&self,
time: f64,
hidden: bool,
time_preempt: f64,
time_fade_in: f64,
) -> f64 {
if time > self.base.start_time {
return 0.0;
}
let fade_in_start_time = self.base.start_time - time_preempt;
let fade_in_duration = time_fade_in;
if hidden {
let fade_out_start_time = self.base.start_time - time_preempt + time_fade_in;
const FADE_OUT_DURATION_MULTIPLIER: f64 = 0.3;
let fade_out_duration = time_preempt * FADE_OUT_DURATION_MULTIPLIER;
(((time - fade_in_start_time) / fade_in_duration).clamp(0.0, 1.0))
.min(1.0 - ((time - fade_out_start_time) / fade_out_duration).clamp(0.0, 1.0))
} else {
((time - fade_in_start_time) / fade_in_duration).clamp(0.0, 1.0)
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Distances {
pub(crate) lazy_jump_dist: f64,
pub(crate) lazy_travel_dist: f32,
pub(crate) min_jump_dist: f64,
pub(crate) min_jump_time: f64,
pub(crate) travel_dist: f64,
pub(crate) travel_time: f64,
pub(crate) angle: Option<f64>,
}
impl Distances {
pub(crate) const NORMALISED_RADIUS: f32 = 50.0;
const MAXIMUM_SLIDER_RADIUS: f32 = Self::NORMALISED_RADIUS * 2.4;
const ASSUMED_SLIDER_RADIUS: f32 = Self::NORMALISED_RADIUS * 1.8;
pub(crate) fn new(
base: &mut OsuObject,
last: &OsuObject,
last_last: Option<&OsuObject>,
clock_rate: f64,
strain_time: f64,
scaling_factor_: &ScalingFactor,
) -> Self {
let mut this =
if let Some(slider_values) = Self::compute_slider_cursor_pos(base, scaling_factor_) {
let SliderValues {
lazy_travel_dist,
slider,
} = slider_values;
let repeat_count = slider.repeat_count();
Self {
travel_dist: (lazy_travel_dist
* (1.0 + repeat_count as f64 / 2.5).powf(1.0 / 2.5) as f32)
as f64,
travel_time: (base.lazy_travel_time() / clock_rate)
.max(OsuDifficultyObject::MIN_DELTA_TIME as f64),
lazy_travel_dist,
..Default::default()
}
} else {
Self::default()
};
if base.is_spinner() || last.is_spinner() {
return this;
}
let scaling_factor = scaling_factor_.factor;
let last_cursor_pos = Self::get_end_cursor_pos(last);
this.lazy_jump_dist = (base.stacked_pos() * scaling_factor
- last_cursor_pos * scaling_factor)
.length() as f64;
this.min_jump_time = strain_time;
this.min_jump_dist = this.lazy_jump_dist;
if let OsuObjectKind::Slider(slider) = &last.kind {
let last_travel_time = (last.lazy_travel_time() / clock_rate)
.max(OsuDifficultyObject::MIN_DELTA_TIME as f64);
this.min_jump_time =
(strain_time - last_travel_time).max(OsuDifficultyObject::MIN_DELTA_TIME as f64);
let stacked_tail_pos =
slider.tail().map_or_else(|| last.pos(), |tail| tail.pos) + last.stack_offset;
let tail_jump_dist = (stacked_tail_pos - base.stacked_pos()).length() * scaling_factor;
let diff = (Self::MAXIMUM_SLIDER_RADIUS - Self::ASSUMED_SLIDER_RADIUS) as f64;
let min = (tail_jump_dist - Self::MAXIMUM_SLIDER_RADIUS) as f64;
#[allow(clippy::manual_clamp)]
let tmp = (this.lazy_jump_dist - diff).min(min).max(0.0);
this.min_jump_dist = tmp;
}
if let Some(last_last) = last_last.filter(|obj| !obj.is_spinner()) {
let last_last_cursor_pos = Self::get_end_cursor_pos(last_last);
let v1 = last_last_cursor_pos - last.stacked_pos();
let v2 = base.stacked_pos() - last_cursor_pos;
let dot = v1.dot(v2) as f64;
let det = (v1.x * v2.y - v1.y * v2.x) as f64;
this.angle = Some(det.atan2(dot).abs());
}
this
}
pub(crate) fn compute_slider_cursor_pos<'h>(
hit_object: &'h mut OsuObject,
scaling_factor_: &ScalingFactor,
) -> Option<SliderValues<'h>> {
let pos = hit_object.pos();
let slider = if let OsuObjectKind::Slider(slider) = &mut hit_object.kind {
slider
} else {
return None;
};
let mut curr_cursor_pos = pos + hit_object.stack_offset;
let scaling_factor = Self::NORMALISED_RADIUS as f64 / scaling_factor_.radius as f64;
let mut lazy_travel_dist: f32 = 0.0;
for (curr_movement_obj, i) in slider.nested_objects.iter().zip(1..) {
let mut curr_movement =
(curr_movement_obj.pos + hit_object.stack_offset) - curr_cursor_pos;
let mut curr_movement_len = scaling_factor * curr_movement.length() as f64;
let mut required_movement = Self::ASSUMED_SLIDER_RADIUS as f64;
if i == slider.nested_objects.len() {
let lazy_movement = slider.lazy_end_pos - curr_cursor_pos;
if lazy_movement.length() < curr_movement.length() {
curr_movement = lazy_movement;
}
curr_movement_len = scaling_factor * curr_movement.length() as f64;
} else if let NestedObjectKind::Repeat = curr_movement_obj.kind {
required_movement = Self::NORMALISED_RADIUS as f64;
}
if curr_movement_len > required_movement {
curr_cursor_pos += curr_movement
* ((curr_movement_len - required_movement) / curr_movement_len) as f32;
curr_movement_len *= (curr_movement_len - required_movement) / curr_movement_len;
lazy_travel_dist += curr_movement_len as f32;
}
}
slider.lazy_end_pos = curr_cursor_pos;
Some(SliderValues {
lazy_travel_dist,
slider,
})
}
fn get_end_cursor_pos(hit_object: &OsuObject) -> Pos2 {
hit_object.lazy_end_pos()
}
}
pub(crate) struct SliderValues<'s> {
lazy_travel_dist: f32,
slider: &'s OsuSlider,
}