diff --git a/phira/src/scene/unlock.rs b/phira/src/scene/unlock.rs index d3040a71..d70283b5 100644 --- a/phira/src/scene/unlock.rs +++ b/phira/src/scene/unlock.rs @@ -193,7 +193,7 @@ impl Scene for UnlockScene { match self.state { State::Playing => { if t > 0.05 { - self.video.render(t, asp, WHITE); + self.video.render(t, asp, WHITE, None); } } State::Loading => { diff --git a/prpr/src/bin.rs b/prpr/src/bin.rs index db268e2a..44cc021d 100644 --- a/prpr/src/bin.rs +++ b/prpr/src/bin.rs @@ -460,14 +460,16 @@ impl BinaryData for JudgeLine { impl BinaryData for ChartSettings { fn read_binary(r: &mut BinaryReader) -> Result { + let pe_byte = r.read::()?; Ok(Self { - pe_alpha_extension: r.read::()? == 1, + pe_alpha_extension: pe_byte & 1 != 0, hold_partial_cover: r.read::()? == 1, + line_reference_y_axis: pe_byte >> 1 & 1 != 0, }) } fn write_binary(&self, w: &mut BinaryWriter) -> Result<()> { - w.write_val(self.pe_alpha_extension as u8)?; + w.write_val((self.pe_alpha_extension as u8) | ((self.line_reference_y_axis as u8) << 1))?; w.write_val(self.hold_partial_cover as u8)?; Ok(()) } diff --git a/prpr/src/core/chart.rs b/prpr/src/core/chart.rs index 898db21f..9aab0ba8 100644 --- a/prpr/src/core/chart.rs +++ b/prpr/src/core/chart.rs @@ -18,6 +18,7 @@ pub struct ChartExtra { pub struct ChartSettings { pub pe_alpha_extension: bool, pub hold_partial_cover: bool, + pub line_reference_y_axis: bool, } pub type HitSoundMap = HashMap; @@ -143,20 +144,31 @@ impl Chart { } pub fn render(&self, ui: &mut Ui, res: &mut Resource) { - #[cfg(feature = "video")] - for (video, attach) in &self.extra.videos { - if let Some(attach) = attach { - let line = &self.lines[attach.line]; - let color = line.color.now_opt().unwrap_or(res.judge_line_color); - let mat = self.lines[attach.line].object.now(res); - res.apply_model_of(&mat, |res| { - video.render(res.time, res.aspect_ratio, color); - }); - } else { - video.render(res.time, res.aspect_ratio, WHITE); - } - } res.apply_model_of(&Matrix::identity().append_nonuniform_scaling(&Vector::new(if res.config.flip_x() { -1. } else { 1. }, -1.)), |res| { + #[cfg(feature = "video")] + for (video, attach) in &self.extra.videos { + if let Some(attach) = attach { + let line = &self.lines[attach.line]; + let color = line.color.now_opt().unwrap_or(res.judge_line_color); + let line_scale = line.object.scale.now_with_def(1.0, 1.0); + let mat = Rotation2::new( + self.lines[attach.line].fetch_rot(&self.lines).to_radians() * attach.rotation_factor).to_homogeneous() + .append_translation(&self.lines[attach.line].fetch_pos(res, &self.lines).component_mul(&Vector::new(attach.position_x_factor, attach.position_y_factor))); + res.apply_model_of(&mat.append_nonuniform_scaling(&Vector::new(1., -1.)), |res| { + video.render( + res.time, + res.aspect_ratio, + color, + Some((line_scale.x, line_scale.y, attach.scale_x_mode, attach.scale_y_mode)), + ); + }); + } else { + res.apply_model_of(&Matrix::identity().append_nonuniform_scaling(&Vector::new(1., -1.)), |res| { + video.render(res.time, res.aspect_ratio, WHITE, None); + }); + } + } + let mut guard = self.bpm_list.borrow_mut(); for id in &self.order { self.lines[*id].render(ui, res, &self.lines, &mut guard, &self.settings, *id); diff --git a/prpr/src/core/line.rs b/prpr/src/core/line.rs index b2c19154..223f9f34 100644 --- a/prpr/src/core/line.rs +++ b/prpr/src/core/line.rs @@ -236,7 +236,6 @@ impl JudgeLine { pub fn render(&self, ui: &mut Ui, res: &mut Resource, lines: &[JudgeLine], bpm_list: &mut BpmList, settings: &ChartSettings, id: usize) { let alpha = self.object.alpha.now_opt().unwrap_or(1.0) * res.alpha; let color = self.color.now_opt(); - let line_scaled = (self.object.scale.1.now() - 1.).abs() > 1e-4; res.with_model(self.now_transform(res, lines), |res| { if res.config.chart_debug { res.apply_model(|_| { @@ -248,8 +247,17 @@ impl JudgeLine { JudgeLineKind::Normal => { let mut color = color.unwrap_or(res.judge_line_color); color.a *= alpha.max(0.0); - let len = res.info.line_length; - draw_line(-len, 0., len, 0., if line_scaled { 0.0076 } else { 0.01 }, color); + let len = if settings.line_reference_y_axis { + res.info.line_length / res.aspect_ratio + } else { + res.info.line_length + }; + let thickness = if settings.line_reference_y_axis { + 0.0150 / res.aspect_ratio + } else { + 0.0100 + }; + draw_line(-len, 0., len, 0., thickness, color); } JudgeLineKind::Texture(texture, _) => { let mut color = color.unwrap_or(WHITE); diff --git a/prpr/src/core/video.rs b/prpr/src/core/video.rs index 4da1f512..a72447bf 100644 --- a/prpr/src/core/video.rs +++ b/prpr/src/core/video.rs @@ -12,9 +12,30 @@ thread_local! { static VIDEO_BUFFERS: RefCell<[Vec; 3]> = RefCell::default(); } +fn f32_one() -> f32 { + 1. +} + + + #[derive(Deserialize)] +#[serde(rename_all = "camelCase")] pub struct VideoAttach { pub line: usize, + #[serde(default = "f32_one")] + pub position_x_factor: f32, + #[serde(default = "f32_one")] + pub position_y_factor: f32, + #[serde(default = "f32_one")] + pub rotation_factor: f32, + #[serde(default = "f32_one")] + pub alpha_factor: f32, + #[serde(default = "f32_one")] + pub tint_factor: f32, + #[serde(default)] + pub scale_x_mode: u8, + #[serde(default)] + pub scale_y_mode: u8, } pub struct Video { @@ -131,7 +152,7 @@ impl Video { Ok(()) } - pub fn render(&self, t: f64, aspect_ratio: f32, color: Color) { + pub fn render(&self, t: f64, aspect_ratio: f32, color: Color, line_scale: Option<(f32, f32, u8, u8)>) { if !(0f64..self.duration).contains(&(t - self.start_time)) { return; } @@ -141,6 +162,57 @@ impl Video { let s = source_of_image(&self.tex_y, r, self.scale_type).unwrap_or_else(|| Rect::new(0., 0., 1., 1.)); let dim = 1. - self.dim.now(); let color = Color::new(dim * color.r, dim * color.g, dim * color.b, self.alpha.now_opt().unwrap_or(1.) * color.a); + + let (r, s) = if let Some((sx, sy, mode_x, mode_y)) = line_scale { + match (mode_x, mode_y) { + (0, 0) => (r, s), + _ => { + let mut r = r; + let mut s = s; + + // Scale mode: scale vertices, mirror with negative values + if mode_x == 1 { + r = Rect::new(r.x, r.y, r.w * sx.abs(), r.h); + if sx < 0. { + s = Rect::new(s.right(), s.y, -s.w, s.h); + } + } + if mode_y == 1 { + r = Rect::new(r.x, r.y, r.w, r.h * sy.abs()); + if sy < 0. { + s = Rect::new(s.x, s.bottom(), s.w, -s.h); + } + } + + // Clip mode: clip both vertices and texture proportionally + if mode_x == 2 { + let clip = sx.abs().min(1.0); + let offset_r = r.w * (1.0 - clip) / 2.0; + let offset_s = s.w * (1.0 - clip) / 2.0; + r = Rect::new(r.x + offset_r, r.y, r.w * clip, r.h); + s = Rect::new(s.x + offset_s, s.y, s.w * clip, s.h); + if sx < 0. { + s = Rect::new(s.right(), s.y, -s.w, s.h); + } + } + if mode_y == 2 { + let clip = sy.abs().min(1.0); + let offset_r = r.h * (1.0 - clip) / 2.0; + let offset_s = s.h * (1.0 - clip) / 2.0; + r = Rect::new(r.x, r.y + offset_r, r.w, r.h * clip); + s = Rect::new(s.x, s.y + offset_s, s.w, s.h * clip); + if sy < 0. { + s = Rect::new(s.x, s.bottom(), s.w, -s.h); + } + } + + (r, s) + } + } + } else { + (r, s) + }; + let vertices = [ Vertex::new(r.x, r.y, 0., s.x, s.y, color), Vertex::new(r.right(), r.y, 0., s.right(), s.y, color), diff --git a/prpr/src/parse/pgr.rs b/prpr/src/parse/pgr.rs index ad731b74..ce102131 100644 --- a/prpr/src/parse/pgr.rs +++ b/prpr/src/parse/pgr.rs @@ -239,6 +239,7 @@ fn parse_judge_line(pgr: PgrJudgeLine, max_time: f64, format_version: u32) -> Re Ok(JudgeLine { object: Object { alpha: parse_float_events(r, pgr.alpha_events).with_context(|| ptl!("alpha-events-parse-failed"))?, + scale: AnimVector(AnimFloat::fixed(5.75 / 6.0), AnimFloat::default()), rotation: parse_float_events(r, pgr.rotate_events).with_context(|| ptl!("rotate-events-parse-failed"))?, translation: { match format_version { @@ -246,8 +247,7 @@ fn parse_judge_line(pgr: PgrJudgeLine, max_time: f64, format_version: u32) -> Re 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(), kind: JudgeLineKind::Normal, @@ -293,5 +293,15 @@ pub fn parse_phigros(source: &str, extra: ChartExtra) -> Result { .collect::>>()?; process_lines(&mut lines); - Ok(Chart::new(pgr.offset, lines, BpmList::default(), ChartSettings::default(), extra, HashMap::new())) + Ok(Chart::new( + pgr.offset, + lines, + BpmList::default(), + ChartSettings { + line_reference_y_axis: true, + ..Default::default() + }, + extra, + HashMap::new() + )) } diff --git a/prpr/src/parse/rpe.rs b/prpr/src/parse/rpe.rs index a13648b4..365eb1b9 100644 --- a/prpr/src/parse/rpe.rs +++ b/prpr/src/parse/rpe.rs @@ -11,7 +11,7 @@ use crate::{ core::{ Anim, AnimFloat, AnimVector, BezierTween, BpmList, Chart, ChartExtra, ChartSettings, ClampedTween, CtrlObject, GeneralIntTween, GifFrames, HitSoundMap, IntClampedTween, IntStaticTween, JudgeLine, JudgeLineCache, JudgeLineKind, Keyframe, Note, NoteKind, Object, StaticTween, - Triple, TweenFunction, Tweenable, UIElement, EPS, HEIGHT_RATIO, + Triple, TweenFunction, Tweenable, UIElement, Vector, EPS, HEIGHT_RATIO, }, ext::{NotNanExt, SafeTexture}, fs::FileSystem, @@ -685,11 +685,17 @@ async fn parse_judge_line( .as_ref() .map(|it| parse_events(r, it, None, bezier_map)) .transpose()? - .unwrap_or_default(); + .unwrap_or( + if factor == 1. { + Anim::default() + } else { + Anim::fixed(1.0) + } + ); res.map_value(|v| v * factor); Ok(res) } - let factor = if rpe.texture == "line.png" { 1. } else { 2. / RPE_WIDTH }; + let image_factor = if rpe.texture == "line.png" { 1. } else { 2. / RPE_WIDTH }; rpe.extended .as_ref() .map(|e| -> Result<_> { @@ -697,26 +703,14 @@ async fn parse_judge_line( parse( r, &e.scale_x_events, - factor - * if rpe.texture == "line.png" - && rpe - .extended - .as_ref() - .and_then(|it| it.text_events.as_ref()) - .is_none_or(|it| it.is_empty()) - && rpe.attach_ui.is_none() - { - 0.5 - } else { - 1. - }, + image_factor, bezier_map, )?, - parse(r, &e.scale_y_events, factor, bezier_map)?, + parse(r, &e.scale_y_events, image_factor, bezier_map)?, )) }) .transpose()? - .unwrap_or_default() + .unwrap_or(AnimVector::fixed(Vector::new(image_factor, image_factor))) }, }, ctrl_obj: RefCell::new(CtrlObject { diff --git a/prpr/src/scene/game.rs b/prpr/src/scene/game.rs index 3a679747..a877a458 100644 --- a/prpr/src/scene/game.rs +++ b/prpr/src/scene/game.rs @@ -11,12 +11,12 @@ use super::{ use crate::{ bin::BinaryReader, config::{Config, Mods}, - core::{copy_fbo, BadNote, Chart, ChartExtra, Effect, Point, Resource, UIElement, Vector, PGR_FONT}, - ext::{parse_time, screen_aspect, semi_white, RectExt, SafeTexture, ScaleType}, + core::{BadNote, Chart, ChartExtra, Effect, PGR_FONT, Point, Resource, UIElement, Vector, copy_fbo}, + ext::{RectExt, SafeTexture, ScaleType, parse_time, screen_aspect, semi_white}, fs::FileSystem, info::{ChartFormat, ChartInfo}, judge::Judge, - parse::{parse_extra, parse_pec, parse_phigros, parse_rpe}, + parse::{RPE_WIDTH, parse_extra, parse_pec, parse_phigros, parse_rpe}, task::Task, time::TimeManager, ui::{RectButton, TextPainter, Ui}, @@ -293,6 +293,10 @@ impl GameScene { .await .context("Failed to load resources")?; + if matches!(chart_format, ChartFormat::Rpe) { + res.info.line_length *= 4000. / RPE_WIDTH / 6.; + } + // Prepare extra sfx from chart.hitsounds chart.hitsounds.drain().for_each(|(name, clip)| { if let Ok(clip) = res.create_sfx(clip) {