diff --git a/Cargo.lock b/Cargo.lock index deffa71..bdab5f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1455,11 +1455,12 @@ dependencies = [ [[package]] name = "libosu" 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 = [ "bitflags", "derive_more", "lazy_static", + "log", "num", "num-derive", "num-rational", diff --git a/Cargo.toml b/Cargo.toml index 99980b6..e1493a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ image = "0.23.12" [dependencies.libosu] git = "https://github.com/iptq/libosu" -rev = "0229c52759ffb70322384e53abb0112cb52e49ae" +rev = "81677d5ed88936c4a3e64af951ff0ae523c2d403" [features] clippy = [] diff --git a/bass-sys/src/macros.rs b/bass-sys/src/macros.rs index 27316e7..b3c99e2 100644 --- a/bass-sys/src/macros.rs +++ b/bass-sys/src/macros.rs @@ -16,9 +16,9 @@ macro_rules! extern_log { } #[allow(non_snake_case)] $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,)*) }; - log::debug!("exiting {} => {:?}", stringify!($name), result); + log::trace!("exiting {} => {:?}", stringify!($name), result); result } )* diff --git a/src/beatmap.rs b/src/beatmap.rs index a32938d..ea19c72 100644 --- a/src/beatmap.rs +++ b/src/beatmap.rs @@ -75,11 +75,7 @@ impl BeatmapExt { let object_n_pos: Point = object_n.inner.pos.to_float().unwrap(); if stack_base_pos.distance(object_n_pos) < STACK_DISTANCE || (stack_base_obj.inner.kind.is_slider() - && stack_base_obj - .inner - .end_pos() - .unwrap() - .distance(object_n_pos) + && stack_base_obj.inner.end_pos().distance(object_n_pos) < STACK_DISTANCE) { stack_base_idx = n; @@ -146,7 +142,6 @@ impl BeatmapExt { && self.hit_objects[n] .inner .end_pos() - .unwrap() .distance(self.hit_objects[iidx].inner.pos.to_float().unwrap()) < STACK_DISTANCE { @@ -157,7 +152,6 @@ impl BeatmapExt { if self.hit_objects[n] .inner .end_pos() - .unwrap() .distance(self.hit_objects[j].inner.pos.to_float().unwrap()) < STACK_DISTANCE { @@ -202,7 +196,6 @@ impl BeatmapExt { if self.hit_objects[n] .inner .end_pos() - .unwrap() .distance(self.hit_objects[iidx].inner.pos.to_float().unwrap()) < STACK_DISTANCE { diff --git a/src/game/mod.rs b/src/game/mod.rs index a1a31ab..0cfbebb 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -22,10 +22,10 @@ use ggez::{ use image::io::Reader as ImageReader; use libosu::{ beatmap::Beatmap, - hitobject::{HitObjectKind, SpinnerInfo}, + hitobject::{HitObjectKind, SliderSplineKind, SpinnerInfo}, math::Point, spline::Spline, - timing::{TimingPoint, TimingPointKind}, + timing::{TimestampMillis, TimingPoint, TimingPointKind}, }; use crate::audio::{AudioEngine, Sound}; @@ -65,6 +65,7 @@ pub struct Game { combo_colors: Vec, selected_objects: Vec, tool: Tool, + partial_slider_state: Option<(SliderSplineKind, Vec>)>, keymap: HashSet, mouse_pos: (f32, f32), @@ -101,6 +102,7 @@ impl Game { left_drag_start: None, right_drag_start: None, tool: Tool::Select, + partial_slider_state: None, current_uninherited_timing_point: None, current_inherited_timing_point: None, }) @@ -195,6 +197,7 @@ impl Game { self.draw_grid(ctx)?; 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()); graphics::queue_text(ctx, &text, [0.0, 0.0], Some(WHITE)); graphics::draw_queued_text(ctx, DrawParam::default(), None, FilterMode::Linear)?; @@ -238,7 +241,8 @@ impl Game { end_time: spinner_end, }) => 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 { hit_object: ho, opacity, @@ -273,19 +277,18 @@ impl Game { let mut color = color; color.a = 0.6 * draw_info.opacity as f32; - let spline = Game::render_slider( + Game::render_slider_body( &mut self.slider_cache, info, control_points.as_ref(), ctx, PLAYFIELD_BOUNDS, &self.beatmap.inner, - &ho.inner, color, )?; slider_info = Some((info, control_points)); - let end_pos = ho.inner.end_pos().unwrap(); + let end_pos = ho.inner.end_pos(); let end_pos = [ PLAYFIELD_BOUNDS.x + osupx_scale_x * end_pos.x 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 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 { Tool::Select => { 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<()> { 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 { println!("song exists! {:?} {:?}", btn, self.tool); if let (MouseButton::Left, Tool::Select) = (btn, &self.tool) { @@ -504,18 +576,16 @@ impl Game { 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 { start_time: TimestampMillis(time), - pos: Point::new(pos_x as i32, pos_y as i32), + pos, kind: HitObjectKind::Circle, new_combo: false, skip_color: 0, additions: Additions::empty(), sample_info: SampleInfo::default(), - timing_point: None, }; + let new_obj = HitObjectExt { inner, 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(()) diff --git a/src/game/sliders.rs b/src/game/sliders.rs index 57056d3..f831687 100644 --- a/src/game/sliders.rs +++ b/src/game/sliders.rs @@ -17,25 +17,13 @@ use libosu::{ use super::{Game, SliderCache}; impl Game { - pub fn render_slider<'a>( - slider_cache: &'a mut SliderCache, - slider_info: &SliderInfo, - control_points: &[Point], + pub fn render_spline( ctx: &mut Context, - rect: Rect, beatmap: &Beatmap, - slider: &HitObject, + spline: &Spline, + rect: Rect, color: Color, - ) -> Result<&'a Spline> { - 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() - }; - + ) -> Result<()> { let cs_scale = rect.w / 640.0; let osupx_scale_x = rect.w as f64 / 512.0; let osupx_scale_y = rect.h as f64 / 384.0; @@ -95,7 +83,52 @@ impl Game { graphics::set_canvas(ctx, None); 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], + 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( @@ -116,13 +149,17 @@ impl Game { .collect::>>(); // draw control points wireframe - let frame = Mesh::new_polyline( - ctx, - DrawMode::Stroke(StrokeOptions::default()), - &points_mapped, - graphics::WHITE, - )?; - graphics::draw(ctx, &frame, DrawParam::default())?; + if control_points.len() > 1 + && !(control_points.len() == 2 && control_points[0] == control_points[1]) + { + let frame = Mesh::new_polyline( + ctx, + DrawMode::Stroke(StrokeOptions::default()), + &points_mapped, + graphics::WHITE, + )?; + graphics::draw(ctx, &frame, DrawParam::default())?; + } // draw points on wireframe let mut i = 0; diff --git a/src/main.rs b/src/main.rs index b34a464..e48b13c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,8 +41,9 @@ fn main() -> Result<()> { let opt = Opt::from_args(); stderrlog::new() .module("editor") - .module("bass_sys") + .module("libosu::spline") .verbosity(opt.verbose) + .show_module_names(true) .init() .unwrap();