slider construction

This commit is contained in:
Michael Zhang 2021-01-13 14:52:21 -06:00
parent d543ab4565
commit b8752dfd51
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
7 changed files with 164 additions and 47 deletions

3
Cargo.lock generated
View file

@ -1455,11 +1455,12 @@ dependencies = [
[[package]] [[package]]
name = "libosu" name = "libosu"
version = "0.0.16" version = "0.0.16"
source = "git+https://github.com/iptq/libosu?rev=0229c52759ffb70322384e53abb0112cb52e49ae#0229c52759ffb70322384e53abb0112cb52e49ae" source = "git+https://github.com/iptq/libosu?rev=81677d5ed88936c4a3e64af951ff0ae523c2d403#81677d5ed88936c4a3e64af951ff0ae523c2d403"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"derive_more", "derive_more",
"lazy_static", "lazy_static",
"log",
"num", "num",
"num-derive", "num-derive",
"num-rational", "num-rational",

View file

@ -26,7 +26,7 @@ image = "0.23.12"
[dependencies.libosu] [dependencies.libosu]
git = "https://github.com/iptq/libosu" git = "https://github.com/iptq/libosu"
rev = "0229c52759ffb70322384e53abb0112cb52e49ae" rev = "81677d5ed88936c4a3e64af951ff0ae523c2d403"
[features] [features]
clippy = [] clippy = []

View file

@ -16,9 +16,9 @@ macro_rules! extern_log {
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
$vis unsafe fn $name ($($arg: $argty,)*) $(-> $ty)? { $vis unsafe fn $name ($($arg: $argty,)*) $(-> $ty)? {
log::debug!("entering {} ({:?})", stringify!($name), ($($arg,)*)); log::trace!("entering {} ({:?})", stringify!($name), ($($arg,)*));
let result = paste::expr! { self::[< __inner_ffi_ $name >]::$name($($arg,)*) }; let result = paste::expr! { self::[< __inner_ffi_ $name >]::$name($($arg,)*) };
log::debug!("exiting {} => {:?}", stringify!($name), result); log::trace!("exiting {} => {:?}", stringify!($name), result);
result result
} }
)* )*

View file

@ -75,11 +75,7 @@ impl BeatmapExt {
let object_n_pos: Point<f64> = object_n.inner.pos.to_float().unwrap(); let object_n_pos: Point<f64> = object_n.inner.pos.to_float().unwrap();
if stack_base_pos.distance(object_n_pos) < STACK_DISTANCE if stack_base_pos.distance(object_n_pos) < STACK_DISTANCE
|| (stack_base_obj.inner.kind.is_slider() || (stack_base_obj.inner.kind.is_slider()
&& stack_base_obj && stack_base_obj.inner.end_pos().distance(object_n_pos)
.inner
.end_pos()
.unwrap()
.distance(object_n_pos)
< STACK_DISTANCE) < STACK_DISTANCE)
{ {
stack_base_idx = n; stack_base_idx = n;
@ -146,7 +142,6 @@ impl BeatmapExt {
&& self.hit_objects[n] && self.hit_objects[n]
.inner .inner
.end_pos() .end_pos()
.unwrap()
.distance(self.hit_objects[iidx].inner.pos.to_float().unwrap()) .distance(self.hit_objects[iidx].inner.pos.to_float().unwrap())
< STACK_DISTANCE < STACK_DISTANCE
{ {
@ -157,7 +152,6 @@ impl BeatmapExt {
if self.hit_objects[n] if self.hit_objects[n]
.inner .inner
.end_pos() .end_pos()
.unwrap()
.distance(self.hit_objects[j].inner.pos.to_float().unwrap()) .distance(self.hit_objects[j].inner.pos.to_float().unwrap())
< STACK_DISTANCE < STACK_DISTANCE
{ {
@ -202,7 +196,6 @@ impl BeatmapExt {
if self.hit_objects[n] if self.hit_objects[n]
.inner .inner
.end_pos() .end_pos()
.unwrap()
.distance(self.hit_objects[iidx].inner.pos.to_float().unwrap()) .distance(self.hit_objects[iidx].inner.pos.to_float().unwrap())
< STACK_DISTANCE < STACK_DISTANCE
{ {

View file

@ -22,10 +22,10 @@ use ggez::{
use image::io::Reader as ImageReader; use image::io::Reader as ImageReader;
use libosu::{ use libosu::{
beatmap::Beatmap, beatmap::Beatmap,
hitobject::{HitObjectKind, SpinnerInfo}, hitobject::{HitObjectKind, SliderSplineKind, SpinnerInfo},
math::Point, math::Point,
spline::Spline, spline::Spline,
timing::{TimingPoint, TimingPointKind}, timing::{TimestampMillis, TimingPoint, TimingPointKind},
}; };
use crate::audio::{AudioEngine, Sound}; use crate::audio::{AudioEngine, Sound};
@ -65,6 +65,7 @@ pub struct Game {
combo_colors: Vec<Color>, combo_colors: Vec<Color>,
selected_objects: Vec<usize>, selected_objects: Vec<usize>,
tool: Tool, tool: Tool,
partial_slider_state: Option<(SliderSplineKind, Vec<Point<i32>>)>,
keymap: HashSet<KeyCode>, keymap: HashSet<KeyCode>,
mouse_pos: (f32, f32), mouse_pos: (f32, f32),
@ -101,6 +102,7 @@ impl Game {
left_drag_start: None, left_drag_start: None,
right_drag_start: None, right_drag_start: None,
tool: Tool::Select, tool: Tool::Select,
partial_slider_state: None,
current_uninherited_timing_point: None, current_uninherited_timing_point: None,
current_inherited_timing_point: None, current_inherited_timing_point: None,
}) })
@ -195,6 +197,7 @@ impl Game {
self.draw_grid(ctx)?; self.draw_grid(ctx)?;
let time = self.song.as_ref().unwrap().position()?; let time = self.song.as_ref().unwrap().position()?;
let time_millis = TimestampMillis((time * 1000.0) as i32);
let text = Text::new(format!("time: {:.4}, mouse: {:?}", time, self.mouse_pos).as_ref()); let text = Text::new(format!("time: {:.4}, mouse: {:?}", time, self.mouse_pos).as_ref());
graphics::queue_text(ctx, &text, [0.0, 0.0], Some(WHITE)); graphics::queue_text(ctx, &text, [0.0, 0.0], Some(WHITE));
graphics::draw_queued_text(ctx, DrawParam::default(), None, FilterMode::Linear)?; graphics::draw_queued_text(ctx, DrawParam::default(), None, FilterMode::Linear)?;
@ -238,7 +241,8 @@ impl Game {
end_time: spinner_end, end_time: spinner_end,
}) => end_time = (spinner_end.0 as f64) / 1000.0, }) => end_time = (spinner_end.0 as f64) / 1000.0,
}; };
if ho_time - preempt < time && time < end_time {
if ho_time - preempt <= time && time <= end_time {
playfield_hitobjects.push(DrawInfo { playfield_hitobjects.push(DrawInfo {
hit_object: ho, hit_object: ho,
opacity, opacity,
@ -273,19 +277,18 @@ impl Game {
let mut color = color; let mut color = color;
color.a = 0.6 * draw_info.opacity as f32; color.a = 0.6 * draw_info.opacity as f32;
let spline = Game::render_slider( Game::render_slider_body(
&mut self.slider_cache, &mut self.slider_cache,
info, info,
control_points.as_ref(), control_points.as_ref(),
ctx, ctx,
PLAYFIELD_BOUNDS, PLAYFIELD_BOUNDS,
&self.beatmap.inner, &self.beatmap.inner,
&ho.inner,
color, color,
)?; )?;
slider_info = Some((info, control_points)); slider_info = Some((info, control_points));
let end_pos = ho.inner.end_pos().unwrap(); let end_pos = ho.inner.end_pos();
let end_pos = [ let end_pos = [
PLAYFIELD_BOUNDS.x + osupx_scale_x * end_pos.x as f32, PLAYFIELD_BOUNDS.x + osupx_scale_x * end_pos.x as f32,
PLAYFIELD_BOUNDS.y + osupx_scale_y * end_pos.y as f32, PLAYFIELD_BOUNDS.y + osupx_scale_y * end_pos.y as f32,
@ -363,6 +366,9 @@ impl Game {
// draw whatever tool user is using // draw whatever tool user is using
let (mx, my) = self.mouse_pos; let (mx, my) = self.mouse_pos;
let pos_x = (mx - PLAYFIELD_BOUNDS.x) / PLAYFIELD_BOUNDS.w * 512.0;
let pos_y = (my - PLAYFIELD_BOUNDS.y) / PLAYFIELD_BOUNDS.h * 384.0;
let mouse_pos = Point::new(pos_x as i32, pos_y as i32);
match self.tool { match self.tool {
Tool::Select => { Tool::Select => {
let (mx, my) = self.mouse_pos; let (mx, my) = self.mouse_pos;
@ -399,6 +405,68 @@ impl Game {
)?; )?;
} }
} }
Tool::Slider => {
let color = Color::new(1.0, 1.0, 1.0, 0.4);
if let Some((kind, nodes)) = &self.partial_slider_state {
let mut nodes2 = nodes.clone();
let mut kind = *kind;
if let Some(last) = nodes2.last() {
if mouse_pos != *last {
nodes2.push(mouse_pos);
if kind == SliderSplineKind::Linear && nodes2.len() == 3 {
kind = SliderSplineKind::Perfect;
} else if kind == SliderSplineKind::Perfect && nodes2.len() == 4 {
kind = SliderSplineKind::Bezier;
}
}
}
if nodes2.len() > 1 && !(nodes2.len() == 2 && nodes2[0] == nodes2[1]) {
let slider_velocity =
self.beatmap.inner.get_slider_velocity_at_time(time_millis);
let slider_multiplier = self.beatmap.inner.difficulty.slider_multiplier;
let pixels_per_beat = slider_multiplier * 100.0 * slider_velocity;
let pixels_per_tick = pixels_per_beat / 4.0; // TODO: FIX!!!
let mut spline = Spline::from_control(kind, &nodes2, None);
let len = spline.pixel_length();
debug!("original len: {}", len);
let num_ticks = (len / pixels_per_tick).floor();
debug!("num ticks: {}", num_ticks);
let fixed_len = num_ticks * pixels_per_tick;
debug!("fixed len: {}", fixed_len);
spline.truncate(fixed_len);
debug!("len: {}", spline.pixel_length());
Game::render_spline(
ctx,
&self.beatmap.inner,
&spline,
PLAYFIELD_BOUNDS,
color,
)?;
debug!("done rendering slider body");
}
Game::render_slider_wireframe(ctx, &nodes2, PLAYFIELD_BOUNDS)?;
debug!("done rendering slider wireframe");
} else {
if rect_contains(&PLAYFIELD_BOUNDS, mx, my) {
let pos = [mx, my];
self.skin.hitcircle.draw(
ctx,
(cs_real * 2.0, cs_real * 2.0),
DrawParam::default().dest(pos).color(color),
)?;
self.skin.hitcircleoverlay.draw(
ctx,
(cs_real * 2.0, cs_real * 2.0),
DrawParam::default().dest(pos).color(color),
)?;
}
}
}
_ => {} _ => {}
} }
@ -482,6 +550,10 @@ impl Game {
fn handle_click(&mut self, btn: MouseButton, x: f32, y: f32) -> Result<()> { fn handle_click(&mut self, btn: MouseButton, x: f32, y: f32) -> Result<()> {
println!("handled click {}", self.song.is_some()); println!("handled click {}", self.song.is_some());
let pos_x = (x - PLAYFIELD_BOUNDS.x) / PLAYFIELD_BOUNDS.w * 512.0;
let pos_y = (y - PLAYFIELD_BOUNDS.y) / PLAYFIELD_BOUNDS.h * 384.0;
let pos = Point::new(pos_x as i32, pos_y as i32);
if let Some(song) = &self.song { if let Some(song) = &self.song {
println!("song exists! {:?} {:?}", btn, self.tool); println!("song exists! {:?} {:?}", btn, self.tool);
if let (MouseButton::Left, Tool::Select) = (btn, &self.tool) { if let (MouseButton::Left, Tool::Select) = (btn, &self.tool) {
@ -504,18 +576,16 @@ impl Game {
timing::TimestampMillis, timing::TimestampMillis,
}; };
let pos_x = (x - PLAYFIELD_BOUNDS.x) / PLAYFIELD_BOUNDS.w * 512.0;
let pos_y = (y - PLAYFIELD_BOUNDS.y) / PLAYFIELD_BOUNDS.h * 384.0;
let inner = HitObject { let inner = HitObject {
start_time: TimestampMillis(time), start_time: TimestampMillis(time),
pos: Point::new(pos_x as i32, pos_y as i32), pos,
kind: HitObjectKind::Circle, kind: HitObjectKind::Circle,
new_combo: false, new_combo: false,
skip_color: 0, skip_color: 0,
additions: Additions::empty(), additions: Additions::empty(),
sample_info: SampleInfo::default(), sample_info: SampleInfo::default(),
timing_point: None,
}; };
let new_obj = HitObjectExt { let new_obj = HitObjectExt {
inner, inner,
stacking: 0, stacking: 0,
@ -529,6 +599,21 @@ impl Game {
} }
} }
} }
} else if let (MouseButton::Left, Tool::Slider) = (btn, &self.tool) {
if let Some((ref mut kind, ref mut nodes)) = &mut self.partial_slider_state {
nodes.push(pos);
if *kind == SliderSplineKind::Linear && nodes.len() == 3 {
*kind = SliderSplineKind::Perfect;
} else if *kind == SliderSplineKind::Perfect && nodes.len() == 4 {
*kind = SliderSplineKind::Bezier;
}
} else {
self.partial_slider_state = Some((SliderSplineKind::Linear, vec![pos]));
}
} else if let (MouseButton::Right, Tool::Slider) = (btn, &self.tool) {
if let Some(_) = &mut self.partial_slider_state {
self.partial_slider_state = None;
}
} }
} }
Ok(()) Ok(())

View file

@ -17,25 +17,13 @@ use libosu::{
use super::{Game, SliderCache}; use super::{Game, SliderCache};
impl Game { impl Game {
pub fn render_slider<'a>( pub fn render_spline(
slider_cache: &'a mut SliderCache,
slider_info: &SliderInfo,
control_points: &[Point<i32>],
ctx: &mut Context, ctx: &mut Context,
rect: Rect,
beatmap: &Beatmap, beatmap: &Beatmap,
slider: &HitObject, spline: &Spline,
rect: Rect,
color: Color, color: Color,
) -> Result<&'a Spline> { ) -> Result<()> {
let spline = if slider_cache.contains_key(control_points) {
slider_cache.get(control_points).unwrap()
} else {
let new_spline =
Spline::from_control(slider_info.kind, control_points, slider_info.pixel_length);
slider_cache.insert(control_points.to_vec(), new_spline);
slider_cache.get(control_points).unwrap()
};
let cs_scale = rect.w / 640.0; let cs_scale = rect.w / 640.0;
let osupx_scale_x = rect.w as f64 / 512.0; let osupx_scale_x = rect.w as f64 / 512.0;
let osupx_scale_y = rect.h as f64 / 384.0; let osupx_scale_y = rect.h as f64 / 384.0;
@ -95,7 +83,52 @@ impl Game {
graphics::set_canvas(ctx, None); graphics::set_canvas(ctx, None);
graphics::draw(ctx, &canvas, DrawParam::default().color(color))?; graphics::draw(ctx, &canvas, DrawParam::default().color(color))?;
Ok(spline) Ok(())
}
pub fn render_slider_body<'a>(
slider_cache: &'a mut SliderCache,
slider_info: &SliderInfo,
control_points: &[Point<i32>],
ctx: &mut Context,
rect: Rect,
beatmap: &Beatmap,
color: Color,
) -> Result<()> {
debug!(
"Rendering slider body with control points {:?}",
control_points
);
if control_points.len() < 2
|| (control_points.len() == 2 && control_points[0] == control_points[1])
{
debug!("Slider too short, not rendering!");
return Ok(());
}
let spline = if slider_cache.contains_key(control_points) {
slider_cache.get(control_points).expect("just checked")
} else {
let new_spline = Spline::from_control(
slider_info.kind,
control_points,
Some(slider_info.pixel_length),
);
slider_cache.insert(control_points.to_vec(), new_spline);
slider_cache.get(control_points).expect("just inserted it")
};
debug!("spline length: {}", spline.spline_points.len());
if spline.spline_points.len() < 2
|| (spline.spline_points.len() == 2
&& spline.spline_points[0] == spline.spline_points[1])
{
debug!("Slider too short, not rendering!");
return Ok(());
}
Game::render_spline(ctx, beatmap, spline, rect, color)
} }
pub fn render_slider_wireframe( pub fn render_slider_wireframe(
@ -116,6 +149,9 @@ impl Game {
.collect::<Vec<Point2<_>>>(); .collect::<Vec<Point2<_>>>();
// draw control points wireframe // draw control points wireframe
if control_points.len() > 1
&& !(control_points.len() == 2 && control_points[0] == control_points[1])
{
let frame = Mesh::new_polyline( let frame = Mesh::new_polyline(
ctx, ctx,
DrawMode::Stroke(StrokeOptions::default()), DrawMode::Stroke(StrokeOptions::default()),
@ -123,6 +159,7 @@ impl Game {
graphics::WHITE, graphics::WHITE,
)?; )?;
graphics::draw(ctx, &frame, DrawParam::default())?; graphics::draw(ctx, &frame, DrawParam::default())?;
}
// draw points on wireframe // draw points on wireframe
let mut i = 0; let mut i = 0;

View file

@ -41,8 +41,9 @@ fn main() -> Result<()> {
let opt = Opt::from_args(); let opt = Opt::from_args();
stderrlog::new() stderrlog::new()
.module("editor") .module("editor")
.module("bass_sys") .module("libosu::spline")
.verbosity(opt.verbose) .verbosity(opt.verbose)
.show_module_names(true)
.init() .init()
.unwrap(); .unwrap();