From 9730882fe405e31cd4042d3556ee5dcc1b025ebb Mon Sep 17 00:00:00 2001 From: minenice55 Date: Thu, 2 Apr 2026 01:32:10 -0400 Subject: [PATCH 1/6] gyro calibration, spectator aiming using gyro also moves all of the motion sensor stuff to g_input.c --- src/g_game.c | 251 +++---------------------------------- src/g_game.h | 28 +---- src/g_input.c | 330 +++++++++++++++++++++++++++++++++++++++++++++++++ src/g_input.h | 29 +++++ src/k_hud.c | 1 + src/st_stuff.c | 21 ++-- 6 files changed, 387 insertions(+), 273 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 679a8d3d4..88672a4a6 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -51,7 +51,6 @@ #include "m_cond.h" // condition sets #include "r_fps.h" // frame interpolation/uncapped #include "lua_hud.h" -#include "m_easing.h" // SRB2kart #include "k_kart.h" @@ -1404,235 +1403,6 @@ static void G_HandleAxisDeadZone(UINT8 splitnum, joystickvector2_t *joystickvect } } -// copy/pasted from the lua version of these routines -inline static vector3_t *QuaternionMulVec3(vector3_t *out, vector3_t *a, vector4_t *b) -{ - fixed_t ax = a->x, ay = a->y, az = a->z, aw = 0; - fixed_t bx = b->x, by = b->y, bz = b->z, bw = b->a; - - FV3_NormalizeEx(out, FV3_Load(out, - FixedMul(aw, bx) + FixedMul(ax, bw) + FixedMul(ay, bz) - FixedMul(az, by), - FixedMul(aw, by) - FixedMul(ax, bz) + FixedMul(ay, bw) + FixedMul(az, bx), - FixedMul(aw, bz) + FixedMul(ax, by) - FixedMul(ay, bx) + FixedMul(az, bw) - )); - - return out; -} - -inline static fixed_t FV3_LengthSquared(const vector3_t *vec) -{ - return FixedMul(vec->x, vec->x) + FixedMul(vec->y, vec->y) + FixedMul(vec->z, vec->z); -} - -inline static fixed_t FV3_Length(const vector3_t *vec) -{ - return FixedSqrt(FV3_LengthSquared(vec)); -} - -inline static vector4_t AngleAxis(fixed_t angle, fixed_t x, fixed_t y, fixed_t z) -{ - fixed_t sinangle = FINESINE(FixedAngle(angle/2) >> ANGLETOFINESHIFT); - fixed_t cosangle = FINECOSINE(FixedAngle(angle/2) >> ANGLETOFINESHIFT); - vector3_t axis = {x, y, z}; - vector4_t result; - - FV3_Normalize(&axis); - - FV4_Load(&result, - FixedMul(axis.x, sinangle), - FixedMul(axis.y, sinangle), - FixedMul(axis.z, sinangle), - cosangle - ); - - return result; -} - -// math sourced from this article -// http://gyrowiki.jibbsmart.com/blog:finding-gravity-with-sensor-fusion -// state -fixed_t localshakinessfac[MAXSPLITSCREENPLAYERS]; -vector3_t localsmoothedaccel[MAXSPLITSCREENPLAYERS]; -vector3_t localgravityvectors[MAXSPLITSCREENPLAYERS]; - -// the time it takes in our acceleration smoothing for 'A' to get halfway to 'B' -#define SmoothingHalfTime (0.01) -// thresholds of trust for accel shakiness. less shakiness = more trust -#define ShakinessMaxThreshold (50*FRACUNIT/100) -#define ShakinessMinThreshold (1*FRACUNIT/100) -// when we trust the accel a lot (the controller is "still"), how quickly do we correct our gravity vector? -#define CorrectionStillRate (FRACUNIT) -// when we don't trust the accel (the controller is "shaky"), how quickly do we correct our gravity vector? -#define CorrectionShakyRate (10*FRACUNIT/100) -// if our old gravity vector is close enough to our new one, limit further corrections to this proportion of the rotation speed -#define CorrectionGyroFactor (40*FRACUNIT/100) -// thresholds for what's considered "close enough" -#define CorrectionGyroMinThreshold (5*FRACUNIT/100) -#define CorrectionGyroMaxThreshold (FRACUNIT/4) -// no matter what, always apply a minimum of this much correction to our gravity vector -#define CorrectionMinimumSpeed (1*FRACUNIT/100) - -// when holding the controller still (shaking and turning included), corrcet this quickly to resolve error -#define CorrectionInstantRate (80*FRACUNIT/100) -#define CorrectionInstantShake (4*FRACUNIT/100) -#define CorrectionInstantTurn (5*FRACUNIT/100) - -void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) -{ - // convert gyro input to reverse rotation - vector3_t invAccel = {-accel.x, -accel.y, -accel.z}; - fixed_t correctionRate = 0; - // scaling is reversed, smaller time scales = larger steps in this code - // (1/timescale)/dt - fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE; - // we don't have exp2 and we actually need it here to take timescale into account :( - fixed_t smoothFactor = FloatToFixed(exp2(-FixedToFloat(deltaseconds) / SmoothingHalfTime)); - fixed_t angleRate; - fixed_t correctionLimit; - vector4_t invRotation = AngleAxis( - FixedMul(FV3_Length(&gyro), deltaseconds), - -gyro.x, - -gyro.y, - -gyro.z - ); - vector3_t gravityDelta = {0}; - vector3_t gravityDeltaDirection = {0}; - vector3_t correction = {0}; - - if (!G_GetGamepadCanUseTilt(p)) return; - CONS_Debug(DBG_IMU, "= Update Gravity Delta Time: %4.3f =\n", FixedToFloat(deltaseconds)); - - // rotate gravity vector - QuaternionMulVec3(&localgravityvectors[p], &localgravityvectors[p], &invRotation); - QuaternionMulVec3(&localsmoothedaccel[p], &localsmoothedaccel[p], &invRotation); - localshakinessfac[p] = FixedMul(localshakinessfac[p], smoothFactor), - FV3_SubEx(&accel, &localsmoothedaccel[p], &correction); - localshakinessfac[p] = max(localshakinessfac[p], FV3_Length(&correction)); - FV3_Load(&localsmoothedaccel[p], - Easing_Linear(smoothFactor, accel.x, localsmoothedaccel[p].x), - Easing_Linear(smoothFactor, accel.y, localsmoothedaccel[p].y), - Easing_Linear(smoothFactor, accel.z, localsmoothedaccel[p].z) - ); - - CONS_Debug(DBG_IMU, "Shakiness: %4.2f\n", FixedToFloat(localshakinessfac[p])); - - FV3_SubEx(&invAccel, &localgravityvectors[p], &gravityDelta); - FV3_NormalizeEx(&gravityDelta, &gravityDeltaDirection); - - if (ShakinessMaxThreshold > ShakinessMinThreshold) - { - fixed_t stillness = CLAMP(FixedDiv((localshakinessfac[p] - ShakinessMinThreshold), (ShakinessMaxThreshold - ShakinessMinThreshold)), 0, FRACUNIT); - correctionRate = CorrectionStillRate + FixedMul((CorrectionShakyRate - CorrectionStillRate), stillness); - } - else if (localshakinessfac[p] > ShakinessMaxThreshold) - { - correctionRate = CorrectionShakyRate; - } - else - { - correctionRate = CorrectionStillRate; - } - - // limit in proportion to rotation rate - angleRate = FixedMul(FV3_Length(&gyro), M_PI_FIXED/180); - correctionLimit = max(FixedMul(FixedMul(angleRate, FV3_Length(&localgravityvectors[p])), CorrectionGyroFactor), CorrectionMinimumSpeed); - - CONS_Debug(DBG_IMU, "Angle Rate: %4.3f\n", FixedToFloat(angleRate)); - CONS_Debug(DBG_IMU, "Correction Limit: %4.3f\n", FixedToFloat(correctionLimit)); - - if (correctionRate > correctionLimit) { - fixed_t closeEnoughFactor; - if (CorrectionGyroMaxThreshold > CorrectionGyroMinThreshold) - { - closeEnoughFactor = CLAMP(FixedDiv((FV3_Length(&gravityDelta) - CorrectionGyroMinThreshold), (CorrectionGyroMaxThreshold - CorrectionGyroMinThreshold)), 0, FRACUNIT); - } - else if (FV3_Length(&gravityDelta) > CorrectionGyroMaxThreshold) - { - closeEnoughFactor = FRACUNIT; - } - else - { - closeEnoughFactor = 0; - } - - CONS_Debug(DBG_IMU, "'Close Enough' Fac: %4.3f\n", FixedToFloat(closeEnoughFactor)); - correctionRate = correctionLimit + FixedMul((correctionRate - correctionLimit), closeEnoughFactor); - } - - if (localshakinessfac[p] < CorrectionInstantShake && angleRate < CorrectionInstantTurn) - { - correctionRate = max(correctionRate, CorrectionInstantRate); - } - - CONS_Debug(DBG_IMU, "Correction Rate: %4.2f\n", FixedToFloat(correctionRate)); - - FV3_Load(&correction, - FixedMul(gravityDelta.x, FixedMul(deltaseconds, correctionRate)), - FixedMul(gravityDelta.y, FixedMul(deltaseconds, correctionRate)), - FixedMul(gravityDelta.z, FixedMul(deltaseconds, correctionRate)) - ); - if ((FV3_LengthSquared(&correction) < FV3_LengthSquared(&gravityDelta))) - { - FV3_Add(&localgravityvectors[p], &correction); - } - else - { - FV3_Load(&localgravityvectors[p], invAccel.x, invAccel.y, invAccel.z); - } -} - -INT32 G_GetGamepadTilt(INT32 p) -{ - fixed_t tilt; - fixed_t curve; - if (!G_GetGamepadCanUseTilt(p)) return 0; - tilt = CLAMP(FixedDiv(G_GetGamepadGravity(p).x + MAXGAMEPADTILT, 2*MAXGAMEPADTILT), 0, FRACUNIT); - CONS_Debug(DBG_IMU, "Tilt: %4.2f\n", FixedToFloat(tilt)); - - curve = FINESINE(FixedAngle(180*(tilt-FRACUNIT/2))>>ANGLETOFINESHIFT); - CONS_Debug(DBG_IMU, "Pinched Tilt: %4.2f\n", FixedToFloat(curve)); - - return (JOYAXISRANGE * curve)/FRACUNIT; -} - -vector3_t G_GetGamepadGravity(INT32 p) -{ - const vector3_t zero = {0, -ACCELEROMETERGRAVITY, 0}; - if (!G_GetGamepadCanUseTilt(p)) return zero; - return localgravityvectors[p]; -} - -vector3_t G_GetGamepadShake(INT32 p) -{ - vector3_t accel = {0}; - if (!G_GetGamepadCanUseTilt(p)) return accel; - accel = G_PlayerInputSensor(p, ACCELEROMETER); - FV3_Add(&accel, &localgravityvectors[p]); - - return accel; -} - -boolean G_GetGamepadCanUseTilt(INT32 p) -{ - if (p >= MAXSPLITSCREENPLAYERS) - { -#ifdef PARANOIA - CONS_Debug(DBG_GAMELOGIC, "G_GetGamepadCanUseTilt: Invalid player ID %d\n", p); -#endif - return false; - } - - return (I_ControllerSupportsSensorAccelerometer(p) && I_ControllerSupportsSensorGyro(p)); -} -#undef ShakinessMaxThreshold -#undef ShakinessMinThreshold -#undef CorrectionStillRate -#undef CorrectionShakyRate -#undef CorrectionGyroFactor -#undef CorrectionGyroMinThreshold -#undef CorrectionGyroMaxThreshold -#undef CorrectionMinimumSpeed - // // G_BuildTiccmd // Builds a ticcmd from all of the available inputs @@ -1699,7 +1469,12 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { vector3_t accel = G_PlayerInputSensor(forplayer, ACCELEROMETER); vector3_t gyro = G_PlayerInputSensor(forplayer, GYROSCOPE); - G_UpdateGamepadGravity(forplayer, gyro, accel); + + G_UpdateGamepadAutoCalibration(forplayer, accel, gyro, + (paused || P_AutoPause() || (menustack[0] == MN_OP_CONTROLSETUP))); + + G_UpdateGamepadGyro(forplayer, gyro); + G_UpdateGamepadGravity(forplayer, G_GetGamepadCalibratedGyro(forplayer), accel); } // why build a ticcmd if we're paused? @@ -1783,9 +1558,15 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) side += (accelerometertilt * 4) / JOYAXISRANGE; //todo: control spectator camera using the gyro - // if (spectating) - // { - // } + if (spectating) + { + fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE; + vector3_t gyro = G_GetGamepadCalibratedGyro(forplayer); + cmd->turning -= (FixedMul(FixedMul(gyro.y, M_PI_FIXED/180), deltaseconds) * 1) * (encoremode ? -1 : 1); + cmd->angle -= (FixedMul(FixedMul(gyro.y, M_PI_FIXED/180), deltaseconds) * 1) * (encoremode ? -1 : 1); + + cmd->aiming -= (FixedMul(FixedMul(gyro.x, M_PI_FIXED/180), deltaseconds) * 1); + } } else if (joystickvector.xaxis != 0) { @@ -2001,8 +1782,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) if (abs(tilt) > MAXGAMEPADTILT) cmd->flags |= TICCMD_EXCESSTILT; } - - CONS_Debug(DBG_IMU, "Shake: %4.2f\n", FixedToFloat(FV3_Length(&shake))); //cmd->shake = CLAMP((GAMEPADSHAKETHRESHOLD*FV3_Length(&shake))/FRACUNIT, 0, UINT8_MAX); //CONS_Debug(DBG_IMU, "CMD Tilt: %d, Shake: %d\n", cmd->tilt, cmd->shake); diff --git a/src/g_game.h b/src/g_game.h index 2b282b72a..dffc38507 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -121,6 +121,9 @@ mapnum_t G_LevelTitleToMapNum(const char * leveltitle); mapnum_t G_KartMapToNative(mapnum_t mapnum); mapnum_t G_NativeMapToKart(mapnum_t mapnum); +#define GAMEPADSHAKETHRESHOLD (UINT8_MAX/2) +#define TILTTOSTICKEASE 6 + void G_ResetAnglePrediction(player_t *player); void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer); @@ -137,31 +140,6 @@ void G_FinalClipAimingPitch(INT32 *aiming, player_t *player, boolean skybox); extern angle_t localangle[MAXSPLITSCREENPLAYERS]; extern INT32 localaiming[MAXSPLITSCREENPLAYERS]; // should be an angle_t but signed -typedef enum -{ - DEADZONE_X, - DEADZONE_Y, - DEADZONE_BUTTON, -} analogdeadzone_e; - -typedef enum -{ - ACCELEROMETER, - GYROSCOPE, -} motionsensortype_e; - -#define MAXGAMEPADTILT (50*FRACUNIT/100) -// #define ACCELEROMETERGRAVITY ((fixed_t)(9.80665f * ((float)FRACUNIT))) -#define ACCELEROMETERGRAVITY 642688 -#define GAMEPADSHAKETHRESHOLD (UINT8_MAX/2) -#define TILTTOSTICKEASE 6 -boolean G_GetGamepadCanUseTilt(INT32 p); -void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel); -INT32 G_GetGamepadTilt(INT32 p); -vector3_t G_GetGamepadShake(INT32 p); -vector3_t G_GetGamepadGravity(INT32 p); -vector3_t G_PlayerInputSensor(UINT8 p, motionsensortype_e sensor); - fixed_t G_GetDeadZoneType(INT32 p, SINT8 type); INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, boolean digital, SINT8 type); boolean G_PlayerInputDown(UINT8 p, INT32 gc, boolean digital, SINT8 type); diff --git a/src/g_input.c b/src/g_input.c index 993679ed0..4e01a3bfa 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -26,6 +26,8 @@ #include "v_video.h" #include "p_local.h" #include "k_kart.h" +#include "m_fixed.h" +#include "m_easing.h" #define MAXMOUSESENSITIVITY 100 // sensitivity steps @@ -1226,3 +1228,331 @@ void Command_Setcontrol4_f(void) setcontrol(3); } + +// accelerometer and gyro stuff + +// when holding the controller still (shaking and turning included), correct this quickly to resolve error +#define GyroCalibrationTrust (1*FRACUNIT/100) + +#define CorrectionInstantRate (80*FRACUNIT/100) +#define CorrectionInstantShake (4*FRACUNIT/100) +#define CorrectionInstantTurn (5*FRACUNIT/100) + +boolean localgyrocalibrating[MAXSPLITSCREENPLAYERS]; +vector3_t localgyrovectors[MAXSPLITSCREENPLAYERS]; + +vector3_t localgyrocalibrationoffset[MAXSPLITSCREENPLAYERS]; + +// assume the accelerometer doesn't need calibration, use this to determine if gyro can be calibrated +vector3_t localaccelcalibrationoffset[MAXSPLITSCREENPLAYERS]; + +fixed_t localshakinessfac[MAXSPLITSCREENPLAYERS]; +vector3_t localsmoothedaccel[MAXSPLITSCREENPLAYERS]; +vector3_t localgravityvectors[MAXSPLITSCREENPLAYERS]; + +// copy/pasted from the lua version of these routines +inline static vector3_t *QuaternionMulVec3(vector3_t *out, vector3_t *a, vector4_t *b) +{ + fixed_t ax = a->x, ay = a->y, az = a->z, aw = 0; + fixed_t bx = b->x, by = b->y, bz = b->z, bw = b->a; + + FV3_NormalizeEx(out, FV3_Load(out, + FixedMul(aw, bx) + FixedMul(ax, bw) + FixedMul(ay, bz) - FixedMul(az, by), + FixedMul(aw, by) - FixedMul(ax, bz) + FixedMul(ay, bw) + FixedMul(az, bx), + FixedMul(aw, bz) + FixedMul(ax, by) - FixedMul(ay, bx) + FixedMul(az, bw) + )); + + return out; +} + +inline static fixed_t FV3_LengthSquared(const vector3_t *vec) +{ + return FixedMul(vec->x, vec->x) + FixedMul(vec->y, vec->y) + FixedMul(vec->z, vec->z); +} + +inline static fixed_t FV3_Length(const vector3_t *vec) +{ + return FixedSqrt(FV3_LengthSquared(vec)); +} + +inline static vector4_t AngleAxis(fixed_t angle, fixed_t x, fixed_t y, fixed_t z) +{ + fixed_t sinangle = FINESINE(FixedAngle(angle/2) >> ANGLETOFINESHIFT); + fixed_t cosangle = FINECOSINE(FixedAngle(angle/2) >> ANGLETOFINESHIFT); + vector3_t axis = {x, y, z}; + vector4_t result; + + FV3_Normalize(&axis); + + FV4_Load(&result, + FixedMul(axis.x, sinangle), + FixedMul(axis.y, sinangle), + FixedMul(axis.z, sinangle), + cosangle + ); + + return result; +} + +vector3_t G_GetCalibratedGyroOffset(INT32 p) +{ + return localgyrocalibrationoffset[p]; +} + +void G_UpdateGamepadAutoCalibration(INT32 p, vector3_t accel, vector3_t gyro, boolean allowautocalibration) +{ + fixed_t trust = FV3_Distance(&localaccelcalibrationoffset[p], &accel); + FV3_Load( + &localaccelcalibrationoffset[p], + (localaccelcalibrationoffset[p].x + accel.x)/2, + (localaccelcalibrationoffset[p].y + accel.y)/2, + (localaccelcalibrationoffset[p].z + accel.z)/2 + ); + + CONS_Debug(DBG_IMU, "== Gyro Update ==\n"); + CONS_Debug(DBG_IMU, "Gyro calibration safety: %4.3f\n", FixedToFloat(trust)); + if (allowautocalibration && (trust < GyroCalibrationTrust)) + { + if (!localgyrocalibrating[p]) + { + FV3_Load( + &localgyrocalibrationoffset[p], + 0, 0, 0 + ); + localgyrocalibrating[p] = true; + } + } + else + { + localgyrocalibrating[p] = false; + } +} + +void G_UpdateGamepadGyro(INT32 p, vector3_t gyro) +{ + vector3_t offset = {0}; + + if (p >= MAXSPLITSCREENPLAYERS) + { +#ifdef PARANOIA + CONS_Debug(DBG_GAMELOGIC, "G_UpdateGamepadGyro: Invalid player ID %d\n", p); +#endif + return; + } + if (!I_ControllerSupportsSensorGyro(p)) return; + + if (localgyrocalibrating[p]) + { + FV3_Load( + &localgyrocalibrationoffset[p], + ((3*localgyrocalibrationoffset[p].x) + gyro.x)/4, + ((3*localgyrocalibrationoffset[p].y) + gyro.y)/4, + ((3*localgyrocalibrationoffset[p].z) + gyro.z)/4 + ); + } + offset = G_GetCalibratedGyroOffset(p); + FV3_SubEx(&gyro, &offset, &localgyrovectors[p]); +} + +// math sourced from this article +// http://gyrowiki.jibbsmart.com/blog:finding-gravity-with-sensor-fusion + +// the time it takes in our acceleration smoothing for 'A' to get halfway to 'B' +#define SmoothingHalfTime (0.01) +// thresholds of trust for accel shakiness. less shakiness = more trust +#define ShakinessMaxThreshold (50*FRACUNIT/100) +#define ShakinessMinThreshold (1*FRACUNIT/100) +// when we trust the accel a lot (the controller is "still"), how quickly do we correct our gravity vector? +#define CorrectionStillRate (FRACUNIT) +// when we don't trust the accel (the controller is "shaky"), how quickly do we correct our gravity vector? +#define CorrectionShakyRate (10*FRACUNIT/100) +// if our old gravity vector is close enough to our new one, limit further corrections to this proportion of the rotation speed +#define CorrectionGyroFactor (40*FRACUNIT/100) +// thresholds for what's considered "close enough" +#define CorrectionGyroMinThreshold (5*FRACUNIT/100) +#define CorrectionGyroMaxThreshold (FRACUNIT/4) +// no matter what, always apply a minimum of this much correction to our gravity vector +#define CorrectionMinimumSpeed (1*FRACUNIT/100) + +void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) +{ + // convert gyro input to reverse rotation + vector3_t invAccel = {-accel.x, -accel.y, -accel.z}; + fixed_t correctionRate = 0; + // scaling is reversed, smaller time scales = larger steps in this code + // (1/timescale)/dt + fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE; + // we don't have exp2 and we actually need it here to take timescale into account :( + fixed_t smoothFactor = FloatToFixed(exp2(-FixedToFloat(deltaseconds) / SmoothingHalfTime)); + fixed_t angleRate; + fixed_t correctionLimit; + vector4_t invRotation = AngleAxis( + FixedMul(FV3_Length(&gyro), deltaseconds), + -gyro.x, + -gyro.y, + -gyro.z + ); + vector3_t gravityDelta = {0}; + vector3_t gravityDeltaDirection = {0}; + vector3_t correction = {0}; + + if (!G_GetGamepadCanUseTilt(p)) return; + CONS_Debug(DBG_IMU, "= Update Gravity Delta Time: %4.3f =\n", FixedToFloat(deltaseconds)); + + // rotate gravity vector + QuaternionMulVec3(&localgravityvectors[p], &localgravityvectors[p], &invRotation); + QuaternionMulVec3(&localsmoothedaccel[p], &localsmoothedaccel[p], &invRotation); + localshakinessfac[p] = FixedMul(localshakinessfac[p], smoothFactor), + FV3_SubEx(&accel, &localsmoothedaccel[p], &correction); + localshakinessfac[p] = max(localshakinessfac[p], FV3_Length(&correction)); + FV3_Load(&localsmoothedaccel[p], + Easing_Linear(smoothFactor, accel.x, localsmoothedaccel[p].x), + Easing_Linear(smoothFactor, accel.y, localsmoothedaccel[p].y), + Easing_Linear(smoothFactor, accel.z, localsmoothedaccel[p].z) + ); + + CONS_Debug(DBG_IMU, "Shakiness: %4.2f\n", FixedToFloat(localshakinessfac[p])); + + FV3_SubEx(&invAccel, &localgravityvectors[p], &gravityDelta); + FV3_NormalizeEx(&gravityDelta, &gravityDeltaDirection); + + if (ShakinessMaxThreshold > ShakinessMinThreshold) + { + fixed_t stillness = CLAMP(FixedDiv((localshakinessfac[p] - ShakinessMinThreshold), (ShakinessMaxThreshold - ShakinessMinThreshold)), 0, FRACUNIT); + correctionRate = CorrectionStillRate + FixedMul((CorrectionShakyRate - CorrectionStillRate), stillness); + } + else if (localshakinessfac[p] > ShakinessMaxThreshold) + { + correctionRate = CorrectionShakyRate; + } + else + { + correctionRate = CorrectionStillRate; + } + + // limit in proportion to rotation rate + angleRate = FixedMul(FV3_Length(&gyro), M_PI_FIXED/180); + correctionLimit = max(FixedMul(FixedMul(angleRate, FV3_Length(&localgravityvectors[p])), CorrectionGyroFactor), CorrectionMinimumSpeed); + + CONS_Debug(DBG_IMU, "Angle Rate: %4.3f\n", FixedToFloat(angleRate)); + CONS_Debug(DBG_IMU, "Correction Limit: %4.3f\n", FixedToFloat(correctionLimit)); + + if (correctionRate > correctionLimit) { + fixed_t closeEnoughFactor; + if (CorrectionGyroMaxThreshold > CorrectionGyroMinThreshold) + { + closeEnoughFactor = CLAMP(FixedDiv((FV3_Length(&gravityDelta) - CorrectionGyroMinThreshold), (CorrectionGyroMaxThreshold - CorrectionGyroMinThreshold)), 0, FRACUNIT); + } + else if (FV3_Length(&gravityDelta) > CorrectionGyroMaxThreshold) + { + closeEnoughFactor = FRACUNIT; + } + else + { + closeEnoughFactor = 0; + } + + CONS_Debug(DBG_IMU, "'Close Enough' Fac: %4.3f\n", FixedToFloat(closeEnoughFactor)); + correctionRate = correctionLimit + FixedMul((correctionRate - correctionLimit), closeEnoughFactor); + } + + if (localshakinessfac[p] < CorrectionInstantShake && angleRate < CorrectionInstantTurn) + { + correctionRate = max(correctionRate, CorrectionInstantRate); + } + + CONS_Debug(DBG_IMU, "Correction Rate: %4.2f\n", FixedToFloat(correctionRate)); + + FV3_Load(&correction, + FixedMul(gravityDelta.x, FixedMul(deltaseconds, correctionRate)), + FixedMul(gravityDelta.y, FixedMul(deltaseconds, correctionRate)), + FixedMul(gravityDelta.z, FixedMul(deltaseconds, correctionRate)) + ); + if ((FV3_LengthSquared(&correction) < FV3_LengthSquared(&gravityDelta))) + { + FV3_Add(&localgravityvectors[p], &correction); + } + else + { + FV3_Load(&localgravityvectors[p], invAccel.x, invAccel.y, invAccel.z); + } +} + +INT32 G_GetGamepadTilt(INT32 p) +{ + fixed_t tilt; + fixed_t curve; + if (!G_GetGamepadCanUseTilt(p)) return 0; + tilt = CLAMP(FixedDiv(G_GetGamepadGravity(p).x + MAXGAMEPADTILT, 2*MAXGAMEPADTILT), 0, FRACUNIT); + CONS_Debug(DBG_IMU, "Tilt: %4.2f\n", FixedToFloat(tilt)); + + curve = FINESINE(FixedAngle(180*(tilt-FRACUNIT/2))>>ANGLETOFINESHIFT); + CONS_Debug(DBG_IMU, "Pinched Tilt: %4.2f\n", FixedToFloat(curve)); + + return (JOYAXISRANGE * curve)/FRACUNIT; +} + +vector3_t G_GetGamepadGravity(INT32 p) +{ + const vector3_t zero = {0, -ACCELEROMETERGRAVITY, 0}; + if (!G_GetGamepadCanUseTilt(p)) return zero; + return localgravityvectors[p]; +} + +vector3_t G_GetGamepadShake(INT32 p) +{ + vector3_t accel = {0}; + if (!G_GetGamepadCanUseTilt(p)) return accel; + accel = G_PlayerInputSensor(p, ACCELEROMETER); + FV3_Add(&accel, &localgravityvectors[p]); + + return accel; +} + +fixed_t G_GetGamepadShakinessFactor(INT32 p) +{ + if (!G_GetGamepadCanUseTilt(p)) return 0; + return localshakinessfac[p]; +} + +vector3_t G_GetGamepadCalibratedGyro(INT32 p) +{ + vector3_t zero = {0}; + if (p >= MAXSPLITSCREENPLAYERS) + { +#ifdef PARANOIA + CONS_Debug(DBG_GAMELOGIC, "G_GetGamepadGyro: Invalid player ID %d\n", p); +#endif + return zero; + } + + if (!I_ControllerSupportsSensorGyro(p)) return zero; + + return localgyrovectors[p]; +} + +boolean G_GetGamepadCanUseTilt(INT32 p) +{ + if (p >= MAXSPLITSCREENPLAYERS) + { +#ifdef PARANOIA + CONS_Debug(DBG_GAMELOGIC, "G_GetGamepadCanUseTilt: Invalid player ID %d\n", p); +#endif + return false; + } + + return (I_ControllerSupportsSensorAccelerometer(p) && I_ControllerSupportsSensorGyro(p)); +} +#undef ShakinessMaxThreshold +#undef ShakinessMinThreshold +#undef CorrectionStillRate +#undef CorrectionShakyRate +#undef CorrectionGyroFactor +#undef CorrectionGyroMinThreshold +#undef CorrectionGyroMaxThreshold +#undef CorrectionMinimumSpeed + +#undef CorrectionInstantRate +#undef CorrectionInstantShake +#undef CorrectionInstantTurn +#undef GyroCalibrationTrust \ No newline at end of file diff --git a/src/g_input.h b/src/g_input.h index 02bd76b7e..09fc49f5c 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -98,6 +98,19 @@ typedef enum num_gamecontrols } gamecontrols_e; +typedef enum +{ + DEADZONE_X, + DEADZONE_Y, + DEADZONE_BUTTON, +} analogdeadzone_e; + +typedef enum +{ + ACCELEROMETER, + GYROSCOPE, +} motionsensortype_e; + // mouse values are used once extern consvar_t cv_mousesens, cv_mouseysens; extern consvar_t cv_mousesens2, cv_mouseysens2; @@ -178,6 +191,22 @@ void G_ResetControls(UINT8 p); void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[MAXINPUTMAPPING], INT32 (*fromcontrolsb)[MAXINPUTMAPPING], INT32 (*fromcontrolsc)[MAXINPUTMAPPING], INT32 (*fromcontrolsd)[MAXINPUTMAPPING]); INT32 G_CheckDoubleUsage(INT32 keynum, INT32 playernum, boolean modify); +#define MAXGAMEPADTILT (50*FRACUNIT/100) +// #define ACCELEROMETERGRAVITY ((fixed_t)(9.80665f * ((float)FRACUNIT))) +#define ACCELEROMETERGRAVITY 642688 +boolean G_GetGamepadCanUseTilt(INT32 p); +void G_ResetGyroCalibration(INT32 p); +void G_UpdateGamepadAutoCalibration(INT32 p, vector3_t accel, vector3_t gyro, boolean allowautocalibration); +void G_UpdateGamepadGyro(INT32 p, vector3_t gyro); +void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel); +fixed_t G_GetGamepadShakinessFactor(INT32 p); +vector3_t G_GetGamepadShake(INT32 p); +vector3_t G_GetGamepadGravity(INT32 p); +vector3_t G_GetCalibratedGyroOffset(INT32 p); +vector3_t G_GetGamepadCalibratedGyro(INT32 p); +vector3_t G_PlayerInputSensor(UINT8 p, motionsensortype_e sensor); +INT32 G_GetGamepadTilt(INT32 p); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_hud.c b/src/k_hud.c index 19b515b39..0f039e5b7 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -32,6 +32,7 @@ #include "doomstat.h" #include "d_clisrv.h" #include "g_game.h" +#include "g_input.h" #include "p_local.h" #include "z_zone.h" #include "m_cond.h" diff --git a/src/st_stuff.c b/src/st_stuff.c index 84e74def2..0f5d84114 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -475,15 +475,10 @@ static void ST_drawMusicDebug(INT32 *height) ST_pushDebugString(height, va(" Song: %8s", mname)); } -static INT32 ST_getFixedFrac(fixed_t val) -{ - return ((abs(val) * 100)>>FRACBITS) % 100; -} - static void ST_drawImuDebug(INT32 *height) { INT32 pnum = stplyrnum; - vector3_t accel, gyro, grav, shake; + vector3_t accel, gyro, gyrocalibrated, grav, shake; if (demo.playback || (!P_IsMachineLocalPlayer(stplyr))) { ST_pushDebugString(height, va("Only for local player!!")); @@ -491,14 +486,16 @@ static void ST_drawImuDebug(INT32 *height) } accel = G_PlayerInputSensor(pnum, ACCELEROMETER); - gyro = G_PlayerInputSensor(pnum, GYROSCOPE); - grav = G_GetGamepadGravity(pnum); + gyro = G_PlayerInputSensor(pnum, GYROSCOPE); + gyrocalibrated = G_GetGamepadCalibratedGyro(pnum); + grav = G_GetGamepadGravity(pnum); shake = G_GetGamepadShake(pnum); - ST_pushDebugString(height, va(" Gyro :%4.2f, %4.2f,%4.2f", FixedToFloat(gyro.x), FixedToFloat(gyro.y), FixedToFloat(gyro.z))); - ST_pushDebugString(height, va("Accel :%4.2f, %4.2f,%4.2f", FixedToFloat(accel.x), FixedToFloat(accel.y), FixedToFloat(accel.z))); - ST_pushDebugString(height, va("Shake :%4.2f, %4.2f,%4.2f", FixedToFloat(shake.x), FixedToFloat(shake.y), FixedToFloat(shake.z))); - ST_pushDebugString(height, va(" Grav :%4.2f, %4.2f,%4.2f", FixedToFloat(grav.x), FixedToFloat(grav.y), FixedToFloat(grav.z))); + ST_pushDebugString(height, va(" Grav : %4.2f, %4.2f,%4.2f", FixedToFloat(grav.x), FixedToFloat(grav.y), FixedToFloat(grav.z))); + ST_pushDebugString(height, va(" Shake : %4.2f, %4.2f,%4.2f", FixedToFloat(shake.x), FixedToFloat(shake.y), FixedToFloat(shake.z))); + ST_pushDebugString(height, va("Cal. Gyro: %4.2f, %4.2f,%4.2f", FixedToFloat(gyrocalibrated.x), FixedToFloat(gyrocalibrated.y), FixedToFloat(gyrocalibrated.z))); + ST_pushDebugString(height, va(" Gyro : %4.2f, %4.2f,%4.2f", FixedToFloat(gyro.x), FixedToFloat(gyro.y), FixedToFloat(gyro.z))); + ST_pushDebugString(height, va(" Accel : %4.2f, %4.2f,%4.2f", FixedToFloat(accel.x), FixedToFloat(accel.y), FixedToFloat(accel.z))); } static void ST_drawRenderDebug(INT32 *height) From 493e6e96fabcdfe8c1eeec1ae19be1bc137ba027 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Thu, 2 Apr 2026 13:39:08 -0400 Subject: [PATCH 2/6] player space gyro and split off from cmd turning --- src/d_clisrv.c | 3 ++- src/d_ticcmd.h | 8 ++++---- src/g_game.c | 31 ++++++++++++++++++++----------- src/p_user.c | 2 +- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index e4db23675..33802170e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -5102,8 +5102,9 @@ static boolean CheckForSpeedHacks(UINT8 p) { if (netcmds[maketic%BACKUPTICS][p].forwardmove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][p].forwardmove < -MAXPLMOVE || netcmds[maketic%BACKUPTICS][p].sidemove > MAXPLMOVE || netcmds[maketic%BACKUPTICS][p].sidemove < -MAXPLMOVE + || netcmds[maketic%BACKUPTICS][p].throwdir > KART_FULLTURN || netcmds[maketic%BACKUPTICS][p].throwdir < -KART_FULLTURN || netcmds[maketic%BACKUPTICS][p].turning > KART_FULLTURN || netcmds[maketic%BACKUPTICS][p].turning < -KART_FULLTURN - || netcmds[maketic%BACKUPTICS][p].throwdir > KART_FULLTURN || netcmds[maketic%BACKUPTICS][p].throwdir < -KART_FULLTURN) + ) { CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), playernode[p]); //D_Clearticcmd(k); diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index d636043c4..495477fc8 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -64,11 +64,11 @@ typedef enum // 16 bytes long now! struct ticcmd_t { - SINT8 forwardmove; // -MAXPLMOVE to MAXPLMOVE (50) - SINT8 sidemove; // -MAXPLMOVE to MAXPLMOVE (50) - INT16 turning; // Turn speed + SINT8 forwardmove; // -MAXPLMOVE to MAXPLMOVE (50) (has anticheat) + SINT8 sidemove; // -MAXPLMOVE to MAXPLMOVE (50) (has anticheat) + INT16 turning; // "Steering Wheel" turn speed when driving (has anticheat) INT16 angle; // Predicted angle, use me if you can! - INT16 throwdir; // Aiming direction + INT16 throwdir; // Forwards/Backwards item use direction (has anticheat) INT16 aiming; // vertical aiming, see G_BuildTicCmd UINT16 buttons; UINT8 latency; // Netgames: how many tics ago was this ticcmd generated from this player's end? diff --git a/src/g_game.c b/src/g_game.c index 88672a4a6..c33bd5976 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1557,15 +1557,22 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) cmd->angle -= (tspeed * KART_FULLTURN) / JOYAXISRANGE; side += (accelerometertilt * 4) / JOYAXISRANGE; - //todo: control spectator camera using the gyro + //control spectator camera using the gyro + // player space gyro from http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained if (spectating) { + fixed_t yawRelaxFactor = 141*FRACUNIT/100; + fixed_t worldYaw; fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE; vector3_t gyro = G_GetGamepadCalibratedGyro(forplayer); - cmd->turning -= (FixedMul(FixedMul(gyro.y, M_PI_FIXED/180), deltaseconds) * 1) * (encoremode ? -1 : 1); - cmd->angle -= (FixedMul(FixedMul(gyro.y, M_PI_FIXED/180), deltaseconds) * 1) * (encoremode ? -1 : 1); - - cmd->aiming -= (FixedMul(FixedMul(gyro.x, M_PI_FIXED/180), deltaseconds) * 1); + vector3_t gravnorm = G_GetGamepadGravity(forplayer); + vector2_t gyroYZ = {gyro.y, gyro.z}; + FV3_Normalize(&gravnorm); + // use world yaw for yaw direction, local combined yaw for magnitude + worldYaw = FixedMul(gyroYZ.x, gravnorm.y) + FixedMul(gyroYZ.y, gravnorm.z); // dot product but just yaw and roll + // yes this is backwards intentionally + cmd->angle += ((FixedMul(min(FixedMul(abs(worldYaw), yawRelaxFactor), FV2_Magnitude(&gyroYZ)), deltaseconds)*180)/FRACUNIT) * intsign(worldYaw) * (encoremode ? -1 : 1); + cmd->aiming -= (((FixedMul(gyro.x, deltaseconds)*180)/FRACUNIT)); } } else if (joystickvector.xaxis != 0) @@ -1579,7 +1586,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) if (spectating) { INT32 mousex = gamekeydown[0][KEY_MOUSEMOVE+3] - gamekeydown[0][KEY_MOUSEMOVE+2]; - cmd->turning -= (mousex * 8) * (encoremode ? -1 : 1); cmd->angle -= (mousex * 8) * (encoremode ? -1 : 1); } @@ -1758,11 +1764,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) cmd->turning = KART_FULLTURN; else if (cmd->turning < -KART_FULLTURN) cmd->turning = -KART_FULLTURN; - - if (cmd->angle > KART_FULLTURN) - cmd->angle = KART_FULLTURN; - else if (cmd->angle < -KART_FULLTURN) - cmd->angle = -KART_FULLTURN; + + if (!spectating) + { + if (cmd->angle > KART_FULLTURN) + cmd->angle = KART_FULLTURN; + else if (cmd->angle < -KART_FULLTURN) + cmd->angle = -KART_FULLTURN; + } if (cmd->throwdir > KART_FULLTURN) cmd->throwdir = KART_FULLTURN; diff --git a/src/p_user.c b/src/p_user.c index 0b70e6fbf..882f09c3a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2831,7 +2831,7 @@ void P_DemoCameraMovement(camera_t *cam, UINT8 num) cam->reset_aiming = false; } - cam->angle += cmd->turning << TICCMD_REDUCE; + cam->angle = cmd->angle << TICCMD_REDUCE; // camera movement: if (!cam->button_a_held) From 4b5658d487bbe1e2650c839623e633c2b30657b8 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Thu, 2 Apr 2026 16:48:05 -0400 Subject: [PATCH 3/6] greatly improve responsiveness of tilt steering is more correct to the GyroWiki article now --- src/g_input.c | 87 +++++++++++++++++++++++++++----------------------- src/st_stuff.c | 10 +++--- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/src/g_input.c b/src/g_input.c index 4e01a3bfa..521977c90 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -1232,15 +1232,14 @@ void Command_Setcontrol4_f(void) // accelerometer and gyro stuff // when holding the controller still (shaking and turning included), correct this quickly to resolve error +#define GyroCalibrationRollingAvgSamples (TICRATE) #define GyroCalibrationTrust (1*FRACUNIT/100) -#define CorrectionInstantRate (80*FRACUNIT/100) -#define CorrectionInstantShake (4*FRACUNIT/100) -#define CorrectionInstantTurn (5*FRACUNIT/100) - boolean localgyrocalibrating[MAXSPLITSCREENPLAYERS]; vector3_t localgyrovectors[MAXSPLITSCREENPLAYERS]; +tic_t localgyrocalibrationsamples[MAXSPLITSCREENPLAYERS]; +vector3_t localgyrocalibrationlastoffset[MAXSPLITSCREENPLAYERS]; vector3_t localgyrocalibrationoffset[MAXSPLITSCREENPLAYERS]; // assume the accelerometer doesn't need calibration, use this to determine if gyro can be calibrated @@ -1249,6 +1248,7 @@ vector3_t localaccelcalibrationoffset[MAXSPLITSCREENPLAYERS]; fixed_t localshakinessfac[MAXSPLITSCREENPLAYERS]; vector3_t localsmoothedaccel[MAXSPLITSCREENPLAYERS]; vector3_t localgravityvectors[MAXSPLITSCREENPLAYERS]; +vector4_t localquaternions[MAXSPLITSCREENPLAYERS]; // copy/pasted from the lua version of these routines inline static vector3_t *QuaternionMulVec3(vector3_t *out, vector3_t *a, vector4_t *b) @@ -1270,11 +1270,6 @@ inline static fixed_t FV3_LengthSquared(const vector3_t *vec) return FixedMul(vec->x, vec->x) + FixedMul(vec->y, vec->y) + FixedMul(vec->z, vec->z); } -inline static fixed_t FV3_Length(const vector3_t *vec) -{ - return FixedSqrt(FV3_LengthSquared(vec)); -} - inline static vector4_t AngleAxis(fixed_t angle, fixed_t x, fixed_t y, fixed_t z) { fixed_t sinangle = FINESINE(FixedAngle(angle/2) >> ANGLETOFINESHIFT); @@ -1315,6 +1310,8 @@ void G_UpdateGamepadAutoCalibration(INT32 p, vector3_t accel, vector3_t gyro, bo { if (!localgyrocalibrating[p]) { + localgyrocalibrationsamples[p] = 0; + FV3_Copy(&localgyrocalibrationlastoffset[p], &localgyrocalibrationoffset[p]); FV3_Load( &localgyrocalibrationoffset[p], 0, 0, 0 @@ -1324,6 +1321,12 @@ void G_UpdateGamepadAutoCalibration(INT32 p, vector3_t accel, vector3_t gyro, bo } else { + // incomplete calibration + if (localgyrocalibrating[p] && localgyrocalibrationsamples[p] <= GyroCalibrationRollingAvgSamples) + { + FV3_Copy(&localgyrocalibrationoffset[p], &localgyrocalibrationlastoffset[p]); + localgyrocalibrationsamples[p] = 0; + } localgyrocalibrating[p] = false; } } @@ -1343,13 +1346,16 @@ void G_UpdateGamepadGyro(INT32 p, vector3_t gyro) if (localgyrocalibrating[p]) { + localgyrocalibrationsamples[p]++; FV3_Load( &localgyrocalibrationoffset[p], - ((3*localgyrocalibrationoffset[p].x) + gyro.x)/4, - ((3*localgyrocalibrationoffset[p].y) + gyro.y)/4, - ((3*localgyrocalibrationoffset[p].z) + gyro.z)/4 + (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].x) + gyro.x)/GyroCalibrationRollingAvgSamples, + (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].y) + gyro.y)/GyroCalibrationRollingAvgSamples, + (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].z) + gyro.z)/GyroCalibrationRollingAvgSamples ); } + CONS_Debug(DBG_IMU, "Gyro calibration samples: %d\n", localgyrocalibrationsamples[p]); + offset = G_GetCalibratedGyroOffset(p); FV3_SubEx(&gyro, &offset, &localgyrovectors[p]); } @@ -1358,16 +1364,16 @@ void G_UpdateGamepadGyro(INT32 p, vector3_t gyro) // http://gyrowiki.jibbsmart.com/blog:finding-gravity-with-sensor-fusion // the time it takes in our acceleration smoothing for 'A' to get halfway to 'B' -#define SmoothingHalfTime (0.01) +#define SmoothingHalfTime (0.25) // thresholds of trust for accel shakiness. less shakiness = more trust -#define ShakinessMaxThreshold (50*FRACUNIT/100) +#define ShakinessMaxThreshold (40*FRACUNIT/100) #define ShakinessMinThreshold (1*FRACUNIT/100) // when we trust the accel a lot (the controller is "still"), how quickly do we correct our gravity vector? #define CorrectionStillRate (FRACUNIT) // when we don't trust the accel (the controller is "shaky"), how quickly do we correct our gravity vector? -#define CorrectionShakyRate (10*FRACUNIT/100) +#define CorrectionShakyRate (1*FRACUNIT/100) // if our old gravity vector is close enough to our new one, limit further corrections to this proportion of the rotation speed -#define CorrectionGyroFactor (40*FRACUNIT/100) +#define CorrectionGyroFactor (5*FRACUNIT/100) // thresholds for what's considered "close enough" #define CorrectionGyroMinThreshold (5*FRACUNIT/100) #define CorrectionGyroMaxThreshold (FRACUNIT/4) @@ -1384,14 +1390,9 @@ void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE; // we don't have exp2 and we actually need it here to take timescale into account :( fixed_t smoothFactor = FloatToFixed(exp2(-FixedToFloat(deltaseconds) / SmoothingHalfTime)); - fixed_t angleRate; + fixed_t angleRate = FixedMul(FV3_Magnitude(&gyro), M_PI_FIXED)/180; fixed_t correctionLimit; - vector4_t invRotation = AngleAxis( - FixedMul(FV3_Length(&gyro), deltaseconds), - -gyro.x, - -gyro.y, - -gyro.z - ); + vector4_t invRotation = {0}; vector3_t gravityDelta = {0}; vector3_t gravityDeltaDirection = {0}; vector3_t correction = {0}; @@ -1399,12 +1400,19 @@ void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) if (!G_GetGamepadCanUseTilt(p)) return; CONS_Debug(DBG_IMU, "= Update Gravity Delta Time: %4.3f =\n", FixedToFloat(deltaseconds)); + invRotation = AngleAxis( + FixedMul(FixedMul(FV3_Magnitude(&gyro), deltaseconds), 190*FRACUNIT/100), + -gyro.x, + -gyro.y, + -gyro.z + ); + // rotate gravity vector QuaternionMulVec3(&localgravityvectors[p], &localgravityvectors[p], &invRotation); QuaternionMulVec3(&localsmoothedaccel[p], &localsmoothedaccel[p], &invRotation); localshakinessfac[p] = FixedMul(localshakinessfac[p], smoothFactor), FV3_SubEx(&accel, &localsmoothedaccel[p], &correction); - localshakinessfac[p] = max(localshakinessfac[p], FV3_Length(&correction)); + localshakinessfac[p] = max(localshakinessfac[p], FV3_Magnitude(&correction)); FV3_Load(&localsmoothedaccel[p], Easing_Linear(smoothFactor, accel.x, localsmoothedaccel[p].x), Easing_Linear(smoothFactor, accel.y, localsmoothedaccel[p].y), @@ -1414,7 +1422,12 @@ void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) CONS_Debug(DBG_IMU, "Shakiness: %4.2f\n", FixedToFloat(localshakinessfac[p])); FV3_SubEx(&invAccel, &localgravityvectors[p], &gravityDelta); - FV3_NormalizeEx(&gravityDelta, &gravityDeltaDirection); + if (FV3_Magnitude(&gravityDelta) > 0) + { + FV3_NormalizeEx(&gravityDelta, &gravityDeltaDirection); + } + + CONS_Debug(DBG_IMU, "Gravity Delta Magnitude: %4.3f\n", FixedToFloat(FV3_Magnitude(&gravityDelta))); if (ShakinessMaxThreshold > ShakinessMinThreshold) { @@ -1431,8 +1444,7 @@ void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) } // limit in proportion to rotation rate - angleRate = FixedMul(FV3_Length(&gyro), M_PI_FIXED/180); - correctionLimit = max(FixedMul(FixedMul(angleRate, FV3_Length(&localgravityvectors[p])), CorrectionGyroFactor), CorrectionMinimumSpeed); + correctionLimit = FixedMul(angleRate, CorrectionGyroFactor); CONS_Debug(DBG_IMU, "Angle Rate: %4.3f\n", FixedToFloat(angleRate)); CONS_Debug(DBG_IMU, "Correction Limit: %4.3f\n", FixedToFloat(correctionLimit)); @@ -1441,9 +1453,9 @@ void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) fixed_t closeEnoughFactor; if (CorrectionGyroMaxThreshold > CorrectionGyroMinThreshold) { - closeEnoughFactor = CLAMP(FixedDiv((FV3_Length(&gravityDelta) - CorrectionGyroMinThreshold), (CorrectionGyroMaxThreshold - CorrectionGyroMinThreshold)), 0, FRACUNIT); + closeEnoughFactor = CLAMP(FixedDiv((FV3_Magnitude(&gravityDelta) - CorrectionGyroMinThreshold), (CorrectionGyroMaxThreshold - CorrectionGyroMinThreshold)), 0, FRACUNIT); } - else if (FV3_Length(&gravityDelta) > CorrectionGyroMaxThreshold) + else if (FV3_Magnitude(&gravityDelta) > CorrectionGyroMaxThreshold) { closeEnoughFactor = FRACUNIT; } @@ -1456,17 +1468,14 @@ void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) correctionRate = correctionLimit + FixedMul((correctionRate - correctionLimit), closeEnoughFactor); } - if (localshakinessfac[p] < CorrectionInstantShake && angleRate < CorrectionInstantTurn) - { - correctionRate = max(correctionRate, CorrectionInstantRate); - } + correctionRate = max(correctionRate, CorrectionMinimumSpeed); CONS_Debug(DBG_IMU, "Correction Rate: %4.2f\n", FixedToFloat(correctionRate)); FV3_Load(&correction, - FixedMul(gravityDelta.x, FixedMul(deltaseconds, correctionRate)), - FixedMul(gravityDelta.y, FixedMul(deltaseconds, correctionRate)), - FixedMul(gravityDelta.z, FixedMul(deltaseconds, correctionRate)) + FixedMul(gravityDeltaDirection.x, FixedMul(deltaseconds, correctionRate)), + FixedMul(gravityDeltaDirection.y, FixedMul(deltaseconds, correctionRate)), + FixedMul(gravityDeltaDirection.z, FixedMul(deltaseconds, correctionRate)) ); if ((FV3_LengthSquared(&correction) < FV3_LengthSquared(&gravityDelta))) { @@ -1474,7 +1483,7 @@ void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel) } else { - FV3_Load(&localgravityvectors[p], invAccel.x, invAccel.y, invAccel.z); + FV3_Add(&localgravityvectors[p], &gravityDelta); } } @@ -1551,8 +1560,6 @@ boolean G_GetGamepadCanUseTilt(INT32 p) #undef CorrectionGyroMinThreshold #undef CorrectionGyroMaxThreshold #undef CorrectionMinimumSpeed +#undef SmoothingHalfTime -#undef CorrectionInstantRate -#undef CorrectionInstantShake -#undef CorrectionInstantTurn #undef GyroCalibrationTrust \ No newline at end of file diff --git a/src/st_stuff.c b/src/st_stuff.c index 0f5d84114..67ef85a01 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -491,11 +491,11 @@ static void ST_drawImuDebug(INT32 *height) grav = G_GetGamepadGravity(pnum); shake = G_GetGamepadShake(pnum); - ST_pushDebugString(height, va(" Grav : %4.2f, %4.2f,%4.2f", FixedToFloat(grav.x), FixedToFloat(grav.y), FixedToFloat(grav.z))); - ST_pushDebugString(height, va(" Shake : %4.2f, %4.2f,%4.2f", FixedToFloat(shake.x), FixedToFloat(shake.y), FixedToFloat(shake.z))); - ST_pushDebugString(height, va("Cal. Gyro: %4.2f, %4.2f,%4.2f", FixedToFloat(gyrocalibrated.x), FixedToFloat(gyrocalibrated.y), FixedToFloat(gyrocalibrated.z))); - ST_pushDebugString(height, va(" Gyro : %4.2f, %4.2f,%4.2f", FixedToFloat(gyro.x), FixedToFloat(gyro.y), FixedToFloat(gyro.z))); - ST_pushDebugString(height, va(" Accel : %4.2f, %4.2f,%4.2f", FixedToFloat(accel.x), FixedToFloat(accel.y), FixedToFloat(accel.z))); + ST_pushDebugString(height, va(" Grav :%4.2f,%4.2f,%4.2f", FixedToFloat(grav.x), FixedToFloat(grav.y), FixedToFloat(grav.z))); + ST_pushDebugString(height, va("Shake :%4.2f,%4.2f,%4.2f", FixedToFloat(shake.x), FixedToFloat(shake.y), FixedToFloat(shake.z))); + ST_pushDebugString(height, va("Cal. G:%4.2f,%4.2f,%4.2f", FixedToFloat(gyrocalibrated.x), FixedToFloat(gyrocalibrated.y), FixedToFloat(gyrocalibrated.z))); + ST_pushDebugString(height, va(" Gyro :%4.2f,%4.2f,%4.2f", FixedToFloat(gyro.x), FixedToFloat(gyro.y), FixedToFloat(gyro.z))); + ST_pushDebugString(height, va("Accel :%4.2f,%4.2f,%4.2f", FixedToFloat(accel.x), FixedToFloat(accel.y), FixedToFloat(accel.z))); } static void ST_drawRenderDebug(INT32 *height) From 03554c03ed779848377c40af7db05bb8c60b6846 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sat, 4 Apr 2026 19:13:50 -0400 Subject: [PATCH 4/6] freelook bind, fix issues with spectator gyro --- src/d_main.cpp | 2 +- src/deh_tables.c | 1 + src/g_game.c | 17 ++++++++++------- src/g_input.c | 1 + src/g_input.h | 1 + src/p_local.h | 11 +++++++---- src/p_tick.c | 44 +++++++++++++++++++++++++++++++++++++++----- src/p_user.c | 4 ++-- 8 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index f9032717a..dd5f633ba 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -102,7 +102,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0xa83b5f0cfc1ffd7b +#define ASSET_HASH_MAIN_PK3 0x9260d8dd984ea7c4 #define ASSET_HASH_MAPPATCH_PK3 0x1745690024efbaf8 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE diff --git a/src/deh_tables.c b/src/deh_tables.c index e172647af..c9b457a2a 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1574,6 +1574,7 @@ struct int_const_s const INT_CONST[] = { {"GC_RESPAWN",gc_respawn}, {"GC_DIRECTOR",gc_director}, {"GC_HORNCODE",gc_horncode}, + {"GC_FREELOOK",gc_freelook}, {"NUM_GAMECONTROLS",num_gamecontrols}, // screen.h constants diff --git a/src/g_game.c b/src/g_game.c index c33bd5976..005603746 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1561,18 +1561,21 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) // player space gyro from http://gyrowiki.jibbsmart.com/blog:player-space-gyro-and-alternatives-explained if (spectating) { - fixed_t yawRelaxFactor = 141*FRACUNIT/100; - fixed_t worldYaw; fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE; + fixed_t yawRelaxFactor = 141*FRACUNIT/100; vector3_t gyro = G_GetGamepadCalibratedGyro(forplayer); vector3_t gravnorm = G_GetGamepadGravity(forplayer); vector2_t gyroYZ = {gyro.y, gyro.z}; - FV3_Normalize(&gravnorm); + // use world yaw for yaw direction, local combined yaw for magnitude - worldYaw = FixedMul(gyroYZ.x, gravnorm.y) + FixedMul(gyroYZ.y, gravnorm.z); // dot product but just yaw and roll - // yes this is backwards intentionally - cmd->angle += ((FixedMul(min(FixedMul(abs(worldYaw), yawRelaxFactor), FV2_Magnitude(&gyroYZ)), deltaseconds)*180)/FRACUNIT) * intsign(worldYaw) * (encoremode ? -1 : 1); - cmd->aiming -= (((FixedMul(gyro.x, deltaseconds)*180)/FRACUNIT)); + FV3_Normalize(&gravnorm); + fixed_t worldYaw = FixedMul(gyro.y, gravnorm.y) + FixedMul(gyro.z, gravnorm.z); // dot product but just yaw and roll + + fixed_t yaw = intsign(worldYaw) * (encoremode ? -1 : 1) * + min(FixedMul(-abs(worldYaw), yawRelaxFactor), FV2_Magnitude(&gyroYZ)); + + cmd->angle -= FixedMul(yaw, deltaseconds*180)/FRACUNIT; + cmd->aiming -= FixedMul(gyro.x, deltaseconds*180)/FRACUNIT; } } else if (joystickvector.xaxis != 0) diff --git a/src/g_input.c b/src/g_input.c index 521977c90..4a6b93930 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -179,6 +179,7 @@ INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING] = { [gc_centerview ] = {KEY_END }, [gc_camreset ] = {KEY_HOME }, [gc_camtoggle ] = {KEY_BACKSPACE }, + [gc_freelook ] = {KEY_RCTRL }, }; // lists of GC codes for selective operation diff --git a/src/g_input.h b/src/g_input.h index 09fc49f5c..f21f3f092 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -95,6 +95,7 @@ typedef enum gc_respawn, gc_director, gc_horncode, + gc_freelook, num_gamecontrols } gamecontrols_e; diff --git a/src/p_local.h b/src/p_local.h index aaa8be214..d754bac74 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -132,14 +132,17 @@ struct camera_t fixed_t pan; // SRB2Kart: camera pitches on slopes angle_t pitch; - + // Freecam: A button was held since entering from menu, so don't move camera UINT8 button_a_held; - + boolean reset_aiming; // camera aiming needs to be reset from chase camera - + // Hold up/down to pan the camera vertically - SINT8 dpad_y_held; + SINT8 freelook_held; + // between -45 deg and 45 deg + fixed_t freelook_pitch; + fixed_t freelook_pitch_add; // Interpolation data fixed_t old_x, old_y, old_z; diff --git a/src/p_tick.c b/src/p_tick.c index ed2c17128..5cba9224b 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -650,16 +650,50 @@ void P_RunChaseCameras(void) { if (camera[i].chase) { - player_t *p = &players[displayplayers[i]]; + INT32 forplayer = displayplayers[i]; + player_t *p = &players[forplayer]; camera_t *cam = &camera[i]; - if (p->mo && p->cmd.throwdir != 0) + // client sided + if (p->mo && G_PlayerInputDown(forplayer, gc_freelook, false, DEADZONE_BUTTON)) { - if (p->speed < 6 * p->mo->scale && abs(cam->dpad_y_held) < 2*TICRATE) - cam->dpad_y_held += intsign(p->cmd.throwdir); + // instantly able to move camera + if (p->speed < 6 * p->mo->scale && abs(cam->freelook_held) < 2*TICRATE) + { + cam->freelook_held = 2*TICRATE; + } + + if (abs(cam->freelook_held) >= 2*TICRATE) + { + cam->freelook_pitch = (45*FixedDiv(p->cmd.throwdir, KART_FULLTURN)); + + // gyro aiming + if (G_GetGamepadCanUseTilt(forplayer) && cv_tiltcontrol[forplayer].value == 1) + { + fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE; + vector3_t gyro = G_GetGamepadCalibratedGyro(forplayer); + cam->freelook_pitch_add -= FixedMul(gyro.x, deltaseconds); + } + } + } + else if (p->mo && p->cmd.throwdir != 0) + { + if (p->speed < 6 * p->mo->scale && abs(cam->freelook_held) < 2*TICRATE) + { + cam->freelook_held += intsign(p->cmd.throwdir); + } + + if (abs(cam->freelook_held) >= 2*TICRATE) + { + cam->freelook_pitch = 45*FRACUNIT*intsign(p->cmd.throwdir); + } } else - cam->dpad_y_held = 0; + { + cam->freelook_held = 0; + cam->freelook_pitch = 0; + cam->freelook_pitch_add = 0; + } P_MoveChaseCamera(p, cam, false); } diff --git a/src/p_user.c b/src/p_user.c index 882f09c3a..747994780 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3098,9 +3098,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall focusaiming = player->aiming; } - if (abs(thiscam->dpad_y_held) >= 2*TICRATE) + if (abs(thiscam->freelook_held) >= 2*TICRATE) { - focusaiming += ANGLE_45 * intsign(thiscam->dpad_y_held) * P_MobjFlip(mo); + focusaiming += FixedAngle(CLAMP(thiscam->freelook_pitch + thiscam->freelook_pitch_add, -45*FRACUNIT, 45*FRACUNIT) * P_MobjFlip(mo)); } if (P_CameraThinker(player, thiscam, resetcalled)) From 41e18998090d0a2fa7720b84a31c99e4325a83c8 Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sun, 5 Apr 2026 16:38:35 -0400 Subject: [PATCH 5/6] only calibrate controllers while in controls setup calibration will need its own screen eventually --- src/g_game.c | 2 +- src/g_input.c | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 005603746..ad20b510b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1471,7 +1471,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) vector3_t gyro = G_PlayerInputSensor(forplayer, GYROSCOPE); G_UpdateGamepadAutoCalibration(forplayer, accel, gyro, - (paused || P_AutoPause() || (menustack[0] == MN_OP_CONTROLSETUP))); + (menustack[0] == MN_OP_CONTROLSETUP)); G_UpdateGamepadGyro(forplayer, gyro); G_UpdateGamepadGravity(forplayer, G_GetGamepadCalibratedGyro(forplayer), accel); diff --git a/src/g_input.c b/src/g_input.c index 4a6b93930..6a4a278fa 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -1233,7 +1233,8 @@ void Command_Setcontrol4_f(void) // accelerometer and gyro stuff // when holding the controller still (shaking and turning included), correct this quickly to resolve error -#define GyroCalibrationRollingAvgSamples (TICRATE) +#define GyroCalibrationRollingAvgSamples (TICRATE/2) +#define GyroCalibrationStart (TICRATE/2) #define GyroCalibrationTrust (1*FRACUNIT/100) boolean localgyrocalibrating[MAXSPLITSCREENPLAYERS]; @@ -1323,7 +1324,7 @@ void G_UpdateGamepadAutoCalibration(INT32 p, vector3_t accel, vector3_t gyro, bo else { // incomplete calibration - if (localgyrocalibrating[p] && localgyrocalibrationsamples[p] <= GyroCalibrationRollingAvgSamples) + if (localgyrocalibrating[p] && localgyrocalibrationsamples[p] <= (GyroCalibrationRollingAvgSamples + GyroCalibrationStart)) { FV3_Copy(&localgyrocalibrationoffset[p], &localgyrocalibrationlastoffset[p]); localgyrocalibrationsamples[p] = 0; @@ -1348,12 +1349,15 @@ void G_UpdateGamepadGyro(INT32 p, vector3_t gyro) if (localgyrocalibrating[p]) { localgyrocalibrationsamples[p]++; - FV3_Load( - &localgyrocalibrationoffset[p], - (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].x) + gyro.x)/GyroCalibrationRollingAvgSamples, - (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].y) + gyro.y)/GyroCalibrationRollingAvgSamples, - (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].z) + gyro.z)/GyroCalibrationRollingAvgSamples - ); + if (localgyrocalibrationsamples[p] > GyroCalibrationStart) + { + FV3_Load( + &localgyrocalibrationoffset[p], + (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].x) + gyro.x)/GyroCalibrationRollingAvgSamples, + (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].y) + gyro.y)/GyroCalibrationRollingAvgSamples, + (((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].z) + gyro.z)/GyroCalibrationRollingAvgSamples + ); + } } CONS_Debug(DBG_IMU, "Gyro calibration samples: %d\n", localgyrocalibrationsamples[p]); @@ -1563,4 +1567,6 @@ boolean G_GetGamepadCanUseTilt(INT32 p) #undef CorrectionMinimumSpeed #undef SmoothingHalfTime -#undef GyroCalibrationTrust \ No newline at end of file +#undef GyroCalibrationTrust +#undef GyroCalibrationStart +#undef GyroCalibrationRollingAvgSamples \ No newline at end of file From 84cfa132c6e084a2b56117604ff86c1958e4e99a Mon Sep 17 00:00:00 2001 From: minenice55 Date: Sun, 5 Apr 2026 16:46:25 -0400 Subject: [PATCH 6/6] Update d_main.cpp --- src/d_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/d_main.cpp b/src/d_main.cpp index dd5f633ba..87055d389 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -102,7 +102,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x9260d8dd984ea7c4 +#define ASSET_HASH_MAIN_PK3 0x5bb203462b8eb19b #define ASSET_HASH_MAPPATCH_PK3 0x1745690024efbaf8 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE