editor/src/slider_render.rs
2021-01-08 02:41:33 -06:00

264 lines
8.3 KiB
Rust

use std::collections::VecDeque;
use anyhow::Result;
use ggez::{
graphics::{
self, Color, DrawMode, DrawParam, FillOptions, LineCap, LineJoin, Mesh, Rect, StrokeOptions,
},
nalgebra::Point2,
Context,
};
use libosu::{Beatmap, HitObject, HitObjectKind, Point, SliderSplineKind};
use crate::math::Math;
pub fn render_slider(
ctx: &mut Context,
rect: Rect,
beatmap: &Beatmap,
slider: &HitObject,
color: Color,
) -> Result<()> {
let mut control_points = vec![slider.pos];
let slider_info = match &slider.kind {
HitObjectKind::Slider(info) => info,
_ => panic!("retard"),
};
control_points.extend(&slider_info.control);
let spline = get_spline(&slider_info.kind, &control_points, slider_info.pixel_length);
let osupx_scale_x = rect.w as f64 / 512.0;
let osupx_scale_y = rect.h as f64 / 384.0;
let cs_osupx = beatmap.difficulty.circle_size_osupx() as f64;
let cs_real = cs_osupx * osupx_scale_x;
let points_mapped = control_points
.iter()
.map(|point| {
let (x, y) = (point.0 as f64, point.1 as f64);
let x2 = rect.x as f64 + osupx_scale_x * x;
let y2 = rect.y as f64 + osupx_scale_y * y;
[x2 as f32, y2 as f32].into()
})
.collect::<Vec<Point2<_>>>();
let (mut boundx, mut boundy, mut boundw, mut boundh) = (0.0f64, 0.0f64, 0.0f64, 0.0f64);
let spline_mapped = spline
.iter()
.map(|point| {
let (x, y) = (point.0, point.1);
boundx = boundx.min(x - cs_osupx);
boundy = boundy.min(y - cs_osupx);
boundw = boundw.max(x + cs_osupx - boundx);
boundh = boundh.max(y + cs_osupx - boundy);
let x2 = rect.x as f64 + osupx_scale_x * x;
let y2 = rect.y as f64 + osupx_scale_y * y;
[x2 as f32, y2 as f32].into()
})
.collect::<Vec<Point2<f32>>>();
let opts = StrokeOptions::default()
.with_line_cap(LineCap::Round)
.with_line_join(LineJoin::Round)
.with_line_width(cs_real as f32 * 2.0);
let body = Mesh::new_polyline(ctx, DrawMode::Stroke(opts), &spline_mapped, color)?;
graphics::draw(ctx, &body, DrawParam::default())?;
let frame = Mesh::new_polyline(
ctx,
DrawMode::Stroke(StrokeOptions::default()),
&points_mapped,
graphics::WHITE,
)?;
graphics::draw(ctx, &frame, DrawParam::default())?;
for point in points_mapped {
let size = 5.0;
let rect = Rect::new(point.x - size, point.y - size, size * 2.0, size * 2.0);
let rect = Mesh::new_rectangle(
ctx,
DrawMode::Fill(FillOptions::default()),
rect,
graphics::WHITE,
)?;
graphics::draw(ctx, &rect, DrawParam::default())?;
}
Ok(())
}
fn get_spline(
kind: &SliderSplineKind,
control_points: &[Point<i32>],
pixel_length: f64,
) -> Vec<Point<f64>> {
// no matter what, if there's 2 control points, it's linear
let mut kind = kind.clone();
if control_points.len() == 2 {
kind = SliderSplineKind::Linear;
}
let points = control_points
.iter()
.map(|p| Point(p.0 as f64, p.1 as f64))
.collect::<Vec<_>>();
match kind {
SliderSplineKind::Linear => {
let start = points[0];
let unit = (points[1] - points[0]).norm();
let end = points[0] + unit * pixel_length;
vec![start, end]
}
SliderSplineKind::Perfect => {
let (p1, p2, p3) = (points[0], points[1], points[2]);
let (center, radius) = Math::circumcircle(p1, p2, p3);
// find the t-values of the start and end of the slider
let t0 = (center.1 - p1.1).atan2(p1.0 - center.0);
let mut mid = (center.1 - p2.1).atan2(p2.0 - center.0);
let mut t1 = (center.1 - p3.1).atan2(p3.0 - center.0);
// make sure t0 is less than t1
while mid < t0 {
mid += std::f64::consts::TAU;
}
while t1 < t0 {
t1 += std::f64::consts::TAU;
}
if mid > t1 {
t1 -= std::f64::consts::TAU;
}
// circumference is 2 * pi * r, slider length over circumference is length/(2 * pi * r)
let direction_unit = (t1 - t0) / (t1 - t0).abs();
let new_t1 = t0 + direction_unit * (pixel_length / radius);
let mut t = t0;
let mut c = Vec::new();
loop {
if !((new_t1 >= t0 && t < new_t1) || (new_t1 < t0 && t > new_t1)) {
break;
}
let rel = Point(t.cos() * radius, -t.sin() * radius);
c.push(center + rel);
t += (new_t1 - t0) / pixel_length;
}
c
}
SliderSplineKind::Bezier => {
// split the curve by red-anchors
let mut idx = 0;
let mut whole = Vec::new();
for i in 1..points.len() {
if points[i].0 == points[i - 1].0 && points[i].1 == points[i - 1].1 {
let spline = calculate_bezier(&points[idx..i]);
whole.extend(spline);
idx = i;
continue;
}
}
let spline = calculate_bezier(&points[idx..]);
whole.extend(spline);
whole
}
_ => todo!(),
}
}
type P = Point<f64>;
type V<T> = (*mut T, usize, usize);
fn calculate_bezier(points: &[P]) -> Vec<P> {
let points = points.to_vec();
let mut output = Vec::new();
let n = points.len() - 1;
let last = points[n];
let mut to_flatten = VecDeque::new();
let mut free_buffers = VecDeque::new();
to_flatten.push_back(points.into_raw_parts());
let mut p = n;
let buf1 = vec![Point(0.0, 0.0); p + 1].into_raw_parts();
let buf2 = vec![Point(0.0, 0.0); p * 2 + 1].into_raw_parts();
let left_child = buf2;
while !to_flatten.is_empty() {
let parent = to_flatten.pop_front().unwrap();
let parent_slice = unsafe { std::slice::from_raw_parts_mut(parent.0, parent.1) };
if bezier_flat_enough(parent_slice) {
bezier_approximate(parent_slice, &mut output, buf1, buf2, p + 1);
free_buffers.push_front(parent);
continue;
}
let right_child = if free_buffers.is_empty() {
let buf = vec![Point(0.0, 0.0); p + 1];
buf.into_raw_parts()
} else {
free_buffers.pop_front().unwrap()
};
bezier_subdivide(parent_slice, left_child, right_child, buf1, p + 1);
let left_child = unsafe { std::slice::from_raw_parts(left_child.0, left_child.1) };
for i in 0..p + 1 {
parent_slice[i] = left_child[i];
}
to_flatten.push_front(right_child);
to_flatten.push_front(parent);
}
output.push(last);
output
}
const TOLERANCE: f64 = 0.25;
fn bezier_flat_enough(curve: &[P]) -> bool {
for i in 1..(curve.len() - 1) {
let p = curve[i - 1] - curve[i] * 2.0 + curve[i + 1];
if p.0 * p.0 + p.1 * p.1 > TOLERANCE * TOLERANCE / 4.0 {
return false;
}
}
true
}
fn bezier_approximate(curve: &[P], output: &mut Vec<P>, buf1: V<P>, buf2: V<P>, count: usize) {
let l = buf2;
let r = buf1;
bezier_subdivide(curve, l, r, buf1, count);
let l = unsafe { std::slice::from_raw_parts_mut(l.0, l.1) };
let r = unsafe { std::slice::from_raw_parts_mut(r.0, r.1) };
for i in 0..(count - 1) {
l[count + i] = r[i + 1];
}
output.push(curve[0]);
for i in 1..(count - 1) {
let idx = 2 * i;
let p = (l[idx - 1] + l[idx] * 2.0 + l[idx + 1]) * 0.25;
output.push(p);
}
}
fn bezier_subdivide(curve: &[P], l: V<P>, r: V<P>, subdiv: V<P>, count: usize) {
let midpoints = unsafe { std::slice::from_raw_parts_mut(subdiv.0, subdiv.1) };
for i in 0..count {
midpoints[i] = curve[i];
}
let l = unsafe { std::slice::from_raw_parts_mut(l.0, l.1) };
let r = unsafe { std::slice::from_raw_parts_mut(r.0, r.1) };
for i in 0..count {
l[i] = midpoints[0];
r[count - i - 1] = midpoints[count - i - 1];
for j in 0..(count - i - 1) {
midpoints[j] = (midpoints[j] + midpoints[j + 1]) * 0.5;
}
}
}