osu: minor refactoring
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user