From 324cba6644357006b25a7aa2e73e743d3ef50a88 Mon Sep 17 00:00:00 2001 From: inspectredc <78732756+inspectredc@users.noreply.github.com> Date: Mon, 27 May 2024 01:13:25 +0100 Subject: [PATCH] Fix Camera Interpolation For Sudden Camera Changes (#479) * Fix Camera Interpolation On Sudden Movements * huge formatting discovery * increase angle tolerance --- .../Camera/CameraInterpolationFixes.cpp | 109 ++++++++++++++++++ .../Camera/CameraInterpolationFixes.h | 6 + mm/2s2h/Enhancements/Enhancements.cpp | 1 + mm/2s2h/Enhancements/Enhancements.h | 1 + .../FrameInterpolation/FrameInterpolation.cpp | 12 ++ .../FrameInterpolation/FrameInterpolation.h | 2 + .../GameInteractor/GameInteractor.cpp | 15 +++ .../GameInteractor/GameInteractor.h | 4 + mm/src/code/z_camera.c | 4 + 9 files changed, 154 insertions(+) create mode 100644 mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.cpp create mode 100644 mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.h diff --git a/mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.cpp b/mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.cpp new file mode 100644 index 000000000..cb02a2143 --- /dev/null +++ b/mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.cpp @@ -0,0 +1,109 @@ +#include "CameraInterpolationFixes.h" +#include +#include "Enhancements/GameInteractor/GameInteractor.h" +#include "CameraUtils.h" +#include "Enhancements/FrameInterpolation/FrameInterpolation.h" + +extern "C" { +#include +#include "macros.h" +#include "functions.h" +extern PlayState* gPlayState; +extern SaveContext gSaveContext; +extern CameraSetting sCameraSettings[]; +} + +// This function should check for outstanding conditions which the distance check would falsly flag +// as something we do not want to interpolate. This mostly applies to general play for now +bool Camera_ShouldOverrideInterpolationCheck(Camera* camera) { + switch (sCameraSettings[camera->setting].cameraModes[camera->mode].funcId) { + case CAM_FUNC_BATTLE0: + case CAM_FUNC_BATTLE1: + case CAM_FUNC_BATTLE2: + case CAM_FUNC_BATTLE3: + case CAM_FUNC_BATTLE4: + case CAM_FUNC_PARALLEL0: + case CAM_FUNC_PARALLEL1: + case CAM_FUNC_PARALLEL2: + case CAM_FUNC_PARALLEL3: + case CAM_FUNC_PARALLEL4: + return true; + } + return false; +} + +// This function checks whether there is too large a distance or change in angle between the expected +// position camera and the actual position of the camera. If there is, then we should not interpolate +bool Camera_ShouldInterpolateDist(Camera* camera) { + // Account for changes in position, pitch and yaw. Roll is rarely used so not currently handled + // `Velocity` is measured as the change across the previous frame + static f32 lastYaw = 0.0f; + static f32 lastYawVelocity = 0.0f; + static f32 lastPitch = 0.0f; + static f32 lastPitchVelocity = 0.0f; + static Vec3f lastEye = { 0.0f, 0.0f, 0.0f }; + static Vec3f lastEyeVelocity = { 0.0f, 0.0f, 0.0f }; + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f eyeVelo; + bool shouldInterpolate = true; + VecGeo eyeGeo = OLib_Vec3fDiffToVecGeo(at, eye); + Vec3f expectedEye; + + // Calculate Current + f32 yaw = BINANG_TO_DEG(eyeGeo.yaw); + if (yaw > 360.0f) { + yaw -= 360.0f; + } else if (yaw < 0.0f) { + yaw += 360.0f; + } + f32 yawVelocity = yaw - lastYaw; + f32 pitch = BINANG_TO_DEG(eyeGeo.pitch); + f32 pitchVelocity = pitch - lastPitch; + Math_Vec3f_Diff(eye, &lastEye, &eyeVelo); + + // Update static variables. + lastYaw = yaw; + lastYawVelocity = yawVelocity; + lastPitch = pitch; + lastPitchVelocity = pitchVelocity; + lastEye = *eye; + lastEyeVelocity = eyeVelo; + + if (Camera_ShouldOverrideInterpolationCheck(camera)) { + return true; + } + + // Calculate Expected + f32 expectedYaw = lastYaw + lastYawVelocity; + if (expectedYaw > 360.0f) { + expectedYaw -= 360.0f; + } else if (expectedYaw < 0.0f) { + expectedYaw += 360.0f; + } + f32 expectedPitch = CLAMP(lastPitch + lastPitchVelocity, -180.0f, 180.0f); + Math_Vec3f_Sum(&lastEye, &lastEyeVelocity, &expectedEye); + + // Check if changes are too great + f32 diffYaw = fabsf(yaw - expectedYaw); + f32 diffPitch = fabsf(pitch - expectedPitch); + f32 diffDistEye = Math_Vec3f_DistXYZ(eye, &expectedEye); + + if ((diffYaw > 90.0f && diffYaw < 270.0f) || diffPitch > 60.0f || diffDistEye > 200.0f) { + shouldInterpolate = false; + } + + // If we aren't interpolating, then reset velocities as they are inaccurate to the camera's current movement + if (!shouldInterpolate) { + lastYawVelocity = 0.0f; + lastPitchVelocity = 0.0f; + lastEyeVelocity = { 0.0f, 0.0f, 0.0f }; + } + + return shouldInterpolate; +} + +void RegisterCameraInterpolationFixes() { + GameInteractor::Instance->RegisterGameHook( + [](Camera* camera) { FrameInterpolation_ShouldInterpolateFrame(Camera_ShouldInterpolateDist(camera)); }); +} diff --git a/mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.h b/mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.h new file mode 100644 index 000000000..f159e197e --- /dev/null +++ b/mm/2s2h/Enhancements/Camera/CameraInterpolationFixes.h @@ -0,0 +1,6 @@ +#ifndef CAMERA_INTERPOLATE_FIXES_H +#define CAMERA_INTERPOLATE_FIXES_H + +void RegisterCameraInterpolationFixes(); + +#endif // CAMERA_INTERPOLATE_FIXES_H diff --git a/mm/2s2h/Enhancements/Enhancements.cpp b/mm/2s2h/Enhancements/Enhancements.cpp index e82498f19..c71ab37af 100644 --- a/mm/2s2h/Enhancements/Enhancements.cpp +++ b/mm/2s2h/Enhancements/Enhancements.cpp @@ -2,6 +2,7 @@ void InitEnhancements() { // Camera + RegisterCameraInterpolationFixes(); RegisterCameraFreeLook(); RegisterDebugCam(); diff --git a/mm/2s2h/Enhancements/Enhancements.h b/mm/2s2h/Enhancements/Enhancements.h index 0cb91d12d..3a6a303b6 100644 --- a/mm/2s2h/Enhancements/Enhancements.h +++ b/mm/2s2h/Enhancements/Enhancements.h @@ -1,6 +1,7 @@ #ifndef ENHANCEMENTS_H #define ENHANCEMENTS_H +#include "Camera/CameraInterpolationFixes.h" #include "Camera/DebugCam.h" #include "Camera/FreeLook.h" #include "Cheats/MoonJump.h" diff --git a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp index 3c7ebb873..a1ae3df68 100644 --- a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp +++ b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.cpp @@ -451,11 +451,23 @@ unordered_map FrameInterpolation_Interpolate(float step) { return ctx.mtx_replacements; } +bool camera_interpolation = false; + +void FrameInterpolation_ShouldInterpolateFrame(bool shouldInterpolate) { + camera_interpolation = shouldInterpolate; +} + void FrameInterpolation_StartRecord(void) { previous_recording = move(current_recording); current_recording = {}; current_path.clear(); current_path.push_back(¤t_recording.root_path); + if (!camera_interpolation) { + // default to interpolating + camera_interpolation = true; + is_recording = false; + return; + } if (OTRGlobals::Instance->GetInterpolationFPS() != 20) { is_recording = true; } diff --git a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h index 64c7197cd..d668535a9 100644 --- a/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h +++ b/mm/2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h @@ -12,6 +12,8 @@ extern "C" { #endif +void FrameInterpolation_ShouldInterpolateFrame(bool shouldInterpolate); + void FrameInterpolation_StartRecord(void); void FrameInterpolation_StopRecord(void); diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp index ab2b8f718..0eafed218 100644 --- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp +++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp @@ -141,6 +141,21 @@ void GameInteractor_ExecuteOnCameraChangeModeFlags(Camera* camera) { GameInteractor::Instance->ExecuteHooksForFilter(camera); } +void GameInteractor_ExecuteAfterCameraUpdate(Camera* camera) { + GameInteractor::Instance->ExecuteHooks(camera); + GameInteractor::Instance->ExecuteHooksForID(camera->uid, camera); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)camera, camera); + GameInteractor::Instance->ExecuteHooksForFilter(camera); +} + +void GameInteractor_ExecuteOnCameraChangeSettingsFlags(Camera* camera) { + GameInteractor::Instance->ExecuteHooks(camera); + GameInteractor::Instance->ExecuteHooksForID(camera->uid, camera); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)camera, + camera); + GameInteractor::Instance->ExecuteHooksForFilter(camera); +} + void GameInteractor_ExecuteOnPassPlayerInputs(Input* input) { GameInteractor::Instance->ExecuteHooks(input); GameInteractor::Instance->ExecuteHooksForFilter(input); diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h index cb9ce0424..8359f3003 100644 --- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h +++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h @@ -269,7 +269,9 @@ class GameInteractor { DEFINE_HOOK(OnFlagSet, (FlagType flagType, u32 flag)); DEFINE_HOOK(OnFlagUnset, (FlagType flagType, u32 flag)); + DEFINE_HOOK(AfterCameraUpdate, (Camera * camera)); DEFINE_HOOK(OnCameraChangeModeFlags, (Camera * camera)); + DEFINE_HOOK(OnCameraChangeSettingsFlags, (Camera * camera)); DEFINE_HOOK(OnPassPlayerInputs, (Input * input)); @@ -308,7 +310,9 @@ void GameInteractor_ExecuteOnSceneFlagUnset(s16 sceneId, FlagType flagType, u32 void GameInteractor_ExecuteOnFlagSet(FlagType flagType, u32 flag); void GameInteractor_ExecuteOnFlagUnset(FlagType flagType, u32 flag); +void GameInteractor_ExecuteAfterCameraUpdate(Camera* camera); void GameInteractor_ExecuteOnCameraChangeModeFlags(Camera* camera); +void GameInteractor_ExecuteOnCameraChangeSettingsFlags(Camera* camera); void GameInteractor_ExecuteOnPassPlayerInputs(Input* input); diff --git a/mm/src/code/z_camera.c b/mm/src/code/z_camera.c index 8d098c3b2..f0b9cdb47 100644 --- a/mm/src/code/z_camera.c +++ b/mm/src/code/z_camera.c @@ -7635,6 +7635,8 @@ Vec3s Camera_Update(Camera* camera) { camera->inputDir.z = 0; } + GameInteractor_ExecuteAfterCameraUpdate(camera); + return camera->inputDir; } @@ -7885,6 +7887,8 @@ s16 Camera_ChangeSettingFlags(Camera* camera, s16 setting, s16 flags) { camera->setting = setting; + GameInteractor_ExecuteOnCameraChangeSettingsFlags(camera); + if (Camera_ChangeModeFlags(camera, camera->mode, true) >= 0) { Camera_ResetActionFuncState(camera, camera->mode); }