mirror of
https://github.com/HarbourMasters/SpaghettiKart
synced 2026-08-30 01:40:23 -04:00
5b28472d47
* Fix garbage frame flash on cinematic camera cuts Frame interpolation blended matrices across instant camera teleports in attract/demo and post-race cameras, flashing 1-2 frames of sheared geometry. Complete the existing camera-epoch mechanism: flag a cut in func_80019890 when the camera moves >100 units, and have the interpolator snap that frame to the new view instead of blending. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Retrigger CI (transient 502 downloading libogg from gitlab.xiph.org) * Name the cinematic shot dispatcher Review feedback on #720: func_80019890 -> camera_start_cinematic_shot. It starts whichever shot D_80164680 has selected for a camera, dispatching to the per-shot setup that teleports the camera to the shot's opening position, which is why the cut detection lives there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Apply fix to freecam Added FrameInterpolation_DontInterpolateCamera calls to improve camera behavior when changing target players. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: MegaMech <MegaMech@users.noreply.github.com>
415 lines
12 KiB
C++
415 lines
12 KiB
C++
#include <libultraship.h>
|
|
#include <ship/window/Window.h>
|
|
#include "port/Engine.h"
|
|
#include "port/Game.h"
|
|
#include <ship/controller/controldevice/controller/mapping/keyboard/KeyboardScancodes.h>
|
|
#include <ship/window/Window.h>
|
|
#include "port/interpolation/FrameInterpolation.h"
|
|
#include "engine/Matrix.h"
|
|
|
|
extern "C" {
|
|
#include <macros.h>
|
|
#include <defines.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 "racing/math_util.h"
|
|
#include "racing/skybox_and_splitscreen.h"
|
|
#include "freecam.h"
|
|
}
|
|
|
|
#include "engine/tracks/Track.h"
|
|
|
|
typedef struct {
|
|
Vec3f pos;
|
|
Vec3f lookAt;
|
|
Vec3s rot;
|
|
} freecamSaveState;
|
|
|
|
freecamSaveState fState;
|
|
|
|
Controller fController;
|
|
|
|
u32 fRankIndex = 0;
|
|
u32 fTargetPlayer = false;
|
|
u32 fMode; // freecam mode should probably be an enum
|
|
u32 fModeInit = false;
|
|
|
|
u32 bFreecamUseController = false;
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
// This function is no longer used because FreeCamera.cpp
|
|
void freecam(Camera* camera, Player* player, s8 index) {
|
|
f32 dirX;
|
|
f32 dirY;
|
|
f32 dirZ;
|
|
f32 length;
|
|
static bool enabled = false; // Tracks previous activation state
|
|
bool freecamEnabled = CVarGetInteger("gFreecam", 0);
|
|
|
|
// Freecam can only activate in single player mode
|
|
if (gActiveScreenMode != SCREEN_MODE_1P) {
|
|
enabled = false;
|
|
freecamEnabled = false;
|
|
CVarSetInteger("gFreecam", false);
|
|
}
|
|
|
|
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 && (player == gPlayerOne)) {
|
|
freecam_loop(camera);
|
|
} else {
|
|
// func_8001E45C(camera, player, index);
|
|
// Required if freecam were to use its own camera instead of borrowing the player camera
|
|
// func_8001EE98(gPlayerOne, camera, index);
|
|
}
|
|
}
|
|
|
|
void on_freecam(void) {
|
|
gIsHUDVisible = false;
|
|
// gPlayerOne->type |= PLAYER_CPU;
|
|
}
|
|
|
|
void off_freecam(void) {
|
|
gIsHUDVisible = true;
|
|
gPlayerOne->type &= ~PLAYER_CPU;
|
|
}
|
|
|
|
void freecam_loop(Camera* camera) {
|
|
if ((fController.buttonPressed & L_TRIG) && (fController.buttonPressed & R_TRIG)) {
|
|
// Toggle freecam
|
|
CVarSetInteger("gFreecam", !CVarGetInteger("gFreecam", 0));
|
|
}
|
|
// Calculate forward direction
|
|
freecam_calculate_forward_vector_allow_rotation(camera, freeCam.forwardVector);
|
|
|
|
// Adjust camera rotation
|
|
if (fTargetPlayer) {
|
|
freecam_target_player(camera, freeCam.forwardVector);
|
|
} else {
|
|
freecam_mouse_manager(camera, freeCam.forwardVector);
|
|
}
|
|
|
|
// Adjust camera position
|
|
freecam_keyboard_manager(camera, freeCam.forwardVector);
|
|
|
|
// Apply final position, velocity, and lookAt
|
|
//if (0) {
|
|
//Tour_Tick(camera);
|
|
//} else {
|
|
freecam_tick(camera, freeCam.forwardVector);
|
|
//}
|
|
}
|
|
|
|
void freecam_mouse_manager(Camera* camera, Vec3f forwardVector) {
|
|
auto wnd = GameEngine::Instance->context->GetWindow();
|
|
Ship::Coords mouse = wnd->GetMouseDelta();
|
|
wnd->SetMouseCapture(false);
|
|
|
|
f32 yawChange = 0.0f;
|
|
f32 pitchChange = 0.0f;
|
|
|
|
if (bFreecamUseController) {
|
|
// Controller controls
|
|
f32 stickX = ((f32)fController.rightRawStickX);
|
|
f32 stickY = ((f32)fController.rightRawStickY);
|
|
|
|
// Sensitivity multipliers
|
|
float controllerSensitivityX = 5.0f; // Adjust as needed
|
|
float controllerSensitivityY = 3.0f;
|
|
|
|
// 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
|
|
if (wnd->GetMouseState(Ship::LUS_MOUSE_BTN_RIGHT)) {
|
|
yawChange = -mouse.x * MOUSE_SENSITIVITY_X;
|
|
pitchChange = mouse.y * MOUSE_SENSITIVITY_Y;
|
|
}
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
f32 gFreecamSpeed = 3.0f;
|
|
f32 gFreecamSpeedMultiplier = 2.0f;
|
|
bool prevPrev = false;
|
|
|
|
#define MAX_KEYS 256
|
|
static bool prevKeyState[MAX_KEYS] = { false };
|
|
|
|
// KeyDown function
|
|
bool FreecamKeyDown(int virtualKey) {
|
|
auto wnd = GameEngine::Instance->context->GetWindow();
|
|
static bool prevKeyState[256] = { false }; // Store previous key states
|
|
bool isDownNow = false;
|
|
|
|
if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_SDL_OPENGL) {
|
|
// Use SDL to check key states
|
|
const uint8_t* keystate = SDL_GetKeyboardState(NULL);
|
|
isDownNow = keystate[virtualKey] != 0;
|
|
}
|
|
#ifdef _WIN32
|
|
else if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_DXGI_DX11) {
|
|
// Use Windows GetKeyState for DirectX
|
|
SHORT keyState = GetKeyState(virtualKey);
|
|
isDownNow = (keyState & 0x8000) != 0;
|
|
}
|
|
#endif
|
|
|
|
// Determine if this is a new key press
|
|
bool isKeyDownEvent = isDownNow && !prevKeyState[virtualKey];
|
|
|
|
// Update the previous state for this key
|
|
prevKeyState[virtualKey] = isDownNow;
|
|
|
|
return isKeyDownEvent;
|
|
}
|
|
|
|
void freecam_keyboard_manager(Camera* camera, Vec3f forwardVector) {
|
|
auto wnd = GameEngine::Instance->context->GetWindow();
|
|
float moveSpeed = gFreecamSpeed;
|
|
|
|
// 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;
|
|
// }
|
|
bool TargetNextPlayer = false, TargetPreviousPlayer = false;
|
|
bool prevNext;
|
|
bool Forward = false, PanLeft = false, Backward = false, PanRight = false;
|
|
bool Up = false, Down = false;
|
|
bool FastMove = false;
|
|
|
|
// Use n64 controls for use with a controller
|
|
//! @todo configure this properly
|
|
if (bFreecamUseController) {
|
|
if (fController.buttonDepressed & R_TRIG) {
|
|
fTargetPlayer = true;
|
|
FrameInterpolation_DontInterpolateCamera();
|
|
}
|
|
if (fController.buttonDepressed & L_TRIG) {
|
|
fTargetPlayer = false;
|
|
FrameInterpolation_DontInterpolateCamera();
|
|
}
|
|
if (fController.buttonPressed & L_JPAD) {
|
|
TargetPreviousPlayer = true;
|
|
}
|
|
if (fController.buttonPressed & R_JPAD) {
|
|
TargetNextPlayer = true;
|
|
}
|
|
if (fController.rawStickY > 10) {
|
|
Forward = true;
|
|
}
|
|
if (fController.rawStickY < -10) {
|
|
Backward = true;
|
|
}
|
|
if (fController.rawStickX > 10) {
|
|
PanRight = true;
|
|
}
|
|
if (fController.rawStickX < -10) {
|
|
PanLeft = true;
|
|
}
|
|
if (fController.button & B_BUTTON) {
|
|
Down = true;
|
|
}
|
|
if (fController.button & A_BUTTON) {
|
|
Up = true;
|
|
}
|
|
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')) {
|
|
fTargetPlayer = !fTargetPlayer;
|
|
FrameInterpolation_DontInterpolateCamera();
|
|
}
|
|
if (FreecamKeyDown('N')) {
|
|
TargetPreviousPlayer = true;
|
|
}
|
|
if (FreecamKeyDown('M')) {
|
|
TargetNextPlayer = true;
|
|
}
|
|
if (GetKeyState('W') & 0x8000) {
|
|
Forward = true;
|
|
}
|
|
if (GetKeyState('S') & 0x8000) {
|
|
Backward = true;
|
|
}
|
|
if (GetKeyState('D') & 0x8000) {
|
|
PanRight = true;
|
|
}
|
|
if (GetKeyState('A') & 0x8000) {
|
|
PanLeft = true;
|
|
}
|
|
if (GetKeyState(VK_SPACE) & 0x8000) {
|
|
Up = true;
|
|
}
|
|
if (GetKeyState(VK_LSHIFT) & 0x8000 || GetKeyState(VK_RSHIFT) & 0x8000) {
|
|
Down = true;
|
|
}
|
|
// Fast movement with Ctrl
|
|
if (GetKeyState(VK_LCONTROL) || GetKeyState(VK_RCONTROL)) {
|
|
FastMove = true;
|
|
}
|
|
// Keyboard/mouse OpenGL/SDL
|
|
}
|
|
#endif
|
|
else if (wnd->GetWindowBackend() == Ship::WindowBackend::FAST3D_SDL_OPENGL) {
|
|
const uint8_t* keystate = SDL_GetKeyboardState(NULL);
|
|
if (FreecamKeyDown(SDL_SCANCODE_F)) {
|
|
fTargetPlayer = !fTargetPlayer;
|
|
FrameInterpolation_DontInterpolateCamera();
|
|
}
|
|
if (FreecamKeyDown(SDL_SCANCODE_N)) {
|
|
TargetPreviousPlayer = true;
|
|
}
|
|
if (FreecamKeyDown(SDL_SCANCODE_M)) {
|
|
TargetNextPlayer = true;
|
|
}
|
|
if (keystate[SDL_SCANCODE_W]) {
|
|
Forward = true;
|
|
}
|
|
if (keystate[SDL_SCANCODE_S]) {
|
|
Backward = true;
|
|
}
|
|
if (keystate[SDL_SCANCODE_D]) {
|
|
PanRight = true;
|
|
}
|
|
if (keystate[SDL_SCANCODE_A]) {
|
|
PanLeft = true;
|
|
}
|
|
if (keystate[SDL_SCANCODE_SPACE]) {
|
|
Up = true;
|
|
}
|
|
if (keystate[SDL_SCANCODE_LSHIFT] || keystate[SDL_SCANCODE_RSHIFT]) {
|
|
Down = true;
|
|
}
|
|
if (keystate[SDL_SCANCODE_LCTRL] || keystate[SDL_SCANCODE_RCTRL]) {
|
|
FastMove = true;
|
|
}
|
|
}
|
|
|
|
// Target previous player
|
|
if (TargetPreviousPlayer) {
|
|
if (fRankIndex > 0) {
|
|
fRankIndex--;
|
|
camera->playerId = fRankIndex;
|
|
gScreenOneCtx->player = &gPlayers[fRankIndex];
|
|
FrameInterpolation_DontInterpolateCamera();
|
|
}
|
|
}
|
|
|
|
// Target next player
|
|
if (TargetNextPlayer) {
|
|
if (fRankIndex < 7) {
|
|
fRankIndex++;
|
|
camera->playerId = fRankIndex;
|
|
gScreenOneCtx->player = &gPlayers[fRankIndex];
|
|
FrameInterpolation_DontInterpolateCamera();
|
|
}
|
|
}
|
|
|
|
if (FastMove) {
|
|
moveSpeed *= gFreecamSpeedMultiplier;
|
|
}
|
|
|
|
if (Forward) {
|
|
totalMove[0] += forwardVector[0] * moveSpeed;
|
|
totalMove[2] += forwardVector[2] * moveSpeed;
|
|
}
|
|
if (Backward) {
|
|
totalMove[0] -= forwardVector[0] * moveSpeed;
|
|
totalMove[2] -= forwardVector[2] * moveSpeed;
|
|
}
|
|
if (PanRight) {
|
|
totalMove[0] -= forwardVector[2] * moveSpeed; // Pan right
|
|
totalMove[2] += forwardVector[0] * moveSpeed;
|
|
}
|
|
if (PanLeft) {
|
|
totalMove[0] += forwardVector[2] * moveSpeed; // Pan left
|
|
totalMove[2] -= forwardVector[0] * moveSpeed;
|
|
}
|
|
if (Up) {
|
|
totalMove[1] += moveSpeed; // Move up
|
|
}
|
|
if (Down) {
|
|
totalMove[1] -= moveSpeed; // Move down
|
|
}
|
|
|
|
freeCam.velocity[0] += totalMove[0];
|
|
freeCam.velocity[1] += totalMove[1];
|
|
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.
|
|
}
|
|
|