fix linear perfect curves and subpaths
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
## Upcoming
|
## Upcoming
|
||||||
|
|
||||||
|
- osu & fruits:
|
||||||
|
- Fixed certain slider patterns
|
||||||
|
|
||||||
- fruits:
|
- fruits:
|
||||||
- Fixed tick timing for reverse sliders
|
- Fixed tick timing for reverse sliders
|
||||||
|
|
||||||
|
|||||||
+14
-19
@@ -13,25 +13,21 @@ pub(crate) enum Points {
|
|||||||
Multi(Vec<Pos2>),
|
Multi(Vec<Pos2>),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum Curve {
|
pub(crate) enum Curve<'p> {
|
||||||
Linear {
|
Linear(&'p [Pos2]),
|
||||||
a: Pos2,
|
|
||||||
b: Pos2,
|
|
||||||
},
|
|
||||||
Bezier(Points),
|
Bezier(Points),
|
||||||
Catmull(Points),
|
Catmull(Points),
|
||||||
Perfect {
|
Perfect {
|
||||||
origin: Pos2,
|
origin: Pos2,
|
||||||
cx: f32,
|
center: Pos2,
|
||||||
cy: f32,
|
|
||||||
radius: f32,
|
radius: f32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Curve {
|
impl<'p> Curve<'p> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn linear(a: Pos2, b: Pos2) -> Self {
|
pub(crate) fn linear(points: &'p [Pos2]) -> Self {
|
||||||
Self::Linear { a, b }
|
Self::Linear(points)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn bezier(points: &[Pos2]) -> Self {
|
pub(crate) fn bezier(points: &[Pos2]) -> Self {
|
||||||
@@ -128,13 +124,13 @@ impl Curve {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn perfect(points: &[Pos2]) -> Self {
|
pub(crate) fn perfect(points: &[Pos2]) -> Self {
|
||||||
let (cx, cy, mut radius) = math_util::get_circum_circle(&points);
|
let (a, b, c) = (points[0], points[1], points[2]);
|
||||||
radius *= ((!math_util::is_left(&points)) as i8 * 2 - 1) as f32;
|
let (center, mut radius) = math_util::get_circum_circle(a, b, c);
|
||||||
|
radius *= ((!math_util::is_left(a, b, c)) as i8 * 2 - 1) as f32;
|
||||||
|
|
||||||
Self::Perfect {
|
Self::Perfect {
|
||||||
origin: points[0],
|
origin: a,
|
||||||
cx,
|
center,
|
||||||
cy,
|
|
||||||
radius,
|
radius,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,13 +139,12 @@ impl Curve {
|
|||||||
let points = match self {
|
let points = match self {
|
||||||
Self::Bezier(points) => points,
|
Self::Bezier(points) => points,
|
||||||
Self::Catmull(points) => points,
|
Self::Catmull(points) => points,
|
||||||
Self::Linear { a, b } => return math_util::point_on_line(*a, *b, len),
|
Self::Linear(points) => return math_util::point_on_lines(points, len),
|
||||||
Self::Perfect {
|
Self::Perfect {
|
||||||
origin,
|
origin,
|
||||||
cx,
|
center,
|
||||||
cy,
|
|
||||||
radius,
|
radius,
|
||||||
} => return math_util::rotate(*cx, *cy, *origin, len / *radius),
|
} => return math_util::rotate(*center, *origin, len / *radius),
|
||||||
};
|
};
|
||||||
|
|
||||||
match points {
|
match points {
|
||||||
|
|||||||
+2
-13
@@ -89,20 +89,9 @@ pub fn stars(map: &Beatmap, mods: impl Mods, passed_objects: Option<usize>) -> S
|
|||||||
/ (map.sv * slider_state.speed_mult)
|
/ (map.sv * slider_state.speed_mult)
|
||||||
/ 100.0;
|
/ 100.0;
|
||||||
|
|
||||||
// Ensure path type validity
|
|
||||||
let path_type = if (*path_type == PathType::PerfectCurve && curve_points.len() > 3)
|
|
||||||
|| (*path_type == PathType::Linear && curve_points.len() != 2)
|
|
||||||
{
|
|
||||||
PathType::Bezier
|
|
||||||
} else if curve_points.len() == 2 {
|
|
||||||
PathType::Linear
|
|
||||||
} else {
|
|
||||||
*path_type
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build the curve w.r.t. the curve points
|
// Build the curve w.r.t. the curve points
|
||||||
let curve = match path_type {
|
let curve = match path_type {
|
||||||
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
PathType::Linear => Curve::linear(curve_points),
|
||||||
PathType::Bezier => Curve::bezier(curve_points),
|
PathType::Bezier => Curve::bezier(curve_points),
|
||||||
PathType::Catmull => Curve::catmull(curve_points),
|
PathType::Catmull => Curve::catmull(curve_points),
|
||||||
PathType::PerfectCurve => Curve::perfect(curve_points),
|
PathType::PerfectCurve => Curve::perfect(curve_points),
|
||||||
@@ -349,7 +338,7 @@ pub fn strains(map: &Beatmap, mods: impl Mods) -> Strains {
|
|||||||
|
|
||||||
// Build the curve w.r.t. the curve points
|
// Build the curve w.r.t. the curve points
|
||||||
let curve = match path_type {
|
let curve = match path_type {
|
||||||
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
PathType::Linear => Curve::linear(curve_points),
|
||||||
PathType::Bezier => Curve::bezier(curve_points),
|
PathType::Bezier => Curve::bezier(curve_points),
|
||||||
PathType::Catmull => Curve::catmull(curve_points),
|
PathType::Catmull => Curve::catmull(curve_points),
|
||||||
PathType::PerfectCurve => Curve::perfect(curve_points),
|
PathType::PerfectCurve => Curve::perfect(curve_points),
|
||||||
|
|||||||
+48
-36
@@ -41,6 +41,23 @@ pub(crate) fn point_on_line(p1: Pos2, p2: Pos2, len: f32) -> Pos2 {
|
|||||||
(p1 * n + p2 * len) / full_len
|
(p1 * n + p2 * len) / full_len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn point_on_lines(points: &[Pos2], len: f32) -> Pos2 {
|
||||||
|
let mut dist = 0.0;
|
||||||
|
|
||||||
|
for (curr, next) in points.iter().zip(points.iter().skip(1)) {
|
||||||
|
let curr_dist = curr.distance(next);
|
||||||
|
|
||||||
|
if dist + curr_dist >= len {
|
||||||
|
return point_on_line(*curr, *next, len - dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
dist += curr_dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
point_on_line(points[points.len() - 2], points[points.len() - 1], len)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn distance_from_points(arr: &[Pos2]) -> f32 {
|
pub(crate) fn distance_from_points(arr: &[Pos2]) -> f32 {
|
||||||
arr.iter()
|
arr.iter()
|
||||||
@@ -59,61 +76,56 @@ pub(crate) fn point_at_distance(array: &[Pos2], distance: f32) -> Pos2 {
|
|||||||
return array[array.len() - 1];
|
return array[array.len() - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut i = 0;
|
|
||||||
let mut current_distance = 0.0;
|
let mut current_distance = 0.0;
|
||||||
let mut new_distance = 0.0;
|
let mut new_distance;
|
||||||
|
|
||||||
while i < array.len() - 2 {
|
for (&curr, &next) in array.iter().zip(array.iter().skip(1)) {
|
||||||
new_distance = (array[i] - array[i + 1]).length();
|
new_distance = (curr - next).length();
|
||||||
current_distance += new_distance;
|
current_distance += new_distance;
|
||||||
|
|
||||||
if distance <= current_distance {
|
if distance <= current_distance {
|
||||||
break;
|
let remaining_dist = distance - (current_distance - new_distance);
|
||||||
}
|
|
||||||
|
|
||||||
i += 1;
|
return if remaining_dist.abs() <= f32::EPSILON {
|
||||||
}
|
curr
|
||||||
|
|
||||||
current_distance -= new_distance;
|
|
||||||
let init_dist = distance - current_distance;
|
|
||||||
|
|
||||||
if init_dist.abs() <= f32::EPSILON {
|
|
||||||
array[i]
|
|
||||||
} else {
|
} else {
|
||||||
array[i] + (array[i + 1] - array[i]) * (init_dist / new_distance)
|
curr + (next - curr) * (remaining_dist / new_distance)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array[array.len() - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_circum_circle(p: &[Pos2]) -> (f32, f32, f32) {
|
pub(crate) fn get_circum_circle(p0: Pos2, p1: Pos2, p2: Pos2) -> (Pos2, f32) {
|
||||||
let d = 2.0
|
let a = 2.0 * (p0.x * (p1.y - p2.y) - p0.y * (p1.x - p2.x) + p1.x * p2.y - p2.x * p1.y);
|
||||||
* (p[0].x * (p[1].y - p[2].y) + p[1].x * (p[2].y - p[0].y) + p[2].x * (p[0].y - p[1].y));
|
|
||||||
|
|
||||||
let p0 = p[0].x * p[0].x + p[0].y * p[0].y;
|
let q0 = p0.length_squared();
|
||||||
let p1 = p[1].x * p[1].x + p[1].y * p[1].y;
|
let q1 = p1.length_squared();
|
||||||
let p2 = p[2].x * p[2].x + p[2].y * p[2].y;
|
let q2 = p2.length_squared();
|
||||||
|
|
||||||
let ux = (p0 * (p[1].y - p[2].y) + p1 * (p[2].y - p[0].y) + p2 * (p[0].y - p[1].y)) / d;
|
let cx = (q0 * (p1.y - p2.y) + q1 * (p2.y - p0.y) + q2 * (p0.y - p1.y)) / a;
|
||||||
let uy = (p0 * (p[2].x - p[1].x) + p1 * (p[0].x - p[2].x) + p2 * (p[1].x - p[0].x)) / d;
|
let cy = (q0 * (p2.x - p1.x) + q1 * (p0.x - p2.x) + q2 * (p1.x - p0.x)) / a;
|
||||||
|
|
||||||
let px = ux - p[0].x;
|
let r = (cx - p0.x).hypot(cy - p0.y);
|
||||||
let py = uy - p[0].y;
|
|
||||||
let r = (px * px + py * py).sqrt();
|
|
||||||
|
|
||||||
(ux, uy, r)
|
(Pos2 { x: cx, y: cy }, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_left(p: &[Pos2]) -> bool {
|
pub(crate) fn is_left(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
||||||
((p[1].x - p[0].x) * (p[2].y - p[0].y) - (p[1].y - p[0].y) * (p[2].x - p[0].x)) < 0.0
|
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) < 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn rotate(cx: f32, cy: f32, p: Pos2, radians: f32) -> Pos2 {
|
pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 {
|
||||||
let cos = radians.cos();
|
let (sin, cos) = theta.sin_cos();
|
||||||
let sin = radians.sin();
|
let diff = origin - center;
|
||||||
|
|
||||||
Pos2 {
|
let offset = Pos2 {
|
||||||
x: (cos * (p.x - cx)) - (sin * (p.y - cy)) + cx,
|
x: cos * diff.x - sin * diff.y,
|
||||||
y: (sin * (p.x - cx)) + (cos * (p.y - cy)) + cy,
|
y: sin * diff.x + cos * diff.y,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
center + offset
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,17 +75,6 @@ impl OsuObject {
|
|||||||
/ 100.0;
|
/ 100.0;
|
||||||
let span_duration = duration / *repeats as f32;
|
let span_duration = duration / *repeats as f32;
|
||||||
|
|
||||||
// Ensure path type validity
|
|
||||||
let path_type = if (*path_type == PathType::PerfectCurve && curve_points.len() > 3)
|
|
||||||
|| (*path_type == PathType::Linear && curve_points.len() != 2)
|
|
||||||
{
|
|
||||||
PathType::Bezier
|
|
||||||
} else if curve_points.len() == 2 {
|
|
||||||
PathType::Linear
|
|
||||||
} else {
|
|
||||||
*path_type
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build the curve w.r.t. the curve points
|
// Build the curve w.r.t. the curve points
|
||||||
let curve = match path_type {
|
let curve = match path_type {
|
||||||
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
||||||
|
|||||||
@@ -62,23 +62,12 @@ impl OsuObject {
|
|||||||
/ 100.0;
|
/ 100.0;
|
||||||
let span_duration = duration / *repeats as f32;
|
let span_duration = duration / *repeats as f32;
|
||||||
|
|
||||||
// Ensure path type validity
|
|
||||||
let path_type = if (*path_type == PathType::PerfectCurve && curve_points.len() > 3)
|
|
||||||
|| (*path_type == PathType::Linear && curve_points.len() != 2)
|
|
||||||
{
|
|
||||||
PathType::Bezier
|
|
||||||
} else if curve_points.len() == 2 {
|
|
||||||
PathType::Linear
|
|
||||||
} else {
|
|
||||||
*path_type
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build the curve w.r.t. the curve points
|
// Build the curve w.r.t. the curve points
|
||||||
let curve = match path_type {
|
let curve = match path_type {
|
||||||
PathType::Linear => Curve::linear(curve_points[0], curve_points[1]),
|
PathType::Linear => Curve::linear(curve_points),
|
||||||
PathType::Bezier => Curve::bezier(&curve_points),
|
PathType::Bezier => Curve::bezier(curve_points),
|
||||||
PathType::Catmull => Curve::catmull(&curve_points),
|
PathType::Catmull => Curve::catmull(curve_points),
|
||||||
PathType::PerfectCurve => Curve::perfect(&curve_points),
|
PathType::PerfectCurve => Curve::perfect(curve_points),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Called on each slider object except for the head.
|
// Called on each slider object except for the head.
|
||||||
|
|||||||
+32
-6
@@ -455,15 +455,25 @@ macro_rules! parse_hitobjects_body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if $self.version <= 6 && curve_points.len() >= 2 {
|
match path_type {
|
||||||
if path_type == PathType::Linear {
|
PathType::Linear if curve_points.len() % 2 == 0 => {
|
||||||
|
// Assert that the points are of the form A|B|B|C|C|E
|
||||||
|
if valid_linear(&curve_points) {
|
||||||
|
for i in (2..curve_points.len() - 1).rev().step_by(2) {
|
||||||
|
curve_points.remove(i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
path_type = PathType::Bezier;
|
path_type = PathType::Bezier;
|
||||||
} else if curve_points.len() == 2
|
}
|
||||||
&& (pos == curve_points[0] || pos == curve_points[1])
|
}
|
||||||
{
|
PathType::PerfectCurve if curve_points.len() == 3 => {
|
||||||
|
if is_linear(curve_points[0], curve_points[1], curve_points[2]) {
|
||||||
path_type = PathType::Linear;
|
path_type = PathType::Linear;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
PathType::Catmull => {},
|
||||||
|
_ => path_type = PathType::Bezier,
|
||||||
|
};
|
||||||
|
|
||||||
// Reduce amount of curvepoints but keep the elements evenly spaced.
|
// Reduce amount of curvepoints but keep the elements evenly spaced.
|
||||||
// Necessary to handle maps like XNOR (2573164) which have
|
// Necessary to handle maps like XNOR (2573164) which have
|
||||||
@@ -765,6 +775,22 @@ fn split_colon(line: &str) -> Option<(&str, &str)> {
|
|||||||
Some((split.next()?, split.next()?.trim()))
|
Some((split.next()?, split.next()?.trim()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn valid_linear(points: &[Pos2]) -> bool {
|
||||||
|
for i in (1..points.len() - 1).step_by(2) {
|
||||||
|
if points[i] != points[i + 1] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_linear(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
||||||
|
((p1.y - p0.y) * (p2.x - p0.x) - (p1.x - p0.x) * (p2.y - p0.y)).abs() <= f32::EPSILON
|
||||||
|
}
|
||||||
|
|
||||||
/// The type of curve of a slider.
|
/// The type of curve of a slider.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum PathType {
|
pub enum PathType {
|
||||||
|
|||||||
+2
-7
@@ -16,12 +16,12 @@ impl Pos2 {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn length(&self) -> f32 {
|
pub fn length(&self) -> f32 {
|
||||||
self.length_squared().sqrt()
|
self.x.hypot(self.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn dot(&self, other: Self) -> f32 {
|
pub fn dot(&self, other: Self) -> f32 {
|
||||||
self.x * other.x + self.y * other.y
|
self.x.mul_add(other.x, self.y * other.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -29,11 +29,6 @@ impl Pos2 {
|
|||||||
(*self - *other).length()
|
(*self - *other).length()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn add_scaled(self, other: Pos2, factor: f32) -> Pos2 {
|
|
||||||
self + other * factor
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn normalize(self) -> Pos2 {
|
pub fn normalize(self) -> Pos2 {
|
||||||
self / self.length()
|
self / self.length()
|
||||||
|
|||||||
Reference in New Issue
Block a user