From 1f77e2e5e82cb8a179f4b1f54a230ebefbf43343 Mon Sep 17 00:00:00 2001 From: Irastris Date: Sun, 12 Apr 2026 12:17:39 -0400 Subject: [PATCH] Frame interp: Fix stars --- include/dusk/frame_interpolation.h | 4 ++ src/d/d_camera.cpp | 4 +- src/d/d_kankyo_rain.cpp | 60 ++++++++++++++++++-- src/dusk/frame_interpolation.cpp | 88 ++++++++++++++++++++++++++++-- src/f_ap/f_ap_game.cpp | 1 + 5 files changed, 145 insertions(+), 12 deletions(-) diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 98495d18db..5c2dc2e1b8 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -7,6 +7,7 @@ #include struct cXyz; +class camera_process_class; #ifdef __cplusplus namespace dusk { @@ -14,6 +15,7 @@ namespace frame_interp { void ensure_initialized(); +void begin_record_camera(); void begin_record(); void end_record(); void interpolate(float step); @@ -25,12 +27,14 @@ void end_presentation_ui_pass(); void open_child(const void* key, int32_t id); void close_child(); +void record_camera(::camera_process_class* cam, int camera_id); void record_final_mtx_raw(const Mtx* dest, const Mtx src); bool lookup_replacement(const void* source, Mtx out); bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out); void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye); +bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, float* o_fovy); } // namespace frame_interp } // namespace dusk diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 0047978e7e..955683e05b 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -11042,8 +11042,8 @@ static int camera_draw(camera_process_class* i_this) { mDoMtx_lookAt(process->view.viewMtx, &process->view.lookat.eye, &process->view.lookat.center, &process->view.lookat.up, process->view.bank); #ifdef TARGET_PC - dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(process->view.viewMtx), - process->view.viewMtx); + dusk::frame_interp::record_camera(process, camera_id); + dusk::frame_interp::record_final_mtx_raw(reinterpret_cast(process->view.viewMtx), process->view.viewMtx); #endif #if WIDESCREEN_SUPPORT diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 4814576c31..953768a525 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -12,6 +12,9 @@ #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_lib.h" #include +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif static void vectle_calc(DOUBLE_POS* i_pos, cXyz* o_out) { double s = sqrt(i_pos->x * i_pos->x + i_pos->y * i_pos->y + i_pos->z * i_pos->z); @@ -4107,28 +4110,62 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { color_reg0.b = 0xFF; color_reg0.a = 0xFF; +#if TARGET_PC + Mtx star_gx_view; + cXyz anchor_eye; + f32 star_fovy = 45.0f; + MtxP gx_load_view = drawMtx; + bool star_use_present_view = false; + + if (dusk::getSettings().game.enableFrameInterpolation) { + star_use_present_view = dusk::frame_interp::build_star_view(star_gx_view, camMtx, &anchor_eye, &star_fovy); + } + + if (star_use_present_view) { + gx_load_view = star_gx_view; + } else { + if (dComIfGd_getView() != NULL) { + MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); + anchor_eye = camera->view.lookat.eye; + star_fovy = dComIfGd_getView()->fovy; + } else { + return; + } + } +#else if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); } else { return; } +#endif if (strcmp(dComIfGp_getStartStageName(), "F_SP200") == 0 && dComIfG_play_c::getLayerNo(0) == 0) { moon_pos = envlight->moon_pos; } else { +#if TARGET_PC + moon_pos = anchor_eye + envlight->moon_pos; +#else moon_pos = camera->view.lookat.eye + envlight->moon_pos; +#endif if (sp38) { +#if TARGET_PC + moon_pos.x = 3900.0f + anchor_eye.x; + moon_pos.y = 8052.0f + anchor_eye.y; + moon_pos.z = -9072.0f + anchor_eye.z; +#else moon_pos.x = 3900.0f + camera->view.lookat.eye.x; moon_pos.y = 8052.0f + camera->view.lookat.eye.y; moon_pos.z = -9072.0f + camera->view.lookat.eye.z; +#endif } } - #if TARGET_PC +#if TARGET_PC mDoLib_project(&moon_pos, &moon_proj, {0, 0, FB_WIDTH, FB_HEIGHT}); - #else +#else mDoLib_project(&moon_pos, &moon_proj); - #endif +#endif // Dusk optimization: we use vertex color rather than GX_TEVREG0 to set star color. // This allows us to merge all the stars into a single draw. @@ -4156,7 +4193,11 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { MTXRotRad(rotMtx, 'Z', DEG_TO_RAD(rot)); MTXConcat(camMtx, rotMtx, camMtx); +#if TARGET_PC + GXLoadPosMtxImm(gx_load_view, GX_PNMTX0); +#else GXLoadPosMtxImm(drawMtx, GX_PNMTX0); +#endif GXSetCurrentMtx(GX_PNMTX0); rot += 0.65f; @@ -4164,12 +4205,23 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { rot = 0.0f; } +#if TARGET_PC + spBC = anchor_eye; +#else spBC.x = camera->view.lookat.eye.x; spBC.y = camera->view.lookat.eye.y; spBC.z = camera->view.lookat.eye.z; +#endif f32 sp34 = -1.0f; int sp30 = 0; +#if TARGET_PC + f32 var_f30 = star_fovy / 45.0f; + if (var_f30 >= 1.0f) { + var_f30 = 1.0f; + } + var_f30 = 1.0f - var_f30; +#else f32 var_f30 = 0.0f; if (dComIfGd_getView() != NULL) { @@ -4179,7 +4231,7 @@ void dKyr_drawStar(Mtx drawMtx, u8** tex) { } var_f30 = 1.0f - var_f30; } - +#endif f32 temp_f27 = 0.28f * (1.0f - var_f30); sp98.x = 0.0f; diff --git a/src/dusk/frame_interpolation.cpp b/src/dusk/frame_interpolation.cpp index 0c21b2b5af..e9b35da1f0 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -1,3 +1,4 @@ +#include "f_op/f_op_camera_mng.h" #include "dusk/frame_interpolation.h" #include @@ -8,7 +9,6 @@ #include namespace { - enum class Op : uint8_t { OpenChild, FinalMtx, @@ -90,6 +90,13 @@ inline void lerp_matrix(Mtx out, const Mtx lhs, const Mtx rhs, float step) { } } +inline void lerp_xyz(cXyz* out, const cXyz& lhs, const cXyz& rhs, float step) { + const float old_weight = 1.0f - step; + out->x = lhs.x * old_weight + rhs.x * step; + out->y = lhs.y * old_weight + rhs.y * step; + out->z = lhs.z * old_weight + rhs.z * step; +} + inline bool matrix_differs(const Mtx lhs, const Mtx rhs, float epsilon = 0.0001f) { for (size_t row = 0; row < 3; ++row) { for (size_t col = 0; col < 4; ++col) { @@ -251,9 +258,7 @@ void clear_replacements() { } // namespace -namespace dusk { -namespace frame_interp { - +namespace dusk::frame_interp { void ensure_initialized() { g_enabled = getSettings().game.enableFrameInterpolation; s_initialized = true; @@ -405,5 +410,76 @@ void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye) { o_eye->z = -(view_mtx[0][2] * view_mtx[0][3] + view_mtx[1][2] * view_mtx[1][3] + view_mtx[2][2] * view_mtx[2][3]); } -} // namespace frame_interp -} // namespace dusk +namespace { +struct CamSnap { + cXyz eye{}; + cXyz center{}; + cXyz up{}; + s16 bank{}; + f32 fovy{}; + bool valid{}; +}; + +CamSnap s_star_prev{}; +CamSnap s_star_curr{}; + +static void copy_view_to_snap(CamSnap* dst, const view_class& v) { + dst->eye = v.lookat.eye; + dst->center = v.lookat.center; + dst->up = v.lookat.up; + dst->bank = v.bank; + dst->fovy = v.fovy; + dst->valid = true; +} + +static void billboard_base_from_view(MtxP view_mtx, MtxP o_cam_billboard_base) { + Mtx rot; + MTXCopy(view_mtx, rot); + rot[0][3] = rot[1][3] = rot[2][3] = 0.0f; + MTXInverse(rot, o_cam_billboard_base); +} +} // namespace + +void begin_record_camera() { + ::camera_process_class* cam = dComIfGp_getCamera(0); + if (cam == nullptr) { + return; + } + copy_view_to_snap(&s_star_prev, cam->view); +} + +void record_camera(::camera_process_class* cam, int camera_id) { + if (!getSettings().game.enableFrameInterpolation || camera_id != 0 || cam == nullptr) { + return; + } + copy_view_to_snap(&s_star_curr, cam->view); +} + +bool build_star_view(Mtx o_view, Mtx o_cam_billboard_base, cXyz* o_anchor_eye, float* o_fovy) { + if (!getSettings().game.enableFrameInterpolation || !s_star_prev.valid || !s_star_curr.valid) { + return false; + } + + const f32 step = get_interpolation_step(); + cXyz eye; + cXyz center; + cXyz up; + lerp_xyz(&eye, s_star_prev.eye, s_star_curr.eye, step); + lerp_xyz(¢er, s_star_prev.center, s_star_curr.center, step); + lerp_xyz(&up, s_star_prev.up, s_star_curr.up, step); + if (!up.normalizeRS()) { + up = s_star_curr.up; + up.normalizeRS(); + } + + const f32 bank_rad = S2RAD(s_star_prev.bank) * (1.0f - step) + S2RAD(s_star_curr.bank) * step; + const s16 bank = cAngle::Radian_to_SAngle(bank_rad); + + mDoMtx_lookAt(o_view, &eye, ¢er, &up, bank); + billboard_base_from_view(o_view, o_cam_billboard_base); + + *o_anchor_eye = eye; + *o_fovy = s_star_prev.fovy + (s_star_curr.fovy - s_star_prev.fovy) * step; + return true; +} +} // namespace dusk::frame_interp diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 8e2b0a367c..f54ca1edad 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -725,6 +725,7 @@ void fapGm_After() { #ifdef TARGET_PC static void fapGm_Before() { + dusk::frame_interp::begin_record_camera(); dusk::frame_interp::begin_record(); }