osu: minor refactoring
This commit is contained in:
@@ -26,7 +26,6 @@ impl<'p> ControlPointIter<'p> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ControlPoint {
|
||||
Timing { time: f32 },
|
||||
Difficulty { time: f32, speed_multiplier: f32 },
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use parse::HitObject;
|
||||
use std::borrow::Cow;
|
||||
|
||||
const NORMALIZED_RADIUS: f32 = 52.0;
|
||||
|
||||
pub(crate) struct DifficultyObject<'h> {
|
||||
pub(crate) base: &'h HitObject,
|
||||
pub(crate) prev: Option<(f32, f32)>, // (jump_dist, strain_time)
|
||||
@@ -21,30 +19,20 @@ impl<'h> DifficultyObject<'h> {
|
||||
prev_vals: Option<(f32, f32)>, // (jump_dist, strain_time)
|
||||
prev_prev: Option<Cow<HitObject>>,
|
||||
clock_rate: f32,
|
||||
radius: f32,
|
||||
scaling_factor: f32,
|
||||
) -> Self {
|
||||
let delta = (base.start_time - prev.start_time) / clock_rate;
|
||||
let strain_time = delta.max(50.0);
|
||||
|
||||
let mut scaling_factor = NORMALIZED_RADIUS / radius;
|
||||
let prev_cursor_pos = prev.pos;
|
||||
|
||||
if radius < 30.0 {
|
||||
let small_circle_bonus = (30.0 - radius).min(5.0) / 50.0;
|
||||
scaling_factor *= 1.0 + small_circle_bonus;
|
||||
}
|
||||
|
||||
let jump_dist = if base.is_spinner() {
|
||||
0.0
|
||||
} else {
|
||||
(base.pos * scaling_factor - prev_cursor_pos * scaling_factor).length()
|
||||
(base.pos * scaling_factor - prev.pos * scaling_factor).length()
|
||||
};
|
||||
|
||||
let angle = prev_prev.map(|prev_prev| {
|
||||
let prev_prev_cursor_pos = prev_prev.pos;
|
||||
|
||||
let v1 = prev_prev_cursor_pos - prev.pos;
|
||||
let v2 = base.pos - prev_cursor_pos;
|
||||
let v1 = prev_prev.pos - prev.pos;
|
||||
let v2 = base.pos - prev.pos;
|
||||
|
||||
let dot = v1.dot(v2);
|
||||
let det = v1.x * v2.y - v1.y * v2.x;
|
||||
@@ -52,8 +40,6 @@ impl<'h> DifficultyObject<'h> {
|
||||
det.atan2(dot).abs()
|
||||
});
|
||||
|
||||
// let prev = prev_diff.map(|o| (o.jump_dist, o.strain_time));
|
||||
|
||||
Self {
|
||||
base,
|
||||
prev: prev_vals,
|
||||
|
||||
@@ -4,11 +4,12 @@ mod control_point_iter;
|
||||
mod difficulty_object;
|
||||
mod skill;
|
||||
mod skill_kind;
|
||||
mod slider_state;
|
||||
|
||||
use control_point_iter::{ControlPoint, ControlPointIter};
|
||||
use difficulty_object::DifficultyObject;
|
||||
use skill::Skill;
|
||||
use skill_kind::SkillKind;
|
||||
use slider_state::SliderState;
|
||||
|
||||
use parse::{Beatmap, HitObject, HitObjectKind, Mods};
|
||||
use std::borrow::Cow;
|
||||
@@ -16,6 +17,7 @@ use std::borrow::Cow;
|
||||
const OBJECT_RADIUS: f32 = 64.0;
|
||||
const SECTION_LEN: f32 = 400.0;
|
||||
const DIFFICULTY_MULTIPLIER: f32 = 0.0675;
|
||||
const NORMALIZED_RADIUS: f32 = 52.0;
|
||||
|
||||
/// Star calculation for osu!standard maps.
|
||||
///
|
||||
@@ -35,6 +37,12 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
|
||||
|
||||
let section_len = SECTION_LEN * attributes.clock_rate;
|
||||
let radius = OBJECT_RADIUS * (1.0 - 0.7 * (attributes.cs - 5.0) / 5.0) / 2.0;
|
||||
let mut scaling_factor = NORMALIZED_RADIUS / radius;
|
||||
|
||||
if radius < 30.0 {
|
||||
let small_circle_bonus = (30.0 - radius).min(5.0) / 50.0;
|
||||
scaling_factor *= 1.0 + small_circle_bonus;
|
||||
}
|
||||
|
||||
let mut max_combo = 0;
|
||||
let mut n_circles = 0;
|
||||
@@ -51,16 +59,14 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
|
||||
HitObjectKind::Slider {
|
||||
pixel_len, repeats, ..
|
||||
} => {
|
||||
max_combo += count_ticks(h.start_time, *pixel_len, *repeats, &map, &mut state);
|
||||
max_combo += state.count_ticks(h.start_time, *pixel_len, *repeats, &map);
|
||||
|
||||
let h = HitObject {
|
||||
Cow::Owned(HitObject {
|
||||
pos: h.pos,
|
||||
start_time: h.start_time,
|
||||
kind: HitObjectKind::Circle,
|
||||
sound: h.sound,
|
||||
};
|
||||
|
||||
Cow::Owned(h)
|
||||
})
|
||||
}
|
||||
HitObjectKind::Spinner { .. } => {
|
||||
max_combo += 1;
|
||||
@@ -74,6 +80,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
|
||||
let mut aim = Skill::new(SkillKind::Aim);
|
||||
let mut speed = Skill::new(SkillKind::Speed);
|
||||
|
||||
// First object has no predecessor and thus no strain, handle distinctly
|
||||
let mut current_section_end =
|
||||
(map.hit_objects[0].start_time / section_len).ceil() * section_len;
|
||||
|
||||
@@ -81,7 +88,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
|
||||
let mut prev = hit_objects.next().unwrap();
|
||||
let mut prev_vals = None;
|
||||
|
||||
// Handle first object separately to remove if-branching
|
||||
// Handle second object separately to remove later if-branching
|
||||
let curr = hit_objects.next().unwrap();
|
||||
let h = DifficultyObject::new(
|
||||
curr.as_ref(),
|
||||
@@ -89,7 +96,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
|
||||
prev_vals,
|
||||
prev_prev,
|
||||
attributes.clock_rate,
|
||||
radius,
|
||||
scaling_factor,
|
||||
);
|
||||
|
||||
aim.process(&h);
|
||||
@@ -107,7 +114,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
|
||||
prev_vals,
|
||||
prev_prev,
|
||||
attributes.clock_rate,
|
||||
radius,
|
||||
scaling_factor,
|
||||
);
|
||||
|
||||
while h.base.start_time > current_section_end {
|
||||
@@ -157,60 +164,6 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
struct SliderState<'p> {
|
||||
control_points: ControlPointIter<'p>,
|
||||
next_time: f32,
|
||||
px_per_beat: f32,
|
||||
prev_sv: f32,
|
||||
}
|
||||
|
||||
impl<'p> SliderState<'p> {
|
||||
#[inline]
|
||||
fn new(map: &'p Beatmap) -> Self {
|
||||
Self {
|
||||
control_points: ControlPointIter::new(map),
|
||||
next_time: std::f32::NEG_INFINITY,
|
||||
px_per_beat: 1.0,
|
||||
prev_sv: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn count_ticks(
|
||||
time: f32,
|
||||
pixel_len: f32,
|
||||
repeats: usize,
|
||||
map: &Beatmap,
|
||||
state: &mut SliderState,
|
||||
) -> usize {
|
||||
while time >= state.next_time {
|
||||
state.px_per_beat = map.sv * 100.0 * state.prev_sv;
|
||||
|
||||
match state.control_points.next() {
|
||||
Some(ControlPoint::Timing { time }) => {
|
||||
state.next_time = time;
|
||||
state.prev_sv = 1.0;
|
||||
}
|
||||
Some(ControlPoint::Difficulty {
|
||||
time,
|
||||
speed_multiplier,
|
||||
}) => {
|
||||
state.next_time = time;
|
||||
state.prev_sv = speed_multiplier;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
let spans = repeats as f32;
|
||||
let beats = pixel_len * spans / state.px_per_beat;
|
||||
let ticks = ((beats - 0.1) / spans * map.tick_rate).ceil() as usize;
|
||||
|
||||
ticks
|
||||
.checked_sub(1)
|
||||
.map_or(0, |ticks| ticks * repeats + repeats + 1)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::stars;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use super::control_point_iter::{ControlPoint, ControlPointIter};
|
||||
|
||||
use parse::Beatmap;
|
||||
|
||||
pub(crate) struct SliderState<'p> {
|
||||
control_points: ControlPointIter<'p>,
|
||||
next_time: f32,
|
||||
px_per_beat: f32,
|
||||
prev_sv: f32,
|
||||
}
|
||||
|
||||
impl<'p> SliderState<'p> {
|
||||
#[inline]
|
||||
pub(crate) fn new(map: &'p Beatmap) -> Self {
|
||||
Self {
|
||||
control_points: ControlPointIter::new(map),
|
||||
next_time: std::f32::NEG_INFINITY,
|
||||
px_per_beat: 1.0,
|
||||
prev_sv: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn count_ticks(
|
||||
&mut self,
|
||||
time: f32,
|
||||
pixel_len: f32,
|
||||
repeats: usize,
|
||||
map: &Beatmap,
|
||||
) -> usize {
|
||||
while time >= self.next_time {
|
||||
self.px_per_beat = map.sv * 100.0 * self.prev_sv;
|
||||
|
||||
match self.control_points.next() {
|
||||
Some(ControlPoint::Timing { time }) => {
|
||||
self.next_time = time;
|
||||
self.prev_sv = 1.0;
|
||||
}
|
||||
Some(ControlPoint::Difficulty {
|
||||
time,
|
||||
speed_multiplier,
|
||||
}) => {
|
||||
self.next_time = time;
|
||||
self.prev_sv = speed_multiplier;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
let spans = repeats as f32;
|
||||
let beats = pixel_len * spans / self.px_per_beat;
|
||||
let ticks = ((beats - 0.1) / spans * map.tick_rate).ceil() as usize;
|
||||
|
||||
ticks
|
||||
.checked_sub(1)
|
||||
.map_or(0, |ticks| ticks * repeats + repeats + 1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user