rewrote slider processing for fruits
This commit is contained in:
+1
-2
@@ -11,8 +11,7 @@ description = "osu! difficulty and pp calculation for all modes"
|
|||||||
keywords = ["osu", "pp", "stars", "async"]
|
keywords = ["osu", "pp", "stars", "async"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# default = ["osu_precise", "taiko", "fruits", "mania"]
|
default = ["osu_precise", "taiko", "fruits", "mania"]
|
||||||
default = ["osu_fast", "taiko", "fruits", "mania"]
|
|
||||||
|
|
||||||
# game modes
|
# game modes
|
||||||
osu_fast = ["osu"]
|
osu_fast = ["osu"]
|
||||||
|
|||||||
+85
-62
@@ -30,7 +30,6 @@ const BASE_SCORING_DISTANCE: f32 = 100.0;
|
|||||||
/// Star calculation for osu!ctb maps
|
/// Star calculation for osu!ctb maps
|
||||||
///
|
///
|
||||||
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
|
/// In case of a partial play, e.g. a fail, one can specify the amount of passed objects.
|
||||||
// Slider parsing based on https://github.com/osufx/catch-the-pp
|
|
||||||
pub fn stars(
|
pub fn stars(
|
||||||
map: &Beatmap,
|
map: &Beatmap,
|
||||||
mods: impl Mods,
|
mods: impl Mods,
|
||||||
@@ -80,7 +79,7 @@ pub fn stars(
|
|||||||
// Responsible for timing point values
|
// Responsible for timing point values
|
||||||
slider_state.update(h.start_time);
|
slider_state.update(h.start_time);
|
||||||
|
|
||||||
let span_count = *repeats + 1;
|
let span_count = (*repeats + 1) as f32;
|
||||||
|
|
||||||
let mut tick_dist = 100.0 * map.slider_mult / map.tick_rate;
|
let mut tick_dist = 100.0 * map.slider_mult / map.tick_rate;
|
||||||
|
|
||||||
@@ -96,58 +95,70 @@ pub fn stars(
|
|||||||
(BASE_SCORING_DISTANCE * map.slider_mult * slider_state.slider_velocity)
|
(BASE_SCORING_DISTANCE * map.slider_mult * slider_state.slider_velocity)
|
||||||
/ slider_state.beat_len;
|
/ slider_state.beat_len;
|
||||||
|
|
||||||
let end_time = h.start_time + span_count as f32 * curve.dist() / velocity;
|
let end_time = h.start_time + span_count * curve.dist() / velocity;
|
||||||
let duration = end_time - h.start_time;
|
let duration = end_time - h.start_time;
|
||||||
|
let span_duration = duration / span_count;
|
||||||
|
|
||||||
|
// * A very lenient maximum length of a slider for ticks to be generated.
|
||||||
|
// * This exists for edge cases such as /b/1573664 where the beatmap has
|
||||||
|
// * been edited by the user, and should never be reached in normal usage.
|
||||||
|
let max_len = 100_000.0;
|
||||||
|
|
||||||
|
let len = curve.dist().min(max_len);
|
||||||
|
tick_dist = tick_dist.clamp(0.0, len);
|
||||||
|
let min_dist_from_end = velocity * 10.0;
|
||||||
|
|
||||||
let mut curr_dist = tick_dist;
|
let mut curr_dist = tick_dist;
|
||||||
let time_add = duration * (tick_dist / (*pixel_len * span_count as f32));
|
let time_add = duration * tick_dist / (*pixel_len * span_count);
|
||||||
|
|
||||||
let target = *pixel_len - tick_dist / 8.0;
|
let target = *pixel_len - tick_dist / 8.0;
|
||||||
|
|
||||||
ticks.reserve((target / tick_dist) as usize);
|
ticks.reserve((target / tick_dist) as usize);
|
||||||
|
|
||||||
// Tick of the first span
|
// Tick of the first span
|
||||||
if curr_dist < target {
|
while curr_dist < len - min_dist_from_end {
|
||||||
for tick_idx in 1.. {
|
let progress = curr_dist / len;
|
||||||
let progress = curr_dist / *pixel_len;
|
let pos = h.pos + curve.position_at(progress);
|
||||||
let pos = h.pos + curve.position_at(progress);
|
let time = h.start_time + progress * span_duration;
|
||||||
let time = h.start_time + time_add * tick_idx as f32;
|
ticks.push((pos, time));
|
||||||
ticks.push((pos, time));
|
curr_dist += tick_dist;
|
||||||
curr_dist += tick_dist;
|
|
||||||
|
|
||||||
if curr_dist >= target {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tiny_droplets +=
|
tiny_droplets += tiny_droplet_count(
|
||||||
tiny_droplet_count(h.start_time, time_add, duration, span_count, &ticks);
|
h.start_time,
|
||||||
|
time_add,
|
||||||
|
duration,
|
||||||
|
span_count as usize,
|
||||||
|
&ticks,
|
||||||
|
);
|
||||||
|
|
||||||
let mut slider_objects = Vec::with_capacity(span_count * (ticks.len() + 1));
|
let mut slider_objects =
|
||||||
|
Vec::with_capacity(span_count as usize * (ticks.len() + 1));
|
||||||
slider_objects.push((h.pos, h.start_time));
|
slider_objects.push((h.pos, h.start_time));
|
||||||
|
|
||||||
// Other spans
|
// Other spans
|
||||||
if span_count <= 1 {
|
if *repeats == 0 {
|
||||||
slider_objects.append(&mut ticks); // automatically empties buffer for next slider
|
slider_objects.append(&mut ticks); // automatically empties buffer for next slider
|
||||||
} else {
|
} else {
|
||||||
slider_objects.extend(&ticks);
|
slider_objects.extend(&ticks);
|
||||||
|
|
||||||
for span_idx in 1..span_count {
|
for span_idx in 1..=*repeats {
|
||||||
let dist = (span_idx % 2) as f32 * *pixel_len;
|
let progress = (span_idx % 2 == 1) as u8 as f32;
|
||||||
let time_offset = (duration / span_count as f32) * span_idx as f32;
|
|
||||||
let progress = dist / *pixel_len;
|
|
||||||
let pos = h.pos + curve.position_at(progress);
|
let pos = h.pos + curve.position_at(progress);
|
||||||
|
let time_offset = span_duration * span_idx as f32;
|
||||||
|
|
||||||
// Reverse tick
|
// Reverse tick
|
||||||
slider_objects.push((pos, h.start_time + time_offset));
|
slider_objects.push((pos, h.start_time + time_offset));
|
||||||
|
|
||||||
|
let new_ticks = ticks.iter().enumerate().map(|(i, (pos, time))| {
|
||||||
|
(*pos, *time + time_offset + time_add * i as f32)
|
||||||
|
});
|
||||||
|
|
||||||
// Actual ticks
|
// Actual ticks
|
||||||
if span_idx & 1 == 1 {
|
if span_idx & 1 == 1 {
|
||||||
slider_objects.extend(ticks.iter().rev().enumerate().map(
|
slider_objects.extend(new_ticks.rev());
|
||||||
|(i, (pos, time))| (*pos, *time + time_add * 2.0 * (i + 1) as f32),
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
slider_objects.extend(ticks.iter().copied());
|
slider_objects.extend(new_ticks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +170,9 @@ pub fn stars(
|
|||||||
let pos = h.pos + curve.position_at(progress);
|
let pos = h.pos + curve.position_at(progress);
|
||||||
slider_objects.push((pos, h.start_time + duration));
|
slider_objects.push((pos, h.start_time + duration));
|
||||||
|
|
||||||
fruits += span_count; // TODO: +1?
|
let new_fruits = *repeats + 2;
|
||||||
droplets += slider_objects.len() - 1 - span_count; // TODO: -1?
|
fruits += new_fruits;
|
||||||
|
droplets += slider_objects.len() - new_fruits;
|
||||||
|
|
||||||
let iter = slider_objects.into_iter().map(CatchObject::new);
|
let iter = slider_objects.into_iter().map(CatchObject::new);
|
||||||
|
|
||||||
@@ -317,7 +329,7 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
// Responsible for timing point values
|
// Responsible for timing point values
|
||||||
slider_state.update(h.start_time);
|
slider_state.update(h.start_time);
|
||||||
|
|
||||||
let span_count = *repeats + 1;
|
let span_count = (*repeats + 1) as f32;
|
||||||
|
|
||||||
let mut tick_dist = 100.0 * map.slider_mult / map.tick_rate;
|
let mut tick_dist = 100.0 * map.slider_mult / map.tick_rate;
|
||||||
|
|
||||||
@@ -333,53 +345,62 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
(BASE_SCORING_DISTANCE * map.slider_mult * slider_state.slider_velocity)
|
(BASE_SCORING_DISTANCE * map.slider_mult * slider_state.slider_velocity)
|
||||||
/ slider_state.beat_len;
|
/ slider_state.beat_len;
|
||||||
|
|
||||||
let end_time = h.start_time + span_count as f32 * curve.dist() / velocity;
|
let end_time = h.start_time + span_count * curve.dist() / velocity;
|
||||||
let duration = end_time - h.start_time;
|
let duration = end_time - h.start_time;
|
||||||
|
let span_duration = duration / span_count;
|
||||||
|
|
||||||
|
// * A very lenient maximum length of a slider for ticks to be generated.
|
||||||
|
// * This exists for edge cases such as /b/1573664 where the beatmap has
|
||||||
|
// * been edited by the user, and should never be reached in normal usage.
|
||||||
|
let max_len = 100_000.0;
|
||||||
|
|
||||||
|
let len = curve.dist().min(max_len);
|
||||||
|
tick_dist = tick_dist.clamp(0.0, len);
|
||||||
|
let min_dist_from_end = velocity * 10.0;
|
||||||
|
|
||||||
let mut curr_dist = tick_dist;
|
let mut curr_dist = tick_dist;
|
||||||
let time_add = duration * (tick_dist / (*pixel_len * span_count as f32));
|
let time_add = duration * tick_dist / (*pixel_len * span_count);
|
||||||
|
|
||||||
let target = *pixel_len - tick_dist / 8.0;
|
let target = *pixel_len - tick_dist / 8.0;
|
||||||
|
|
||||||
ticks.reserve((target / tick_dist) as usize);
|
ticks.reserve((target / tick_dist) as usize);
|
||||||
|
|
||||||
// Tick of the first span
|
// Tick of the first span
|
||||||
if curr_dist < target {
|
while curr_dist < len - min_dist_from_end {
|
||||||
for tick_idx in 1.. {
|
let progress = curr_dist / len;
|
||||||
let progress = curr_dist / *pixel_len;
|
let pos = h.pos + curve.position_at(progress);
|
||||||
let pos = curve.position_at(progress);
|
let time = h.start_time + progress * span_duration;
|
||||||
let time = h.start_time + time_add * tick_idx as f32;
|
ticks.push((pos, time));
|
||||||
ticks.push((pos, time));
|
curr_dist += tick_dist;
|
||||||
curr_dist += tick_dist;
|
|
||||||
|
|
||||||
if curr_dist >= target {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut slider_objects = Vec::with_capacity(span_count * (ticks.len() + 1));
|
let mut slider_objects =
|
||||||
|
Vec::with_capacity(span_count as usize * (ticks.len() + 1));
|
||||||
slider_objects.push((h.pos, h.start_time));
|
slider_objects.push((h.pos, h.start_time));
|
||||||
|
|
||||||
// Other spans
|
// Other spans
|
||||||
if span_count <= 1 {
|
if *repeats == 0 {
|
||||||
slider_objects.append(&mut ticks); // automatically empties buffer for next slider
|
slider_objects.append(&mut ticks); // automatically empties buffer for next slider
|
||||||
} else {
|
} else {
|
||||||
slider_objects.extend(&ticks);
|
slider_objects.extend(&ticks);
|
||||||
|
|
||||||
for span_idx in 1..span_count {
|
for span_idx in 1..=*repeats {
|
||||||
let dist = (span_idx % 2) as f32 * *pixel_len;
|
let progress = (span_idx % 2 == 1) as u8 as f32;
|
||||||
let time_offset = (duration / span_count as f32) * span_idx as f32;
|
let pos = h.pos + curve.position_at(progress);
|
||||||
let progress = dist / *pixel_len;
|
let time_offset = span_duration * span_idx as f32;
|
||||||
let pos = curve.position_at(progress);
|
|
||||||
|
|
||||||
// Reverse tick
|
// Reverse tick
|
||||||
slider_objects.push((pos, h.start_time + time_offset));
|
slider_objects.push((pos, h.start_time + time_offset));
|
||||||
|
|
||||||
|
let new_ticks = ticks.iter().enumerate().map(|(i, (pos, time))| {
|
||||||
|
(*pos, *time + time_offset + time_add * i as f32)
|
||||||
|
});
|
||||||
|
|
||||||
// Actual ticks
|
// Actual ticks
|
||||||
if span_idx & 1 == 1 {
|
if span_idx & 1 == 1 {
|
||||||
slider_objects.extend(ticks.iter().copied().rev());
|
slider_objects.extend(new_ticks.rev());
|
||||||
} else {
|
} else {
|
||||||
slider_objects.extend(ticks.iter().copied());
|
slider_objects.extend(new_ticks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +409,7 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
|
|
||||||
// Slider tail
|
// Slider tail
|
||||||
let progress = (*repeats % 2 == 0) as u8 as f32;
|
let progress = (*repeats % 2 == 0) as u8 as f32;
|
||||||
let pos = curve.position_at(progress);
|
let pos = h.pos + curve.position_at(progress);
|
||||||
slider_objects.push((pos, h.start_time + duration));
|
slider_objects.push((pos, h.start_time + duration));
|
||||||
|
|
||||||
let iter = slider_objects.into_iter().map(CatchObject::new);
|
let iter = slider_objects.into_iter().map(CatchObject::new);
|
||||||
@@ -504,7 +525,7 @@ fn tiny_droplet_count(
|
|||||||
start_time: f32,
|
start_time: f32,
|
||||||
time_between_ticks: f32,
|
time_between_ticks: f32,
|
||||||
duration: f32,
|
duration: f32,
|
||||||
spans: usize,
|
span_count: usize,
|
||||||
ticks: &[(Pos2, f32)],
|
ticks: &[(Pos2, f32)],
|
||||||
) -> usize {
|
) -> usize {
|
||||||
// tiny droplets preceeding a _tick_
|
// tiny droplets preceeding a _tick_
|
||||||
@@ -521,7 +542,7 @@ fn tiny_droplet_count(
|
|||||||
|
|
||||||
// tiny droplets preceeding a _reverse_
|
// tiny droplets preceeding a _reverse_
|
||||||
let last = ticks.last().map_or(start_time, |(_, last)| *last);
|
let last = ticks.last().map_or(start_time, |(_, last)| *last);
|
||||||
let repeat_time = start_time + duration / spans as f32;
|
let repeat_time = start_time + duration / span_count as f32;
|
||||||
let since_last_tick = repeat_time - last;
|
let since_last_tick = repeat_time - last;
|
||||||
|
|
||||||
let span_last_section = if since_last_tick > 80.0 {
|
let span_last_section = if since_last_tick > 80.0 {
|
||||||
@@ -535,7 +556,7 @@ fn tiny_droplet_count(
|
|||||||
// tiny droplets preceeding the slider tail
|
// tiny droplets preceeding the slider tail
|
||||||
// necessary to handle distinctly because of the legacy last tick
|
// necessary to handle distinctly because of the legacy last tick
|
||||||
let last = ticks.last().map_or(start_time, |(_, last)| *last);
|
let last = ticks.last().map_or(start_time, |(_, last)| *last);
|
||||||
let end_time = start_time + duration / spans as f32 - LEGACY_LAST_TICK_OFFSET;
|
let end_time = start_time + duration / span_count as f32 - LEGACY_LAST_TICK_OFFSET;
|
||||||
let since_last_tick = end_time - last;
|
let since_last_tick = end_time - last;
|
||||||
|
|
||||||
let last_section = if since_last_tick > 80.0 {
|
let last_section = if since_last_tick > 80.0 {
|
||||||
@@ -547,7 +568,9 @@ fn tiny_droplet_count(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Combine tiny droplets counts
|
// Combine tiny droplets counts
|
||||||
per_tick * ticks.len() * spans + span_last_section * (spans.saturating_sub(1)) + last_section
|
per_tick * ticks.len() * span_count
|
||||||
|
+ span_last_section * (span_count.saturating_sub(1))
|
||||||
|
+ last_section
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -638,18 +661,18 @@ impl PerformanceAttributes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
// #[ignore]
|
||||||
fn custom_fruits() {
|
fn custom_fruits() {
|
||||||
use std::{fs::File, time::Instant};
|
use std::{fs::File, time::Instant};
|
||||||
|
|
||||||
use crate::{Beatmap, FruitsPP};
|
use crate::{Beatmap, FruitsPP};
|
||||||
|
|
||||||
let path = "./maps/2753127.osu";
|
let path = "E:Games/osu!/beatmaps/2919116_.osu";
|
||||||
let file = File::open(path).unwrap();
|
let file = File::open(path).unwrap();
|
||||||
let map = Beatmap::parse(file).unwrap();
|
let map = Beatmap::parse(file).unwrap();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let result = FruitsPP::new(&map).mods(0).calculate();
|
let result = FruitsPP::new(&map).mods(256).calculate();
|
||||||
|
|
||||||
let iters = 100;
|
let iters = 100;
|
||||||
let accum = start.elapsed();
|
let accum = start.elapsed();
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ impl FruitsPPInner {
|
|||||||
let len_bonus = 0.95
|
let len_bonus = 0.95
|
||||||
+ 0.3 * (combo_hits as f32 / 2500.0).min(1.0)
|
+ 0.3 * (combo_hits as f32 / 2500.0).min(1.0)
|
||||||
+ (combo_hits > 2500) as u8 as f32 * (combo_hits as f32 / 2500.0).log10() * 0.475;
|
+ (combo_hits > 2500) as u8 as f32 * (combo_hits as f32 / 2500.0).log10() * 0.475;
|
||||||
|
|
||||||
pp *= len_bonus;
|
pp *= len_bonus;
|
||||||
|
|
||||||
// Penalize misses exponentially
|
// Penalize misses exponentially
|
||||||
|
|||||||
Reference in New Issue
Block a user