From 433f058aeae4db50b0d7a915065e44b91e431544 Mon Sep 17 00:00:00 2001 From: Irastris Date: Wed, 15 Apr 2026 20:11:18 -0400 Subject: [PATCH] Frame interp: Revised camera system FOV is now smooth, and the individual hacks for stars and Epona's reins are removed --- include/dusk/frame_interpolation.h | 15 +- src/d/d_camera.cpp | 1 - src/d/d_kankyo_rain.cpp | 50 ------- src/dusk/frame_interpolation.cpp | 219 ++++++++++++++++++++--------- src/f_ap/f_ap_game.cpp | 1 - src/m_Do/m_Do_graphic.cpp | 4 +- src/m_Do/m_Do_main.cpp | 5 +- 7 files changed, 168 insertions(+), 127 deletions(-) diff --git a/include/dusk/frame_interpolation.h b/include/dusk/frame_interpolation.h index 3aadfe8520..383d16d288 100644 --- a/include/dusk/frame_interpolation.h +++ b/include/dusk/frame_interpolation.h @@ -6,7 +6,6 @@ #include #include -struct cXyz; class camera_process_class; #ifdef __cplusplus @@ -15,7 +14,6 @@ namespace frame_interp { void ensure_initialized(); -void begin_record_camera(); void begin_record(); void end_record(); void interpolate(float step); @@ -39,8 +37,17 @@ void record_final_mtx_raw_tagged(const Mtx* dest, const Mtx src, uint64_t stable 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); +void begin_presentation_camera(); +void end_presentation_camera(); + +struct PresentationCameraScope { + PresentationCameraScope() { begin_presentation_camera(); } + ~PresentationCameraScope() { end_presentation_camera(); } + PresentationCameraScope(const PresentationCameraScope&) = delete; + PresentationCameraScope& operator=(const PresentationCameraScope&) = delete; + PresentationCameraScope(PresentationCameraScope&&) = delete; + PresentationCameraScope& operator=(PresentationCameraScope&&) = delete; +}; uint64_t alloc_simple_shadow_pair_base(); } // namespace frame_interp diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 7b95a0d8ea..b93d569003 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -11079,7 +11079,6 @@ static int camera_draw(camera_process_class* i_this) { &process->view.lookat.up, process->view.bank); #ifdef TARGET_PC 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 953768a525..31ba59e33e 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -4110,54 +4110,20 @@ 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 } } @@ -4193,11 +4159,7 @@ 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; @@ -4205,23 +4167,12 @@ 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) { @@ -4231,7 +4182,6 @@ 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 0fa7d036e0..e3de48bcfc 100644 --- a/src/dusk/frame_interpolation.cpp +++ b/src/dusk/frame_interpolation.cpp @@ -1,12 +1,7 @@ -#include "f_op/f_op_camera_mng.h" #include "dusk/frame_interpolation.h" -#include -#include -#include #include -#include -#include +#include "f_op/f_op_camera_mng.h" namespace { enum class Op : uint8_t { @@ -80,6 +75,36 @@ std::vector g_current_path; std::unordered_map g_replacements; +struct CameraSnapshot { + cXyz eye{}; + cXyz center{}; + cXyz up{}; + s16 bank{}; + f32 fovy{}; + f32 aspect{}; + f32 near_{}; + f32 far_{}; + bool valid{}; +}; + +CameraSnapshot s_cam_prev{}; +CameraSnapshot s_cam_curr{}; + +view_class s_presentation_view_backup{}; +int s_presentation_depth = 0; + +void copy_view_to_snap(CameraSnapshot* 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->aspect = v.aspect; + dst->near_ = v.near_; + dst->far_ = v.far_; + dst->valid = true; +} + inline void copy_matrix(const Mtx src, Mtx dst) { MTXCopy(src, dst); } @@ -104,6 +129,12 @@ inline void lerp_xyz(cXyz* out, const cXyz& lhs, const cXyz& rhs, float step) { out->z = lhs.z * old_weight + rhs.z * step; } +static s16 lerp_bank(s16 a, s16 b, f32 t) { + const f32 ra = S2RAD(a); + const f32 d = remainderf(S2RAD(b) - ra, 2.0f * static_cast(M_PI)); + return cAngle::Radian_to_SAngle(ra + d * t); +} + 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) { @@ -287,6 +318,7 @@ void ensure_initialized() { void begin_record() { ensure_initialized(); + if (!g_enabled) { g_interpolating = false; g_sync_presentation = false; @@ -294,6 +326,8 @@ void begin_record() { g_current_recording = {}; g_current_path.clear(); clear_replacements(); + s_cam_prev.valid = false; + s_cam_curr.valid = false; return; } @@ -305,6 +339,15 @@ void begin_record() { g_recording = true; g_interpolating = false; clear_replacements(); + + ::camera_process_class* cam = dComIfGp_getCamera(0); + if (cam == nullptr) { + s_cam_prev.valid = false; + s_cam_curr.valid = false; + return; + } else { + copy_view_to_snap(&s_cam_prev, cam->view); + } } void end_record() { @@ -454,84 +497,126 @@ bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out) { return true; } -// TODO: Is there already a built-in function for this? -void camera_eye_from_view_mtx(MtxP view_mtx, cXyz* o_eye) { - o_eye->x = -(view_mtx[0][0] * view_mtx[0][3] + view_mtx[1][0] * view_mtx[1][3] + view_mtx[2][0] * view_mtx[2][3]); - o_eye->y = -(view_mtx[0][1] * view_mtx[0][3] + view_mtx[1][1] * view_mtx[1][3] + view_mtx[2][1] * view_mtx[2][3]); - 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 { -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) { + if (!g_enabled || camera_id != 0 || cam == nullptr) { return; } - copy_view_to_snap(&s_star_curr, cam->view); + copy_view_to_snap(&s_cam_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; +void begin_presentation_camera() { + ensure_initialized(); + if (!g_enabled) { + return; } + if (s_presentation_depth > 0) { + s_presentation_depth++; + return; + } + if (!s_cam_prev.valid || !s_cam_curr.valid) { + return; + } + + view_class* const view = dComIfGd_getView(); + if (view == nullptr) { + return; + } + + std::memcpy(&s_presentation_view_backup, view, sizeof(view_class)); 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); + lerp_xyz(&eye, s_cam_prev.eye, s_cam_curr.eye, step); + lerp_xyz(¢er, s_cam_prev.center, s_cam_curr.center, step); + lerp_xyz(&up, s_cam_prev.up, s_cam_curr.up, step); if (!up.normalizeRS()) { - up = s_star_curr.up; + up = s_cam_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); + view->lookat.eye = eye; + view->lookat.center = center; + view->lookat.up = up; + view->bank = lerp_bank(s_cam_prev.bank, s_cam_curr.bank, step); + view->fovy = s_cam_prev.fovy + (s_cam_curr.fovy - s_cam_prev.fovy) * step; + view->aspect = s_cam_prev.aspect + (s_cam_curr.aspect - s_cam_prev.aspect) * step; + view->near_ = s_cam_prev.near_ + (s_cam_curr.near_ - s_cam_prev.near_) * step; + view->far_ = s_cam_prev.far_ + (s_cam_curr.far_ - s_cam_prev.far_) * step; - mDoMtx_lookAt(o_view, &eye, ¢er, &up, bank); - billboard_base_from_view(o_view, o_cam_billboard_base); + // FRAME INTERP TODO: Largely copied from d_camera's camera_draw function from this point, got any better ideas? + C_MTXPerspective(view->projMtx, view->fovy, view->aspect, view->near_, view->far_); + mDoMtx_lookAt(view->viewMtx, &view->lookat.eye, &view->lookat.center, &view->lookat.up, view->bank); +#if WIDESCREEN_SUPPORT + mDoGph_gInf_c::setWideZoomProjection(view->projMtx); +#endif + j3dSys.setViewMtx(view->viewMtx); + cMtx_inverse(view->viewMtx, view->invViewMtx); - *o_anchor_eye = eye; - *o_fovy = s_star_prev.fovy + (s_star_curr.fovy - s_star_prev.fovy) * step; - return true; + bool camera_attention_status = dComIfGp_getCameraAttentionStatus(0) & 0x80; + Z2GetAudience()->setAudioCamera(view->viewMtx, view->lookat.eye, view->lookat.center, view->fovy, view->aspect, camera_attention_status, 0, false); + + dBgS_GndChk gndchk; + gndchk.OnWaterGrp(); + gndchk.SetPos(&view->lookat.eye); + f32 cross = dComIfG_Bgsp().GroundCross(&gndchk); + if (cross != -G_CM3D_F_INF) { + if (dComIfG_Bgsp().ChkGrpInf(gndchk, 0x100)) { + mDoAud_getCameraMapInfo(6); + } else { + mDoAud_getCameraMapInfo(dComIfG_Bgsp().GetMtrlSndId(gndchk)); + } + mDoAud_setCameraGroupInfo(dComIfG_Bgsp().GetGrpSoundId(gndchk)); + Vec spDC; + spDC.x = view->lookat.eye.x; + spDC.y = cross; + spDC.z = view->lookat.eye.z; + Z2AudioMgr::getInterface()->setCameraPolygonPos(&spDC); + } else { + Z2AudioMgr::getInterface()->setCameraPolygonPos(nullptr); + } + + MTXCopy(view->viewMtx, view->viewMtxNoTrans); + view->viewMtxNoTrans[0][3] = 0.0f; + view->viewMtxNoTrans[1][3] = 0.0f; + view->viewMtxNoTrans[2][3] = 0.0f; + cMtx_concatProjView(view->projMtx, view->viewMtx, view->projViewMtx); + + f32 far_; + f32 var_f30; + if (dComIfGp_getCameraAttentionStatus(0) & 8) { + far_ = view->far_; + } else { +#if DEBUG + if (g_envHIO.mOther.mAdjustCullFar != 0) { + var_f30 = g_envHIO.mOther.mCullFarValue; + } else +#endif + { + var_f30 = dStage_stagInfo_GetCullPoint(dComIfGp_getStageStagInfo()); + } + far_ = var_f30; + } + + mDoLib_clipper::setup(view->fovy, view->aspect, view->near_, far_); + + s_presentation_depth = 1; +} + +void end_presentation_camera() { + if (s_presentation_depth == 0) { + return; + } + s_presentation_depth--; + if (s_presentation_depth > 0) { + return; + } + + view_class* const view = dComIfGd_getView(); + if (view != nullptr) { + std::memcpy(view, &s_presentation_view_backup, sizeof(view_class)); + } } uint64_t alloc_simple_shadow_pair_base() { diff --git a/src/f_ap/f_ap_game.cpp b/src/f_ap/f_ap_game.cpp index 84d2ab8e97..93a9148957 100644 --- a/src/f_ap/f_ap_game.cpp +++ b/src/f_ap/f_ap_game.cpp @@ -725,7 +725,6 @@ void fapGm_After() { #ifdef TARGET_PC static void fapGm_Before() { - dusk::frame_interp::begin_record_camera(); dusk::frame_interp::begin_record(); } diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index 92c8707782..5440d63da8 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -2229,13 +2229,11 @@ int mDoGph_Painter() { #if TARGET_PC if (dusk::getSettings().game.enableFrameInterpolation) { - cXyz pres_eye; - dusk::frame_interp::camera_eye_from_view_mtx(j3dSys.getViewMtx(), &pres_eye); // FRAME INTERP NOTE: Currently only recalculating points for Epona's reins. Need a more global solution. if (daHorse_c* horse = dComIfGp_getHorseActor()) { horse->lerpControlPoints(dusk::frame_interp::get_interpolation_step()); } - g_dComIfG_gameInfo.drawlist.refresh3DlineMats(pres_eye); + g_dComIfG_gameInfo.drawlist.refresh3DlineMats(camera_p->view.lookat.eye); } #endif diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 3568e044e1..7e34770f11 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -253,7 +253,10 @@ void main01(void) { accumulator -= kSimStepSeconds; } dusk::frame_interp::interpolate(static_cast(accumulator / kSimStepSeconds)); - cAPIGph_Painter(); + { + dusk::frame_interp::PresentationCameraScope presentation_camera; + cAPIGph_Painter(); + } } else { accumulator = 0.0;