Added haptics engine
This commit is contained in:
parent
75ef08c755
commit
f573112e2a
10 changed files with 574 additions and 33 deletions
|
|
@ -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
|
||||
|
|
|
|||
381
src/common/engine/m_haptics.cpp
Normal file
381
src/common/engine/m_haptics.cpp
Normal 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);
|
||||
}
|
||||
34
src/common/engine/m_haptics.h
Normal file
34
src/common/engine/m_haptics.h
Normal 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue