yea
This commit is contained in:
parent
e23fed9f5c
commit
aa8fced2d0
13 changed files with 234 additions and 91 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -559,6 +559,7 @@ dependencies = [
|
|||
"libosu",
|
||||
"log",
|
||||
"num",
|
||||
"ordered-float 2.0.1",
|
||||
"stderrlog",
|
||||
]
|
||||
|
||||
|
@ -891,7 +892,7 @@ checksum = "5fca6f9d679bff1322c76c9a1ad4b8553b30a94f3f75bea6936e19032c2f2ec3"
|
|||
dependencies = [
|
||||
"glyph_brush_layout",
|
||||
"log",
|
||||
"ordered-float",
|
||||
"ordered-float 1.1.1",
|
||||
"rustc-hash",
|
||||
"rusttype 0.8.3",
|
||||
"twox-hash",
|
||||
|
@ -1485,6 +1486,15 @@ dependencies = [
|
|||
"num-traits 0.2.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dacdec97876ef3ede8c50efc429220641a0b11ba0048b4b0c357bccbc47c5204"
|
||||
dependencies = [
|
||||
"num-traits 0.2.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osmesa-sys"
|
||||
version = "0.1.2"
|
||||
|
@ -1909,7 +1919,7 @@ dependencies = [
|
|||
"crossbeam-utils 0.7.2",
|
||||
"linked-hash-map",
|
||||
"num_cpus",
|
||||
"ordered-float",
|
||||
"ordered-float 1.1.1",
|
||||
"rustc-hash",
|
||||
"stb_truetype",
|
||||
]
|
||||
|
|
|
@ -16,6 +16,7 @@ ggez = "0.5.1"
|
|||
log = "0.4.11"
|
||||
stderrlog = "0.5.0"
|
||||
num = "0.3.1"
|
||||
ordered-float = "2.0.1"
|
||||
|
||||
[dependencies.libosu]
|
||||
git = "https://github.com/iptq/libosu"
|
||||
|
|
|
@ -52,3 +52,27 @@ pub const BASS_POS_INEXACT: DWORD = 0x8000000;
|
|||
pub const BASS_POS_DECODE: DWORD = 0x10000000;
|
||||
pub const BASS_POS_DECODETO: DWORD = 0x20000000;
|
||||
pub const BASS_POS_SCAN: DWORD = 0x40000000;
|
||||
|
||||
pub const BASS_ATTRIB_FREQ: DWORD = 1;
|
||||
pub const BASS_ATTRIB_VOL: DWORD = 2;
|
||||
pub const BASS_ATTRIB_PAN: DWORD = 3;
|
||||
pub const BASS_ATTRIB_EAXMIX: DWORD = 4;
|
||||
pub const BASS_ATTRIB_NOBUFFER: DWORD = 5;
|
||||
pub const BASS_ATTRIB_VBR: DWORD = 6;
|
||||
pub const BASS_ATTRIB_CPU: DWORD = 7;
|
||||
pub const BASS_ATTRIB_SRC: DWORD = 8;
|
||||
pub const BASS_ATTRIB_NET_RESUME: DWORD = 9;
|
||||
pub const BASS_ATTRIB_SCANINFO: DWORD = 10;
|
||||
pub const BASS_ATTRIB_NORAMP: DWORD = 11;
|
||||
pub const BASS_ATTRIB_BITRATE: DWORD = 12;
|
||||
pub const BASS_ATTRIB_BUFFER: DWORD = 13;
|
||||
pub const BASS_ATTRIB_GRANULE: DWORD = 14;
|
||||
pub const BASS_ATTRIB_MUSIC_AMPLIFY: DWORD = 0x100;
|
||||
pub const BASS_ATTRIB_MUSIC_PANSEP: DWORD = 0x101;
|
||||
pub const BASS_ATTRIB_MUSIC_PSCALER: DWORD = 0x102;
|
||||
pub const BASS_ATTRIB_MUSIC_BPM: DWORD = 0x103;
|
||||
pub const BASS_ATTRIB_MUSIC_SPEED: DWORD = 0x104;
|
||||
pub const BASS_ATTRIB_MUSIC_VOL_GLOBAL: DWORD = 0x105;
|
||||
pub const BASS_ATTRIB_MUSIC_ACTIVE: DWORD = 0x106;
|
||||
pub const BASS_ATTRIB_MUSIC_VOL_CHAN: DWORD = 0x200;
|
||||
pub const BASS_ATTRIB_MUSIC_VOL_INST: DWORD = 0x300;
|
||||
|
|
|
@ -12,14 +12,16 @@ extern_log! {
|
|||
pub fn BASS_GetConfig(option: DWORD) -> DWORD;
|
||||
|
||||
pub fn BASS_ChannelGetDevice(handle: DWORD) -> DWORD;
|
||||
pub fn BASS_ChannelGetAttribute(handle:DWORD, attrib:DWORD, value:*mut c_float);
|
||||
pub fn BASS_ChannelSetAttribute(handle:DWORD, attrib:DWORD, value:c_float);
|
||||
pub fn BASS_ChannelSetDevice(handle: DWORD, device: DWORD) -> BOOL;
|
||||
pub fn BASS_ChannelPlay(handle: DWORD, restart: BOOL) -> BOOL;
|
||||
pub fn BASS_ChannelPause(handle: DWORD) -> BOOL;
|
||||
pub fn BASS_ChannelGetLength(handle: DWORD, mode: DWORD) -> QWORD;
|
||||
pub fn BASS_ChannelGetPosition(handle: DWORD, mode: DWORD) -> QWORD;
|
||||
pub fn BASS_ChannelSetPosition(handle: DWORD, pos: QWORD, mode: DWORD);
|
||||
pub fn BASS_ChannelBytes2Seconds(handle: DWORD, pos: QWORD) -> f64;
|
||||
pub fn BASS_ChannelSeconds2Bytes(handle: DWORD, pos: f64) -> QWORD;
|
||||
pub fn BASS_ChannelBytes2Seconds(handle: DWORD, pos: QWORD) -> c_double;
|
||||
pub fn BASS_ChannelSeconds2Bytes(handle: DWORD, pos: c_double) -> QWORD;
|
||||
|
||||
pub fn BASS_GetDevice() -> DWORD;
|
||||
pub fn BASS_GetDeviceInfo(device: DWORD, info: *mut BASS_DEVICEINFO) -> BOOL;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub use std::os::raw::{c_char, c_float, c_int, c_uint, c_ulong, c_void};
|
||||
pub use std::os::raw::*;
|
||||
|
||||
pub type DWORD = c_uint;
|
||||
pub type QWORD = c_ulong;
|
||||
|
|
2
run.sh
2
run.sh
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
export LD_LIBRARY_PATH=$(pwd)/bass-sys/linux/bass24/x64
|
||||
echo $LD_LIBRARY_PATH
|
||||
exec cargo run "$@"
|
||||
exec cargo run --release "$@"
|
||||
|
|
|
@ -84,4 +84,12 @@ impl Sound {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_playback_rate(&self, rate: f64) {
|
||||
unsafe {
|
||||
let mut val = 0.0f32;
|
||||
bass::BASS_ChannelGetAttribute(self.handle, BASS_ATTRIB_FREQ, &mut val as *mut _);
|
||||
bass::BASS_ChannelSetAttribute(self.handle, BASS_ATTRIB_FREQ, val * rate as f32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
50
src/game.rs
50
src/game.rs
|
@ -1,3 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
@ -11,10 +12,12 @@ use ggez::{
|
|||
},
|
||||
Context, GameError, GameResult,
|
||||
};
|
||||
use libosu::{Beatmap, HitObject, HitObjectKind, SpinnerInfo};
|
||||
use libosu::{Beatmap, HitObject, HitObjectKind, Point, SpinnerInfo};
|
||||
|
||||
use crate::audio::{AudioEngine, Sound};
|
||||
use crate::slider_render::render_slider;
|
||||
use crate::slider_render::{render_slider, Spline};
|
||||
|
||||
pub type SliderCache = HashMap<Vec<Point<i32>>, Spline>;
|
||||
|
||||
pub struct Game {
|
||||
is_playing: bool,
|
||||
|
@ -22,6 +25,8 @@ pub struct Game {
|
|||
song: Option<Sound>,
|
||||
beatmap: Beatmap,
|
||||
hit_objects: Vec<HitObject>,
|
||||
|
||||
slider_cache: SliderCache,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
|
@ -36,6 +41,7 @@ impl Game {
|
|||
beatmap,
|
||||
hit_objects,
|
||||
song: None,
|
||||
slider_cache: SliderCache::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -51,6 +57,7 @@ impl Game {
|
|||
|
||||
let song = Sound::create(dir.join(&self.beatmap.audio_filename))?;
|
||||
song.set_position(113.0)?;
|
||||
song.set_playback_rate(0.6);
|
||||
self.song = Some(song);
|
||||
|
||||
Ok(())
|
||||
|
@ -136,9 +143,44 @@ impl Game {
|
|||
];
|
||||
let color = graphics::Color::new(1.0, 1.0, 1.0, draw_info.opacity as f32);
|
||||
|
||||
if let HitObjectKind::Slider(_) = ho.kind {
|
||||
if let HitObjectKind::Slider(info) = &ho.kind {
|
||||
let color = graphics::Color::new(1.0, 1.0, 1.0, 0.6 * draw_info.opacity as f32);
|
||||
render_slider(ctx, EDITOR_SCREEN, &self.beatmap, ho, color)?;
|
||||
let spline = render_slider(
|
||||
&mut self.slider_cache,
|
||||
ctx,
|
||||
EDITOR_SCREEN,
|
||||
&self.beatmap,
|
||||
ho,
|
||||
color,
|
||||
)?;
|
||||
|
||||
if time > ho_time && time < draw_info.end_time {
|
||||
let elapsed_time = time - ho_time;
|
||||
let total_duration = draw_info.end_time - ho_time;
|
||||
let single_duration = total_duration / info.num_repeats as f64;
|
||||
let finished_repeats = (elapsed_time / single_duration).floor();
|
||||
let this_repeat_time = elapsed_time - finished_repeats * single_duration;
|
||||
let mut travel_percent = this_repeat_time / single_duration;
|
||||
// reverse direction on odd trips
|
||||
if finished_repeats as u32 % 2 == 1 {
|
||||
travel_percent = 1.0 - travel_percent;
|
||||
}
|
||||
let travel_length = travel_percent * info.pixel_length;
|
||||
let pos = spline.position_at_length(travel_length);
|
||||
let ball_pos = [
|
||||
EDITOR_SCREEN.x + osupx_scale_x * pos.0 as f32,
|
||||
EDITOR_SCREEN.y + osupx_scale_y * pos.1 as f32,
|
||||
];
|
||||
let ball = Mesh::new_circle(
|
||||
ctx,
|
||||
DrawMode::Fill(FillOptions::default()),
|
||||
ball_pos,
|
||||
cs_real,
|
||||
1.0,
|
||||
graphics::WHITE,
|
||||
)?;
|
||||
graphics::draw(ctx, &ball, DrawParam::default())?;
|
||||
}
|
||||
}
|
||||
|
||||
let circ = Mesh::new_circle(
|
||||
|
|
|
@ -9,6 +9,7 @@ extern crate bass_sys as bass;
|
|||
mod audio;
|
||||
mod game;
|
||||
mod math;
|
||||
mod skin;
|
||||
mod slider_render;
|
||||
|
||||
use std::env;
|
||||
|
|
|
@ -24,12 +24,6 @@ impl<T: Float> Math<T> {
|
|||
/ d;
|
||||
|
||||
let center = Point(ux, uy);
|
||||
(center, Self::distance(center, p1))
|
||||
}
|
||||
|
||||
pub fn distance(p1: Point<T>, p2: Point<T>) -> T {
|
||||
let dx = p2.0 - p1.0;
|
||||
let dy = p2.1 - p1.1;
|
||||
(dx * dx + dy * dy).sqrt()
|
||||
(center, center.distance(p1))
|
||||
}
|
||||
}
|
||||
|
|
1
src/skin.rs
Normal file
1
src/skin.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub struct Skin {}
|
|
@ -9,16 +9,19 @@ use ggez::{
|
|||
Context,
|
||||
};
|
||||
use libosu::{Beatmap, HitObject, HitObjectKind, Point, SliderSplineKind};
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use crate::game::SliderCache;
|
||||
use crate::math::Math;
|
||||
|
||||
pub fn render_slider(
|
||||
pub fn render_slider<'a>(
|
||||
slider_cache: &'a mut SliderCache,
|
||||
ctx: &mut Context,
|
||||
rect: Rect,
|
||||
beatmap: &Beatmap,
|
||||
slider: &HitObject,
|
||||
color: Color,
|
||||
) -> Result<()> {
|
||||
) -> Result<&'a Spline> {
|
||||
let mut control_points = vec![slider.pos];
|
||||
let slider_info = match &slider.kind {
|
||||
HitObjectKind::Slider(info) => info,
|
||||
|
@ -26,7 +29,14 @@ pub fn render_slider(
|
|||
};
|
||||
control_points.extend(&slider_info.control);
|
||||
|
||||
let spline = get_spline(&slider_info.kind, &control_points, slider_info.pixel_length);
|
||||
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.clone(), new_spline);
|
||||
slider_cache.get(&control_points).unwrap()
|
||||
};
|
||||
|
||||
let osupx_scale_x = rect.w as f64 / 512.0;
|
||||
let osupx_scale_y = rect.h as f64 / 384.0;
|
||||
|
@ -45,6 +55,7 @@ pub fn render_slider(
|
|||
|
||||
let (mut boundx, mut boundy, mut boundw, mut boundh) = (0.0f64, 0.0f64, 0.0f64, 0.0f64);
|
||||
let spline_mapped = spline
|
||||
.spline_points
|
||||
.iter()
|
||||
.map(|point| {
|
||||
let (x, y) = (point.0, point.1);
|
||||
|
@ -85,86 +96,131 @@ pub fn render_slider(
|
|||
graphics::draw(ctx, &rect, DrawParam::default())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(spline)
|
||||
}
|
||||
|
||||
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;
|
||||
pub struct Spline {
|
||||
spline_points: Vec<P>,
|
||||
cumulative_lengths: Vec<NotNan<f64>>,
|
||||
}
|
||||
|
||||
impl Spline {
|
||||
fn from_control(
|
||||
kind: &SliderSplineKind,
|
||||
control_points: &[Point<i32>],
|
||||
pixel_length: f64,
|
||||
) -> Self {
|
||||
// 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<_>>();
|
||||
let spline_points = 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!(),
|
||||
};
|
||||
|
||||
let mut cumulative_lengths = Vec::with_capacity(spline_points.len());
|
||||
let mut curr = 0.0;
|
||||
cumulative_lengths.push(unsafe { NotNan::unchecked_new(curr) });
|
||||
for points in spline_points.windows(2) {
|
||||
let dist = points[0].distance(points[1]);
|
||||
curr += dist;
|
||||
cumulative_lengths.push(unsafe { NotNan::unchecked_new(curr) });
|
||||
}
|
||||
|
||||
Spline {
|
||||
spline_points,
|
||||
cumulative_lengths,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
pub fn position_at_length(&self, length: f64) -> P {
|
||||
let length_notnan = unsafe { NotNan::unchecked_new(length) };
|
||||
match self.cumulative_lengths.binary_search(&length_notnan) {
|
||||
Ok(idx) => self.spline_points[idx],
|
||||
Err(idx) => {
|
||||
let n = self.spline_points.len() - 1;
|
||||
if idx == 0 && self.spline_points.len() > 2 {
|
||||
return self.spline_points[0];
|
||||
} else if idx >= n {
|
||||
return self.spline_points[n];
|
||||
}
|
||||
|
||||
let rel = Point(t.cos() * radius, -t.sin() * radius);
|
||||
c.push(center + rel);
|
||||
let (len1, len2) = (
|
||||
self.cumulative_lengths[idx].into_inner(),
|
||||
self.cumulative_lengths[idx + 1].into_inner(),
|
||||
);
|
||||
let proportion = (length - len1) / (len2 - len1);
|
||||
|
||||
t += (new_t1 - t0) / pixel_length;
|
||||
let (p1, p2) = (self.spline_points[idx], self.spline_points[idx + 1]);
|
||||
(p2 - p1) * proportion + p1
|
||||
}
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
4
todo.txt
Normal file
4
todo.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
- stacking
|
||||
- fix the overlaps in notch hell slider (circle size wrong?)
|
||||
- don't draw overlaps between slider segments
|
||||
- use skin components
|
Loading…
Reference in a new issue