diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp index 81729f2bf..e8559c0be 100644 --- a/mm/2s2h/BenGui/BenMenuBar.cpp +++ b/mm/2s2h/BenGui/BenMenuBar.cpp @@ -305,6 +305,50 @@ extern std::shared_ptr mHudEditorWindow; void DrawEnhancementsMenu() { if (UIWidgets::BeginMenu("Enhancements")) { + if (UIWidgets::BeginMenu("Camera")) { + ImGui::SeparatorText("Right Stick Camera"); + UIWidgets::CVarCheckbox("Invert Camera X Axis", "gEnhancements.Camera.RightStick.InvertXAxis", { + .tooltip = "Inverts the Camera X Axis in Free Look." + }); + UIWidgets::CVarCheckbox("Invert Camera Y Axis", "gEnhancements.Camera.RightStick.InvertYAxis", { + .tooltip = "Inverts the Camera Y Axis in Free Look.", + .defaultValue = true + }); + UIWidgets::CVarSliderFloat("Third-Person Horizontal Sensitivity: %.0f %", "gEnhancements.Camera.RightStick.CameraSensitivity.X", 0.01f, 5.0f, 1.0f); + UIWidgets::CVarSliderFloat("Third-Person Vertical Sensitivity: %.0f %", "gEnhancements.Camera.RightStick.CameraSensitivity.Y", 0.01f, 5.0f, 1.0f); + + ImGui::SeparatorText("Free Look"); + if (UIWidgets::CVarCheckbox("Free Look", "gEnhancements.Camera.FreeLook.Enable", { + .tooltip = "Enables free look camera control\nNote: You must remap C buttons off of the right stick in the controller config menu, and map the camera stick to the right stick.", + .disabled = CVarGetInteger("gEnhancements.Camera.DebugCam.Enable", 0) != 0 + })) { + RegisterCameraFreeLook(); + } + + UIWidgets::CVarSliderInt("Camera Distance: %d", "gEnhancements.Camera.FreeLook.MaxCameraDistance", 100, 900, 185); + UIWidgets::CVarSliderInt("Camera Transition Speed: %d", "gEnhancements.Camera.FreeLook.TransitionSpeed", 1, 900, 25); + UIWidgets::CVarSliderFloat("Max Camera Height Angle: %.0f %°", "gEnhancements.Camera.FreeLook.MaxPitch", -89.0f, 89.0f, 72.0f); + UIWidgets::CVarSliderFloat("Min Camera Height Angle: %.0f %°", "gEnhancements.Camera.FreeLook.MinPitch", -89.0f, 89.0f, -49.0f); + f32 maxY = CVarGetFloat("gEnhancements.Camera.FreeLook.MaxPitch", 72.0f); + f32 minY = CVarGetFloat("gEnhancements.Camera.FreeLook.MinPitch", -49.0f); + CVarSetFloat("gEnhancements.Camera.FreeLook.MaxPitch", std::max(maxY, minY)); + CVarSetFloat("gEnhancements.Camera.FreeLook.MinPitch", std::min(maxY, minY)); + + ImGui::SeparatorText("'Debug' Camera"); + if (UIWidgets::CVarCheckbox("Debug Camera", "gEnhancements.Camera.DebugCam.Enable", { + .tooltip = "Enables free camera control.", + .disabled = CVarGetInteger("gEnhancements.Camera.FreeLook.Enable", 0) != 0 + })) { + RegisterDebugCam(); + } + UIWidgets::CVarCheckbox("Enable Roll (Six Degrees Of Freedom)", "gEnhancements.Camera.DebugCam.6DOF", { + .tooltip = "This allows for all six degrees of movement with the camera, NOTE: Yaw will work differently in this system, instead rotating around the focal point, rather than a polar axis." + }); + UIWidgets::CVarSliderFloat("Camera Speed: %.0f %", "gEnhancements.Camera.DebugCam.CameraSpeed", 0.1f, 3.0f, 0.5f); + + ImGui::EndMenu(); + } + if (UIWidgets::BeginMenu("Cutscenes")) { UIWidgets::CVarCheckbox("Hide Title Cards", "gEnhancements.Cutscenes.HideTitleCards"); UIWidgets::CVarCheckbox("Skip Entrance Cutscenes", "gEnhancements.Cutscenes.SkipEntranceCutscenes"); diff --git a/mm/2s2h/Enhancements/Camera/CameraUtils.h b/mm/2s2h/Enhancements/Camera/CameraUtils.h new file mode 100644 index 000000000..2c06cc1c3 --- /dev/null +++ b/mm/2s2h/Enhancements/Camera/CameraUtils.h @@ -0,0 +1,45 @@ +#ifndef CAMERA_UTILS_H +#define CAMERA_UTILS_H +#include "ultratypes.h" + +// Camera will reload its paramData. Usually that means setting the read-only data from what is stored in +// CameraModeValue arrays. Although sometimes some read-write data is reset as well +#define RELOAD_PARAMS(camera) ((camera->animState == 0) || (camera->animState == 10) || (camera->animState == 20)) + +/** + * Camera data is stored in both read-only data and OREG as s16, and then converted to the appropriate type during + * runtime. If a small f32 is being stored as an s16, it is common to store that value 100 times larger than the + * original value. This is then scaled back down during runtime with the CAM_RODATA_UNSCALE macro. + */ +#define CAM_RODATA_SCALE(x) ((x)*100.0f) +#define CAM_RODATA_UNSCALE(x) ((x)*0.01f) + +// Load the next value from camera read-only data stored in CameraModeValue +#define GET_NEXT_RO_DATA(values) ((values++)->val) +// Load the next value and scale down from camera read-only data stored in CameraModeValue +#define GET_NEXT_SCALED_RO_DATA(values) CAM_RODATA_UNSCALE(GET_NEXT_RO_DATA(values)) + +typedef struct { + /* 0x0 */ s16 val; + /* 0x2 */ s16 param; +} CameraModeValue; // size = 0x4 + +typedef struct { + /* 0x0 */ s16 funcId; + /* 0x2 */ s16 numValues; + /* 0x4 */ CameraModeValue* values; +} CameraMode; // size = 0x8 + +/** + * Flags: + * (flags & 0xF): Priority (lower value has higher priority) + * (flags & 0x40000000): Store previous setting and bgCamData, also ignores water checks + * (flags & 0x80000000): Set camera setting based on bg/scene data and reset action function state + */ +typedef struct { + /* 0x0 */ u32 validModes; + /* 0x4 */ u32 flags; + /* 0x8 */ CameraMode* cameraModes; +} CameraSetting; // size = 0xC + +#endif // CAMERA_UTILS_H diff --git a/mm/2s2h/Enhancements/Camera/DebugCam.cpp b/mm/2s2h/Enhancements/Camera/DebugCam.cpp new file mode 100644 index 000000000..12c8105db --- /dev/null +++ b/mm/2s2h/Enhancements/Camera/DebugCam.cpp @@ -0,0 +1,243 @@ +#include "DebugCam.h" +#include +#include "Enhancements/GameInteractor/GameInteractor.h" +#include "CameraUtils.h" + +extern "C" { +#include +#include +extern PlayState* gPlayState; +extern PlayState* sCamPlayState; +extern f32 Camera_ScaledStepToCeilF(f32 target, f32 cur, f32 stepScale, f32 minDiff); +extern s16 Camera_ScaledStepToCeilS(s16 target, s16 cur, f32 stepScale, s16 minDiff); +extern Vec3f Camera_CalcUpVec(s16 pitch, s16 yaw, s16 roll); +} + +#define CAMERA_DEBUG_DEFAULT_PORT 1 + +// Static Data Used For Free Camera +static bool sDebugCamRefreshParams = true; + +Vec3f Camera_RotatePointAroundAxis(Vec3f* point, Vec3f* axis, s16 angle) { + f32 q0 = Math_CosS(angle / 2); + f32 q1 = Math_SinS(angle / 2) * axis->x; + f32 q2 = Math_SinS(angle / 2) * axis->y; + f32 q3 = Math_SinS(angle / 2) * axis->z; + Vec3f endPoint; + + endPoint.x = (SQ(q0) + SQ(q1) - SQ(q2) - SQ(q3)) * point->x + 2 * (q1 * q2 - q0 * q3) * point->y + 2 * (q1 * q3 + q0 * q2) * point->z; + endPoint.y = 2 * (q2 * q1 + q0 * q3) * point->x + (SQ(q0) - SQ(q1) + SQ(q2) - SQ(q3)) * point->y + 2 * (q2 * q3 - q0 * q1) * point->z; + endPoint.z = 2 * (q3 * q1 - q0 * q2) * point->x + 2 * (q3 * q2 + q0 * q1) * point->y + (SQ(q0) - SQ(q1) - SQ(q2) + SQ(q3)) * point->z; + + return endPoint; +} + +void Camera_SetRollFromUp(Camera* camera) { + Vec3f* eyeNext = &camera->eyeNext; + Vec3f* at = &camera->at; + Vec3f* up = &camera->up; + VecGeo diffGeo = OLib_Vec3fDiffToVecGeo(eyeNext, at); + f32 pitch = diffGeo.pitch; + f32 yaw = diffGeo.yaw; + f32 sinP = Math_SinS(pitch); + f32 cosP = Math_CosS(pitch); + f32 sinY = Math_SinS(yaw); + f32 cosY = Math_CosS(yaw); + Vec3f preRollUp = { -sinP * sinY, cosP, -sinP * cosY }; + Vec3f normal; + + f32 dot = DOTXYZ(preRollUp, (*up)); + Math_Vec3f_Diff(eyeNext, at, &normal); + Math3D_Normalize(&normal); + // Setup Matrix With Normal + // ( preRollUp.x up->x normal.x) + // ( preRollUp.y up->y normal.y) + // ( preRollUp.z up->z normal.z) + f32 det = preRollUp.x * up->y * normal.z + up->x * normal.y * preRollUp.z + normal.x * preRollUp.y * up->z + - normal.x * up->y * preRollUp.z - normal.y * up->z * preRollUp.x - normal.z * up->x * preRollUp.y; + + camera->roll = RAD_TO_BINANG(atan2(-det, -dot) - M_PI); +} + +void Camera_RotateGeographic(Camera* camera, f32 pitchDiff, f32 yawDiff) { + Vec3f* eyeNext = &camera->eyeNext; + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f camOut = OLib_Vec3fDistNormalize(at, eye); + VecGeo transGeo = OLib_Vec3fToVecGeo(&camOut); + transGeo.pitch += pitchDiff; + transGeo.pitch = OLib_ClampMaxDist(transGeo.pitch, DEG_TO_BINANG(89.0f)); + transGeo.yaw += yawDiff; + transGeo.r = camera->dist; + *eyeNext = OLib_AddVecGeoToVec3f(at, &transGeo); +} + +void Camera_Rotate6DOF(Camera* camera, f32 pitchDiff, f32 yawDiff) { + Vec3f* eyeNext = &camera->eyeNext; + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f* up = &camera->up; + + Vec3f camOut = OLib_Vec3fDistNormalize(at, eye); + Math3D_Normalize(&camOut); + Math3D_Normalize(up); + Vec3f right; + Math3D_Vec3f_Cross(up, &camOut, &right); + Math3D_Normalize(&right); + Vec3f force = { + up->x * pitchDiff + right.x * yawDiff, + up->y * pitchDiff + right.y * yawDiff, + up->z * pitchDiff + right.z * yawDiff + }; + f32 angle = Math3D_Vec3fMagnitude(&force); + + Vec3f rotAxis; + Math3D_Vec3f_Cross(&camOut, &force, &rotAxis); + Math3D_Normalize(&rotAxis); + + Vec3f transPos = Camera_RotatePointAroundAxis(&camOut, &rotAxis, angle); + VecGeo transGeo = OLib_Vec3fToVecGeo(&transPos); + transGeo.r = camera->dist; + *eyeNext = OLib_AddVecGeoToVec3f(at, &transGeo); + *up = Camera_RotatePointAroundAxis(up, &rotAxis, angle); + + Camera_SetRollFromUp(camera); +} + +void Camera_DebugCam(Camera* camera) { + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f* eyeNext = &camera->eyeNext; + VecGeo eyeAdjustment; + VecGeo originalEyeGeo = OLib_Vec3fDiffToVecGeo(at, eye); + f32 distTarget; + + if (sDebugCamRefreshParams) { + // Move camera focus to eye + Vec3f unit = OLib_Vec3fDistNormalize(eye, at); + Math_Vec3f_Sum(eye, &unit, at); + sDebugCamRefreshParams = false; + } + + s32 controllerPort = CVarGetInteger("gEnhancements.Camera.DebugCam.Port", CAMERA_DEBUG_DEFAULT_PORT) - 1; + if (controllerPort > 3 || controllerPort < 0) { + controllerPort = CAMERA_DEBUG_DEFAULT_PORT - 1; + CVarSetInteger("gEnhancements.Camera.DebugCam.Port", CAMERA_DEBUG_DEFAULT_PORT); + } + + /* + Camera Speed + */ + + f32 camSpeed = CVarGetFloat("gEnhancements.Camera.DebugCam.CameraSpeed", 0.5f); + if (CHECK_BTN_ANY(sCamPlayState->state.input[controllerPort].cur.button, BTN_A | BTN_B | BTN_L)) { + camSpeed *= 3.0f; + } + + /* + Camera distance + */ + + // Transition speed set to be responsive + f32 transitionSpeed = camSpeed * 200.0f; + distTarget = camera->dist + (CHECK_BTN_ANY(sCamPlayState->state.input[controllerPort].cur.button, BTN_DDOWN) - CHECK_BTN_ANY(sCamPlayState->state.input[controllerPort].cur.button, BTN_DUP)) * 1200.0f * camSpeed; + distTarget = CLAMP_MIN(distTarget, 0.1f); + // Smooth step camera away to max camera distance. + camera->dist = Camera_ScaledStepToCeilF(distTarget, camera->dist, transitionSpeed / (ABS(distTarget - camera->dist) + transitionSpeed), 0.0f); + + /* + Set Camera Pitch and Yaw + */ + f32 yawDiff = -sCamPlayState->state.input[controllerPort].cur.right_stick_x * 10.0f * (CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.X", 1.0f)); + f32 pitchDiff = sCamPlayState->state.input[controllerPort].cur.right_stick_y * 10.0f * (CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.Y", 1.0f)); + + yawDiff *= (CVarGetInteger("gEnhancements.Camera.RightStick.InvertXAxis", 0) ? -1 : 1); + pitchDiff *= (CVarGetInteger("gEnhancements.Camera.RightStick.InvertYAxis", 1) ? 1 : -1); + + // Setup new camera angle based on the calculations from right stick inputs + if (CVarGetInteger("gEnhancements.Camera.DebugCam.6DOF", 0)) { + Camera_Rotate6DOF(camera, pitchDiff, yawDiff); + } else { + Camera_RotateGeographic(camera, pitchDiff, yawDiff); + } + + /* + Camera Roll + */ + if (CVarGetInteger("gEnhancements.Camera.DebugCam.6DOF", 0)) { + camera->roll += (CHECK_BTN_ANY(sCamPlayState->state.input[controllerPort].cur.button, BTN_DLEFT) - CHECK_BTN_ANY(sCamPlayState->state.input[controllerPort].cur.button, BTN_DRIGHT)) * 1200.0f * camSpeed; + } else { + camera->roll = Camera_ScaledStepToCeilS(0, camera->roll, 0.1f, 5); + } + /* + Camera Movement + */ + + // Movement differences from the point of view of the camera. Use max stick value for buttons scale + Vec3f posDiff; + posDiff.x = sCamPlayState->state.input[controllerPort].cur.stick_x * camSpeed; + posDiff.y = (CHECK_BTN_ANY(sCamPlayState->state.input[controllerPort].cur.button, BTN_Z) - CHECK_BTN_ANY(sCamPlayState->state.input[controllerPort].cur.button, BTN_R)) * 120.0f * camSpeed; + posDiff.z = -sCamPlayState->state.input[controllerPort].cur.stick_y * camSpeed; + + // Adjust movement to camera's current direction + Vec3f camPosDiff = { 0.0f, 0.0f, 0.0f }; + + VecGeo diffGeo = OLib_Vec3fDiffToVecGeo(eyeNext, at); + Vec3f normX; + Vec3f normY = camera->up = Camera_CalcUpVec(diffGeo.pitch, diffGeo.yaw, camera->roll); + Vec3f normZ = OLib_Vec3fDistNormalize(at, eyeNext); + + // Cross Product + Math3D_Vec3f_Cross(&normY, &normZ, &normX); + + Math_Vec3f_Scale(&normX, posDiff.x); + Math_Vec3f_Scale(&normY, posDiff.y); + Math_Vec3f_Scale(&normZ, posDiff.z); + + Math_Vec3f_Sum(&camPosDiff, &normX, &camPosDiff); + Math_Vec3f_Sum(&camPosDiff, &normY, &camPosDiff); + Math_Vec3f_Sum(&camPosDiff, &normZ, &camPosDiff); + + // "Move" camera eye around with controls + Math_Vec3f_Sum(at, &camPosDiff, at); + Math_Vec3f_Sum(eyeNext, &camPosDiff, eyeNext); + + *eye = *eyeNext; +} + +static uint32_t freeCamVBHookId = 0; +static uint32_t freeCamDisableInputsId = 0; + +void RegisterDebugCam() { + sDebugCamRefreshParams = true; + + if (freeCamVBHookId) { + GameInteractor::Instance->UnregisterGameHookForID(freeCamVBHookId); + freeCamVBHookId = 0; + } + + if (freeCamDisableInputsId) { + GameInteractor::Instance->UnregisterGameHook(freeCamDisableInputsId); + freeCamDisableInputsId = 0; + } + + if (CVarGetInteger("gEnhancements.Camera.DebugCam.Enable", 0)) { + freeCamVBHookId = REGISTER_VB_SHOULD(GI_VB_USE_CUSTOM_CAMERA, { + Camera* camera = static_cast(opt); + Camera_DebugCam(camera); + *should = false; + }); + + freeCamDisableInputsId = GameInteractor::Instance->RegisterGameHook([](Input* input) { + s32 controllerPort = CVarGetInteger("gEnhancements.Camera.DebugCam.Port", CAMERA_DEBUG_DEFAULT_PORT) - 1; + if (controllerPort > 3 || controllerPort < 0) { + controllerPort = CAMERA_DEBUG_DEFAULT_PORT - 1; + CVarSetInteger("gEnhancements.Camera.DebugCam.Port", CAMERA_DEBUG_DEFAULT_PORT); + } + if (controllerPort == 0) { + // Disable Link Inputs + memset(input, 0, sizeof(Input)); + } + }); + } +} diff --git a/mm/2s2h/Enhancements/Camera/DebugCam.h b/mm/2s2h/Enhancements/Camera/DebugCam.h new file mode 100644 index 000000000..c61a12695 --- /dev/null +++ b/mm/2s2h/Enhancements/Camera/DebugCam.h @@ -0,0 +1,6 @@ +#ifndef FREE_CAM_H +#define FREE_CAM_H + +void RegisterDebugCam(); + +#endif // FREE_CAM_H diff --git a/mm/2s2h/Enhancements/Camera/FreeLook.cpp b/mm/2s2h/Enhancements/Camera/FreeLook.cpp new file mode 100644 index 000000000..d37585015 --- /dev/null +++ b/mm/2s2h/Enhancements/Camera/FreeLook.cpp @@ -0,0 +1,183 @@ +#include "FreeLook.h" +#include +#include "Enhancements/GameInteractor/GameInteractor.h" +#include "CameraUtils.h" + +extern "C" { +#include +#include +extern PlayState* gPlayState; +extern PlayState* sCamPlayState; +extern f32 Camera_ScaledStepToCeilF(f32 target, f32 cur, f32 stepScale, f32 minDiff); +extern s16 Camera_ScaledStepToCeilS(s16 target, s16 cur, f32 stepScale, s16 minDiff); +extern s32 Camera_BgCheckInfo(Camera* camera, Vec3f* from, CameraCollision* to); +extern void Camera_ResetActionFuncState(Camera* camera, s32 mode); +extern void func_800CBFA4(Camera* camera, Vec3f* arg1, Vec3f* arg2, s32 arg3); +extern CameraSetting sCameraSettings[]; +extern s32 sCameraInterfaceFlags; +} + +// Static Data Used For Free Camera +static bool sCanFreeLook = false; + +void UpdateFreeLookState(Camera* camera) { + if (CAM_MODE_TARGET <= camera->mode && camera->mode <= CAM_MODE_BATTLE) { + sCanFreeLook = false; + } + + if (CAM_MODE_FIRSTPERSON <= camera->mode && camera->mode <= CAM_MODE_CLIMBZ) { + sCanFreeLook = false; + } + + if (camera->mode == CAM_MODE_HANGZ) { + sCanFreeLook = false; + } + + if (CAM_MODE_BOOMERANG <= camera->mode && camera->mode <= CAM_MODE_ZORAFINZ) { + sCanFreeLook = false; + } + + if (gPlayState != nullptr && Player_InCsMode(gPlayState)) { + sCanFreeLook = false; + } +} + +// Function based on several camera functions, including Camera_Parallel1 +bool Camera_FreeLook(Camera* camera) { + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f* eyeNext = &camera->eyeNext; + Player* player = GET_PLAYER(gPlayState); + VecGeo eyeAdjustment = OLib_Vec3fDiffToVecGeo(at, eye); + Parallel1ReadOnlyData* roData = &camera->paramData.para1.roData; + f32 playerHeight; + f32 pitch = eyeAdjustment.pitch; + f32 yaw = eyeAdjustment.yaw; + + // Smooth step camera 'at' towards player + f32 playerYOffset = Player_GetHeight(player) / 1.2f; + if (player->rideActor != NULL) { + playerYOffset /= 2; + } + at->x = Camera_ScaledStepToCeilF(player->actor.world.pos.x, at->x, 0.5f, 1.0f); + at->y = Camera_ScaledStepToCeilF(player->actor.world.pos.y + playerYOffset, at->y, 0.5f, 1.0f); + at->z = Camera_ScaledStepToCeilF(player->actor.world.pos.z, at->z, 0.5f, 1.0f); + + // Equivalent to 'Camera_GetFocalActorHeight' + playerHeight = Player_GetHeight(player); + + // Camera param reloading based on Camera_Parallel1, only updating roData as only that seems necessary + if (RELOAD_PARAMS(camera)) { + CameraModeValue* values = sCameraSettings[camera->setting].cameraModes[camera->mode].values; + f32 yNormal = (0.8f - ((68.0f / playerHeight) * -0.2f)); + + roData->unk_00 = GET_NEXT_SCALED_RO_DATA(values) * playerHeight * yNormal; + roData->unk_04 = GET_NEXT_SCALED_RO_DATA(values) * playerHeight * yNormal; + roData->unk_08 = GET_NEXT_SCALED_RO_DATA(values) * playerHeight * yNormal; + roData->unk_20 = CAM_DEG_TO_BINANG(GET_NEXT_RO_DATA(values)); + roData->unk_22 = CAM_DEG_TO_BINANG(GET_NEXT_RO_DATA(values)); + roData->unk_0C = GET_NEXT_RO_DATA(values); + roData->unk_10 = GET_NEXT_RO_DATA(values); + roData->unk_14 = GET_NEXT_RO_DATA(values); + roData->unk_18 = GET_NEXT_SCALED_RO_DATA(values); + roData->interfaceFlags = GET_NEXT_RO_DATA(values); + roData->unk_1C = GET_NEXT_SCALED_RO_DATA(values); + roData->unk_24 = GET_NEXT_RO_DATA(values); + } + + // 1 acts as a default value of sorts for sCameraInterfaceFlags + sCameraInterfaceFlags = 1; + + Camera_ResetActionFuncState(camera, camera->mode); + + f32 yawDiff = -sCamPlayState->state.input[0].cur.right_stick_x * 10.0f * (CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.X", 1.0f)); + f32 pitchDiff = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f * (CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.Y", 1.0f)); + + yaw += yawDiff * (CVarGetInteger("gEnhancements.Camera.RightStick.InvertXAxis", 0) ? -1 : 1); + pitch += pitchDiff * (CVarGetInteger("gEnhancements.Camera.RightStick.InvertYAxis", 1) ? 1 : -1); + + s16 maxPitch = DEG_TO_BINANG(CVarGetFloat("gEnhancements.Camera.FreeLook.MaxPitch", 72.0f)); + s16 minPitch = DEG_TO_BINANG(CVarGetFloat("gEnhancements.Camera.FreeLook.MinPitch", -49.0f)); + + if (pitch > maxPitch) { + pitch = maxPitch; + } + if (pitch < minPitch) { + pitch = minPitch; + } + + f32 distTarget = CVarGetInteger("gEnhancements.Camera.FreeLook.MaxCameraDistance", roData->unk_04); + f32 transitionSpeed = CVarGetInteger("gEnhancements.Camera.FreeLook.TransitionSpeed", 25); + // Smooth step camera away to max camera distance. Camera collision is calculated later + camera->dist = Camera_ScaledStepToCeilF(distTarget, camera->dist, transitionSpeed / (ABS(distTarget - camera->dist) + transitionSpeed), 0.0f); + + // Setup new camera angle based on the calculations from stick inputs + eyeAdjustment.r = camera->dist; + eyeAdjustment.yaw = yaw; + eyeAdjustment.pitch = pitch; + + *eyeNext = OLib_AddVecGeoToVec3f(at, &eyeAdjustment); + // Apply new camera angle only when camera active + if (camera->status == CAM_STATUS_ACTIVE) { + *eye = *eyeNext; + // Adjust camera for collision with floors, walls and ceilings. + func_800CBFA4(camera, at, eye, 0); + } + + // 65.0f based on value from SoH + f32 fovTarget = 65.0f; + camera->fov = Camera_ScaledStepToCeilF(fovTarget, camera->fov, camera->fovUpdateRate, 0.1f); + camera->roll = Camera_ScaledStepToCeilS(0, camera->roll, 0.5f, 5); + + return true; +} + +bool Camera_CanFreeLook(Camera* camera) { + f32 camX = sCamPlayState->state.input[0].cur.right_stick_x * 10.0f; + f32 camY = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f; + if (!sCanFreeLook && (fabsf(camX) >= 15.0f || fabsf(camY) >= 15.0f)) { + sCanFreeLook = true; + } + return sCanFreeLook; +} + +static uint32_t freeLookCameraSettingChangeHookId = 0; +static uint32_t freeLookCameraVBHookId = 0; + +void RegisterCameraFreeLook() { + if (freeLookCameraVBHookId) { + GameInteractor::Instance->UnregisterGameHookForID(freeLookCameraVBHookId); + freeLookCameraVBHookId = 0; + } + + if (freeLookCameraSettingChangeHookId) { + GameInteractor::Instance->UnregisterGameHook(freeLookCameraSettingChangeHookId); + freeLookCameraSettingChangeHookId = 0; + } + + if (CVarGetInteger("gEnhancements.Camera.FreeLook.Enable", 0)) { + freeLookCameraVBHookId = REGISTER_VB_SHOULD(GI_VB_USE_CUSTOM_CAMERA, { + Camera* camera = static_cast(opt); + switch (sCameraSettings[camera->setting].cameraModes[camera->mode].funcId) { + case CAM_FUNC_NORMAL0: + case CAM_FUNC_NORMAL1: + case CAM_FUNC_NORMAL3: + case CAM_FUNC_NORMAL4: + case CAM_FUNC_JUMP2: + case CAM_FUNC_JUMP3: + case CAM_FUNC_BATTLE1: + case CAM_FUNC_UNIQUE2: + if (Camera_CanFreeLook(camera)) { + Camera_FreeLook(camera); + *should = false; + } + break; + default: + break; + } + }); + freeLookCameraSettingChangeHookId = GameInteractor::Instance->RegisterGameHook([](Camera* camera) { + UpdateFreeLookState(camera); + }); + } +} diff --git a/mm/2s2h/Enhancements/Camera/FreeLook.h b/mm/2s2h/Enhancements/Camera/FreeLook.h new file mode 100644 index 000000000..96c236fc1 --- /dev/null +++ b/mm/2s2h/Enhancements/Camera/FreeLook.h @@ -0,0 +1,6 @@ +#ifndef FREE_LOOK_H +#define FREE_LOOK_H + +void RegisterCameraFreeLook(); + +#endif // FREE_LOOK_H diff --git a/mm/2s2h/Enhancements/Enhancements.cpp b/mm/2s2h/Enhancements/Enhancements.cpp index 1de640507..7065d9f44 100644 --- a/mm/2s2h/Enhancements/Enhancements.cpp +++ b/mm/2s2h/Enhancements/Enhancements.cpp @@ -1,6 +1,10 @@ #include "Enhancements.h" void InitEnhancements() { + // Camera + RegisterCameraFreeLook(); + RegisterDebugCam(); + // Cheats RegisterInfiniteCheats(); RegisterMoonJumpOnL(); diff --git a/mm/2s2h/Enhancements/Enhancements.h b/mm/2s2h/Enhancements/Enhancements.h index 9f53f3d4e..751c1b1a2 100644 --- a/mm/2s2h/Enhancements/Enhancements.h +++ b/mm/2s2h/Enhancements/Enhancements.h @@ -1,6 +1,8 @@ #ifndef ENHANCEMENTS_H #define ENHANCEMENTS_H +#include "Camera/DebugCam.h" +#include "Camera/FreeLook.h" #include "Cheats/MoonJump.h" #include "Cheats/Infinite.h" #include "Cheats/UnbreakableRazorSword.h" diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp index 75a521bd6..7ea15b4e3 100644 --- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp +++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.cpp @@ -117,6 +117,18 @@ void GameInteractor_ExecuteOnFlagUnset(FlagType flagType, u32 flag) { GameInteractor::Instance->ExecuteHooksForFilter(flagType, flag); } +void GameInteractor_ExecuteOnCameraChangeModeFlags(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); +} + void GameInteractor_ExecuteOnOpenText(u16 textId) { GameInteractor::Instance->ExecuteHooks(textId); GameInteractor::Instance->ExecuteHooksForID(textId, textId); diff --git a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h index 14c35b22a..9e9545397 100644 --- a/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h +++ b/mm/2s2h/Enhancements/GameInteractor/GameInteractor.h @@ -5,6 +5,7 @@ extern "C" { #endif #include "z64actor.h" +#include "z64camera.h" #ifdef __cplusplus } #endif @@ -39,6 +40,7 @@ typedef enum { GI_VB_PATCH_SIDEROLL, GI_VB_PREVENT_MASK_TRANSFORMATION_CS, GI_VB_SONG_AVAILABLE_TO_PLAY, + GI_VB_USE_CUSTOM_CAMERA, } GIVanillaBehavior; #ifdef __cplusplus @@ -236,6 +238,10 @@ public: DEFINE_HOOK(OnFlagSet, (FlagType flagType, u32 flag)); DEFINE_HOOK(OnFlagUnset, (FlagType flagType, u32 flag)); + DEFINE_HOOK(OnCameraChangeModeFlags, (Camera* camera)); + + DEFINE_HOOK(OnPassPlayerInputs, (Input* input)); + DEFINE_HOOK(OnOpenText, (u16 textId)); DEFINE_HOOK(ShouldItemGive, (u8 item, bool* should)); @@ -270,6 +276,10 @@ void GameInteractor_ExecuteOnSceneFlagUnset(s16 sceneId, FlagType flagType, u32 void GameInteractor_ExecuteOnFlagSet(FlagType flagType, u32 flag); void GameInteractor_ExecuteOnFlagUnset(FlagType flagType, u32 flag); +void GameInteractor_ExecuteOnCameraChangeModeFlags(Camera* camera); + +void GameInteractor_ExecuteOnPassPlayerInputs(Input* input); + void GameInteractor_ExecuteOnOpenText(u16 textId); bool GameInteractor_ShouldItemGive(u8 item); diff --git a/mm/include/PR/gu.h b/mm/include/PR/gu.h index 9ffb7c39f..d112b55a4 100644 --- a/mm/include/PR/gu.h +++ b/mm/include/PR/gu.h @@ -54,13 +54,13 @@ void guNormalize(float* x, float* y, float* z); #endif // 0 -f32 sinf(f32 __x); -f32 cosf(f32 __x); +// f32 sinf(f32 __x); +// f32 cosf(f32 __x); s16 sins(u16 x); s16 coss(u16 x); -f32 sqrtf(f32 f); +// f32 sqrtf(f32 f); #ifdef __sgi #pragma intrinsic(sqrtf); #endif diff --git a/mm/src/code/z_camera.c b/mm/src/code/z_camera.c index 9b8cb8eb1..2f65c3ea9 100644 --- a/mm/src/code/z_camera.c +++ b/mm/src/code/z_camera.c @@ -63,6 +63,8 @@ Vec3f D_801EDDD0; Vec3f D_801EDDE0; Vec3f D_801EDDF0; #include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h" +#include "2s2h/Enhancements/GameInteractor/GameInteractor.h" +#include "2s2h/Enhancements/Camera/FreeLook.h" // Camera will reload its paramData. Usually that means setting the read-only data from what is stored in // CameraModeValue arrays. Although sometimes some read-write data is reset as well @@ -7552,7 +7554,9 @@ Vec3s Camera_Update(Camera* camera) { } // Call the camera update function - sCameraUpdateHandlers[sCameraSettings[camera->setting].cameraModes[camera->mode].funcId](camera); + if (GameInteractor_Should(GI_VB_USE_CUSTOM_CAMERA, true, camera)) { + sCameraUpdateHandlers[sCameraSettings[camera->setting].cameraModes[camera->mode].funcId](camera); + } // Update the interface if (sCameraInitSceneTimer != 0) { @@ -7806,6 +7810,7 @@ s32 Camera_ChangeModeFlags(Camera* camera, s16 mode, u8 forceChange) { func_800DF498(camera); camera->mode = mode; + GameInteractor_ExecuteOnCameraChangeModeFlags(camera); return mode | 0x80000000; } diff --git a/mm/src/overlays/actors/ovl_player_actor/z_player.c b/mm/src/overlays/actors/ovl_player_actor/z_player.c index e811953a6..8a5ba9339 100644 --- a/mm/src/overlays/actors/ovl_player_actor/z_player.c +++ b/mm/src/overlays/actors/ovl_player_actor/z_player.c @@ -12616,6 +12616,8 @@ void Player_Update(Actor* thisx, PlayState* play) { } } + GameInteractor_ExecuteOnPassPlayerInputs(&input); + Player_UpdateCommon(this, play, &input); skipUpdate: play->actorCtx.unk268 = 0;