summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMegaMech <MegaMech@users.noreply.github.com>2024-09-10 16:19:19 -0600
committerGitHub <noreply@github.com>2024-09-10 16:19:19 -0600
commit47e95c0e182ed21486648d88e2ce52aa05d2fe7c (patch)
treeb9025bed5e0393286914d6a76112ce1d79c33d48
parentcf05266bf93e23072aa890c753d328bedf2a5cb0 (diff)
[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>
-rw-r--r--CMakeLists.txt3
-rw-r--r--courses/koopa_troopa_beach/course_offsets.c3
-rw-r--r--src/camera.c7
-rw-r--r--src/enhancements/freecam/freecam.c374
-rw-r--r--src/enhancements/freecam/freecam.h24
-rw-r--r--src/enhancements/freecam/freecam_engine.c149
-rw-r--r--src/enhancements/freecam/freecam_engine.h39
-rw-r--r--src/enhancements/freecam/n64_freecam.c99
-rw-r--r--src/enhancements/freecam/n64_freecam.h13
-rw-r--r--src/main.c31
-rw-r--r--src/main.h1
-rw-r--r--src/port/ui/FreecamWindow.cpp109
-rw-r--r--src/port/ui/FreecamWindow.h16
-rw-r--r--src/port/ui/ImguiUI.cpp15
-rw-r--r--src/racing/render_courses.c11
15 files changed, 890 insertions, 4 deletions
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 <libultraship.h>
+#include <macros.h>
+#include <defines.h>
+#include <camera.h>
+#include "main.h"
+#include <libc/math.h>
+#include <common_structs.h>
+#include "racing/collision.h"
+#include "player_controller.h"
+#include "code_80057C60.h"
+#include "code_800029B0.h"
+#include "code_80005FD0.h"
+#include <SDL2/SDL.h>
+#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 <libultraship.h>
+#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 <libultraship.h>
+#include <macros.h>
+#include <defines.h>
+#include <common_structs.h>
+#include "player_controller.h"
+#include "code_80057C60.h"
+#include "code_800029B0.h"
+#include "code_80005FD0.h"
+#include "racing/collision.h"
+#include "main.h"
+#include <camera.h>
+#include "freecam_engine.h"
+
+FreeCam freeCam;
+
+#include <math.h>
+
+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 <libultraship.h>
+#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 <libultraship.h>
+#include <defines.h>
+#include <macros.h>
+#include "code_80057C60.h"
+#include "code_800029B0.h"
+#include "code_80005FD0.h"
+#include "camera.h"
+#include <common_structs.h>
+#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 <libultraship.h>
+
+
+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 <bridge/gfxdebuggerbridge.h>
+#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 <imgui.h>
+#include <libultraship/libultraship.h>
+#include "spdlog/spdlog.h"
+#include <common_structs.h>
+#include <defines.h>
+#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", &current_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 <libultraship/libultraship.h>
+
+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 <spdlog/spdlog.h>
#include <imgui.h>
@@ -22,6 +23,7 @@ std::shared_ptr<Ship::GuiWindow> mStatsWindow;
std::shared_ptr<Ship::GuiWindow> mInputEditorWindow;
std::shared_ptr<Ship::GuiWindow> mGfxDebuggerWindow;
std::shared_ptr<Ship::GuiWindow> mGameInfoWindow;
+std::shared_ptr<Ship::GuiWindow> mFreecamWindow;
std::shared_ptr<AdvancedResolutionSettings::AdvancedResolutionSettingsWindow> 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<Freecam::FreecamWindow>("gFreecamEnabled", "Freecam");
+ gui->AddGuiWindow(mFreecamWindow);
+
mGameInfoWindow = std::make_shared<GameInfo::GameInfoWindow>("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;