Added haptics engine

This commit is contained in:
Marcus Minhorst 2025-08-23 09:24:01 -04:00 committed by Rachael Alexanderson
commit f573112e2a
10 changed files with 574 additions and 33 deletions

View file

@ -60,6 +60,7 @@ if( WIN32 )
wsock32
winmm
dinput8
Xinput
ole32
user32
gdi32
@ -1094,6 +1095,7 @@ set (PCH_SOURCES
common/engine/renderstyle.cpp
common/engine/v_colortables.cpp
common/engine/serializer.cpp
common/engine/m_haptics.cpp
common/engine/m_joy.cpp
common/engine/m_random.cpp
common/objects/autosegs.cpp

View file

@ -0,0 +1,381 @@
/*
** m_haptics.cpp
**
** Haptic feedback implementation
**
**---------------------------------------------------------------------------
**
** Copyright 2025 Marcus Minhorst
** Copyright 2025 ZDoom + GZDoom teams, and contributors
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see http://www.gnu.org/licenses/
**
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include <math.h>
#include "c_cvars.h"
#include "doomstat.h"
#include "m_haptics.h"
#include "name.h"
#include "printf.h"
#include "s_soundinternal.h"
#include "tarray.h"
#include "zstring.h"
// MACROS ------------------------------------------------------------------
#ifndef MAX_TRY_DEPTH
#define MAX_TRY_DEPTH 8
#endif
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern bool AppActive;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
struct {
int tic = gametic; // last tic processed
bool dirty = false; // do we need to do something next tick ?
bool enabled = true; // do we need to do anything ever ?
bool active = true; // is the game currently not paused ?
struct Haptics current = {0,0,0,0,0}; // current state of the controller
TMap<FName, struct Haptics> channels; // active rumbles (that will be mixed)
} Haptics;
TMap<FName, struct Haptics> RumbleDefinition = {};
TMap<FName, FName> RumbleMapping = {};
TMap<FName, FName> RumbleAlias = {};
// fallback names. these exist in base sndinfo
const FName HapticIntense = "INTENSE",
HapticHeavy = "HEAVY",
HapticMedium = "MEDIUM",
HapticLight = "LIGHT",
HapticSubtle = "SUBTLE";
// PUBLIC DATA DEFINITIONS -------------------------------------------------
CVARD(Bool, haptics_debug, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "print diagnostics for haptic feedback");
// CODE --------------------------------------------------------------------
//==========================================================================
//
// Joy_GetMapping
//
// Takes a sound name, and find its corresponding rumble identifier.
//
//==========================================================================
const FName * Joy_GetMapping(const FName identifier)
{
const FName * mapping = RumbleMapping.CheckKey(identifier);
if (!mapping && identifier != "")
{
Printf(DMSG_WARNING, "Unknown rumble mapping '%s'\n", identifier.GetChars());
}
return mapping;
}
//==========================================================================
//
// Joy_GetRumble
//
// Takes a rumble identifier, and returns its haptics definition.
//
//==========================================================================
const struct Haptics * Joy_GetRumble(FName identifier)
{
struct Haptics * rumble = RumbleDefinition.CheckKey(identifier);
if (!rumble) {
Printf(DMSG_ERROR, TEXTCOLOR_RED "Rumble mapping not found! '%s'\n", identifier.GetChars());
return nullptr;
}
return rumble;
}
//==========================================================================
//
// Joy_AddRumbleType
//
// Ties a rumble identifier to a haptics definition.
//
//==========================================================================
void Joy_AddRumbleType(const FName identifier, const struct Haptics data)
{
RumbleDefinition.Insert(identifier, data);
}
//==========================================================================
//
// Joy_AddRumbleAlias
//
// Adds an alternative rumble identifier for a haptics definition.
// Joy_ReadyRumbleMapping must be called after adding any aliases.
//
//==========================================================================
void Joy_AddRumbleAlias(const FName alias, const FName actual)
{
RumbleAlias.Insert(alias, actual);
}
//==========================================================================
//
// Joy_MapRumbleType
//
// Ties a sound to a rumble identifier.
//
//==========================================================================
void Joy_MapRumbleType(const FName sound, const FName identifier)
{
RumbleMapping.Insert(sound, identifier);
}
//==========================================================================
//
// Joy_ResetRumbleMapping
//
// Resets entire rumble state
//
//==========================================================================
void Joy_ResetRumbleMapping()
{
RumbleMapping.Clear();
RumbleAlias.Clear();
RumbleDefinition.Clear();
Haptics.channels.Clear();
Haptics.current.high_frequency = Haptics.current.low_frequency =
Haptics.current.left_trigger = Haptics.current.right_trigger =
Haptics.current.ticks = 0;
I_Rumble(0, 0, 0, 0);
}
//==========================================================================
//
// Joy_ReadyRumbleMapping
//
// Resets entire rumble state
//
//==========================================================================
void Joy_ReadyRumbleMapping()
{
TArray<FName> found;
TMapIterator<FName, FName> it(RumbleAlias);
TMap<FName, FName>::Pair* pair;
while (RumbleAlias.CountUsed())
{
while (it.NextPair(pair))
{
auto predefined = RumbleDefinition.CheckKey(pair->Key);
if (predefined)
{
Printf(DMSG_ERROR, TEXTCOLOR_RED "Rumble alias trying to redefine mapping! '%s'\n", pair->Key.GetChars());
continue;
}
auto mapping = RumbleDefinition.CheckKey(pair->Value);
if (!mapping) continue;
found.AddUnique(pair->Key);
RumbleDefinition.Insert(pair->Key, *mapping);
}
it.Reset();
if (found.Size() == 0)
{
FString list = "[";
while (it.NextPair(pair))
list.AppendFormat(" '%s'->'%s'", pair->Key.GetChars(), pair->Value.GetChars());
Printf(DMSG_ERROR, TEXTCOLOR_RED "Circular rumble alias found! (%d) %s ]\n", RumbleAlias.CountUsed(), list.GetChars());
break;
}
while (found.size() > 0) {
RumbleAlias.Remove(found.Last());
found.Pop();
}
}
}
//==========================================================================
//
// Joy_RumbleTick
//
// Called once per gametic. Manages rumble state.
//
//==========================================================================
void Joy_RumbleTick() {
if (!Haptics.enabled) return;
// pause detection
if (AppActive != Haptics.active)
{
Haptics.active = AppActive;
if (Haptics.active && Haptics.current.ticks != 0)
{
I_Rumble(
Haptics.current.high_frequency,
Haptics.current.low_frequency,
Haptics.current.left_trigger,
Haptics.current.right_trigger
);
}
else
{
I_Rumble(0, 0, 0, 0);
}
}
if (Haptics.tic >= gametic) return;
Haptics.tic = gametic;
// new value OR time elapsed
Haptics.dirty |= Haptics.current.ticks != 0 && Haptics.current.ticks < Haptics.tic;
if (!Haptics.dirty) return;
// init
Haptics.current.ticks = 0;
Haptics.current.high_frequency = 0;
Haptics.current.low_frequency = 0;
Haptics.current.left_trigger = 0;
Haptics.current.right_trigger = 0;
TMapIterator<FName, struct Haptics> it(Haptics.channels);
TMap<FName, struct Haptics>::Pair* pair;
TArray<FName> stale;
// remove old rumble data
while (it.NextPair(pair))
{
if (pair->Value.ticks < Haptics.tic)
{
stale.Push(pair->Key);
}
}
for (auto key: stale)
{
Haptics.channels.Remove(key);
}
it.Reset();
while (it.NextPair(pair))
{
// grab upcoming event time
Haptics.current.ticks = Haptics.current.ticks == 0
? pair->Value.ticks
: std::min(Haptics.current.ticks, pair->Value.ticks);
// simple intensity mixing
Haptics.current.high_frequency += pair->Value.high_frequency;
Haptics.current.low_frequency += pair->Value.low_frequency;
Haptics.current.left_trigger += pair->Value.left_trigger;
Haptics.current.right_trigger += pair->Value.right_trigger;
}
// should this be clamped to [0,1]? Maybe a controller api will support "hdr" haptics lol
// Haptics.current.high_frequency = std::min(std::max(0.0, Haptics.current.high_frequency), 1.0);
// Haptics.current.low_frequency = std::min(std::max(0.0, Haptics.current.low_frequency), 1.0);
// Haptics.current.left_trigger = std::min(std::max(0.0, Haptics.current.left_trigger), 1.0);
// Haptics.current.right_trigger = std::min(std::max(0.0, Haptics.current.right_trigger), 1.0);
I_Rumble(
Haptics.current.high_frequency,
Haptics.current.low_frequency,
Haptics.current.left_trigger,
Haptics.current.right_trigger
);
Haptics.dirty = false;
}
//==========================================================================
//
// Joy_Rumble
//
// Requests haptic feedback. Attenuation of >=1 results in no-op.
//
//==========================================================================
void Joy_Rumble(const FName source, const struct Haptics data, double attenuation)
{
if (!Haptics.enabled) return;
if (data.ticks <= 0) return;
if (attenuation >= 1) return;
float strength = 1 - (attenuation < 0? 0: attenuation);
if (haptics_debug)
Printf("r %s * %g\n", source.GetChars(), strength);
// this will overwrite stuff from same source mapping (weapons/pistol not W_BULLET)
Haptics.channels.Insert(source, {
Haptics.tic + data.ticks + 1,
data.high_frequency * strength,
data.low_frequency * strength,
data.left_trigger * strength,
data.right_trigger * strength,
});
Haptics.dirty = true;
}
//==========================================================================
//
// Joy_Rumble
//
// Requests haptic feedback. Attenuation of >=1 results in no-op.
//
//==========================================================================
void Joy_Rumble(const FName identifier, double attenuation)
{
const FName * mapping = Joy_GetMapping(identifier);
if (mapping == nullptr) return;
const struct Haptics * rumble = Joy_GetRumble(*mapping);
if (rumble == nullptr) return;
Joy_Rumble(identifier, * rumble, attenuation);
}

View file

@ -0,0 +1,34 @@
#ifndef M_HAPTICS_H
#define M_HAPTICS_H
#include "name.h"
enum EHapticCompat {
HAPTCOMPAT_WARN,
HAPTCOMPAT_NONE,
HAPTCOMPAT_MATCH,
HAPTCOMPAT_ALL,
NUM_HAPTCOMPAT
};
struct Haptics {
int ticks;
double high_frequency;
double low_frequency;
double left_trigger;
double right_trigger;
};
void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig);
void Joy_AddRumbleType(const FName idenifier, const struct Haptics data);
void Joy_AddRumbleAlias(const FName alias, const FName actual);
void Joy_MapRumbleType(const FName sound, const FName idenifier);
void Joy_ResetRumbleMapping();
void Joy_ReadyRumbleMapping();
void Joy_RumbleTick();
void Joy_Rumble(const FName source, const struct Haptics data, double attenuation = 0);
void Joy_Rumble(const FName identifier, double attenuation = 0);
#endif

View file

@ -36,7 +36,6 @@
// HEADER FILES ------------------------------------------------------------
#include "m_joy.h"
#include <math.h>
#include "c_cvars.h"
@ -45,6 +44,7 @@
#include "configfile.h"
#include "d_eventbase.h"
#include "i_interface.h"
#include "m_joy.h"
#include "name.h"
#include "printf.h"
#include "vectors.h"
@ -70,6 +70,8 @@ extern const float JOYDEADZONE_DEFAULT = 0.1; // reduced from 0.25
extern const float JOYSENSITIVITY_DEFAULT = 1.0;
extern const float JOYHAPSTRENGTH_DEFAULT = 1.0;
extern const float JOYTHRESH_DEFAULT = 0.05;
extern const float JOYTHRESH_TRIGGER = 0.05;
extern const float JOYTHRESH_STICK_X = 0.65;

View file

@ -29,6 +29,8 @@ extern const float JOYDEADZONE_DEFAULT;
extern const float JOYSENSITIVITY_DEFAULT;
extern const float JOYHAPSTRENGTH_DEFAULT;
extern const float JOYTHRESH_DEFAULT;
extern const float JOYTHRESH_TRIGGER;

View file

@ -1,35 +1,36 @@
/*
** i_joystick.cpp
**
**---------------------------------------------------------------------------
** Copyright 2012-2015 Alexey Lysiuk
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
** i_joystick.cpp
**
**---------------------------------------------------------------------------
** Copyright 2012-2015 Alexey Lysiuk
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOMessage.h>
@ -46,6 +47,7 @@
EXTERN_CVAR(Bool, joy_axespolling)
EXTERN_CVAR(Bool, use_joystick)
namespace
@ -1379,3 +1381,10 @@ CUSTOM_CVAR(Bool, joy_axespolling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR
s_joystickManager->UseAxesPolling(self);
}
}
void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig)
{
if (!use_joystick) return;
// stub
}

View file

@ -46,6 +46,7 @@
#include "i_input.h"
#include "i_interface.h"
#include "keydef.h"
#include "m_haptics.h"
#include "m_joy.h"
#include "utf8.h"
#include "v_video.h"
@ -609,6 +610,7 @@ void I_StartTic ()
I_CheckGUICapture ();
I_CheckNativeMouse ();
I_GetEvent ();
Joy_RumbleTick();
}
void I_ProcessJoysticks ();

View file

@ -55,6 +55,11 @@ static const EAxisCodes ControllerAxisCodes[][2] =
{ AXIS_CODE_PAD_RTRIGGER, AXIS_CODE_NULL }
};
#define HAPTICS 0b0001
#define HAPTICS_TRIGGERS 0b0010
EXTERN_CVAR(Bool, use_joystick)
class SDLInputJoystick: public IJoystickConfig
{
public:
@ -63,6 +68,8 @@ public:
InstanceID(SDL_JoystickGetDeviceInstanceID(DeviceIndex)),
Multiplier(JOYSENSITIVITY_DEFAULT),
Enabled(true),
Haptics(0),
HapticsStrength(JOYHAPSTRENGTH_DEFAULT),
SettingsChanged(false)
{
if (SDL_IsGameController(DeviceIndex))
@ -77,6 +84,7 @@ public:
{
NumAxes = SDL_CONTROLLER_AXIS_MAX;
NumHats = 0;
Haptics = SDL_GameControllerHasRumble(Mapping) | SDL_GameControllerHasRumbleTriggers(Mapping) << 1;
SetDefaultConfig();
}
@ -224,6 +232,29 @@ public:
return Axes[axis].ResponseCurvePreset == DefaultAxes[axis].ResponseCurvePreset;
}
void Rumble(float high_freq, float low_freq, float left_trig, float right_trig)
{
uint16_t duration_ms = -1; // turn on for max time (we'll turn it off later ourselves)
if (Haptics & HAPTICS)
{
SDL_GameControllerRumble(
Mapping,
static_cast<uint16_t> (0xffff * clamp(high_freq*HapticsStrength, 0.f, 1.f)),
static_cast<uint16_t> (0xffff * clamp(low_freq*HapticsStrength, 0.f, 1.f)),
duration_ms);
}
if (Haptics & HAPTICS_TRIGGERS)
{
SDL_GameControllerRumbleTriggers(
Mapping,
static_cast<uint16_t> (0xffff * clamp(left_trig*HapticsStrength, 0.f, 1.f)),
static_cast<uint16_t> (0xffff * clamp(right_trig*HapticsStrength, 0.f, 1.f)),
duration_ms);
}
}
void SetDefaultConfig()
{
if (Axes.size() == 0)
@ -528,6 +559,8 @@ protected:
TArray<AxisInfo> Axes;
int NumAxes;
int NumHats;
int Haptics;
float HapticsStrength;
bool SettingsChanged;
friend class SDLInputJoystickManager;
@ -588,6 +621,16 @@ public:
}
}
void Rumble(float high_freq, float low_freq, float left_trig, float right_trig)
{
for(unsigned int i = 0;i < Joysticks.Size();i++)
{
if (Joysticks[i]->Enabled) {
Joysticks[i]->Rumble(high_freq, low_freq, left_trig, right_trig);
}
}
}
void ProcessInput() const
{
for(unsigned int i = 0;i < Joysticks.Size();++i)
@ -615,7 +658,7 @@ static SDLInputJoystickManager *JoystickManager;
void I_StartupJoysticks()
{
#ifndef NO_SDL_JOYSTICK
if(SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) >= 0)
if(SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) >= 0)
JoystickManager = new SDLInputJoystickManager();
#endif
}
@ -649,6 +692,13 @@ void I_GetAxes(float axes[NUM_AXIS_CODES])
}
}
void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig)
{
if (!use_joystick) return;
JoystickManager->Rumble(high_freq, low_freq, left_trig, right_trig);
}
void I_ProcessJoysticks()
{
if (use_joystick && JoystickManager)

View file

@ -75,6 +75,7 @@
#include "c_buttons.h"
#include "cmdlib.h"
#include "i_mainwindow.h"
#include "m_haptics.h"
// Compensate for w32api's lack
#ifndef GET_XBUTTON_WPARAM
@ -612,6 +613,7 @@ void I_StartTic ()
EventHandlerResultForNativeMouse = sysCallbacks.WantNativeMouse && sysCallbacks.WantNativeMouse();
I_CheckNativeMouse (false, EventHandlerResultForNativeMouse);
I_GetEvent ();
Joy_RumbleTick();
}
//

View file

@ -1,8 +1,11 @@
/*
** i_xinput.cpp
**
** Handles direct input gamepads
**
**---------------------------------------------------------------------------
** Copyright 2005-2016 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -33,6 +36,7 @@
// HEADER FILES ------------------------------------------------------------
#include "m_joy.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <xinput.h>
@ -67,6 +71,8 @@
extern bool AppActive;
EXTERN_CVAR(Bool, use_joystick)
// TYPES -------------------------------------------------------------------
typedef DWORD (WINAPI *XInputGetStateType)(DWORD index, XINPUT_STATE *state);
@ -112,6 +118,8 @@ public:
bool GetEnabled();
void SetEnabled(bool enabled);
void Rumble(float low_freq, float high_freq);
bool AllowsEnabledInBackground() { return true; }
bool GetEnabledInBackground() { return EnabledInBackground; }
void SetEnabledInBackground(bool enabled) { EnabledInBackground = enabled; }
@ -137,6 +145,7 @@ protected:
float DigitalThreshold;
EJoyCurve ResponseCurvePreset;
};
XINPUT_VIBRATION Vibration;
enum
{
AXIS_ThumbLX,
@ -157,6 +166,8 @@ protected:
bool Connected;
bool Enabled;
bool EnabledInBackground;
bool Haptics;
float HapticStrength;
void Attached();
void Detached();
@ -178,6 +189,8 @@ public:
void GetDevices(TArray<IJoystickConfig *> &sticks);
IJoystickConfig *Rescan();
void Rumble(float low_freq, float high_freq);
protected:
HMODULE XInputDLL;
FXInputController *Devices[XUSER_MAX_COUNT];
@ -405,9 +418,27 @@ void FXInputController::Attached()
Axes[i].Value = 0;
Axes[i].ButtonValue = 0;
}
XINPUT_CAPABILITIES capabilities;
if (XInputGetCapabilities(Index, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS)
{
Haptics = capabilities.Vibration.wLeftMotorSpeed != 0 || capabilities.Vibration.wRightMotorSpeed != 0;
}
UpdateJoystickMenu(this);
}
//==========================================================================
//
// FXInputController :: Rumble
//
//==========================================================================
void FXInputController::Rumble(float low_freq, float high_freq)
{
Vibration.wLeftMotorSpeed = static_cast<unsigned short>(USHRT_MAX*clamp(high_freq*HapticStrength, 0.f, 1.f));
Vibration.wRightMotorSpeed = static_cast<unsigned short>(USHRT_MAX*clamp( low_freq*HapticStrength, 0.f, 1.f));
XInputSetState(Index, &Vibration);
}
//==========================================================================
//
// FXInputController :: Detached
@ -476,6 +507,7 @@ void FXInputController::AddAxes(float axes[NUM_AXIS_CODES])
void FXInputController::SetDefaultConfig()
{
Multiplier = JOYSENSITIVITY_DEFAULT;
HapticStrength = JOYHAPSTRENGTH_DEFAULT;
for (int i = 0; i < NUM_AXES; ++i)
{
Axes[i].DeadZone = DefaultAxes[i].DeadZone;
@ -944,6 +976,22 @@ bool FXInputManager::WndProcHook(HWND hWnd, uint32_t message, WPARAM wParam, LPA
return false;
}
//===========================================================================
//
// FXInputManager :: Rumble
//
//===========================================================================
void FXInputManager::Rumble(float low_freq, float high_freq) {
for (int i = 0; i < XUSER_MAX_COUNT; ++i)
{
if (Devices[i] && Devices[i]->IsConnected() && Devices[i]->GetEnabled())
{
Devices[i]->Rumble(low_freq, high_freq);
}
}
}
//===========================================================================
//
// FXInputManager :: Rescan
@ -989,3 +1037,12 @@ void I_StartupXInput()
}
}
void I_Rumble(double high_freq, double low_freq, double _left_trig, double _right_trig) {
if (!use_joystick) return;
FXInputManager* XInputManager = & static_cast<FXInputManager&> (*JoyDevices[INPUT_XInput]);
if (XInputManager != NULL)
{
XInputManager->Rumble(high_freq, low_freq);
}
}