diff --git a/include/common_structs.h b/include/common_structs.h index 8ae06ea1e..b5efa387a 100644 --- a/include/common_structs.h +++ b/include/common_structs.h @@ -66,6 +66,8 @@ struct D_80150158 { struct Controller { s16 rawStickX; s16 rawStickY; + s16 rightRawStickX; + s16 rightRawStickY; u16 button; // HeldButton u16 buttonPressed; // OnTriggered u16 buttonDepressed; // OffTriggered diff --git a/src/camera.c b/src/camera.c index 5410b33fc..953f1f96b 100644 --- a/src/camera.c +++ b/src/camera.c @@ -992,11 +992,7 @@ void func_8001EE98(Player* player, Camera* camera, s8 index) { func_8001E8E8(camera, player, index); break; } - if (CVarGetInteger("gFreecam", 0) == 1) { - freecam(camera, player, index); - } else { - func_8001E45C(camera, player, index); - } + freecam(camera, player, index); // Runs func_8001E45C when freecam is disabled break; case 8: func_8001E0C4(camera, player, index); diff --git a/src/enhancements/freecam/freecam.cpp b/src/enhancements/freecam/freecam.cpp index 00d8ee84a..dd92d9c2a 100644 --- a/src/enhancements/freecam/freecam.cpp +++ b/src/enhancements/freecam/freecam.cpp @@ -33,6 +33,8 @@ typedef struct { freecamSaveState fState; +Controller fController; + u32 fRankIndex = 0; u32 fTargetPlayer = false; u32 fMode; // freecam mode should probably be an enum @@ -40,7 +42,7 @@ u32 fModeInit = false; int rightMouseButtonDown = 0; // Track if right mouse button is held down -u32 gFreecamControllerType = 0; +u32 bFreecamUseController = false; /** * Controls @@ -66,25 +68,43 @@ u32 gFreecamControllerType = 0; * */ void freecam(Camera* camera, Player* player, s8 index) { - struct Controller* controller = &gControllers[0]; f32 dirX; f32 dirY; f32 dirZ; f32 length; + static bool enabled = false; // Tracks previous activation state + bool freecamEnabled = CVarGetInteger("gFreecam", 0); - if (controller->buttonPressed & L_TRIG) { + if (freecamEnabled && !enabled) { + enabled = true; // Mark that freecam was activated + on_freecam(); + } else if (!freecamEnabled && enabled) { + off_freecam(); + enabled = false; // Reset when freecam is turned off + } + + // Freecam mode is enabled + if (enabled) { + freecam_loop(camera, player, index); + } else { + func_8001E45C(camera, player, index); + } +} + +void on_freecam(void) { + gIsHUDVisible = false; + gPlayerOne->type |= PLAYER_KART_AI; +} + +void off_freecam(void) { + gIsHUDVisible = true; + gPlayerOne->type &= ~PLAYER_KART_AI; +} + +void freecam_loop(Camera* camera, Player* player, s8 index) { + if ((fController.buttonPressed & L_TRIG) && (fController.buttonPressed & R_TRIG)) { // Toggle freecam CVarSetInteger("gFreecam", !CVarGetInteger("gFreecam", 0)); - - // Don't use `bool = !bool` here as the game code can swap these on you. - // Which will confuse the code. This forces it to always be correct - if (CVarGetInteger("gFreecam", 0) == 1) { - player->type |= PLAYER_KART_AI; - } else { - player->type &= PLAYER_KART_AI; - } - - gIsHUDVisible = !CVarGetInteger("gFreecam", 0); } // Calculate forward direction @@ -104,47 +124,39 @@ void freecam(Camera* camera, Player* player, s8 index) { freecam_tick(camera, freeCam.forwardVector); } -f32 gFreecamRotateSmoothingFactor = 0.85f; - void freecam_mouse_manager(Camera* camera, Vec3f forwardVector) { - static int prevMouseX = 0, prevMouseY = 0; auto wnd = GameEngine::Instance->context->GetWindow(); Ship::Coords mouse = wnd->GetMouseDelta(); - // Uint32 mouseState = SDL_GetRelativeMouseState(&mouse.x, &mouse.y); + f32 yawChange = 0.0f; + f32 pitchChange = 0.0f; - // printf("MOUSE %d %d\n", mouse.x, mouse.y); + if (bFreecamUseController) { + // Controller controls + f32 stickX = ((f32)fController.rightRawStickX); + f32 stickY = ((f32)fController.rightRawStickY); - mouse.x = (mouse.x + prevMouseX) / 2; - mouse.y = (mouse.y + prevMouseY) / 2; - prevMouseX = mouse.x; - prevMouseY = mouse.y; + // Sensitivity multipliers + float controllerSensitivityX = 5.0f; // Adjust as needed + float controllerSensitivityY = 3.0f; - if (wnd->GetMouseState(Ship::LUS_MOUSE_BTN_RIGHT)) { + // Deadzone handling (ignore tiny stick movements) + const float deadzone = 0.1f; + if (fabs(stickX) < deadzone) stickX = 0.0f; + if (fabs(stickY) < deadzone) stickY = 0.0f; + + // Instead of adding, directly set rotation velocity (so holding gives a steady rotation) + freeCam.rotVelocity[1] += stickX * controllerSensitivityX; // Yaw (left/right) + freeCam.rotVelocity[2] += stickY * controllerSensitivityY; // Pitch (up/down) + } else { // Mouse controls // Calculate yaw (left/right) and pitch (up/down) changes - f32 yawChange = mouse.x * MOUSE_SENSITIVITY_X; - f32 pitchChange = mouse.y * MOUSE_SENSITIVITY_Y; - - // Smoothly update yaw and pitch - camera->rot[1] += (short) (yawChange * 65535.0f / (2 * M_PI)); // Yaw (left/right) - camera->rot[2] += (short) (pitchChange * 65535.0f / (2 * M_PI)); // Pitch (up/down) - - // Clamp pitch to avoid extreme values - if (camera->rot[2] > 15999) { - camera->rot[2] = 15999; + if (wnd->GetMouseState(Ship::LUS_MOUSE_BTN_RIGHT)) { + yawChange = mouse.x * MOUSE_SENSITIVITY_X; + pitchChange = mouse.y * MOUSE_SENSITIVITY_Y; } - if (camera->rot[2] < -15999) { - camera->rot[2] = -15999; - } - - // Smoothly interpolate the lookAt position - Vec3f targetLookAt = { camera->pos[0] + forwardVector[0], camera->pos[1] + forwardVector[1], - camera->pos[2] + forwardVector[2] }; - - // Smoothing - camera->lookAt[0] += (targetLookAt[0] - camera->lookAt[0]) * gFreecamRotateSmoothingFactor; - camera->lookAt[1] += (targetLookAt[1] - camera->lookAt[1]) * gFreecamRotateSmoothingFactor; - camera->lookAt[2] += (targetLookAt[2] - camera->lookAt[2]) * gFreecamRotateSmoothingFactor; + // Update rotational velocity based on mouse movement + freeCam.rotVelocity[1] += yawChange * 65535.0f / (2 * M_PI); // Yaw (left/right) + freeCam.rotVelocity[2] += pitchChange * 65535.0f / (2 * M_PI); // Pitch (up/down) } } @@ -186,7 +198,6 @@ bool FreecamKeyDown(int virtualKey) { void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector) { auto wnd = GameEngine::Instance->context->GetWindow(); float moveSpeed = gFreecamSpeed; - Controller* controller = &gControllers[0]; // Determine movement direction based on keys pressed Vec3f totalMove = { 0.0f, 0.0f, 0.0f }; @@ -207,44 +218,42 @@ void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector) { // Use n64 controls for use with a controller //! @todo configure this properly - if (gFreecamControllerType == 1) { - // Targeting /fMode is broken - // if (controller->buttonPressed & U_JPAD) { - // fMode = !fMode; - // } - // Target a player - if (controller->buttonPressed & R_TRIG) { - fTargetPlayer = !fTargetPlayer; + if (bFreecamUseController) { + if (fController.buttonDepressed & R_TRIG) { + fTargetPlayer = true; } - if (controller->buttonPressed & L_CBUTTONS) { + if (fController.buttonDepressed & L_TRIG) { + fTargetPlayer = false; + } + if (fController.buttonPressed & L_JPAD) { TargetPreviousPlayer = true; } - if (controller->buttonPressed & R_CBUTTONS) { + if (fController.buttonPressed & R_JPAD) { TargetNextPlayer = true; } - if (controller->button & A_BUTTON) { + if (fController.rawStickY > 10) { Forward = true; } - if (controller->button & B_BUTTON) { + if (fController.rawStickY < -10) { Backward = true; } - if (controller->button & L_JPAD) { + if (fController.rawStickX > 10) { + PanRight = true; + } + if (fController.rawStickX < -10) { PanLeft = true; } - if (controller->button & R_JPAD) { - PanLeft = true; - } - if (controller->button & U_CBUTTONS) { + if (fController.button & B_BUTTON) { Down = true; } - if (controller->button & U_CBUTTONS) { + if (fController.button & A_BUTTON) { Up = true; } - // if (controller->button ??) { - // FastMove = true; - // } - // Keyboard and mouse DX + if (fController.button & Z_TRIG) { + FastMove = true; + } } + // Keyboard and mouse DX #ifdef _WIN32 else if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) { if (FreecamKeyDown('F')) { @@ -364,6 +373,23 @@ void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector) { freeCam.velocity[2] += totalMove[2]; } +void freecam_update_controller(void) { + fController.rawStickX = gControllerPads[0].stick_x; + fController.rawStickY = gControllerPads[0].stick_y; + + fController.rightRawStickX = gControllerPads[0].right_stick_x; + fController.rightRawStickY = gControllerPads[0].right_stick_y; + + if ((gControllerPads[0].button & 4) != 0) { + gControllerPads[0].button |= Z_TRIG; + } + fController.buttonPressed = gControllerPads[0].button & (gControllerPads[0].button ^ fController.button); + fController.buttonDepressed = fController.button & (gControllerPads[0].button ^ fController.button); + fController.button = gControllerPads[0].button; + + // Note that D Pad as stick code has been removed. So if it's needed, it needs to be put back in. +} + void freecam_render_setup(void) { u16 perspNorm; Mat4 matrix; diff --git a/src/enhancements/freecam/freecam.h b/src/enhancements/freecam/freecam.h index e7a1a1ece..db55866ed 100644 --- a/src/enhancements/freecam/freecam.h +++ b/src/enhancements/freecam/freecam.h @@ -9,16 +9,19 @@ extern "C" { #include "camera.h" void freecam(Camera*, Player*, s8); +void on_freecam(void); +void off_freecam(void); +void freecam_loop(Camera*, Player*, s8); +void freecam_update_controller(void); void freecam_render_setup(void); void freecam_mouse_manager(Camera*, Vec3f); void freecam_keyboard_manager(Camera*, Vec3f); extern f32 gFreecamSpeed; extern f32 gFreecamSpeedMultiplier; -extern f32 gFreecamRotateSmoothingFactor; extern f32 gFreecamRotateFollowFactor; extern u32 fRankIndex; -extern u32 gFreecamControllerType; +extern u32 bFreecamUseController; #ifdef __cplusplus } diff --git a/src/enhancements/freecam/freecam_engine.c b/src/enhancements/freecam/freecam_engine.c index 02501ba1f..350ab192b 100644 --- a/src/enhancements/freecam/freecam_engine.c +++ b/src/enhancements/freecam/freecam_engine.c @@ -16,18 +16,32 @@ FreeCam freeCam; #include f32 gDampValue = 0.99f; +f32 gRotDampValue = 0.96f; +// Update FreeCam state void freecam_tick(Camera* camera, Vec3f forwardVector) { - // Update FreeCam state + + // Apply camera movement camera->pos[0] += freeCam.velocity[0] * gDeltaTime; camera->pos[1] += freeCam.velocity[1] * gDeltaTime; camera->pos[2] += freeCam.velocity[2] * gDeltaTime; - // Apply damping to velocity + // Damp the velocity back to zero over time (camera slowly comes to a stop) freeCam.velocity[0] *= gDampValue; freeCam.velocity[1] *= gDampValue; freeCam.velocity[2] *= gDampValue; + // Apply camera rotation + camera->rot[0] += freeCam.rotVelocity[0] * gDeltaTime; + camera->rot[1] += freeCam.rotVelocity[1] * gDeltaTime; + camera->rot[2] += freeCam.rotVelocity[2] * gDeltaTime; + + // Damp the velocity back to zero over time (camera rot slowly comes to a stop) + freeCam.rotVelocity[0] *= gRotDampValue; + freeCam.rotVelocity[1] *= gRotDampValue; + freeCam.rotVelocity[2] *= gRotDampValue; + + // Update lookAt camera->lookAt[0] = camera->pos[0] + forwardVector[0]; camera->lookAt[1] = camera->pos[1] + forwardVector[1]; camera->lookAt[2] = camera->pos[2] + forwardVector[2]; diff --git a/src/enhancements/freecam/freecam_engine.h b/src/enhancements/freecam/freecam_engine.h index 7c7ecc754..c817d8986 100644 --- a/src/enhancements/freecam/freecam_engine.h +++ b/src/enhancements/freecam/freecam_engine.h @@ -14,11 +14,13 @@ extern "C" { typedef struct { Vec3f velocity; + Vec3f rotVelocity; Vec3f forwardVector; } FreeCam; extern FreeCam freeCam; extern f32 gDampValue; +extern f32 gRotDampValue; extern u32 fTargetPlayer; extern u32 fRankIndex; @@ -26,8 +28,6 @@ void freecam_calculate_forward_vector_allow_rotation(Camera* camera, Vec3f forwa void freecam_target_player(Camera* camera, Vec3f forwardVector); void freecam_tick(Camera* camera, Vec3f forwardVector); -extern f32 gDampValue; - #ifdef __cplusplus } #endif diff --git a/src/main.c b/src/main.c index e4e49d155..48fcec7ac 100644 --- a/src/main.c +++ b/src/main.c @@ -367,13 +367,18 @@ void update_controller(s32 index) { } // Prevents pause menu intereference while controlling flycam - if ((CVarGetInteger("gFreecam", 0) == 1) && (gFreecamControllerType == 0) && (gGamestate == RACING)) { + // Freecam only works with controller 1 + if ((CVarGetInteger("gFreecam", 0) == 1) && (gGamestate == RACING) && (index == 0)) { + freecam_update_controller(); return; } controller->rawStickX = gControllerPads[index].stick_x; controller->rawStickY = gControllerPads[index].stick_y; + controller->rightRawStickX = gControllerPads[index].right_stick_x; + controller->rightRawStickY = gControllerPads[index].right_stick_y; + if ((gControllerPads[index].button & 4) != 0) { gControllerPads[index].button |= Z_TRIG; } diff --git a/src/port/ui/FreecamWindow.cpp b/src/port/ui/FreecamWindow.cpp index e5cf5eea1..b5a4a0ba4 100644 --- a/src/port/ui/FreecamWindow.cpp +++ b/src/port/ui/FreecamWindow.cpp @@ -22,14 +22,14 @@ extern "C" { // } FreeCam; extern f32 gDampValue; +extern f32 gRotDampValue; extern f32 gFreecamSpeed; extern f32 gFreecamSpeedMultiplier; -extern f32 gFreecamRotateSmoothingFactor; extern f32 gFreecamFollowFactor; extern char* D_800E76A8[]; extern u32 fRankIndex; extern u32 fTargetPlayer; -extern u32 gFreecamControllerType; +extern u32 bFreecamUseController; void freecam_get_player_from_character(s32 characterId); } @@ -58,12 +58,13 @@ void RegisterFreecamWidgets() { //mPortMenu->AddWidget(path, "Controller mode is not configured yet.", WIDGET_TEXT); //static int current_item = 0; - mPortMenu->AddWidget(path, "Control Type", WIDGET_COMBOBOX) + mPortMenu->AddWidget(path, "Human Interface Device", WIDGET_COMBOBOX) .ValuePointer(&controllerType) - .Callback([](WidgetInfo& info) { gFreecamControllerType = (uint32_t)*std::get(info.valuePointer); }) + .Callback([](WidgetInfo& info) { bFreecamUseController = (uint32_t)*std::get(info.valuePointer); }) .Options(UIWidgets::ComboboxOptions().ComboMap(controlType)); - mPortMenu->AddWidget(path, "Move: W,A,S,D\nUp: Space, Down: Shift\nFaster: Ctrl\nLook: Right-mouse button\nTarget Player Mode: F, Next: M, Previous: N", WIDGET_TEXT); + mPortMenu->AddWidget(path, "Keyboard: Move: W,A,S,D, Up: Space, Down: Shift\n Faster: Ctrl, Look: Right-mouse button\n Target Player Mode: F, Next: M, Previous: N", WIDGET_TEXT); + mPortMenu->AddWidget(path, "Controller: Up: A, Down: B, Faster: Z\n Target Player Mode: R, Next: Right DPad, Previous: Left DPad\n Driving Mode: L and R Buttons", WIDGET_TEXT); mPortMenu->AddWidget(path, "Enable Flycam", WIDGET_CVAR_CHECKBOX) .CVar("gFreecam") .Options(UIWidgets::CheckboxOptions({{ .tooltip = "Allows you to fly around the course"}})); @@ -77,8 +78,8 @@ void RegisterFreecamWidgets() { mPortMenu->AddWidget(path, "Camera Speed Multiplier", WIDGET_SLIDER_FLOAT) .ValuePointer(&gFreecamSpeedMultiplier) .Options(UIWidgets::FloatSliderOptions().Min(1.5f).Max(15.0f).Step(0.1f).Format("%.1f")); - mPortMenu->AddWidget(path, "Camera Rotation Smoothing", WIDGET_SLIDER_FLOAT) - .ValuePointer(&gFreecamRotateSmoothingFactor) + mPortMenu->AddWidget(path, "Camera Rotator Damping", WIDGET_SLIDER_FLOAT) + .ValuePointer(&gRotDampValue) .Options(UIWidgets::FloatSliderOptions().Min(0.0f).Max(1.0f).Step(0.01f).Format("%.2f")); mPortMenu->AddWidget(path, "Follow Factor", WIDGET_SLIDER_FLOAT) .ValuePointer(&gFreecamFollowFactor)