Skip to content

Commit

Permalink
'Free/Debug' Camera & Free Look Camera (#323)
Browse files Browse the repository at this point in the history
* Remove BenPort.h from functions.h

* use specific z64 includes

* free cam

* this -> thisx

* fl newline

* whitespace

* remove debugging stuff

* min and max cam heights. use correct func_800CBFA4 arg

* document and re-organise free cam

* fix linux building

* Custom Camera VB

* remove redundant section in header

* fix hook and re-organise menu

* Free Cam

* fix control flipping and some speed tweaks

* Fix Player flags not being reset

* Fix movement under roll: todo fix rotation under roll

* six degrees of freedom

* Multiple camera mode options, remove black bars, start with focal point by eye

* update free look

* cvar rename, menu disabling

* remove player state flag change

* renaming and remove some camera jumpiness

* remove scam comment

* remove port selection

* correct debugcam at initialised position

* fix focal point for non roll
  • Loading branch information
inspectredc authored May 11, 2024
1 parent 0c94bda commit 1004fdc
Show file tree
Hide file tree
Showing 13 changed files with 566 additions and 4 deletions.
44 changes: 44 additions & 0 deletions mm/2s2h/BenGui/BenMenuBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,50 @@ extern std::shared_ptr<HudEditorWindow> 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");
Expand Down
45 changes: 45 additions & 0 deletions mm/2s2h/Enhancements/Camera/CameraUtils.h
Original file line number Diff line number Diff line change
@@ -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
243 changes: 243 additions & 0 deletions mm/2s2h/Enhancements/Camera/DebugCam.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
#include "DebugCam.h"
#include <libultraship/bridge.h>
#include "Enhancements/GameInteractor/GameInteractor.h"
#include "CameraUtils.h"

extern "C" {
#include <macros.h>
#include <functions.h>
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<GameInteractor::ShouldVanillaBehavior>(freeCamVBHookId);
freeCamVBHookId = 0;
}

if (freeCamDisableInputsId) {
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPassPlayerInputs>(freeCamDisableInputsId);
freeCamDisableInputsId = 0;
}

if (CVarGetInteger("gEnhancements.Camera.DebugCam.Enable", 0)) {
freeCamVBHookId = REGISTER_VB_SHOULD(GI_VB_USE_CUSTOM_CAMERA, {
Camera* camera = static_cast<Camera*>(opt);
Camera_DebugCam(camera);
*should = false;
});

freeCamDisableInputsId = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPassPlayerInputs>([](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));
}
});
}
}
6 changes: 6 additions & 0 deletions mm/2s2h/Enhancements/Camera/DebugCam.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef FREE_CAM_H
#define FREE_CAM_H

void RegisterDebugCam();

#endif // FREE_CAM_H
Loading

0 comments on commit 1004fdc

Please sign in to comment.