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_main.cpp b/src/d_main.cpp index f9032717a..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 0xa83b5f0cfc1ffd7b +#define ASSET_HASH_MAIN_PK3 0x5bb203462b8eb19b #define ASSET_HASH_MAPPATCH_PK3 0x1745690024efbaf8 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE 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/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 9905bc76f..6bc4fd964 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, + (menustack[0] == MN_OP_CONTROLSETUP)); + + G_UpdateGamepadGyro(forplayer, gyro); + G_UpdateGamepadGravity(forplayer, G_GetGamepadCalibratedGyro(forplayer), accel); } // why build a ticcmd if we're paused? @@ -1782,10 +1557,26 @@ 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 - // if (spectating) - // { - // } + //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 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}; + + // use world yaw for yaw direction, local combined yaw for magnitude + 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) { @@ -1798,7 +1589,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); } @@ -1977,11 +1767,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; @@ -2001,8 +1794,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..6a4a278fa 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 @@ -177,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 @@ -1226,3 +1229,344 @@ 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 GyroCalibrationRollingAvgSamples (TICRATE/2) +#define GyroCalibrationStart (TICRATE/2) +#define GyroCalibrationTrust (1*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 +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) +{ + 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 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]) + { + localgyrocalibrationsamples[p] = 0; + FV3_Copy(&localgyrocalibrationlastoffset[p], &localgyrocalibrationoffset[p]); + FV3_Load( + &localgyrocalibrationoffset[p], + 0, 0, 0 + ); + localgyrocalibrating[p] = true; + } + } + else + { + // incomplete calibration + if (localgyrocalibrating[p] && localgyrocalibrationsamples[p] <= (GyroCalibrationRollingAvgSamples + GyroCalibrationStart)) + { + FV3_Copy(&localgyrocalibrationoffset[p], &localgyrocalibrationlastoffset[p]); + localgyrocalibrationsamples[p] = 0; + } + 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]) + { + localgyrocalibrationsamples[p]++; + 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]); + + 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.25) +// thresholds of trust for accel shakiness. less shakiness = more trust +#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 (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 (5*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 = FixedMul(FV3_Magnitude(&gyro), M_PI_FIXED)/180; + fixed_t correctionLimit; + vector4_t invRotation = {0}; + 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)); + + 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_Magnitude(&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); + 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) + { + 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 + 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)); + + if (correctionRate > correctionLimit) { + fixed_t closeEnoughFactor; + if (CorrectionGyroMaxThreshold > CorrectionGyroMinThreshold) + { + closeEnoughFactor = CLAMP(FixedDiv((FV3_Magnitude(&gravityDelta) - CorrectionGyroMinThreshold), (CorrectionGyroMaxThreshold - CorrectionGyroMinThreshold)), 0, FRACUNIT); + } + else if (FV3_Magnitude(&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); + } + + correctionRate = max(correctionRate, CorrectionMinimumSpeed); + + CONS_Debug(DBG_IMU, "Correction Rate: %4.2f\n", FixedToFloat(correctionRate)); + + FV3_Load(&correction, + 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))) + { + FV3_Add(&localgravityvectors[p], &correction); + } + else + { + FV3_Add(&localgravityvectors[p], &gravityDelta); + } +} + +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 SmoothingHalfTime + +#undef GyroCalibrationTrust +#undef GyroCalibrationStart +#undef GyroCalibrationRollingAvgSamples \ No newline at end of file diff --git a/src/g_input.h b/src/g_input.h index 02bd76b7e..f21f3f092 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -95,9 +95,23 @@ typedef enum gc_respawn, gc_director, gc_horncode, + gc_freelook, 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 +192,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/p_local.h b/src/p_local.h index 5d8e255f5..ab53d5f6c 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 51d2279d1..37a9fa9ff 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) @@ -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)) diff --git a/src/st_stuff.c b/src/st_stuff.c index 84e74def2..67ef85a01 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. 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)