osu: minor refactoring

This commit is contained in:
MaxOhn
2021-01-15 10:22:05 +01:00
parent b9966feb14
commit 4c551813c8
4 changed files with 77 additions and 82 deletions
@@ -26,7 +26,6 @@ impl<'p> ControlPointIter<'p> {
} }
} }
#[derive(Debug)]
pub(crate) enum ControlPoint { pub(crate) enum ControlPoint {
Timing { time: f32 }, Timing { time: f32 },
Difficulty { time: f32, speed_multiplier: f32 }, Difficulty { time: f32, speed_multiplier: f32 },
@@ -1,8 +1,6 @@
use parse::HitObject; use parse::HitObject;
use std::borrow::Cow; use std::borrow::Cow;
const NORMALIZED_RADIUS: f32 = 52.0;
pub(crate) struct DifficultyObject<'h> { pub(crate) struct DifficultyObject<'h> {
pub(crate) base: &'h HitObject, pub(crate) base: &'h HitObject,
pub(crate) prev: Option<(f32, f32)>, // (jump_dist, strain_time) 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_vals: Option<(f32, f32)>, // (jump_dist, strain_time)
prev_prev: Option<Cow<HitObject>>, prev_prev: Option<Cow<HitObject>>,
clock_rate: f32, clock_rate: f32,
radius: f32, scaling_factor: f32,
) -> Self { ) -> Self {
let delta = (base.start_time - prev.start_time) / clock_rate; let delta = (base.start_time - prev.start_time) / clock_rate;
let strain_time = delta.max(50.0); 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() { let jump_dist = if base.is_spinner() {
0.0 0.0
} else { } 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 angle = prev_prev.map(|prev_prev| {
let prev_prev_cursor_pos = prev_prev.pos; let v1 = prev_prev.pos - prev.pos;
let v2 = base.pos - prev.pos;
let v1 = prev_prev_cursor_pos - prev.pos;
let v2 = base.pos - prev_cursor_pos;
let dot = v1.dot(v2); let dot = v1.dot(v2);
let det = v1.x * v2.y - v1.y * v2.x; let det = v1.x * v2.y - v1.y * v2.x;
@@ -52,8 +40,6 @@ impl<'h> DifficultyObject<'h> {
det.atan2(dot).abs() det.atan2(dot).abs()
}); });
// let prev = prev_diff.map(|o| (o.jump_dist, o.strain_time));
Self { Self {
base, base,
prev: prev_vals, prev: prev_vals,
+16 -63
View File
@@ -4,11 +4,12 @@ mod control_point_iter;
mod difficulty_object; mod difficulty_object;
mod skill; mod skill;
mod skill_kind; mod skill_kind;
mod slider_state;
use control_point_iter::{ControlPoint, ControlPointIter};
use difficulty_object::DifficultyObject; use difficulty_object::DifficultyObject;
use skill::Skill; use skill::Skill;
use skill_kind::SkillKind; use skill_kind::SkillKind;
use slider_state::SliderState;
use parse::{Beatmap, HitObject, HitObjectKind, Mods}; use parse::{Beatmap, HitObject, HitObjectKind, Mods};
use std::borrow::Cow; use std::borrow::Cow;
@@ -16,6 +17,7 @@ use std::borrow::Cow;
const OBJECT_RADIUS: f32 = 64.0; const OBJECT_RADIUS: f32 = 64.0;
const SECTION_LEN: f32 = 400.0; const SECTION_LEN: f32 = 400.0;
const DIFFICULTY_MULTIPLIER: f32 = 0.0675; const DIFFICULTY_MULTIPLIER: f32 = 0.0675;
const NORMALIZED_RADIUS: f32 = 52.0;
/// Star calculation for osu!standard maps. /// 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 section_len = SECTION_LEN * attributes.clock_rate;
let radius = OBJECT_RADIUS * (1.0 - 0.7 * (attributes.cs - 5.0) / 5.0) / 2.0; 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 max_combo = 0;
let mut n_circles = 0; let mut n_circles = 0;
@@ -51,16 +59,14 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
HitObjectKind::Slider { HitObjectKind::Slider {
pixel_len, repeats, .. 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, pos: h.pos,
start_time: h.start_time, start_time: h.start_time,
kind: HitObjectKind::Circle, kind: HitObjectKind::Circle,
sound: h.sound, sound: h.sound,
}; })
Cow::Owned(h)
} }
HitObjectKind::Spinner { .. } => { HitObjectKind::Spinner { .. } => {
max_combo += 1; 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 aim = Skill::new(SkillKind::Aim);
let mut speed = Skill::new(SkillKind::Speed); let mut speed = Skill::new(SkillKind::Speed);
// First object has no predecessor and thus no strain, handle distinctly
let mut current_section_end = let mut current_section_end =
(map.hit_objects[0].start_time / section_len).ceil() * section_len; (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 = hit_objects.next().unwrap();
let mut prev_vals = None; 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 curr = hit_objects.next().unwrap();
let h = DifficultyObject::new( let h = DifficultyObject::new(
curr.as_ref(), curr.as_ref(),
@@ -89,7 +96,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
prev_vals, prev_vals,
prev_prev, prev_prev,
attributes.clock_rate, attributes.clock_rate,
radius, scaling_factor,
); );
aim.process(&h); aim.process(&h);
@@ -107,7 +114,7 @@ pub fn stars(map: &Beatmap, mods: impl Mods) -> DifficultyAttributes {
prev_vals, prev_vals,
prev_prev, prev_prev,
attributes.clock_rate, attributes.clock_rate,
radius, scaling_factor,
); );
while h.base.start_time > current_section_end { 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)] #[cfg(test)]
mod tests { mod tests {
use super::stars; 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)
}
}