From 47e95c0e182ed21486648d88e2ce52aa05d2fe7c Mon Sep 17 00:00:00 2001 From: MegaMech Date: Tue, 10 Sep 2024 16:19:19 -0600 Subject: [PATCH] [enhancement] Freecam (#56) * Create noclip.c * Create noclip.h * Update camera.c * Update render_courses.c * Update ImguiUI.cpp * Freecam Done * Add smoothing to window * Update freecam * focus player * fix * Update freecam.c * Update freecam.h * Update render_courses.c * Update FreecamWindow.cpp * Update Engine.cpp * Move vars --------- Co-authored-by: MegaMech <7255464+MegaMech@users.noreply.github.com> --- CMakeLists.txt | 3 + courses/koopa_troopa_beach/course_offsets.c | 3 +- src/camera.c | 7 +- src/enhancements/freecam/freecam.c | 374 ++++++++++++++++++++ src/enhancements/freecam/freecam.h | 24 ++ src/enhancements/freecam/freecam_engine.c | 149 ++++++++ src/enhancements/freecam/freecam_engine.h | 39 ++ src/enhancements/freecam/n64_freecam.c | 99 ++++++ src/enhancements/freecam/n64_freecam.h | 13 + src/main.c | 31 ++ src/main.h | 1 + src/port/ui/FreecamWindow.cpp | 109 ++++++ src/port/ui/FreecamWindow.h | 16 + src/port/ui/ImguiUI.cpp | 15 + src/racing/render_courses.c | 11 +- 15 files changed, 890 insertions(+), 4 deletions(-) create mode 100644 src/enhancements/freecam/freecam.c create mode 100644 src/enhancements/freecam/freecam.h create mode 100644 src/enhancements/freecam/freecam_engine.c create mode 100644 src/enhancements/freecam/freecam_engine.h create mode 100644 src/enhancements/freecam/n64_freecam.c create mode 100644 src/enhancements/freecam/n64_freecam.h create mode 100644 src/port/ui/FreecamWindow.cpp create mode 100644 src/port/ui/FreecamWindow.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 34483a55e..5f5f55961 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src/audio ${CMAKE_CURRENT_SOURCE_DIR}/src/debug ${CMAKE_CURRENT_SOURCE_DIR}/src/networking + ${CMAKE_CURRENT_SOURCE_DIR}/src/enhancements/freecam ) # Collect source files to build the executable @@ -133,6 +134,8 @@ file(GLOB_RECURSE ALL_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "courses/*.h" "src/networking/*.c" "src/networking/*.h" + "src/enhancements/freecam/*.c" + "src/enhancements/freecam/*.h" ) list(APPEND ALL_FILES ${ALL_FILES_ROOT}) diff --git a/courses/koopa_troopa_beach/course_offsets.c b/courses/koopa_troopa_beach/course_offsets.c index e288e62bf..33271cd95 100644 --- a/courses/koopa_troopa_beach/course_offsets.c +++ b/courses/koopa_troopa_beach/course_offsets.c @@ -130,7 +130,7 @@ Gfx* koopa_troopa_beach_dls2[] = { // 0x06014828 // animations? lights? -u32 d_course_koopa_troopa_beach_unk_data1[] = { +uintptr_t d_course_koopa_troopa_beach_unk_data1[] = { 0x00000000, 0x00000001, 0x00000000, @@ -319,3 +319,4 @@ Animation* d_course_koopa_troopa_beach_unk_data5[] = { u32 d_course_koopa_troopa_beach_unk_data6[] = { 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000001, }; + diff --git a/src/camera.c b/src/camera.c index 8838bb04e..a1ddc0f16 100644 --- a/src/camera.c +++ b/src/camera.c @@ -16,6 +16,7 @@ #include "code_80005FD0.h" #include "main.h" #include "spawn_players.h" +#include "enhancements/freecam/freecam_engine.h" f32 D_800DDB30[] = { 0.4f, 0.6f, 0.275f, 0.3f }; @@ -984,7 +985,11 @@ void func_8001EE98(Player* player, Camera* camera, s8 index) { func_8001E8E8(camera, player, index); break; } - func_8001E45C(camera, player, index); + if (CVarGetInteger("gFreecam", 0) == 1) { + freecam(camera, player, index); + } else { + func_8001E45C(camera, player, index); + } break; case 8: func_8001E0C4(camera, player, index); diff --git a/src/enhancements/freecam/freecam.c b/src/enhancements/freecam/freecam.c new file mode 100644 index 000000000..b7b148425 --- /dev/null +++ b/src/enhancements/freecam/freecam.c @@ -0,0 +1,374 @@ +#include +#include +#include +#include +#include "main.h" +#include +#include +#include "racing/collision.h" +#include "player_controller.h" +#include "code_80057C60.h" +#include "code_800029B0.h" +#include "code_80005FD0.h" +#include +#include "freecam_engine.h" +#include "n64_freecam.h" + +typedef struct { + Vec3f pos; + Vec3f lookAt; + Vec3s rot; +} freecamSaveState; + +freecamSaveState fState; + +u32 fRankIndex = -1; +u32 fTargetPlayer = false; +u32 fMode; // freecam mode should probably be an enum +u32 fModeInit = false; + +int rightMouseButtonDown = 0; // Track if right mouse button is held down + +u32 gFreecamControllerType = 0; + +// void freecam_n64_calculate_forward_vector(Camera* camera, Vec3f forwardVector); +// void freecam_n64_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance); +// void freecam_calculate_forward_vector(Camera* camera, Vec3f forwardVector); +// void freecam_move_camera_forward(Camera* camera, f32 distance); + +// void freecam_update(Camera* camera, struct Controller *controller); +// void freecam_n64_controller_manager(Camera *camera, struct Controller *controller, Player *player); +// void freecam_target_player(Camera *camera, u32 playerIndex); +// void freecam_move_camera_up(Camera* camera, struct Controller *controller, f32 distance); +void freecam_save_state(Camera *camera); +void freecam_load_state(Camera *camera); +void freecam_mouse_manager(Camera *camera); +void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector); + +/** + * Controls + * + * Forward: A + * Backward: B + * + * Go faster: Z + * + * Up: C-up + * Down: C-down + * + * Targets players based on rank position + * + * Target player: R-trig + * Target next player: C-right + * Target previous player: C-left + * + * Switch camera modes: D-pad left + * + * Camera mode 1: Enter freecam at the player's position + * Camera mode 2: Enter freecam at previous freecam spot + * +*/ + +void freecam(Camera *camera, Player *player, s8 index) { + struct Controller *controller = &gControllers[0]; + f32 dirX; + f32 dirY; + f32 dirZ; + f32 length; + + if (controller->buttonPressed & L_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); + + if (CVarGetInteger("gFreecam", 0) == 1) { + + if (fMode && fModeInit) { + freecam_load_state(camera); + } else { + // !fMode or fMode not initialized + freecam_target_player(camera, get_player_index_for_player(player)); + } + + return; + } else { + if(fMode) { + freecam_save_state(camera); + } + } + } + + // Driving mode + if (CVarGetInteger("gFreecam", 0) == 0) { + // Use normal camera code + func_8001E45C(camera, &gPlayers[fRankIndex], index); + return; + } + + //if (player == gPlayerOne) { return; } + + //player->type &= ~PLAYER_HUMAN; + //player->type |= PLAYER_HUMAN; + + // if ((player->type & PLAYER_START_SEQUENCE)) { return; } + + + // Mouse/Keyboard + if (gFreecamControllerType == 0) { + freecam_calculate_forward_vector_allow_rotation(camera, freeCam.forwardVector); + freecam_mouse_manager(camera, freeCam.forwardVector); + freecam_keyboard_manager(camera, freeCam.forwardVector); + } else { // Stock N64 controller + freecam_n64_controller_manager(camera, controller, player); + } + if (!fTargetPlayer) { + freecam_update(camera, freeCam.forwardVector); + } +} + +void freecam_save_state(Camera *camera) { + fState.pos[0] = camera->pos[0]; + fState.pos[1] = camera->pos[1]; + fState.pos[2] = camera->pos[2]; + + fState.lookAt[0] = camera->lookAt[0]; + fState.lookAt[1] = camera->lookAt[1]; + fState.lookAt[2] = camera->lookAt[2]; + + fState.rot[0] = camera->rot[0]; + fState.rot[1] = camera->rot[1]; + fState.rot[2] = camera->rot[2]; + fModeInit = true; +} + +void freecam_load_state(Camera *camera) { + camera->pos[0] = fState.pos[0]; + camera->pos[1] = fState.pos[1]; + camera->pos[2] = fState.pos[2]; + + camera->lookAt[0] = fState.lookAt[0]; + camera->lookAt[1] = fState.lookAt[1]; + camera->lookAt[2] = fState.lookAt[2]; + + camera->rot[0] = fState.rot[0]; + camera->rot[1] = fState.rot[1]; + camera->rot[2] = fState.rot[2]; +} + +f32 gFreecamRotateSmoothingFactor = 0.85f; + +void freecam_mouse_manager(Camera* camera, Vec3f forwardVector) { + int mouseX, mouseY; + static int prevMouseX = 0, prevMouseY = 0; + Uint32 mouseState = SDL_GetRelativeMouseState(&mouseX, &mouseY); + + mouseX = (mouseX + prevMouseX) / 2; + mouseY = (mouseY + prevMouseY) / 2; + prevMouseX = mouseX; + prevMouseY = mouseY; + + if (mouseState & SDL_BUTTON(SDL_BUTTON_RIGHT)) { + // Calculate yaw (left/right) and pitch (up/down) changes + f32 yawChange = mouseX * MOUSE_SENSITIVITY_X; + f32 pitchChange = mouseY * 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 (camera->rot[2] < -15999) camera->rot[2] = -15999; + + // Calculate the forward vector based on the new yaw and pitch + //freecam_calculate_forward_vector_allow_rotation(camera, forwardVector); + + // 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; + } +} + + +f32 gFreecamSpeed = 3.0f; +f32 gFreecamSpeedMultiplier = 2.0f; + +void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector) { + const uint8_t* keystate = SDL_GetKeyboardState(NULL); + float moveSpeed = gFreecamSpeed; + + // Fast movement with Ctrl + if (keystate[SDL_SCANCODE_LCTRL] || keystate[SDL_SCANCODE_RCTRL]) { + moveSpeed *= gFreecamSpeedMultiplier; + } + + // Determine movement direction based on keys pressed + Vec3f totalMove = {0.0f, 0.0f, 0.0f}; + + + // if (keystate[SDL_SCANCODE_F]) { + // fMode = true; + // } + + // // Target a player + // if (keystate[SDL_SCANCODE_G]) { + // fTargetPlayer = false; + // } + + // Target next player + if (keystate[SDL_SCANCODE_N]) { + if (fRankIndex > 0) { + fRankIndex--; + camera->playerId = fRankIndex; + D_800DC5EC->player = &gPlayers[fRankIndex]; + } + } + + // Target previous player + if (keystate[SDL_SCANCODE_M]) { + if (fRankIndex < 7) { + fRankIndex++; + camera->playerId = fRankIndex; + D_800DC5EC->player = &gPlayers[fRankIndex]; + } + } + + // Target camera at chosen player + if (fRankIndex != -1) { + freecam_target_player(camera, gGPCurrentRacePlayerIdByRank[fRankIndex]); + // Don't run the other camera code. + return; + } + + + if (keystate[SDL_SCANCODE_W]) { + totalMove[0] += forwardVector[0] * moveSpeed; + totalMove[2] += forwardVector[2] * moveSpeed; + } + if (keystate[SDL_SCANCODE_S]) { + totalMove[0] -= forwardVector[0] * moveSpeed; + totalMove[2] -= forwardVector[2] * moveSpeed; + } + if (keystate[SDL_SCANCODE_D]) { + totalMove[0] -= forwardVector[2] * moveSpeed; // Pan right + totalMove[2] += forwardVector[0] * moveSpeed; + } + if (keystate[SDL_SCANCODE_A]) { + totalMove[0] += forwardVector[2] * moveSpeed; // Pan left + totalMove[2] -= forwardVector[0] * moveSpeed; + } + if (keystate[SDL_SCANCODE_SPACE]) { + totalMove[1] += moveSpeed; // Move up + } + if (keystate[SDL_SCANCODE_LSHIFT] || keystate[SDL_SCANCODE_RSHIFT]) { + totalMove[1] -= moveSpeed; // Move down + } + freeCam.velocity[0] += totalMove[0]; + freeCam.velocity[1] += totalMove[1]; + freeCam.velocity[2] += totalMove[2]; + + // Update camera's lookAt position + camera->lookAt[0] = camera->pos[0] + forwardVector[0]; + camera->lookAt[2] = camera->pos[2] + forwardVector[2]; +} + +// Control the camera using the n64 controller +void freecam_n64_controller_manager(Camera *camera, struct Controller *controller, Player *player) { + + if (controller->buttonPressed & U_JPAD) { + fMode = !fMode; + } + + // Target a player + if (controller->buttonPressed & R_TRIG) { + fTargetPlayer = !fTargetPlayer; + } + + // Target next player + if (controller->buttonPressed & L_CBUTTONS) { + if (fRankIndex > 0) { + fRankIndex--; + camera->playerId = fRankIndex; + D_800DC5EC->player = &gPlayers[fRankIndex]; + } + } + + // Target previous player + if (controller->buttonPressed & R_CBUTTONS) { + if (fRankIndex < 7) { + fRankIndex++; + camera->playerId = fRankIndex; + D_800DC5EC->player = &gPlayers[fRankIndex]; + } + } + + // Target camera at chosen player + if (fTargetPlayer) { + freecam_target_player(camera, gGPCurrentRacePlayerIdByRank[fRankIndex]); + // Don't run the other camera code. + return; + } + + // Rotation + if (!fTargetPlayer) { + if (controller->stickDirection != 0) { + freecam_n64_update(camera, controller); + } + } + + // Forward + if (controller->button & A_BUTTON) { + freecam_n64_move_camera_forward(camera, controller, 3.0f); + } + + // Backward B button but not A button. + if (controller->button & B_BUTTON && !(controller->button & A_BUTTON)) { + freecam_n64_move_camera_forward(camera, controller, -3.0f); + } + + // Up + if (controller->button & U_CBUTTONS) { + freecam_n64_move_camera_up(camera, controller, 2.0f); + } + // Up + if (controller->button & D_CBUTTONS) { + freecam_n64_move_camera_up(camera, controller, -2.0f); + } +} + +void freecam_render_setup(void) { + u16 perspNorm; + Mat4 matrix; + init_rdp(); + func_802A53A4(); + init_rdp(); + func_80057FC4(0); + gSPSetGeometryMode(gDisplayListHead++, G_ZBUFFER | G_SHADE | G_SHADING_SMOOTH); + gSPClearGeometryMode(gDisplayListHead++, G_CULL_BACK | G_CULL_BOTH | G_CULL_FRONT); + guPerspective(&gGfxPool->mtxPersp[0], &perspNorm, gCameraZoom[0], gScreenAspect, D_80150150, D_8015014C, 1.0f); + gSPPerspNormalize(gDisplayListHead++, perspNorm); + gSPMatrix(gDisplayListHead++, (&gGfxPool->mtxPersp[0]), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); + guLookAt(&gGfxPool->mtxLookAt[0], camera1->pos[0], camera1->pos[1], camera1->pos[2], camera1->lookAt[0], camera1->lookAt[1], camera1->lookAt[2], camera1->up[0], camera1->up[1], camera1->up[2]); + gSPMatrix(gDisplayListHead++, (&gGfxPool->mtxLookAt[0]), G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION); + mtxf_identity(matrix); + gSPSetGeometryMode(gDisplayListHead++, G_CULL_BACK); + render_set_position(matrix, 0); + init_rdp(); +} diff --git a/src/enhancements/freecam/freecam.h b/src/enhancements/freecam/freecam.h new file mode 100644 index 000000000..5727ee977 --- /dev/null +++ b/src/enhancements/freecam/freecam.h @@ -0,0 +1,24 @@ +#ifndef FREECAM_H +#define FREECAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "camera.h" + +void freecam(Camera*, Player*, s8); +void freecam_render_setup(void); + +extern f32 gFreecamSpeed; +extern f32 gFreecamSpeedMultiplier; +extern f32 gFreecamRotateSmoothingFactor; +extern u32 fRankIndex; +extern u32 gFreecamControllerType; + +#ifdef __cplusplus +} +#endif + +#endif // FREECAM_H diff --git a/src/enhancements/freecam/freecam_engine.c b/src/enhancements/freecam/freecam_engine.c new file mode 100644 index 000000000..ff06330b8 --- /dev/null +++ b/src/enhancements/freecam/freecam_engine.c @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include "player_controller.h" +#include "code_80057C60.h" +#include "code_800029B0.h" +#include "code_80005FD0.h" +#include "racing/collision.h" +#include "main.h" +#include +#include "freecam_engine.h" + +FreeCam freeCam; + +#include + +f32 gDampValue = 0.99f; + +void freecam_update(Camera *camera, Vec3f forwardVector) { + // Update FreeCam state + 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 + freeCam.velocity[0] *= gDampValue; + freeCam.velocity[1] *= gDampValue; + freeCam.velocity[2] *= gDampValue; + + camera->lookAt[0] = camera->pos[0] + forwardVector[0]; + camera->lookAt[1] = camera->pos[1] + forwardVector[1]; + camera->lookAt[2] = camera->pos[2] + forwardVector[2]; +} + +// Function to move the camera forward or backward without changing its height +void freecam_move_camera_forward(Camera *camera, f32 moveSpeed, Vec3f forwardVector) { + // Calculate the forward vector + forwardVector[0] = camera->lookAt[0] - camera->pos[0]; + //forwardVector[1] = 0; // Don't change the height + forwardVector[2] = camera->lookAt[2] - camera->pos[2]; + + // Normalize the forward vector + f32 length = sqrtf(forwardVector[0] * forwardVector[0] + forwardVector[2] * forwardVector[2]); + if (length != 0) { + forwardVector[0] /= length; + forwardVector[2] /= length; + } + + // Update FreeCam velocity + freeCam.velocity[0] = forwardVector[0] * moveSpeed; + freeCam.velocity[2] = forwardVector[2] * moveSpeed; + + camera->lookAt[0] = camera->pos[0] + forwardVector[0]; + camera->lookAt[2] = camera->pos[0] + forwardVector[2]; + +} + + +void freecam_pan_camera(Camera *camera, f32 moveSpeed) { + Vec3f rightVector; + f32 length; + + // Calculate the right vector for pan movement + rightVector[0] = camera->lookAt[2] - camera->pos[2]; + // rightVector[1] = 0; // Maintain the same height + rightVector[2] = -(camera->lookAt[0] - camera->pos[0]); + + // Normalize the right vector + length = sqrtf(rightVector[0] * rightVector[0] + rightVector[2] * rightVector[2]); + if (length != 0) { + rightVector[0] /= length; + rightVector[2] /= length; + } + + // Update camera's position + freeCam.velocity[0] = rightVector[0] * moveSpeed; + freeCam.velocity[2] = rightVector[2] * moveSpeed; + +} + + + + +void freecam_calculate_forward_vector_allow_rotation(Camera* camera, Vec3f forwardVector) { + // Convert yaw from 0-65535 to degrees + f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; + f32 yaw = (camera->rot[1] / 65535.0f) * 360.0f; + + // Convert degrees to radians + pitch = pitch * M_PI / 180.0f; + yaw = yaw * M_PI / 180.0f; + + // Calculate the forward vector based on yaw, ignoring pitch to keep height unchanged + forwardVector[0] = -sinf(yaw); + forwardVector[1] = -sinf(pitch); // Keep the height unchanged + forwardVector[2] = cosf(yaw); +} + +// Calculates the forward direction vector based on camera orientation +void freecam_calculate_forward_vector(Camera *camera, Vec3f forwardVector) { + // Convert yaw from 0-65535 to degrees + f32 yaw = (camera->rot[0] / 65535.0f) * 360.0f; + f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; + + // Convert degrees to radians + yaw = yaw * M_PI / 180.0f; + pitch = pitch * M_PI / 180.0f; + + // Calculate the forward vector based on yaw and pitch + forwardVector[0] = -sinf(yaw) * cosf(pitch); // x-component + forwardVector[1] = sinf(pitch); // y-component (vertical) + forwardVector[2] = cosf(yaw) * cosf(pitch); // z-component +} + +// Function to move the camera forward +void freecam_move_camera_up(Camera* camera, f32 distance) { + // Update the velocity of the camera in the Y direction + freeCam.velocity[1] += distance; + camera->lookAt[1] += distance; +} +void freecam_move_camera_down(Camera* camera, f32 distance) { + // Update the velocity of the camera in the Y direction + freeCam.velocity[1] += distance; + camera->lookAt[1] += distance; +} + +void freecam_target_player(Camera *camera, u32 playerIndex) { + Vec3f forwardVector;// = 2.0f; + Player *player = &gPlayers[playerIndex]; + + // Calculate the direction from the player to the camera + f32 dirX = player->pos[0] - camera->pos[0]; + f32 dirY = player->pos[1] - camera->pos[1]; + f32 dirZ = player->pos[2] - camera->pos[2]; + + // Normalize the direction vector (if needed) + f32 length = sqrtf(dirX * dirX + dirY * dirY + dirZ * dirZ); + if (length > 0) { + dirX /= length; + dirY /= length; + dirZ /= length; + } + + // Update the camera's look-at direction + camera->lookAt[0] = camera->pos[0] + dirX; + camera->lookAt[1] = camera->pos[1] + dirY; + camera->lookAt[2] = camera->pos[2] + dirZ; +} diff --git a/src/enhancements/freecam/freecam_engine.h b/src/enhancements/freecam/freecam_engine.h new file mode 100644 index 000000000..f6a69052f --- /dev/null +++ b/src/enhancements/freecam/freecam_engine.h @@ -0,0 +1,39 @@ +#ifndef FREE_CAM_ENGINE_H +#define FREE_CAM_ENGINE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "camera.h" + +// Yaw/pitch rotation sensitivity +#define MOUSE_SENSITIVITY_X 0.006f +#define MOUSE_SENSITIVITY_Y 0.006f + +typedef struct { + Vec3f velocity; + Vec3f forwardVector; +} FreeCam; + +extern FreeCam freeCam; +extern f32 gDampValue; + +//void update_camera(Camera *camera, f32 deltaTime); +void freecam_pan_camera(Camera *camera, f32 moveSpeed); +void freecam_calculate_forward_vector_allow_rotation(Camera* camera, Vec3f forwardVector); +void freecam_calculate_forward_vector(Camera* camera, Vec3f forwardVector); +void freecam_move_camera_forward(Camera* camera, f32 distance, Vec3f forwardVector); +void freecam_target_player(Camera *camera, u32 playerIndex); +void freecam_update(Camera* camera, Vec3f forwardVector); +void freecam_move_camera_up(Camera* camera, f32); +void freecam_move_camera_down(Camera* camera, f32); + +extern f32 gDampValue; + +#ifdef __cplusplus +} +#endif + +#endif // FREE_CAM_ENGINE_H \ No newline at end of file diff --git a/src/enhancements/freecam/n64_freecam.c b/src/enhancements/freecam/n64_freecam.c new file mode 100644 index 000000000..5a3ffb9e8 --- /dev/null +++ b/src/enhancements/freecam/n64_freecam.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include "code_80057C60.h" +#include "code_800029B0.h" +#include "code_80005FD0.h" +#include "camera.h" +#include +#include "player_controller.h" + +#define N64_SENSITIVITY_X 0.0003f +#define N64_SENSITIVITY_Y 0.0003f + +// Calculates the forward direction vector based on camera orientation +void freecam_n64_calculate_forward_vector(Camera* camera, Vec3f forwardVector) { + f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; // Convert pitch from 0-65535 to degrees + f32 yaw = (camera->rot[1] / 65535.0f) * 360.0f; // Convert yaw from 0-65535 to degrees + + // Convert degrees to radians + pitch = pitch * M_PI / 180.0f; + yaw = yaw * M_PI / 180.0f; + + forwardVector[0] = -sinf(yaw) * cosf(pitch); + forwardVector[1] = -sinf(pitch); + forwardVector[2] = cosf(yaw) * cosf(pitch); +} + +// Function to move the camera forward +void freecam_n64_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance) { + Vec3f forwardVector; + Vec3f rightVector; + f32 length; + freecam_n64_calculate_forward_vector(camera, forwardVector); + + if (controller->button & Z_TRIG) { + distance *= 3; + } + + // Normalize the forward vector + length = sqrtf(forwardVector[0] * forwardVector[0] + forwardVector[1] * forwardVector[1] + forwardVector[2] * forwardVector[2]); + forwardVector[0] /= length; + forwardVector[1] /= length; + forwardVector[2] /= length; + + // Calculate the right vector by taking the cross product of forward and up + rightVector[0] = forwardVector[1] * camera->up[2] - forwardVector[2] * camera->up[1]; + rightVector[1] = forwardVector[2] * camera->up[0] - forwardVector[0] * camera->up[2]; + rightVector[2] = forwardVector[0] * camera->up[1] - forwardVector[1] * camera->up[0]; + + // Move the camera's position along the forward vector while considering its up vector + camera->pos[0] += forwardVector[0] * distance; + camera->pos[1] += forwardVector[1] * distance; + camera->pos[2] += forwardVector[2] * distance; + + // Optionally, you can also adjust the lookAt point to maintain the same relative position + camera->lookAt[0] += forwardVector[0] * distance; + camera->lookAt[1] += forwardVector[1] * distance; + camera->lookAt[2] += forwardVector[2] * distance; +} + +// Function to move the camera forward +void freecam_n64_move_camera_up(Camera* camera, struct Controller *controller, f32 distance) { + // Check if the Z button is pressed (for faster movement) + if (controller->button & Z_TRIG) { + distance *= 3; + } + + // Move the camera's position along its up vector (Y-axis) + camera->pos[1] += distance; + + // Optionally, adjust the lookAt point to maintain the same relative position + camera->lookAt[1] += distance; +} + +// Update camera rotation and lookAt point based on input +void freecam_n64_update(Camera* camera, struct Controller *controller) { + // Calculate yaw (horizontal movement) + f32 yawChange = controller->rawStickX * N64_SENSITIVITY_X; + f32 pitchChange = controller->rawStickY * N64_SENSITIVITY_Y; + Vec3f forwardVector; + + check_bounding_collision(&camera->collision, 50, camera->pos[0], camera->pos[1], camera->pos[2]); + + camera->rot[1] += (short)(yawChange * 65535.0f / (2 * M_PI)); // Convert radians to 0-65535 range + + camera->rot[2] += (short)(-pitchChange * 65535.0f / (2 * M_PI)); // Convert radians to 0-65535 range + + if (camera->rot[2] > 15999) { + camera->rot[2] = 15999; + } else if (camera->rot[2] < -15999) { + camera->rot[2] = -15999; + } + + // Update the lookAt point based on the new orientation + freecam_calculate_forward_vector(camera, forwardVector); + camera->lookAt[0] = camera->pos[0] + forwardVector[0]; + camera->lookAt[1] = camera->pos[1] + forwardVector[1]; + camera->lookAt[2] = camera->pos[2] + forwardVector[2]; +} \ No newline at end of file diff --git a/src/enhancements/freecam/n64_freecam.h b/src/enhancements/freecam/n64_freecam.h new file mode 100644 index 000000000..703383d4d --- /dev/null +++ b/src/enhancements/freecam/n64_freecam.h @@ -0,0 +1,13 @@ +#ifndef N64_FREECAM_H +#define N64_FREECAM_H + +#include + + +void freecam_n64_move_camera_up(Camera* camera, struct Controller *controller, f32 distance); +void freecam_n64_controller_manager(Camera *camera, struct Controller *controller, Player *player); +void freecam_n64_calculate_forward_vector(Camera* camera, Vec3f forwardVector); +void freecam_n64_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance); +void freecam_n64_update(Camera* camera, struct Controller *controller); + +#endif // N64_FREECAM_H \ No newline at end of file diff --git a/src/main.c b/src/main.c index 414568181..de999c9f6 100644 --- a/src/main.c +++ b/src/main.c @@ -43,6 +43,7 @@ #include "crash_screen.h" #include "buffers/gfx_output_buffer.h" #include +#include "enhancements/freecam/freecam.h" // Declarations (not in this file) void func_80091B78(void); @@ -317,6 +318,29 @@ void create_gfx_task_structure(void) { #endif } +f32 gDeltaTime = 0.0f; +f32 calculate_delta_time(void) { + static u32 prevtime = 0; + u32 now = osGetCount(); + f32 deltaTime; + + if (now > prevtime) { + deltaTime = (f32)(now - prevtime) / OS_CPU_COUNTER; + } else { + // Handle counter reset + deltaTime = (f32)((0xffffffff - prevtime) + 1 + now) / OS_CPU_COUNTER; + } + + prevtime = now; + + // Cap deltaTime to avoid large jumps (e.g., on pause/resume) + if (deltaTime > 1.0f / 15.0f) { // Assume 15 FPS is the lowest acceptable rate + deltaTime = 1.0f / 15.0f; + } + + gDeltaTime = deltaTime; +} + void init_controllers(void) { osCreateMesgQueue(&gSIEventMesgQueue, &gSIEventMesgBuf[0], ARRAY_COUNT(gSIEventMesgBuf)); osSetEventMesg(OS_EVENT_SI, &gSIEventMesgQueue, OS_MESG_32(0x33333333)); @@ -336,6 +360,11 @@ void update_controller(s32 index) { return; } + // Prevents pause menu intereference while controlling flycam + if ((CVarGetInteger("gFreecam", 0) == 1) && (gFreecamControllerType == 0) && (gGamestate == RACING)) { + return; + } + controller->rawStickX = gControllerPads[index].stick_x; controller->rawStickY = gControllerPads[index].stick_y; @@ -1226,6 +1255,8 @@ void thread5_game_loop(void) { } void thread5_iteration(void) { + // func_800CB2C4(); + calculate_delta_time(); #ifdef TARGET_N64 while (true) { func_800CB2C4(); diff --git a/src/main.h b/src/main.h index 3a5ceaf56..55611629f 100644 --- a/src/main.h +++ b/src/main.h @@ -104,6 +104,7 @@ void update_gamestate(void); void thread5_game_loop(void); void thread5_iteration(void); void thread4_audio(void*); +extern f32 gDeltaTime; extern struct VblankHandler* gVblankHandler1; extern struct VblankHandler* gVblankHandler2; diff --git a/src/port/ui/FreecamWindow.cpp b/src/port/ui/FreecamWindow.cpp new file mode 100644 index 000000000..445140e83 --- /dev/null +++ b/src/port/ui/FreecamWindow.cpp @@ -0,0 +1,109 @@ +#include "FreecamWindow.h" +#include "UIWidgets.h" +#include "libultraship/src/Context.h" + +#include +#include +#include "spdlog/spdlog.h" +#include +#include +#include "enhancements/freecam/freecam_engine.h" +#include "enhancements/freecam/freecam.h" + +extern "C" { +// typedef struct { +// Vec3f pos; +// Vec3f velocity; +// Vec3f lookAt; +// Vec3f lookAtVelocity; +// } FreeCam; + +extern f32 gDampValue; +extern f32 gFreecamSpeed; +extern f32 gFreecamSpeedMultiplier; +extern f32 gFreecamRotateSmoothingFactor; +extern char* D_800E76A8[]; +extern u32 fRankIndex; +extern u32 gFreecamControllerType; +} + +namespace Freecam { +FreecamWindow::~FreecamWindow() { + SPDLOG_TRACE("destruct freecam window"); +} + +void FreecamWindow::InitElement() { +} + +static s32 sReadyUpBool = false; + +float dampMin = 0.995; +float dampMax = 1.0f; +float minSpeed = 1.0; +float maxSpeed = 30.0f; +float minSpeedMultiplier = 1.5f; +float maxSpeedMultiplier = 15.0f; +float minFreecamRotateFactor = 0.0f; +float maxFreecamRotateFactor = 1.0f; + +uint32_t focusPlayer; + +void FreecamWindow::DrawElement() { + const float framerate = ImGui::GetIO().Framerate; + const float deltatime = ImGui::GetIO().DeltaTime; + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); + + ImGui::Text("Freecam: Mouse/Keyboard Requires OpenGL. DX is not supported"); + + const char* items[] = { "Mouse/Keyboard", "N64 Controls" }; + static int current_item = 0; + if (ImGui::Combo("Dropdown", ¤t_item, items, IM_ARRAYSIZE(items))) { + gFreecamControllerType = current_item; + } + + ImGui::Text("Move: W,A,S,D\nUp: Space, Down: Shift\nFaster: Ctrl\nLook: Right-mouse button\nTarget previous player: N, Target next player: M"); + ImGui::Spacing(); + UIWidgets::CVarCheckbox("Enable Flycam", "gFreecam", { + .tooltip = "Allows you to fly around the course" + }); + + ImGui::Spacing(); + + if (ImGui::SliderScalar("Camera Damping", ImGuiDataType_Float, &gDampValue, &dampMin, &dampMax, "%f")) { + + }; + if (ImGui::SliderScalar("Camera Speed", ImGuiDataType_Float, &gFreecamSpeed, &minSpeed, &maxSpeed, "%f")) { + + }; + if (ImGui::SliderScalar("Camera Speed Multiplier", ImGuiDataType_Float, &gFreecamSpeedMultiplier, &minSpeedMultiplier, &maxSpeedMultiplier, "%f")) { + + }; + if (ImGui::SliderScalar("Camera Rotation Smoothing", ImGuiDataType_Float, &gFreecamRotateSmoothingFactor, &minFreecamRotateFactor, &maxFreecamRotateFactor, "%f")) { + + }; + + ImGui::Spacing(); + + ImGui::Text("Target Player"); + + if (ImGui::Button("None")) { + fRankIndex = -1; + } + + for (size_t i = 0; i < NUM_PLAYERS; i++) { + if (D_800E76A8[i] == NULL || D_800E76A8[i] == "ーーーー") { + break; + } + ImGui::SameLine(); + if (ImGui::Button(D_800E76A8[i])) { + fRankIndex = i; + } + } + + + ImGui::PopStyleColor(); +} + +void FreecamWindow::UpdateElement() { +} +} // namespace Freecam diff --git a/src/port/ui/FreecamWindow.h b/src/port/ui/FreecamWindow.h new file mode 100644 index 000000000..73ac2b00a --- /dev/null +++ b/src/port/ui/FreecamWindow.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace Freecam { +class FreecamWindow : public Ship::GuiWindow { + public: + using Ship::GuiWindow::GuiWindow; + ~FreecamWindow(); + + private: + void InitElement() override; + void DrawElement() override; + void UpdateElement() override; +}; +} // namespace GameInfo \ No newline at end of file diff --git a/src/port/ui/ImguiUI.cpp b/src/port/ui/ImguiUI.cpp index de12476fc..a4f39a1ad 100644 --- a/src/port/ui/ImguiUI.cpp +++ b/src/port/ui/ImguiUI.cpp @@ -2,6 +2,7 @@ #include "UIWidgets.h" #include "ResolutionEditor.h" #include "GameInfoWindow.h" +#include "FreecamWindow.h" #include #include @@ -22,6 +23,7 @@ std::shared_ptr mStatsWindow; std::shared_ptr mInputEditorWindow; std::shared_ptr mGfxDebuggerWindow; std::shared_ptr mGameInfoWindow; +std::shared_ptr mFreecamWindow; std::shared_ptr mAdvancedResolutionSettingsWindow; void SetupGuiElements() { @@ -61,6 +63,14 @@ void SetupGuiElements() { SPDLOG_ERROR("Could not find input GfxDebuggerWindow"); } + mFreecamWindow = gui->GetGuiWindow("FreecamWindow"); + if (mFreecamWindow == nullptr) { + SPDLOG_ERROR("Could not find input FreecamWindow"); + } + + mFreecamWindow = std::make_shared("gFreecamEnabled", "Freecam"); + gui->AddGuiWindow(mFreecamWindow); + mGameInfoWindow = std::make_shared("gGameInfoEnabled", "Game info"); gui->AddGuiWindow(mGameInfoWindow); @@ -441,6 +451,11 @@ void DrawEnhancementsMenu() { if (UIWidgets::BeginMenu("Enhancements")) { if (UIWidgets::BeginMenu("Gameplay")) { + + + UIWidgets::WindowButton("Freecam", "gFreecam", GameUI::mFreecamWindow, { + .tooltip = "Allows you to fly around the course" + }); UIWidgets::CVarCheckbox( "No Level of Detail (LOD)", "gDisableLod", { .tooltip = "Disable Level of Detail (LOD) to avoid models using lower poly versions at a distance" }); diff --git a/src/racing/render_courses.c b/src/racing/render_courses.c index 0ad802e62..e2017cad2 100644 --- a/src/racing/render_courses.c +++ b/src/racing/render_courses.c @@ -1282,7 +1282,7 @@ void render_big_donut(struct UnkStruct_800DC5EC* arg0) { gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH); gSPClearGeometryMode(gDisplayListHead++, G_LIGHTING); - func_802B5D64((uintptr_t) D_800DC610, D_802B87D4, 0, 1); + func_802B5D64(D_800DC610, D_802B87D4, 0, 1); gSPTexture(gDisplayListHead++, 0xFFFF, 0xFFFF, 0, G_TX_RENDERTILE, G_ON); gSPSetGeometryMode(gDisplayListHead++, G_SHADING_SMOOTH); @@ -1367,9 +1367,16 @@ void func_8029569C(void) { } } -void render_course(struct UnkStruct_800DC5EC* arg0) { +void render_course(struct UnkStruct_800DC5EC *arg0) { func_802B5D64(D_800DC610, D_802B87D4, 0, 1); + + if ((CVarGetInteger("gFreecam", 0) == 1) ) { + // Render credits courses + func_8029569C(); + return; + } + if (creditsRenderMode) { func_8029569C(); return;