Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: note render, hitfx render, rating #472

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ inner.rs
/test
/log*
/build_event_debug.sh
/.idea
22 changes: 22 additions & 0 deletions prpr/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ impl BpmList {
BpmList { elements, cursor: 0 }
}

pub fn new_time(ranges: Vec<(f32, f32)>) -> Self {
let mut elements = Vec::new();
for (time, bpm) in ranges {
elements.push((time, time, bpm));
}
BpmList { elements, cursor: 0 }
}

/// Get the time in seconds for a given beats
pub fn time_beats(&mut self, beats: f32) -> f32 {
while let Some(kf) = self.elements.get(self.cursor + 1) {
Expand Down Expand Up @@ -157,4 +165,18 @@ impl BpmList {
let (beats, start_time, bpm) = &self.elements[self.cursor];
beats + (time - start_time) / (60. / bpm)
}

pub fn now_bpm(&mut self, time: f32) -> f32 {
while let Some(kf) = self.elements.get(self.cursor + 1) {
if kf.1 > time {
break;
}
self.cursor += 1;
}
while self.cursor != 0 && self.elements[self.cursor].1 > time {
self.cursor -= 1;
}
let (_, _, bpm) = &self.elements[self.cursor];
*bpm
}
}
6 changes: 4 additions & 2 deletions prpr/src/core/chart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,11 @@ impl Chart {
}
// TODO optimize
let trs = self.lines.iter().map(|it| it.now_transform(res, &self.lines)).collect::<Vec<_>>();
for (line, tr) in self.lines.iter_mut().zip(trs) {
line.update(res, tr);
let mut guard = self.bpm_list.borrow_mut();
for (index, (line, tr)) in self.lines.iter_mut().zip(trs).enumerate() {
line.update(res, tr, &mut guard, index);
}
drop(guard);
for effect in &mut self.extra.effects {
effect.update(res);
}
Expand Down
4 changes: 2 additions & 2 deletions prpr/src/core/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,15 @@ pub struct JudgeLine {
}

impl JudgeLine {
pub fn update(&mut self, res: &mut Resource, tr: Matrix) {
pub fn update(&mut self, res: &mut Resource, tr: Matrix, bpm_list: &mut BpmList, index: usize) {
// self.object.set_time(res.time); // this is done by chart, chart has to calculate transform for us
let rot = self.object.rotation.now();
self.height.set_time(res.time);
let line_height = self.height.now();
let mut ctrl_obj = self.ctrl_obj.borrow_mut();
self.cache.update_order.retain(|id| {
let note = &mut self.notes[*id as usize];
note.update(res, rot, &tr, &mut ctrl_obj, line_height);
note.update(res, rot, &tr, &mut ctrl_obj, line_height, bpm_list, index);
!note.dead()
});
drop(ctrl_obj);
Expand Down
34 changes: 26 additions & 8 deletions prpr/src/core/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use super::{chart::ChartSettings, BpmList, CtrlObject, JudgeLine, Matrix, Object
pub use crate::{
judge::{HitSound, JudgeStatus},
parse::RPE_HEIGHT,
core::HEIGHT_RATIO,
info::ChartFormat,
};
use macroquad::prelude::*;

const HOLD_PARTICLE_INTERVAL: f32 = 0.15;
//const HOLD_PARTICLE_INTERVAL: f32 = 0.15;
const FADEOUT_TIME: f32 = 0.16;
const BAD_TIME: f32 = 0.5;

Expand Down Expand Up @@ -134,11 +136,14 @@ impl Note {
// && self.ctrl_obj.is_default()
}

pub fn update(&mut self, res: &mut Resource, parent_rot: f32, parent_tr: &Matrix, ctrl_obj: &mut CtrlObject, line_height: f32) {
pub fn update(&mut self, res: &mut Resource, parent_rot: f32, parent_tr: &Matrix, ctrl_obj: &mut CtrlObject, line_height: f32, bpm_list: &mut BpmList, index: usize) {
self.object.set_time(res.time);
if let Some(color) = if let JudgeStatus::Hold(perfect, at, ..) = &mut self.judge {
if res.time > *at {
*at += HOLD_PARTICLE_INTERVAL / res.config.speed;
if let Some(color) = if let JudgeStatus::Hold(perfect, ref mut at, ..) = &mut self.judge {
if res.time >= *at {
let beat = 30. / bpm_list.now_bpm(
if matches!(res.chart_format, ChartFormat::Pgr) { index as f32 } else { self.time }
);
*at = res.time + beat / res.config.speed;
Some(if *perfect {
res.res_pack.info.fx_perfect()
} else {
Expand Down Expand Up @@ -200,7 +205,8 @@ impl Note {
self.init_ctrl_obj(ctrl_obj, config.line_height);
let mut color = self.object.now_color();
color.a *= res.alpha * ctrl_obj.alpha.now_opt().unwrap_or(1.);
let spd = self.speed * ctrl_obj.y.now_opt().unwrap_or(1.);
let end_spd = self.speed * ctrl_obj.y.now_opt().unwrap_or(1.);
let spd = if matches!(res.chart_format, ChartFormat::Pgr) && matches!(self.kind, NoteKind::Hold { .. }) { 1. } else { end_spd };

let line_height = config.line_height / res.aspect_ratio * spd;
let height = self.height / res.aspect_ratio * spd;
Expand Down Expand Up @@ -262,11 +268,23 @@ impl Note {

let h = if self.time <= res.time { line_height } else { height };
let bottom = h - line_height;
let top = end_height - line_height;
let top = if matches!(res.chart_format, ChartFormat::Pgr) {
if end_spd == 0. {
return;
}
let time = if res.time >= self.time {res.time} else {self.time};
let hold_height = end_height - height;
let hold_line_height = (time - self.time) * end_spd / res.aspect_ratio / HEIGHT_RATIO;
bottom + hold_height - hold_line_height
} else {
end_height - line_height
};
if res.time < self.time && bottom < -1e-6 && !config.settings.hold_partial_cover && !matches!(res.chart_format, ChartFormat::Pgr) {
return;
}
let tex = &style.hold;
let ratio = style.hold_ratio();
// body
// TODO (end_height - height) is not always total height
draw_tex(
res,
**(if res.res_pack.info.hold_repeat {
Expand Down
4 changes: 4 additions & 0 deletions prpr/src/core/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::{
path::Path,
sync::atomic::AtomicU32,
};
use crate::info::ChartFormat;

pub const MAX_SIZE: usize = 64; // needs tweaking
pub static DPI_VALUE: AtomicU32 = AtomicU32::new(250);
Expand Down Expand Up @@ -349,6 +350,7 @@ pub type SfxMap = HashMap<String, Sfx>;

pub struct Resource {
pub config: Config,
pub chart_format: ChartFormat,
pub info: ChartInfo,
pub aspect_ratio: f32,
pub dpi: u32,
Expand Down Expand Up @@ -416,6 +418,7 @@ impl Resource {

pub async fn new(
config: Config,
chart_format: ChartFormat,
info: ChartInfo,
mut fs: Box<dyn FileSystem>,
player: Option<SafeTexture>,
Expand Down Expand Up @@ -456,6 +459,7 @@ impl Resource {
macroquad::window::gl_set_drawcall_buffer_capacity(MAX_SIZE * 4, MAX_SIZE * 6);
Ok(Self {
config,
chart_format,
info,
aspect_ratio,
dpi: DPI_VALUE.load(std::sync::atomic::Ordering::SeqCst),
Expand Down
14 changes: 8 additions & 6 deletions prpr/src/judge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,6 @@ impl Judge {
}
}
for (line_id, id) in judgements.into_iter() {
self.commit(t, Judgement::Perfect, line_id as _, id, 0.);
let (note_transform, note_hitsound) = {
let line = &mut chart.lines[line_id];
let note = &mut line.notes[id as usize];
Expand All @@ -863,11 +862,14 @@ impl Judge {
(note.object.now(res), note.hitsound.clone())
};
let line = &chart.lines[line_id];
res.with_model(line.now_transform(res, &chart.lines) * note_transform, |res| {
res.emit_at_origin(line.notes[id as usize].rotation(&line), res.res_pack.info.fx_perfect())
});
if !matches!(chart.lines[line_id].notes[id as usize].kind, NoteKind::Hold { .. }) {
self.commit(t, Judgement::Perfect, line_id as _, id, 0.);
res.with_model(line.now_transform(res, &chart.lines) * note_transform, |res| {
res.emit_at_origin(line.notes[id as usize].rotation(line), res.res_pack.info.fx_perfect())
});
note_hitsound.play(res);
} else {
self.commit(t, Judgement::Perfect, line_id as _, id, 0.);
}
}
}
Expand Down Expand Up @@ -968,13 +970,13 @@ pub struct PlayResult {

pub fn icon_index(score: u32, full_combo: bool) -> usize {
match (score, full_combo) {
(1000000, true) => 7,
(_, true) => 6,
(x, _) if x < 700000 => 0,
(x, _) if x < 820000 => 1,
(x, _) if x < 880000 => 2,
(x, _) if x < 920000 => 3,
(x, _) if x < 960000 => 4,
(1000000, _) => 7,
(_, false) => 5,
(_, true) => 6,
}
}
85 changes: 63 additions & 22 deletions prpr/src/parse/pgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub struct PgrNote {
position_x: f32,
hold_time: f32,
speed: f32,
floor_position: f32,
#[allow(unused)]floor_position: f32,
}

#[derive(Deserialize)]
Expand All @@ -66,6 +66,7 @@ struct PgrJudgeLine {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct PgrChart {
format_version: u32,
offset: f32,
judge_line_list: Vec<PgrJudgeLine>,
}
Expand All @@ -80,11 +81,11 @@ macro_rules! validate_events {
true
}
});
for i in 0..($pgr.len() - 1) {
if $pgr[i].end_time != $pgr[i + 1].start_time {
ptl!(bail "event-not-contiguous");
}
}
// for i in 0..($pgr.len() - 1) {
// if $pgr[i].end_time != $pgr[i + 1].start_time {
// ptl!(bail "event-not-contiguous");
// }
// }
// if $pgr.last().unwrap().end_time <= 900000000.0 {
// bail!("End time is not great enough ({})", $pgr.last().unwrap().end_time);
// }
Expand All @@ -93,7 +94,7 @@ macro_rules! validate_events {

fn parse_speed_events(r: f32, mut pgr: Vec<PgrSpeedEvent>, max_time: f32) -> Result<(AnimFloat, AnimFloat)> {
validate_events!(pgr);
assert_eq!(pgr[0].start_time, 0.0);
pgr[0].start_time = 0.;
let mut kfs = Vec::new();
let mut pos = 0.;
kfs.extend(pgr[..pgr.len().saturating_sub(1)].iter().map(|it| {
Expand Down Expand Up @@ -150,7 +151,38 @@ fn parse_move_events(r: f32, mut pgr: Vec<PgrEvent>) -> Result<AnimVector> {
Ok(AnimVector(AnimFloat::new(kf1), AnimFloat::new(kf2)))
}

fn parse_notes(r: f32, mut pgr: Vec<PgrNote>, speed: &mut AnimFloat, height: &mut AnimFloat, above: bool) -> Result<Vec<Note>> {
fn parse_move_events_fv1(r: f32, mut pgr: Vec<PgrEvent>) -> Result<AnimVector> {
validate_events!(pgr);
let mut kf1 = Vec::<Keyframe<f32>>::new();
let mut kf2 = Vec::<Keyframe<f32>>::new();
for e in pgr {
let st = (e.start_time * r).max(0.);
let en = e.end_time * r;
if !kf1.last().map_or(false, |it| it.value == e.start) {
let start = (e.start - e.start % 1000.) / 1000.;
kf1.push(Keyframe::new(st, start, 2));
}
if !kf2.last().map_or(false, |it| it.value == e.start2) {
let start2 = e.start % 1000.;
kf2.push(Keyframe::new(st, start2, 2));
}
let end = (e.end - e.end % 1000.) / 1000.;
let end2 = e.end % 1000.;
kf1.push(Keyframe::new(en, end, 2));
kf2.push(Keyframe::new(en, end2, 2));
}
kf1.pop();
kf2.pop();
for kf in &mut kf1 {
kf.value = (-880. + kf.value * 2.) / 880.;
}
for kf in &mut kf2 {
kf.value = (-520. + kf.value * 2.) / 520.;
}
Ok(AnimVector(AnimFloat::new(kf1), AnimFloat::new(kf2)))
}

fn parse_notes(r: f32, mut pgr: Vec<PgrNote>, _speed: &mut AnimFloat, height: &mut AnimFloat, above: bool) -> Result<Vec<Note>> {
// is_sorted is unstable...
if pgr.is_empty() {
return Ok(Vec::new());
Expand All @@ -159,13 +191,16 @@ fn parse_notes(r: f32, mut pgr: Vec<PgrNote>, speed: &mut AnimFloat, height: &mu
pgr.into_iter()
.map(|pgr| {
let time = pgr.time * r;
let height = {
height.set_time(time);
height.now()
};
let kind = match pgr.kind {
1 => NoteKind::Click,
2 => NoteKind::Drag,
3 => {
let end_time = (pgr.time + pgr.hold_time) * r;
height.set_time(end_time);
let end_height = height.now();
let end_height = height + (pgr.hold_time * pgr.speed * r / HEIGHT_RATIO);
NoteKind::Hold { end_time, end_height }
}
4 => NoteKind::Flick,
Expand All @@ -180,13 +215,8 @@ fn parse_notes(r: f32, mut pgr: Vec<PgrNote>, speed: &mut AnimFloat, height: &mu
kind,
hitsound,
time,
speed: if pgr.kind == 3 {
speed.set_time(time);
pgr.speed / speed.now()
} else {
pgr.speed
},
height: pgr.floor_position / HEIGHT_RATIO,
speed: pgr.speed,
height,

above,
multiple_hint: false,
Expand All @@ -197,7 +227,7 @@ fn parse_notes(r: f32, mut pgr: Vec<PgrNote>, speed: &mut AnimFloat, height: &mu
.collect()
}

fn parse_judge_line(pgr: PgrJudgeLine, max_time: f32) -> Result<JudgeLine> {
fn parse_judge_line(pgr: PgrJudgeLine, max_time: f32, format_version: u32) -> Result<JudgeLine> {
let r = 60. / pgr.bpm / 32.;
let (mut speed, mut height) = parse_speed_events(r, pgr.speed_events, max_time).context("Failed to parse speed events")?;
let notes_above = parse_notes(r, pgr.notes_above, &mut speed, &mut height, true).context("Failed to parse notes above")?;
Expand All @@ -209,7 +239,13 @@ fn parse_judge_line(pgr: PgrJudgeLine, max_time: f32) -> Result<JudgeLine> {
object: Object {
alpha: parse_float_events(r, pgr.alpha_events).with_context(|| ptl!("alpha-events-parse-failed"))?,
rotation: parse_float_events(r, pgr.rotate_events).with_context(|| ptl!("rotate-events-parse-failed"))?,
translation: parse_move_events(r, pgr.move_events).with_context(|| ptl!("move-events-parse-failed"))?,
translation: {
match format_version {
1 => parse_move_events_fv1(r, pgr.move_events).with_context(|| ptl!("move-events-parse-failed"))?,
3 => parse_move_events(r, pgr.move_events).with_context(|| ptl!("move-events-parse-failed"))?,
_ => ptl!(bail "unknown-format-version"),
}
},
..Default::default()
},
ctrl_obj: RefCell::default(),
Expand All @@ -220,7 +256,7 @@ fn parse_judge_line(pgr: PgrJudgeLine, max_time: f32) -> Result<JudgeLine> {
color: Anim::default(),
parent: None,
z_index: 0,
show_below: true,
show_below: false,
attach_ui: None,

cache,
Expand All @@ -229,6 +265,11 @@ fn parse_judge_line(pgr: PgrJudgeLine, max_time: f32) -> Result<JudgeLine> {

pub fn parse_phigros(source: &str, extra: ChartExtra) -> Result<Chart> {
let pgr: PgrChart = serde_json::from_str(source).with_context(|| ptl!("json-parse-failed"))?;
let format_version = pgr.format_version;
let mut bpm_values = Vec::new();
for (index, judge_line) in pgr.judge_line_list.iter().enumerate() {
bpm_values.push((index as f32, judge_line.bpm));
}
let max_time = *pgr
.judge_line_list
.iter()
Expand All @@ -248,8 +289,8 @@ pub fn parse_phigros(source: &str, extra: ChartExtra) -> Result<Chart> {
.judge_line_list
.into_iter()
.enumerate()
.map(|(id, pgr)| parse_judge_line(pgr, max_time).with_context(|| ptl!("judge-line-location", "jlid" => id)))
.map(|(id, pgr)| parse_judge_line(pgr, max_time, format_version).with_context(|| ptl!("judge-line-location", "jlid" => id)))
.collect::<Result<Vec<_>>>()?;
process_lines(&mut lines);
Ok(Chart::new(pgr.offset, lines, BpmList::default(), ChartSettings::default(), extra, HashMap::new()))
Ok(Chart::new(pgr.offset, lines, BpmList::new_time(bpm_values), ChartSettings::default(), extra, HashMap::new()))
}
Loading
Loading