use std::f64::consts::PI;
use crate::{
taiko::difficulty::{
object::TaikoDifficultyObject,
rhythm::data::same_rhythm_hit_object_grouping::SameRhythmHitObjectGrouping,
},
util::{
difficulty::{bell_curve, logistic},
sync::RefCount,
},
};
pub struct RhythmEvaluator;
impl RhythmEvaluator {
pub fn evaluate_diff_of(hit_object: &TaikoDifficultyObject, hit_window: f64) -> f64 {
let rhythm_data = &hit_object.rhythm_data;
let mut difficulty = 0.0;
let mut same_rhythm = 0.0;
let mut same_pattern = 0.0;
let mut interval_penalty = 0.0;
if let Some(ref same_rhythm_grouped) = rhythm_data.same_rhythm_grouped_hit_objects
&& same_rhythm_grouped
.get()
.first_hit_object()
.is_some_and(|h| &*h.get() == hit_object)
{
same_rhythm += 10.0 * Self::evaluate_diff_of_(same_rhythm_grouped, hit_window);
interval_penalty =
Self::repeated_interval_penalty(same_rhythm_grouped, hit_window, None);
}
if let Some(ref same_pattern_grouped) = rhythm_data.same_patterns_grouped_hit_objects
&& same_pattern_grouped
.get()
.first_hit_object()
.is_some_and(|h| &*h.get() == hit_object)
{
same_pattern +=
1.15 * Self::ratio_difficulty(same_pattern_grouped.get().interval_ratio(), None);
}
difficulty += f64::max(same_rhythm, same_pattern) * interval_penalty;
difficulty
}
fn evaluate_diff_of_(
same_rhythm_grouped_hit_objects: &RefCount<SameRhythmHitObjectGrouping>,
hit_window: f64,
) -> f64 {
let mut interval_diff = Self::ratio_difficulty(
same_rhythm_grouped_hit_objects
.get()
.hit_object_interval_ratio,
None,
);
let prev_interval = same_rhythm_grouped_hit_objects
.get()
.upgraded_previous()
.and_then(|h| h.get().hit_object_interval);
interval_diff *=
Self::repeated_interval_penalty(same_rhythm_grouped_hit_objects, hit_window, None);
let borrowed = same_rhythm_grouped_hit_objects.get();
let duration = borrowed.duration();
if let Some(prev_interval) = prev_interval.filter(|_| borrowed.hit_objects.len() > 1)
&& let Some(duration) = duration
{
let expected_duration_from_prev = prev_interval * borrowed.hit_objects.len() as f64;
let duration_diff = duration - expected_duration_from_prev;
if duration_diff > 0.0 {
interval_diff *= logistic(duration_diff / hit_window, 0.7, 1.0, Some(1.0));
}
}
if let Some(duration) = duration {
interval_diff *= logistic(duration / hit_window, 0.6, 1.0, Some(1.0));
}
f64::powf(interval_diff, 0.75)
}
fn repeated_interval_penalty(
same_rhythm_grouped_hit_objects: &RefCount<SameRhythmHitObjectGrouping>,
hit_window: f64,
threshold: Option<f64>,
) -> f64 {
let threshold = threshold.unwrap_or(0.1);
let same_interval =
|start_object: RefCount<SameRhythmHitObjectGrouping>, interval_count: usize| -> f64 {
let mut intervals = Vec::new();
let mut curr_object = Some(start_object);
let mut i = 0;
while let Some(curr) = curr_object.filter(|_| i < interval_count) {
let curr = curr.get();
if let Some(interval) = curr.hit_object_interval {
intervals.push(interval);
}
curr_object = curr.upgraded_previous();
i += 1;
}
if intervals.len() < interval_count {
return 1.0; }
for i in 0..intervals.len() {
for j in i + 1..intervals.len() {
let ratio = intervals[i] / intervals[j];
if f64::abs(1.0 - ratio) <= threshold {
return 0.8;
}
}
}
1.0
};
let long_interval_penalty =
same_interval(RefCount::clone(same_rhythm_grouped_hit_objects), 3);
let short_interval_penalty = if same_rhythm_grouped_hit_objects.get().hit_objects.len() < 6
{
same_interval(RefCount::clone(same_rhythm_grouped_hit_objects), 4)
} else {
1.0
};
let duration_penalty = same_rhythm_grouped_hit_objects
.get()
.duration()
.map_or(0.5, |duration| {
f64::max(1.0 - duration * 2.0 / hit_window, 0.5)
});
f64::min(long_interval_penalty, short_interval_penalty) * duration_penalty
}
fn ratio_difficulty(mut ratio: f64, terms: Option<i32>) -> f64 {
let terms = terms.unwrap_or(8);
let mut difficulty = 0.0;
ratio = if ratio.is_normal() { ratio } else { 0.0 };
for i in 1..=terms {
difficulty += Self::term_penalty(ratio, i, 4.0, 1.0);
}
difficulty += f64::from(terms) / (1.0 + ratio);
difficulty += bell_curve(ratio, 1.0, 0.5, None);
difficulty -= bell_curve(ratio, 1.0, 0.3, None);
difficulty = f64::max(difficulty, 0.0);
difficulty /= f64::sqrt(8.0);
difficulty
}
fn term_penalty(ratio: f64, denominator: i32, power: f64, multiplier: f64) -> f64 {
-multiplier * f64::powf(f64::cos(f64::from(denominator) * PI * ratio), power)
}
}