applied recent changes to all mode feature combinations

This commit is contained in:
MaxOhn
2021-11-06 19:18:34 +01:00
parent b2b5422259
commit b1c470eb55
7 changed files with 254 additions and 202 deletions
+26 -25
View File
@@ -6,8 +6,10 @@
use std::mem; use std::mem;
use self::osu_object::ObjectParameters;
use super::super::DifficultyAttributes; use super::super::DifficultyAttributes;
use crate::parse::Pos2; use crate::{curve::CurveBuffers, parse::Pos2};
mod difficulty_object; mod difficulty_object;
mod osu_object; mod osu_object;
@@ -79,21 +81,21 @@ pub fn stars(
scaling_factor *= 1.0 + small_circle_bonus; scaling_factor *= 1.0 + small_circle_bonus;
} }
let mut slider_state = SliderState::new(map); let mut params = ObjectParameters {
let mut ticks_buf = Vec::new();
let hit_objects_iter = map.hit_objects.iter().take(take).filter_map(|h| {
OsuObject::new(
h,
map, map,
radius, radius,
scaling_factor, scaling_factor,
hr, attributes: &mut diff_attributes,
&mut ticks_buf, slider_state: SliderState::new(map),
&mut diff_attributes, ticks: Vec::new(),
&mut slider_state, curve_bufs: CurveBuffers::default(),
) };
});
let hit_objects_iter = map
.hit_objects
.iter()
.take(take)
.filter_map(|h| OsuObject::new(h, hr, &mut params));
let mut hit_objects = Vec::with_capacity(take); let mut hit_objects = Vec::with_capacity(take);
hit_objects.extend(hit_objects_iter); hit_objects.extend(hit_objects_iter);
@@ -277,21 +279,20 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
scaling_factor *= 1.0 + small_circle_bonus; scaling_factor *= 1.0 + small_circle_bonus;
} }
let mut slider_state = SliderState::new(map); let mut params = ObjectParameters {
let mut ticks_buf = Vec::new();
let hit_objects_iter = map.hit_objects.iter().filter_map(|h| {
OsuObject::new(
h,
map, map,
radius, radius,
scaling_factor, scaling_factor,
hr, attributes: &mut diff_attributes,
&mut ticks_buf, slider_state: SliderState::new(map),
&mut diff_attributes, ticks: Vec::new(),
&mut slider_state, curve_bufs: CurveBuffers::default(),
) };
});
let hit_objects_iter = map
.hit_objects
.iter()
.filter_map(|h| OsuObject::new(h, hr, &mut params));
let mut hit_objects = Vec::with_capacity(map.hit_objects.len()); let mut hit_objects = Vec::with_capacity(map.hit_objects.len());
hit_objects.extend(hit_objects_iter); hit_objects.extend(hit_objects_iter);
+51 -33
View File
@@ -2,12 +2,13 @@ use super::super::super::DifficultyAttributes;
use super::slider_state::SliderState; use super::slider_state::SliderState;
use crate::{ use crate::{
curve::Curve, curve::{Curve, CurveBuffers},
parse::{HitObject, HitObjectKind, Pos2}, parse::{HitObject, HitObjectKind, Pos2},
Beatmap, Beatmap,
}; };
const LEGACY_LAST_TICK_OFFSET: f32 = 36.0; const LEGACY_LAST_TICK_OFFSET: f32 = 36.0;
const BASE_SCORING_DISTANCE: f32 = 100.0;
pub(crate) struct OsuObject { pub(crate) struct OsuObject {
pub(crate) time: f32, pub(crate) time: f32,
@@ -29,18 +30,29 @@ enum OsuObjectKind {
}, },
} }
pub(crate) struct ObjectParameters<'a> {
pub(crate) map: &'a Beatmap,
pub(crate) radius: f32,
pub(crate) scaling_factor: f32,
pub(crate) attributes: &'a mut DifficultyAttributes,
pub(crate) ticks: Vec<f32>,
pub(crate) slider_state: SliderState<'a>,
pub(crate) curve_bufs: CurveBuffers,
}
impl OsuObject { impl OsuObject {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) fn new( pub(crate) fn new(h: &HitObject, hr: bool, params: &mut ObjectParameters) -> Option<Self> {
h: &HitObject, let ObjectParameters {
map: &Beatmap, map,
radius: f32, radius,
scaling_factor: f32, scaling_factor,
hr: bool, attributes,
ticks: &mut Vec<f32>, ticks,
attributes: &mut DifficultyAttributes, slider_state,
slider_state: &mut SliderState, curve_bufs,
) -> Option<Self> { } = params;
attributes.max_combo += 1; // hitcircle, slider head, or spinner attributes.max_combo += 1; // hitcircle, slider head, or spinner
let mut pos = h.pos; let mut pos = h.pos;
@@ -58,8 +70,7 @@ impl OsuObject {
HitObjectKind::Slider { HitObjectKind::Slider {
pixel_len, pixel_len,
repeats, repeats,
curve_points, control_points,
path_type,
} => { } => {
// Key values which are computed here // Key values which are computed here
let mut lazy_end_pos = pos; let mut lazy_end_pos = pos;
@@ -68,21 +79,26 @@ impl OsuObject {
// Responsible for timing point values // Responsible for timing point values
slider_state.update(h.start_time); slider_state.update(h.start_time);
let approx_follow_circle_radius = radius * 3.0; let span_count = (*repeats + 1) as f32;
let mut tick_distance = 100.0 * map.sv / map.tick_rate;
let approx_follow_circle_radius = *radius * 3.0;
let mut tick_dist = 100.0 * map.slider_mult / map.tick_rate;
if map.version >= 8 { if map.version >= 8 {
tick_distance /= tick_dist /=
(100.0 / slider_state.speed_mult).max(10.0).min(1000.0) / 100.0; (100.0 / slider_state.slider_velocity).max(10.0).min(1000.0) / 100.0;
} }
let duration = *repeats as f32 * slider_state.beat_len * pixel_len
/ (map.sv * slider_state.speed_mult)
/ 100.0;
let span_duration = duration / *repeats as f32;
// Build the curve w.r.t. the curve points // Build the curve w.r.t. the curve points
let curve = Curve::new(curve_points, *path_type); let curve = Curve::new(control_points, *pixel_len, curve_bufs);
let velocity =
(BASE_SCORING_DISTANCE * map.slider_mult * slider_state.slider_velocity)
/ slider_state.beat_len;
let end_time = h.start_time + span_count * curve.dist() / velocity;
let duration = end_time - h.start_time;
let span_duration = duration / span_count;
// Called on each slider object except for the head. // Called on each slider object except for the head.
// Increases combo and adjusts `end_pos` and `travel_dist` // Increases combo and adjusts `end_pos` and `travel_dist`
@@ -98,8 +114,8 @@ impl OsuObject {
progress %= 1.0; progress %= 1.0;
} }
let curr_dist = pixel_len * progress; // TODO: Correct addition?
let mut curr_pos = curve.point_at_distance(curr_dist); let mut curr_pos = h.pos + curve.position_at(progress);
if hr { if hr {
curr_pos.y = 384.0 - curr_pos.y; curr_pos.y = 384.0 - curr_pos.y;
@@ -109,17 +125,18 @@ impl OsuObject {
let mut dist = diff.length(); let mut dist = diff.length();
if dist > approx_follow_circle_radius { if dist > approx_follow_circle_radius {
// * The cursor would be outside the follow circle, we need to move it
dist -= approx_follow_circle_radius; dist -= approx_follow_circle_radius;
lazy_end_pos += diff.normalize() * dist; lazy_end_pos += diff.normalize() * dist;
travel_dist += dist; travel_dist += dist;
} }
}; };
let mut current_distance = tick_distance; let mut current_distance = tick_dist;
let time_add = duration * (tick_distance / (pixel_len * *repeats as f32)); let time_add = duration * (tick_dist / (pixel_len * span_count));
let target = pixel_len - tick_distance / 8.0; let target = pixel_len - tick_dist / 8.0;
ticks.reserve((target / tick_distance) as usize); ticks.reserve((target / tick_dist) as usize);
// Tick of the first span // Tick of the first span
if current_distance < target { if current_distance < target {
@@ -127,7 +144,7 @@ impl OsuObject {
let time = h.start_time + time_add * tick_idx as f32; let time = h.start_time + time_add * tick_idx as f32;
compute_vertex(time); compute_vertex(time);
ticks.push(time); ticks.push(time);
current_distance += tick_distance; current_distance += tick_dist;
if current_distance >= target { if current_distance >= target {
break; break;
@@ -161,9 +178,10 @@ impl OsuObject {
ticks.clear(); ticks.clear();
travel_dist *= scaling_factor; // TODO: what if reversing odd amount?
// TODO: Correct addition?
let mut end_pos = curve.point_at_distance(*pixel_len); let mut end_pos = h.pos + curve.position_at(1.0);
travel_dist *= *scaling_factor;
if hr { if hr {
end_pos.y = 384.0 - end_pos.y; end_pos.y = 384.0 - end_pos.y;
+12 -8
View File
@@ -4,7 +4,7 @@ pub(crate) struct SliderState<'p> {
control_points: ControlPointIter<'p>, control_points: ControlPointIter<'p>,
next: Option<ControlPoint>, next: Option<ControlPoint>,
pub(crate) beat_len: f32, pub(crate) beat_len: f32,
pub(crate) speed_mult: f32, pub(crate) slider_velocity: f32,
} }
impl<'p> SliderState<'p> { impl<'p> SliderState<'p> {
@@ -12,9 +12,11 @@ impl<'p> SliderState<'p> {
pub(crate) fn new(map: &'p Beatmap) -> Self { pub(crate) fn new(map: &'p Beatmap) -> Self {
let mut control_points = ControlPointIter::new(map); let mut control_points = ControlPointIter::new(map);
let (beat_len, speed_mult) = match control_points.next() { let (beat_len, slider_velocity) = match control_points.next() {
Some(ControlPoint::Timing { beat_len, .. }) => (beat_len, 1.0), Some(ControlPoint::Timing { beat_len, .. }) => (beat_len, 1.0),
Some(ControlPoint::Difficulty { speed_mult, .. }) => (1000.0, speed_mult), Some(ControlPoint::Difficulty {
slider_velocity, ..
}) => (1000.0, slider_velocity),
None => (1000.0, 1.0), None => (1000.0, 1.0),
}; };
@@ -22,7 +24,7 @@ impl<'p> SliderState<'p> {
next: control_points.next(), next: control_points.next(),
control_points, control_points,
beat_len, beat_len,
speed_mult, slider_velocity,
} }
} }
@@ -32,9 +34,11 @@ impl<'p> SliderState<'p> {
match next { match next {
ControlPoint::Timing { beat_len, .. } => { ControlPoint::Timing { beat_len, .. } => {
self.beat_len = *beat_len; self.beat_len = *beat_len;
self.speed_mult = 1.0; self.slider_velocity = 1.0;
} }
ControlPoint::Difficulty { speed_mult, .. } => self.speed_mult = *speed_mult, ControlPoint::Difficulty {
slider_velocity, ..
} => self.slider_velocity = *slider_velocity,
} }
self.next = self.control_points.next(); self.next = self.control_points.next();
@@ -88,10 +92,10 @@ mod test {
state.update(3.0); state.update(3.0);
assert_eq!(state.beat_len, 20.0); assert_eq!(state.beat_len, 20.0);
assert_eq!(state.speed_mult, 1.0); assert_eq!(state.slider_velocity, 1.0);
state.update(5.0); state.update(5.0);
assert_eq!(state.beat_len, 30.0); assert_eq!(state.beat_len, 30.0);
assert_eq!(state.speed_mult, 45.0); assert_eq!(state.slider_velocity, 45.0);
} }
} }
+3 -1
View File
@@ -161,7 +161,9 @@ impl OsuObject {
ticks.clear(); ticks.clear();
let end_pos = curve.position_at(1.0); // TODO: what if reversing odd amount? // TODO: what if reversing odd amount?
// TODO: + h.pos?
let end_pos = curve.position_at(1.0);
travel_dist *= *scaling_factor; travel_dist *= *scaling_factor;
Self { Self {
@@ -1,3 +1,5 @@
use std::f32::NEG_INFINITY;
use crate::{Beatmap, ControlPoint, ControlPointIter}; use crate::{Beatmap, ControlPoint, ControlPointIter};
pub(crate) struct SliderState<'p> { pub(crate) struct SliderState<'p> {
@@ -12,7 +14,7 @@ impl<'p> SliderState<'p> {
pub(crate) fn new(map: &'p Beatmap) -> Self { pub(crate) fn new(map: &'p Beatmap) -> Self {
Self { Self {
control_points: ControlPointIter::new(map), control_points: ControlPointIter::new(map),
next_time: std::f32::NEG_INFINITY, next_time: NEG_INFINITY,
px_per_beat: 1.0, px_per_beat: 1.0,
prev_sv: 1.0, prev_sv: 1.0,
} }
@@ -26,16 +28,19 @@ impl<'p> SliderState<'p> {
map: &Beatmap, map: &Beatmap,
) -> usize { ) -> usize {
while time >= self.next_time { while time >= self.next_time {
self.px_per_beat = map.sv * 100.0 * self.prev_sv; self.px_per_beat = map.slider_mult * 100.0 * self.prev_sv;
match self.control_points.next() { match self.control_points.next() {
Some(ControlPoint::Timing { time, .. }) => { Some(ControlPoint::Timing { time, .. }) => {
self.next_time = time; self.next_time = time;
self.prev_sv = 1.0; self.prev_sv = 1.0;
} }
Some(ControlPoint::Difficulty { time, speed_mult }) => { Some(ControlPoint::Difficulty {
time,
slider_velocity,
}) => {
self.next_time = time; self.next_time = time;
self.prev_sv = speed_mult; self.prev_sv = slider_velocity;
} }
None => break, None => break,
} }
+3 -1
View File
@@ -1,9 +1,11 @@
use super::{PathControlPoint, Pos2}; use super::Pos2;
#[cfg(any( #[cfg(any(
feature = "fruits", feature = "fruits",
all(feature = "osu", not(feature = "no_sliders_no_leniency")) all(feature = "osu", not(feature = "no_sliders_no_leniency"))
))] ))]
use super::PathControlPoint;
use std::cmp::Ordering; use std::cmp::Ordering;
/// "Intermediate" hitobject created through parsing. /// "Intermediate" hitobject created through parsing.
+70 -50
View File
@@ -1,5 +1,3 @@
use crate::math_util::is_linear;
mod attributes; mod attributes;
mod control_point; mod control_point;
mod error; mod error;
@@ -16,7 +14,7 @@ pub use hitsound::HitSound;
pub use pos2::Pos2; pub use pos2::Pos2;
use sort::legacy_sort; use sort::legacy_sort;
use std::{cmp::Ordering, mem}; use std::cmp::Ordering;
#[cfg(not(any(feature = "async_std", feature = "async_tokio")))] #[cfg(not(any(feature = "async_std", feature = "async_tokio")))]
use std::io::{BufRead, BufReader, Read}; use std::io::{BufRead, BufReader, Read};
@@ -27,6 +25,12 @@ use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader};
#[cfg(feature = "async_std")] #[cfg(feature = "async_std")]
use async_std::io::{prelude::BufReadExt, BufReader as AsyncBufReader, Read as AsyncRead}; use async_std::io::{prelude::BufReadExt, BufReader as AsyncBufReader, Read as AsyncRead};
#[cfg(any(
feature = "fruits",
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
))]
pub use osu_fruits::*;
fn sort_unstable<T: PartialOrd>(slice: &mut [T]) { fn sort_unstable<T: PartialOrd>(slice: &mut [T]) {
slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal)); slice.sort_unstable_by(|p1, p2| p1.partial_cmp(p2).unwrap_or(Ordering::Equal));
} }
@@ -722,12 +726,6 @@ pub struct Beatmap {
pub(crate) const OSU_FILE_HEADER: &str = "osu file format v"; pub(crate) const OSU_FILE_HEADER: &str = "osu file format v";
#[cfg(any(
feature = "fruits",
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
))]
const MAX_COORDINATE_VALUE: f32 = 131_072.0;
impl Beatmap { impl Beatmap {
const CIRCLE_FLAG: u8 = 1 << 0; const CIRCLE_FLAG: u8 = 1 << 0;
const SLIDER_FLAG: u8 = 1 << 1; const SLIDER_FLAG: u8 = 1 << 1;
@@ -763,12 +761,20 @@ impl Beatmap {
let mut prev_time = 0.0; let mut prev_time = 0.0;
let mut empty = true; let mut empty = true;
#[cfg(any(
feature = "fruits",
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
))]
// `point_split` will be of type `Vec<&str> // `point_split` will be of type `Vec<&str>
// with each element having its lifetime bound to `buf`. // with each element having its lifetime bound to `buf`.
// To cirvumvent this, `point_split_raw` will contain // To cirvumvent this, `point_split_raw` will contain
// the actual `&str` elements transmuted into `usize`. // the actual `&str` elements transmuted into `usize`.
let mut point_split_raw: Vec<usize> = Vec::new(); let mut point_split_raw: Vec<usize> = Vec::new();
#[cfg(any(
feature = "fruits",
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
))]
// Buffer to re-use for all sliders // Buffer to re-use for all sliders
let mut vertices = Vec::new(); let mut vertices = Vec::new();
@@ -834,7 +840,7 @@ impl Beatmap {
// SAFETY: `Vec<usize>` and `Vec<&str>` have the same size and layout. // SAFETY: `Vec<usize>` and `Vec<&str>` have the same size and layout.
let point_split: &mut Vec<&str> = let point_split: &mut Vec<&str> =
unsafe { mem::transmute(&mut point_split_raw) }; unsafe { std::mem::transmute(&mut point_split_raw) };
point_split.clear(); point_split.clear();
point_split.extend(control_point_iter); point_split.extend(control_point_iter);
@@ -903,8 +909,8 @@ impl Beatmap {
all(feature = "osu", not(feature = "no_sliders_no_leniency")) all(feature = "osu", not(feature = "no_sliders_no_leniency"))
)))] )))]
{ {
let repeats: usize = next_field!(split.nth(1), "repeats").parse()?; let repeats = split.nth(1).next_field("repeats")?.parse()?;
let pixel_len: f32 = next_field!(split.next(), "pixel len").parse()?; let pixel_len = split.next().next_field("pixel len")?.parse()?;
HitObjectKind::Slider { repeats, pixel_len } HitObjectKind::Slider { repeats, pixel_len }
} }
@@ -954,14 +960,26 @@ impl Beatmap {
} }
} }
fn convert_points( // TODO: Replace with `sliders` auxiliary feature
#[cfg(any(
feature = "fruits",
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
))]
mod osu_fruits {
use crate::{math_util::is_linear, ParseError};
use super::Pos2;
pub(super) const MAX_COORDINATE_VALUE: f32 = 131_072.0;
pub(super) fn convert_points(
points: &[&str], points: &[&str],
end_point: Option<&str>, end_point: Option<&str>,
first: bool, first: bool,
offset: Pos2, offset: Pos2,
curve_points: &mut Vec<PathControlPoint>, curve_points: &mut Vec<PathControlPoint>,
vertices: &mut Vec<PathControlPoint>, vertices: &mut Vec<PathControlPoint>,
) -> Result<(), ParseError> { ) -> Result<(), ParseError> {
let mut path_kind = PathType::from_str(points[0]); let mut path_kind = PathType::from_str(points[0]);
let read_offset = first as usize; let read_offset = first as usize;
@@ -1041,15 +1059,51 @@ fn convert_points(
} }
Ok(()) Ok(())
} }
fn read_point(value: &str, start_pos: Pos2) -> Result<PathControlPoint, ParseError> { pub(super) fn read_point(value: &str, start_pos: Pos2) -> Result<PathControlPoint, ParseError> {
let mut v = value.split(':').map(str::parse); let mut v = value.split(':').map(str::parse);
match (v.next(), v.next()) { match (v.next(), v.next()) {
(Some(Ok(x)), Some(Ok(y))) => Ok(PathControlPoint::from(Pos2 { x, y } - start_pos)), (Some(Ok(x)), Some(Ok(y))) => Ok(PathControlPoint::from(Pos2 { x, y } - start_pos)),
_ => Err(ParseError::InvalidCurvePoints), _ => Err(ParseError::InvalidCurvePoints),
} }
}
/// Control point for slider curve calculation
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct PathControlPoint {
pub pos: Pos2,
pub kind: Option<PathType>,
}
impl From<Pos2> for PathControlPoint {
#[inline]
fn from(pos: Pos2) -> Self {
Self { pos, kind: None }
}
}
/// The type of curve of a slider.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PathType {
Catmull = 0,
Bezier = 1,
Linear = 2,
PerfectCurve = 3,
}
impl PathType {
#[inline]
fn from_str(s: &str) -> Self {
match s {
"L" => Self::Linear,
"B" => Self::Bezier,
"P" => Self::PerfectCurve,
_ => Self::Catmull,
}
}
}
} }
#[cfg(feature = "async_tokio")] #[cfg(feature = "async_tokio")]
@@ -1077,40 +1131,6 @@ fn split_colon(line: &str) -> Option<(&str, &str)> {
Some((split.next()?, split.next()?.trim())) Some((split.next()?, split.next()?.trim()))
} }
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct PathControlPoint {
pub pos: Pos2,
pub kind: Option<PathType>,
}
impl From<Pos2> for PathControlPoint {
#[inline]
fn from(pos: Pos2) -> Self {
Self { pos, kind: None }
}
}
/// The type of curve of a slider.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PathType {
Catmull = 0,
Bezier = 1,
Linear = 2,
PerfectCurve = 3,
}
impl PathType {
#[inline]
fn from_str(s: &str) -> Self {
match s {
"L" => Self::Linear,
"B" => Self::Bezier,
"P" => Self::PerfectCurve,
_ => Self::Catmull,
}
}
}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
enum Section { enum Section {
None, None,