Add device and gamepad haptics to web port (#613)

* Add device and gamepad haptics to web port

* Update skip.yml
This commit is contained in:
Christian Semmler 2025-07-15 16:50:14 -07:00 committed by GitHub
parent d0dc595fc5
commit deca5e5a2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 125 additions and 17 deletions

View File

@ -555,6 +555,7 @@ if (ISLE_BUILD_APP)
ISLE/emscripten/config.cpp
ISLE/emscripten/events.cpp
ISLE/emscripten/filesystem.cpp
ISLE/emscripten/haptic.cpp
ISLE/emscripten/messagebox.cpp
ISLE/emscripten/window.cpp
)

View File

@ -0,0 +1,52 @@
#include "haptic.h"
#include "compat.h"
#include "lego/sources/misc/legoutil.h"
#include "legoinputmanager.h"
#include "misc.h"
#include <emscripten/html5.h>
void Emscripten_HandleRumbleEvent(float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds)
{
std::visit(
overloaded{
[](LegoInputManager::SDL_KeyboardID_v p_id) {},
[](LegoInputManager::SDL_MouseID_v p_id) {},
[p_lowFrequencyRumble, p_highFrequencyRumble, p_milliseconds](LegoInputManager::SDL_JoystickID_v p_id) {
const char* name = SDL_GetJoystickNameForID((SDL_JoystickID) p_id);
if (name) {
MAIN_THREAD_EM_ASM(
{
const name = UTF8ToString($0);
const gamepads = navigator.getGamepads();
for (const gamepad of gamepads) {
if (gamepad && gamepad.connected && gamepad.id == name && gamepad.vibrationActuator) {
gamepad.vibrationActuator.playEffect("dual-rumble", {
startDelay : 0,
weakMagnitude : $1,
strongMagnitude : $2,
duration : $3,
});
break;
}
}
},
name,
SDL_clamp(p_lowFrequencyRumble, 0, 1),
SDL_clamp(p_highFrequencyRumble, 0, 1),
p_milliseconds
);
}
},
[](LegoInputManager::SDL_TouchID_v p_id) {
MAIN_THREAD_EM_ASM({
if (navigator.vibrate) {
navigator.vibrate(700);
}
});
}
},
InputManager()->GetLastInputMethod()
);
}

8
ISLE/emscripten/haptic.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef EMSCRIPTEN_HAPTIC_H
#define EMSCRIPTEN_HAPTIC_H
#include "mxtypes.h"
void Emscripten_HandleRumbleEvent(float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds);
#endif // EMSCRIPTEN_HAPTIC_H

View File

@ -54,6 +54,7 @@
#include "emscripten/config.h"
#include "emscripten/events.h"
#include "emscripten/filesystem.h"
#include "emscripten/haptic.h"
#include "emscripten/messagebox.h"
#include "emscripten/window.h"
#endif
@ -501,6 +502,16 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
}
break;
}
case SDL_EVENT_KEYBOARD_ADDED:
if (InputManager()) {
InputManager()->AddKeyboard(event->kdevice.which);
}
break;
case SDL_EVENT_KEYBOARD_REMOVED:
if (InputManager()) {
InputManager()->RemoveKeyboard(event->kdevice.which);
}
break;
case SDL_EVENT_MOUSE_ADDED:
if (InputManager()) {
InputManager()->AddMouse(event->mdevice.which);
@ -789,8 +800,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
}
}
else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) {
if (InputManager()) {
InputManager()->HandleRumbleEvent();
if (!InputManager()->HandleRumbleEvent(0.5f, 0.5f, 0.5f, 700)) {
// Platform-specific handling
#ifdef __EMSCRIPTEN__
Emscripten_HandleRumbleEvent(0.5f, 0.5f, 700);
#endif
}
}

View File

@ -154,24 +154,29 @@ public:
void GetKeyboardState();
MxResult GetNavigationKeyStates(MxU32& p_keyFlags);
MxResult GetNavigationTouchStates(MxU32& p_keyFlags);
LEGO1_EXPORT void AddKeyboard(SDL_KeyboardID p_keyboardID);
LEGO1_EXPORT void RemoveKeyboard(SDL_KeyboardID p_keyboardID);
LEGO1_EXPORT void AddMouse(SDL_MouseID p_mouseID);
LEGO1_EXPORT void RemoveMouse(SDL_MouseID p_mouseID);
LEGO1_EXPORT void AddJoystick(SDL_JoystickID p_joystickID);
LEGO1_EXPORT void RemoveJoystick(SDL_JoystickID p_joystickID);
LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme);
LEGO1_EXPORT MxBool HandleRumbleEvent();
LEGO1_EXPORT MxBool
HandleRumbleEvent(float p_strength, float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds);
LEGO1_EXPORT void UpdateLastInputMethod(SDL_Event* p_event);
const auto& GetLastInputMethod() { return m_lastInputMethod; }
// SYNTHETIC: LEGO1 0x1005b8d0
// LegoInputManager::`scalar deleting destructor'
private:
// clang-format off
enum class SDL_KeyboardID_v : SDL_KeyboardID {};
enum class SDL_MouseID_v : SDL_MouseID {};
enum class SDL_JoystickID_v : SDL_JoystickID {};
enum class SDL_TouchID_v : SDL_TouchID {};
// clang-format on
// SYNTHETIC: LEGO1 0x1005b8d0
// LegoInputManager::`scalar deleting destructor'
private:
void InitializeHaptics();
MxCriticalSection m_criticalSection; // 0x58
@ -197,10 +202,11 @@ private:
SDL_Point m_touchVirtualThumb = {0, 0};
SDL_FPoint m_touchVirtualThumbOrigin;
std::map<SDL_FingerID, MxU32> m_touchFlags;
std::map<SDL_KeyboardID, std::pair<void*, void*>> m_keyboards;
std::map<SDL_MouseID, std::pair<void*, SDL_Haptic*>> m_mice;
std::map<SDL_JoystickID, std::pair<SDL_Gamepad*, SDL_Haptic*>> m_joysticks;
std::map<SDL_HapticID, SDL_Haptic*> m_otherHaptics;
std::variant<SDL_MouseID_v, SDL_JoystickID_v, SDL_TouchID_v> m_lastInputMethod;
std::variant<SDL_KeyboardID_v, SDL_MouseID_v, SDL_JoystickID_v, SDL_TouchID_v> m_lastInputMethod;
};
// TEMPLATE: LEGO1 0x10028850

View File

@ -160,7 +160,8 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags)
// FUNCTION: LEGO1 0x1005c320
MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, MxU32* p_povPosition)
{
if (m_joysticks.empty() && m_touchScheme != e_gamepad) {
if (!std::holds_alternative<SDL_JoystickID_v>(m_lastInputMethod) &&
!(std::holds_alternative<SDL_TouchID_v>(m_lastInputMethod) && m_touchScheme == e_gamepad)) {
return FAILURE;
}
@ -513,6 +514,24 @@ MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates)
return SUCCESS;
}
void LegoInputManager::AddKeyboard(SDL_KeyboardID p_keyboardID)
{
if (m_keyboards.count(p_keyboardID)) {
return;
}
m_keyboards[p_keyboardID] = {nullptr, nullptr};
}
void LegoInputManager::RemoveKeyboard(SDL_KeyboardID p_keyboardID)
{
if (!m_keyboards.count(p_keyboardID)) {
return;
}
m_keyboards.erase(p_keyboardID);
}
void LegoInputManager::AddMouse(SDL_MouseID p_mouseID)
{
if (m_mice.count(p_mouseID)) {
@ -656,7 +675,12 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc
return TRUE;
}
MxBool LegoInputManager::HandleRumbleEvent()
MxBool LegoInputManager::HandleRumbleEvent(
float p_strength,
float p_lowFrequencyRumble,
float p_highFrequencyRumble,
MxU32 p_milliseconds
)
{
static bool g_hapticsInitialized = false;
@ -668,6 +692,7 @@ MxBool LegoInputManager::HandleRumbleEvent()
SDL_Haptic* haptic = nullptr;
std::visit(
overloaded{
[](SDL_KeyboardID_v p_id) {},
[&haptic, this](SDL_MouseID_v p_id) {
if (m_mice.count((SDL_MouseID) p_id)) {
haptic = m_mice[(SDL_MouseID) p_id].second;
@ -688,11 +713,8 @@ MxBool LegoInputManager::HandleRumbleEvent()
m_lastInputMethod
);
const float strength = 0.5f;
const Uint32 durationMs = 700;
if (haptic) {
return SDL_PlayHapticRumble(haptic, strength, durationMs);
return SDL_PlayHapticRumble(haptic, p_strength, p_milliseconds);
}
// A joystick isn't necessarily a haptic device; try basic rumble instead
@ -700,9 +722,9 @@ MxBool LegoInputManager::HandleRumbleEvent()
if (m_joysticks.count((SDL_JoystickID) *joystick)) {
return SDL_RumbleGamepad(
m_joysticks[(SDL_JoystickID) *joystick].first,
strength * 65535,
strength * 65535,
durationMs
SDL_clamp(p_lowFrequencyRumble, 0, 1) * USHRT_MAX,
SDL_clamp(p_highFrequencyRumble, 0, 1) * USHRT_MAX,
p_milliseconds
);
}
}
@ -752,6 +774,10 @@ void LegoInputManager::InitializeHaptics()
void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event)
{
switch (p_event->type) {
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
m_lastInputMethod = SDL_KeyboardID_v{p_event->key.which};
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
m_lastInputMethod = SDL_MouseID_v{p_event->button.which};

View File

@ -74,6 +74,7 @@ cksize: "Re-defined Windows name"
fccType: "Re-defined Windows name"
dwDataOffset: "Re-defined Windows name"
fccType: "Re-defined Windows name"
SDL_KeyboardID_v: "SDL-based name"
SDL_MouseID_v: "SDL-based name"
SDL_JoystickID_v: "SDL-based name"
SDL_TouchID_v: "SDL-based name"