Merge pull request 'Clean up Controller Sensors code, Gyro calibration, Gyro cameras' (#234) from gyrostuff into next

Reviewed-on: https://codeberg.org/NepDisk/blankart/pulls/234
This commit is contained in:
minenice55 2026-04-05 22:50:22 +02:00
commit 782f3b6184
13 changed files with 478 additions and 298 deletions

View file

@ -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);

View file

@ -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

View file

@ -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?

View file

@ -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

View file

@ -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);
}
@ -1978,10 +1768,13 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
else if (cmd->turning < -KART_FULLTURN)
cmd->turning = -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);

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -139,7 +139,10 @@ struct camera_t
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;

View file

@ -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);
}

View file

@ -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))

View file

@ -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!!"));
@ -492,13 +487,15 @@ static void ST_drawImuDebug(INT32 *height)
accel = G_PlayerInputSensor(pnum, ACCELEROMETER);
gyro = G_PlayerInputSensor(pnum, GYROSCOPE);
gyrocalibrated = G_GetGamepadCalibratedGyro(pnum);
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. 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)));
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)));
}
static void ST_drawRenderDebug(INT32 *height)