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/config.cpp
ISLE/emscripten/events.cpp ISLE/emscripten/events.cpp
ISLE/emscripten/filesystem.cpp ISLE/emscripten/filesystem.cpp
ISLE/emscripten/haptic.cpp
ISLE/emscripten/messagebox.cpp ISLE/emscripten/messagebox.cpp
ISLE/emscripten/window.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/config.h"
#include "emscripten/events.h" #include "emscripten/events.h"
#include "emscripten/filesystem.h" #include "emscripten/filesystem.h"
#include "emscripten/haptic.h"
#include "emscripten/messagebox.h" #include "emscripten/messagebox.h"
#include "emscripten/window.h" #include "emscripten/window.h"
#endif #endif
@ -501,6 +502,16 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
} }
break; 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: case SDL_EVENT_MOUSE_ADDED:
if (InputManager()) { if (InputManager()) {
InputManager()->AddMouse(event->mdevice.which); 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()) { else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) {
if (InputManager()) { if (!InputManager()->HandleRumbleEvent(0.5f, 0.5f, 0.5f, 700)) {
InputManager()->HandleRumbleEvent(); // Platform-specific handling
#ifdef __EMSCRIPTEN__
Emscripten_HandleRumbleEvent(0.5f, 0.5f, 700);
#endif
} }
} }

View File

@ -154,24 +154,29 @@ class LegoInputManager : public MxPresenter {
void GetKeyboardState(); void GetKeyboardState();
MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationKeyStates(MxU32& p_keyFlags);
MxResult GetNavigationTouchStates(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 AddMouse(SDL_MouseID p_mouseID);
LEGO1_EXPORT void RemoveMouse(SDL_MouseID p_mouseID); LEGO1_EXPORT void RemoveMouse(SDL_MouseID p_mouseID);
LEGO1_EXPORT void AddJoystick(SDL_JoystickID p_joystickID); LEGO1_EXPORT void AddJoystick(SDL_JoystickID p_joystickID);
LEGO1_EXPORT void RemoveJoystick(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 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); 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 // clang-format off
enum class SDL_KeyboardID_v : SDL_KeyboardID {};
enum class SDL_MouseID_v : SDL_MouseID {}; enum class SDL_MouseID_v : SDL_MouseID {};
enum class SDL_JoystickID_v : SDL_JoystickID {}; enum class SDL_JoystickID_v : SDL_JoystickID {};
enum class SDL_TouchID_v : SDL_TouchID {}; enum class SDL_TouchID_v : SDL_TouchID {};
// clang-format on // clang-format on
// SYNTHETIC: LEGO1 0x1005b8d0
// LegoInputManager::`scalar deleting destructor'
private:
void InitializeHaptics(); void InitializeHaptics();
MxCriticalSection m_criticalSection; // 0x58 MxCriticalSection m_criticalSection; // 0x58
@ -197,10 +202,11 @@ class LegoInputManager : public MxPresenter {
SDL_Point m_touchVirtualThumb = {0, 0}; SDL_Point m_touchVirtualThumb = {0, 0};
SDL_FPoint m_touchVirtualThumbOrigin; SDL_FPoint m_touchVirtualThumbOrigin;
std::map<SDL_FingerID, MxU32> m_touchFlags; 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_MouseID, std::pair<void*, SDL_Haptic*>> m_mice;
std::map<SDL_JoystickID, std::pair<SDL_Gamepad*, SDL_Haptic*>> m_joysticks; std::map<SDL_JoystickID, std::pair<SDL_Gamepad*, SDL_Haptic*>> m_joysticks;
std::map<SDL_HapticID, SDL_Haptic*> m_otherHaptics; 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 // TEMPLATE: LEGO1 0x10028850

View File

@ -160,7 +160,8 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags)
// FUNCTION: LEGO1 0x1005c320 // FUNCTION: LEGO1 0x1005c320
MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, MxU32* p_povPosition) 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; return FAILURE;
} }
@ -513,6 +514,24 @@ MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates)
return SUCCESS; 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) void LegoInputManager::AddMouse(SDL_MouseID p_mouseID)
{ {
if (m_mice.count(p_mouseID)) { if (m_mice.count(p_mouseID)) {
@ -656,7 +675,12 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc
return TRUE; 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; static bool g_hapticsInitialized = false;
@ -668,6 +692,7 @@ MxBool LegoInputManager::HandleRumbleEvent()
SDL_Haptic* haptic = nullptr; SDL_Haptic* haptic = nullptr;
std::visit( std::visit(
overloaded{ overloaded{
[](SDL_KeyboardID_v p_id) {},
[&haptic, this](SDL_MouseID_v p_id) { [&haptic, this](SDL_MouseID_v p_id) {
if (m_mice.count((SDL_MouseID) p_id)) { if (m_mice.count((SDL_MouseID) p_id)) {
haptic = m_mice[(SDL_MouseID) p_id].second; haptic = m_mice[(SDL_MouseID) p_id].second;
@ -688,11 +713,8 @@ MxBool LegoInputManager::HandleRumbleEvent()
m_lastInputMethod m_lastInputMethod
); );
const float strength = 0.5f;
const Uint32 durationMs = 700;
if (haptic) { 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 // 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)) { if (m_joysticks.count((SDL_JoystickID) *joystick)) {
return SDL_RumbleGamepad( return SDL_RumbleGamepad(
m_joysticks[(SDL_JoystickID) *joystick].first, m_joysticks[(SDL_JoystickID) *joystick].first,
strength * 65535, SDL_clamp(p_lowFrequencyRumble, 0, 1) * USHRT_MAX,
strength * 65535, SDL_clamp(p_highFrequencyRumble, 0, 1) * USHRT_MAX,
durationMs p_milliseconds
); );
} }
} }
@ -752,6 +774,10 @@ void LegoInputManager::InitializeHaptics()
void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event) void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event)
{ {
switch (p_event->type) { 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_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP: case SDL_EVENT_MOUSE_BUTTON_UP:
m_lastInputMethod = SDL_MouseID_v{p_event->button.which}; 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" fccType: "Re-defined Windows name"
dwDataOffset: "Re-defined Windows name" dwDataOffset: "Re-defined Windows name"
fccType: "Re-defined Windows name" fccType: "Re-defined Windows name"
SDL_KeyboardID_v: "SDL-based name"
SDL_MouseID_v: "SDL-based name" SDL_MouseID_v: "SDL-based name"
SDL_JoystickID_v: "SDL-based name" SDL_JoystickID_v: "SDL-based name"
SDL_TouchID_v: "SDL-based name" SDL_TouchID_v: "SDL-based name"