optimized catmull & removed previous curve code
This commit is contained in:
+49
-410
@@ -3,12 +3,9 @@
|
||||
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||
))]
|
||||
|
||||
use std::{borrow::Cow, cmp::Ordering, convert::identity, f32::consts::PI};
|
||||
use std::{borrow::Cow, cmp::Ordering, convert::identity, f32::consts::PI, iter};
|
||||
|
||||
use crate::{
|
||||
math_util,
|
||||
parse::{PathControlPoint, PathType, Pos2},
|
||||
};
|
||||
use crate::parse::{PathControlPoint, PathType, Pos2};
|
||||
|
||||
const BEZIER_TOLERANCE: f32 = 0.25;
|
||||
const CATMULL_DETAIL: usize = 50;
|
||||
@@ -219,26 +216,20 @@ impl Curve {
|
||||
fn approximate_catmull(path: &mut Vec<Pos2>, points: &[Pos2]) {
|
||||
path.reserve_exact((points.len() - 1) * CATMULL_DETAIL * 2);
|
||||
|
||||
let catmull_detail = CATMULL_DETAIL as f32;
|
||||
// Handle first iteration distinctly because of v1
|
||||
let v1 = points[0];
|
||||
let v2 = points[0];
|
||||
let v3 = points.get(1).copied().unwrap_or(v2);
|
||||
let v4 = points.get(2).copied().unwrap_or_else(|| v3 * 2.0 - v2);
|
||||
|
||||
for i in 0..points.len() - 1 {
|
||||
let v2 = points[i];
|
||||
Self::catmull_subpath(path, v1, v2, v3, v4);
|
||||
|
||||
let v1 = i
|
||||
.checked_sub(1)
|
||||
.and_then(|i| points.get(i).copied())
|
||||
.unwrap_or(v2);
|
||||
// Remaining iterations
|
||||
for (i, (&v1, &v2)) in (2..).zip(points.iter().zip(points.iter().skip(1))) {
|
||||
let v3 = points.get(i).copied().unwrap_or_else(|| v2 * 2.0 - v1);
|
||||
let v4 = points.get(i + 1).copied().unwrap_or_else(|| v3 * 2.0 - v2);
|
||||
|
||||
let v3 = points.get(i + 1).copied().unwrap_or_else(|| v2 * 2.0 - v1);
|
||||
let v4 = points.get(i + 2).copied().unwrap_or_else(|| v3 * 2.0 - v2);
|
||||
|
||||
for c in 0..CATMULL_DETAIL {
|
||||
let p1 = Self::catmull_find_point(v1, v2, v3, v4, c as f32 / catmull_detail);
|
||||
let p2 = Self::catmull_find_point(v1, v2, v3, v4, (c + 1) as f32 / catmull_detail);
|
||||
|
||||
path.push(p1);
|
||||
path.push(p2);
|
||||
}
|
||||
Self::catmull_subpath(path, v1, v2, v3, v4);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,23 +389,45 @@ impl Curve {
|
||||
path.extend(subpath);
|
||||
}
|
||||
|
||||
fn catmull_find_point(v1: Pos2, v2: Pos2, v3: Pos2, v4: Pos2, t: f32) -> Pos2 {
|
||||
let t2 = t * t;
|
||||
let t3 = t * t * t;
|
||||
fn catmull_subpath(path: &mut Vec<Pos2>, v1: Pos2, v2: Pos2, v3: Pos2, v4: Pos2) {
|
||||
let x1 = 2.0 * v1.x;
|
||||
let x2 = -v1.x + v3.x;
|
||||
let x3 = 2.0 * v1.x - 5.0 * v2.x + 4.0 * v3.x - v4.x;
|
||||
let x4 = -v1.x + 3.0 * (v2.x - v3.x) + v4.x;
|
||||
|
||||
let x = 0.5
|
||||
* (2.0 * v2.x
|
||||
+ (-v1.x + v3.x) * t
|
||||
+ (2.0 * v1.x - 5.0 * v2.x + 4.0 * v3.x - v4.x) * t2
|
||||
+ (-v1.x + 3.0 * v2.x - 3.0 * v3.x + v4.x) * t3);
|
||||
let y1 = 2.0 * v1.y;
|
||||
let y2 = -v1.y + v3.y;
|
||||
let y3 = 2.0 * v1.y - 5.0 * v2.y + 4.0 * v3.y - v4.y;
|
||||
let y4 = -v1.y + 3.0 * (v2.y - v3.y) + v4.y;
|
||||
|
||||
let y = 0.5
|
||||
* (2.0 * v2.y
|
||||
+ (-v1.y + v3.y) * t
|
||||
+ (2.0 * v1.y - 5.0 * v2.y + 4.0 * v3.y - v4.y) * t2
|
||||
+ (-v1.y + 3.0 * v2.y - 3.0 * v3.y + v4.y) * t3);
|
||||
let catmull_detail = CATMULL_DETAIL as f32;
|
||||
|
||||
Pos2 { x, y }
|
||||
let subpath = (0..CATMULL_DETAIL)
|
||||
.map(|c| {
|
||||
let c = c as f32;
|
||||
let t1 = c / catmull_detail;
|
||||
let t2 = t1 * t1;
|
||||
let t3 = t2 * t1;
|
||||
|
||||
let pos1 = Pos2 {
|
||||
x: 0.5 * (x1 + x2 * t1 + x3 * t2 + x4 * t3),
|
||||
y: 0.5 * (y1 + y2 * t1 + y3 * t2 + y4 * t3),
|
||||
};
|
||||
|
||||
let t1 = (c + 1.0) / catmull_detail;
|
||||
let t2 = t1 * t1;
|
||||
let t3 = t2 * t1;
|
||||
|
||||
let pos2 = Pos2 {
|
||||
x: 0.5 * (x1 + x2 * t1 + x3 * t2 + x4 * t3),
|
||||
y: 0.5 * (y1 + y2 * t1 + y3 * t2 + y4 * t3),
|
||||
};
|
||||
|
||||
iter::once(pos1).chain(iter::once(pos2))
|
||||
})
|
||||
.flatten();
|
||||
|
||||
path.extend(subpath);
|
||||
}
|
||||
|
||||
fn circular_arc_properties(a: Pos2, b: Pos2, c: Pos2) -> Option<CircularArcProperties> {
|
||||
@@ -472,377 +485,3 @@ impl Curve {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Points {
|
||||
Single(Pos2),
|
||||
Multi(Vec<Pos2>),
|
||||
}
|
||||
|
||||
impl Points {
|
||||
#[inline]
|
||||
fn point_at_distance(&self, dist: f32) -> Pos2 {
|
||||
match self {
|
||||
Points::Multi(points) => math_util::point_at_distance(points, dist),
|
||||
Points::Single(point) => *point,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum Curve_<'p> {
|
||||
Bezier {
|
||||
path: Vec<Pos2>,
|
||||
lengths: Vec<f32>,
|
||||
},
|
||||
Catmull(Points),
|
||||
Linear(&'p [Pos2]),
|
||||
Perfect {
|
||||
origin: Pos2,
|
||||
center: Pos2,
|
||||
radius: f32,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'p> Curve_<'p> {
|
||||
#[inline]
|
||||
pub(crate) fn new(points: &'p [Pos2], kind: PathType, expected_len: f32) -> Self {
|
||||
match kind {
|
||||
PathType::Bezier => Self::bezier(points, expected_len),
|
||||
PathType::Catmull => Self::catmull(points),
|
||||
PathType::Linear => Self::Linear(points),
|
||||
PathType::PerfectCurve => Self::perfect(points),
|
||||
}
|
||||
}
|
||||
|
||||
fn bezier(points: &[Pos2], expected_len: f32) -> Self {
|
||||
let points: Vec<_> = points
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|point| point - points[0])
|
||||
.collect();
|
||||
|
||||
let len = points.len();
|
||||
|
||||
if len == 1 {
|
||||
return Self::Bezier {
|
||||
path: points,
|
||||
lengths: vec![0.0],
|
||||
};
|
||||
}
|
||||
|
||||
// First calculate a path of coordinates
|
||||
let mut start = 0;
|
||||
let mut path = Vec::new();
|
||||
let mut bufs = BezierBuffers::new(len);
|
||||
|
||||
for (end, (curr, next)) in (1..).zip(points.iter().zip(points.iter().skip(1))) {
|
||||
if end - start > 1 && curr == next {
|
||||
Self::bezier_subpath(&mut path, &points[start..end], &mut bufs);
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
|
||||
Self::bezier_subpath(&mut path, &points[start..], &mut bufs);
|
||||
let last_point = &points[len - 1];
|
||||
path.push(*last_point);
|
||||
|
||||
// Then calculated cumulative lenghts
|
||||
let mut calculated_len = 0.0;
|
||||
let mut cumulative_len = vec![0.0];
|
||||
|
||||
for i in 0..path.len() - 1 {
|
||||
let diff = path[i + 1] - path[i];
|
||||
calculated_len += diff.length();
|
||||
cumulative_len.push(calculated_len);
|
||||
}
|
||||
|
||||
if (expected_len - calculated_len).abs() > f32::EPSILON {
|
||||
// * In osu-stable, if the last two control points of a slider are equal, extension is not performed
|
||||
if points
|
||||
.get(len - 2)
|
||||
.filter(|&p| p == last_point && expected_len > calculated_len)
|
||||
.is_some()
|
||||
{
|
||||
cumulative_len.push(calculated_len);
|
||||
|
||||
return Self::Bezier {
|
||||
path,
|
||||
lengths: cumulative_len,
|
||||
};
|
||||
}
|
||||
|
||||
// * The last length is always incorrect
|
||||
cumulative_len.pop();
|
||||
|
||||
let mut path_end_idx = path.len() - 1;
|
||||
|
||||
if calculated_len > expected_len {
|
||||
// * The path will be shortened further, in which case we should trim
|
||||
// * any more unnecessary lengths and their associated path segments
|
||||
while cumulative_len
|
||||
.last()
|
||||
.filter(|&l| *l > expected_len)
|
||||
.is_some()
|
||||
{
|
||||
cumulative_len.pop();
|
||||
path.remove(path_end_idx);
|
||||
path_end_idx -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if path_end_idx == 0 {
|
||||
// * The expected distance is negative or zero
|
||||
// * Perhaps negative path lengths should be disallowed altogether
|
||||
cumulative_len.push(0.0);
|
||||
|
||||
return Self::Bezier {
|
||||
path,
|
||||
lengths: cumulative_len,
|
||||
};
|
||||
}
|
||||
|
||||
// * The direction of the segment to shorten or lengthen
|
||||
let dir = (path[path_end_idx] - path[path_end_idx - 1]).normalize();
|
||||
|
||||
path[path_end_idx] =
|
||||
path[path_end_idx - 1] + dir * (expected_len - cumulative_len.last().unwrap());
|
||||
cumulative_len.push(expected_len);
|
||||
}
|
||||
|
||||
Self::Bezier {
|
||||
path,
|
||||
lengths: cumulative_len,
|
||||
}
|
||||
}
|
||||
|
||||
fn bezier_subpath(result: &mut Vec<Pos2>, points: &[Pos2], bufs: &mut BezierBuffers) {
|
||||
let p = points.len();
|
||||
|
||||
let mut to_flatten = Vec::new();
|
||||
let mut free_bufs = Vec::with_capacity(1);
|
||||
|
||||
// In osu!lazer's code, `p` is always 0 when approximating bezier
|
||||
// so the first big `if` can be omitted
|
||||
|
||||
to_flatten.push(Cow::Borrowed(points));
|
||||
|
||||
// * "toFlatten" contains all the curves which are not yet approximated well enough.
|
||||
// * We use a stack to emulate recursion without the risk of running into a stack overflow.
|
||||
// * (More specifically, we iteratively and adaptively refine our curve with a
|
||||
// * <a href="https://en.wikipedia.org/wiki/Depth-first_search">Depth-first search</a>
|
||||
// * over the tree resulting from the subdivisions we make.)
|
||||
|
||||
let mut left_child = bufs.buf2.to_owned();
|
||||
|
||||
while let Some(mut parent) = to_flatten.pop() {
|
||||
if Self::bezier_is_flat_enough(&parent) {
|
||||
// * If the control points we currently operate on are sufficiently "flat", we use
|
||||
// * an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation
|
||||
// * of the bezier curve represented by our control points, consisting of the same amount
|
||||
// * of points as there are control points.
|
||||
Self::bezier_approximate(&parent, result, bufs);
|
||||
free_bufs.push(parent);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// * If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep
|
||||
// * subdividing the curve we are currently operating on.
|
||||
let mut right_child = free_bufs
|
||||
.pop()
|
||||
.unwrap_or_else(|| Cow::Owned(vec![Pos2::zero(); p]));
|
||||
|
||||
Self::bezier_subdivide(
|
||||
&parent,
|
||||
&mut left_child,
|
||||
right_child.to_mut(),
|
||||
&mut bufs.buf1,
|
||||
);
|
||||
|
||||
// * We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration.
|
||||
parent.to_mut().copy_from_slice(&left_child[..p]);
|
||||
|
||||
to_flatten.push(right_child);
|
||||
to_flatten.push(parent);
|
||||
}
|
||||
}
|
||||
|
||||
fn bezier_is_flat_enough(points: &[Pos2]) -> bool {
|
||||
let limit = BEZIER_TOLERANCE * BEZIER_TOLERANCE * 4.0;
|
||||
|
||||
!points
|
||||
.iter()
|
||||
.zip(points.iter().skip(1))
|
||||
.zip(points.iter().skip(2))
|
||||
.any(|((&prev, &curr), &next)| (prev - curr * 2.0 + next).length_squared() > limit)
|
||||
}
|
||||
|
||||
fn bezier_subdivide(points: &[Pos2], l: &mut [Pos2], r: &mut [Pos2], buf: &mut [Pos2]) {
|
||||
let count = points.len();
|
||||
let midpoints = buf;
|
||||
midpoints[..count].copy_from_slice(&points[..count]);
|
||||
|
||||
for i in (1..count).rev() {
|
||||
l[count - i - 1] = midpoints[0];
|
||||
r[i] = midpoints[i];
|
||||
|
||||
for j in 0..i {
|
||||
midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
l[count - 1] = midpoints[0];
|
||||
r[0] = midpoints[0];
|
||||
}
|
||||
|
||||
// * https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
|
||||
fn bezier_approximate(points: &[Pos2], output: &mut Vec<Pos2>, bufs: &mut BezierBuffers) {
|
||||
let count = points.len();
|
||||
let r = &mut bufs.buf1;
|
||||
let l = &mut bufs.buf2;
|
||||
|
||||
Self::bezier_subdivide(points, l, r, &mut bufs.buf3);
|
||||
l[count..2 * count - 1].copy_from_slice(&r[1..count]);
|
||||
output.push(points[0]);
|
||||
|
||||
let new_points = l
|
||||
.iter()
|
||||
.skip(1)
|
||||
.zip(l.iter().skip(2))
|
||||
.zip(l.iter().skip(3))
|
||||
.step_by(2)
|
||||
.take(count.saturating_sub(2))
|
||||
.map(|((&prev, &curr), &next)| (prev + curr * 2.0 + next) * 0.25);
|
||||
|
||||
output.extend(new_points);
|
||||
}
|
||||
|
||||
fn catmull(points: &[Pos2]) -> Self {
|
||||
let len = points.len();
|
||||
|
||||
if len == 1 {
|
||||
return Self::Catmull(Points::Single(points[0]));
|
||||
}
|
||||
|
||||
let mut result = Vec::with_capacity((len * CATMULL_DETAIL * 2) as usize);
|
||||
|
||||
// Handle first iteration distinctly because of v1
|
||||
let v1 = points[0];
|
||||
let v2 = points[0];
|
||||
let v3 = points.get(1).copied().unwrap_or(v2);
|
||||
let v4 = points.get(2).copied().unwrap_or_else(|| v3 * 2.0 - v2);
|
||||
|
||||
Self::catmull_points(&mut result, v1, v2, v3, v4);
|
||||
|
||||
// Remaining iterations
|
||||
for (i, (&v1, &v2)) in (2..).zip(points.iter().zip(points.iter().skip(1))) {
|
||||
let v3 = points.get(i).copied().unwrap_or_else(|| v2 * 2.0 - v1);
|
||||
let v4 = points.get(i + 1).copied().unwrap_or_else(|| v3 * 2.0 - v2);
|
||||
|
||||
Self::catmull_points(&mut result, v1, v2, v3, v4);
|
||||
}
|
||||
|
||||
Self::Catmull(Points::Multi(result))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn catmull_points(result: &mut Vec<Pos2>, v1: Pos2, v2: Pos2, v3: Pos2, v4: Pos2) {
|
||||
let mut c = 0.0;
|
||||
|
||||
let x1 = 2.0 * v1.x;
|
||||
let x2 = -v1.x + v3.x;
|
||||
let x3 = 2.0 * v1.x - 5.0 * v2.x + 4.0 * v3.x - v4.x;
|
||||
let x4 = -v1.x + 3.0 * (v2.x - v3.x) + v4.x;
|
||||
|
||||
let y1 = 2.0 * v1.y;
|
||||
let y2 = -v1.y + v3.y;
|
||||
let y3 = 2.0 * v1.y - 5.0 * v2.y + 4.0 * v3.y - v4.y;
|
||||
let y4 = -v1.y + 3.0 * (v2.y - v3.y) + v4.y;
|
||||
|
||||
let catmull_detail = CATMULL_DETAIL as f32;
|
||||
|
||||
loop {
|
||||
let t1 = c / catmull_detail;
|
||||
let t2 = t1 * t1;
|
||||
let t3 = t2 * t1;
|
||||
|
||||
result.push(Pos2 {
|
||||
x: 0.5 * (x1 + x2 * t1 + x3 * t2 + x4 * t3),
|
||||
y: 0.5 * (y1 + y2 * t1 + y3 * t2 + y4 * t3),
|
||||
});
|
||||
|
||||
let t1 = (c + 1.0) / catmull_detail;
|
||||
let t2 = t1 * t1;
|
||||
let t3 = t2 * t1;
|
||||
|
||||
result.push(Pos2 {
|
||||
x: 0.5 * (x1 + x2 * t1 + x3 * t2 + x4 * t3),
|
||||
y: 0.5 * (y1 + y2 * t1 + y3 * t2 + y4 * t3),
|
||||
});
|
||||
|
||||
c += 1.0;
|
||||
|
||||
if c >= catmull_detail {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn perfect(points: &[Pos2]) -> Self {
|
||||
let (a, b, c) = (points[0], points[1], points[2]);
|
||||
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 {
|
||||
origin: a,
|
||||
center,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
|
||||
fn interpolate_vertices(path: &[Pos2], lengths: &[f32], i: usize, d: f32) -> Pos2 {
|
||||
if path.is_empty() {
|
||||
return Pos2::zero();
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
return path[0];
|
||||
} else if i >= path.len() {
|
||||
return path[path.len() - 1];
|
||||
}
|
||||
|
||||
let p0 = path[i - 1];
|
||||
let p1 = path[i];
|
||||
|
||||
let d0 = lengths[i - 1];
|
||||
let d1 = lengths[i];
|
||||
|
||||
// * Avoid division by an almost-zero number in case
|
||||
// * two points are extremely close to each other
|
||||
if (d0 - d1).abs() <= f32::EPSILON {
|
||||
return p0;
|
||||
}
|
||||
|
||||
let w = (d - d0) / (d1 - d0);
|
||||
|
||||
p0 + (p1 - p0) * w
|
||||
}
|
||||
|
||||
pub(crate) fn point_at_distance(&self, dist: f32) -> Pos2 {
|
||||
match self {
|
||||
Self::Bezier { path, lengths } => {
|
||||
let idx = lengths
|
||||
.binary_search_by(|len| len.partial_cmp(&dist).unwrap_or(Ordering::Equal))
|
||||
.map_or_else(identity, identity);
|
||||
|
||||
Self::interpolate_vertices(path, lengths, idx, dist)
|
||||
}
|
||||
Self::Catmull(points) => points.point_at_distance(dist),
|
||||
Self::Linear(points) => math_util::point_at_distance(points, dist),
|
||||
Self::Perfect {
|
||||
origin,
|
||||
center,
|
||||
radius,
|
||||
} => math_util::rotate(*center, *origin, dist / *radius),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-93
@@ -2,99 +2,13 @@
|
||||
feature = "fruits",
|
||||
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||
))]
|
||||
pub(crate) use fruits_osu::*;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "fruits",
|
||||
all(feature = "osu", not(feature = "no_sliders_no_leniency"))
|
||||
))]
|
||||
mod fruits_osu {
|
||||
use crate::parse::Pos2;
|
||||
|
||||
pub(crate) fn point_at_distance(points: &[Pos2], dist: f32) -> Pos2 {
|
||||
if points.len() < 2 {
|
||||
return Pos2::zero();
|
||||
} else if dist.abs() <= f32::EPSILON {
|
||||
return points[0];
|
||||
}
|
||||
|
||||
let mut curr_dist = 0.0;
|
||||
|
||||
// If points.len() < 2 it wont be reassigned and would cause division by zero.
|
||||
// Before that division happens though, unwrapping the last two elements
|
||||
// would already have panicked so this is fine to keep at zero.
|
||||
let mut new_dist = 0.0;
|
||||
|
||||
for (&curr, &next) in points.iter().zip(points.iter().skip(1)) {
|
||||
new_dist = (curr - next).length().max(f32::EPSILON);
|
||||
curr_dist += new_dist;
|
||||
|
||||
if dist <= curr_dist {
|
||||
let remaining_dist = dist - (curr_dist - new_dist);
|
||||
|
||||
return if remaining_dist.abs() <= f32::EPSILON {
|
||||
curr
|
||||
} else {
|
||||
curr + (next - curr) * (remaining_dist / new_dist)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let remaining_dist = dist - (curr_dist - new_dist);
|
||||
let pre_last = points[points.len() - 2];
|
||||
let last = points[points.len() - 1];
|
||||
|
||||
pre_last + (last - pre_last) * (remaining_dist / new_dist)
|
||||
}
|
||||
|
||||
pub(crate) fn get_circum_circle(p0: Pos2, p1: Pos2, p2: Pos2) -> (Pos2, f32) {
|
||||
let a = 2.0 * (p0.x * (p1.y - p2.y) - p0.y * (p1.x - p2.x) + p1.x * p2.y - p2.x * p1.y);
|
||||
|
||||
let q0 = p0.length_squared();
|
||||
let q1 = p1.length_squared();
|
||||
let q2 = p2.length_squared();
|
||||
|
||||
let cx = (q0 * (p1.y - p2.y) + q1 * (p2.y - p0.y) + q2 * (p0.y - p1.y)) / a;
|
||||
let cy = (q0 * (p2.x - p1.x) + q1 * (p0.x - p2.x) + q2 * (p1.x - p0.x)) / a;
|
||||
|
||||
let r = (cx - p0.x).hypot(cy - p0.y);
|
||||
|
||||
(Pos2 { x: cx, y: cy }, r)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_left(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
||||
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)) < 0.0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn is_linear(p0: Pos2, p1: Pos2, p2: Pos2) -> bool {
|
||||
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)).abs() <= f32::EPSILON
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn valid_linear(points: &[Pos2]) -> bool {
|
||||
for (curr, next) in points.iter().skip(1).zip(points.iter().skip(2)).step_by(2) {
|
||||
if curr != next {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn rotate(center: Pos2, origin: Pos2, theta: f32) -> Pos2 {
|
||||
let (sin, cos) = theta.sin_cos();
|
||||
let diff = origin - center;
|
||||
|
||||
let offset = Pos2 {
|
||||
x: cos * diff.x - sin * diff.y,
|
||||
y: sin * diff.x + cos * diff.y,
|
||||
};
|
||||
|
||||
center + offset
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn is_linear(
|
||||
p0: crate::parse::Pos2,
|
||||
p1: crate::parse::Pos2,
|
||||
p2: crate::parse::Pos2,
|
||||
) -> bool {
|
||||
((p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)).abs() <= f32::EPSILON
|
||||
}
|
||||
|
||||
#[cfg(feature = "osu")]
|
||||
|
||||
Reference in New Issue
Block a user