Skip to content

Commit 0360006

Browse files
committed
Add support for SDL haptics
1 parent 5b4cbd2 commit 0360006

File tree

4 files changed

+307
-1
lines changed

4 files changed

+307
-1
lines changed

core/input/input.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ void Input::_bind_methods() {
143143
ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0));
144144
ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration);
145145
ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0));
146+
ClassDB::bind_method(D_METHOD("constant_force_feedback", "device", "force", "duration"), &Input::constant_force_feedback);
147+
ClassDB::bind_method(D_METHOD("spring_force_feedback", "device", "cw_coef", "ccw_coef", "offset", "duration"), &Input::spring_force_feedback);
148+
ClassDB::bind_method(D_METHOD("friction_force_feedback", "device", "cw_coef", "ccw_coef", "duration"), &Input::friction_force_feedback);
149+
ClassDB::bind_method(D_METHOD("damper_force_feedback", "device", "cw_coef", "ccw_coef", "offset", "duration"), &Input::damper_force_feedback);
150+
ClassDB::bind_method(D_METHOD("inertia_force_feedback", "device", "cw_coef", "ccw_coef", "offset", "duration"), &Input::inertia_force_feedback);
151+
ClassDB::bind_method(D_METHOD("stop_force_feedback", "device"), &Input::stop_force_feedback);
146152
ClassDB::bind_method(D_METHOD("get_gravity"), &Input::get_gravity);
147153
ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer);
148154
ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer);
@@ -1044,6 +1050,60 @@ void Input::vibrate_handheld(int p_duration_ms, float p_amplitude) {
10441050
OS::get_singleton()->vibrate_handheld(p_duration_ms, p_amplitude);
10451051
}
10461052

1053+
void Input::constant_force_feedback(int p_device, float p_force, float p_duration) {
1054+
_THREAD_SAFE_METHOD_
1055+
Joypad *joypad = joy_names.getptr(p_device);
1056+
if (!joypad || joypad->features == nullptr) {
1057+
return;
1058+
}
1059+
joypad->features->constant_force_feedback(p_force, p_duration);
1060+
}
1061+
1062+
void Input::spring_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {
1063+
_THREAD_SAFE_METHOD_
1064+
Joypad *joypad = joy_names.getptr(p_device);
1065+
if (!joypad || joypad->features == nullptr) {
1066+
return;
1067+
}
1068+
joypad->features->spring_force_feedback(p_cw_coef, p_ccw_coef, p_offset, p_duration);
1069+
}
1070+
1071+
void Input::friction_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_duration) {
1072+
_THREAD_SAFE_METHOD_
1073+
Joypad *joypad = joy_names.getptr(p_device);
1074+
if (!joypad || joypad->features == nullptr) {
1075+
return;
1076+
}
1077+
joypad->features->friction_force_feedback(p_cw_coef, p_ccw_coef, p_duration);
1078+
}
1079+
1080+
void Input::damper_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {
1081+
_THREAD_SAFE_METHOD_
1082+
Joypad *joypad = joy_names.getptr(p_device);
1083+
if (!joypad || joypad->features == nullptr) {
1084+
return;
1085+
}
1086+
joypad->features->damper_force_feedback(p_cw_coef, p_ccw_coef, p_offset, p_duration);
1087+
}
1088+
1089+
void Input::inertia_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {
1090+
_THREAD_SAFE_METHOD_
1091+
Joypad *joypad = joy_names.getptr(p_device);
1092+
if (!joypad || joypad->features == nullptr) {
1093+
return;
1094+
}
1095+
joypad->features->inertia_force_feedback(p_cw_coef, p_ccw_coef, p_offset, p_duration);
1096+
}
1097+
1098+
void Input::stop_force_feedback(int p_device) {
1099+
_THREAD_SAFE_METHOD_
1100+
Joypad *joypad = joy_names.getptr(p_device);
1101+
if (!joypad || joypad->features == nullptr) {
1102+
return;
1103+
}
1104+
joypad->features->stop_force_feedback();
1105+
}
1106+
10471107
void Input::set_gravity(const Vector3 &p_gravity) {
10481108
_THREAD_SAFE_METHOD_
10491109

core/input/input.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ class Input : public Object {
8585

8686
virtual bool has_joy_light() const { return false; }
8787
virtual bool set_joy_light(const Color &p_color) { return false; }
88+
89+
virtual void constant_force_feedback(float p_force, float p_duration) {}
90+
virtual void spring_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {}
91+
virtual void friction_force_feedback(float p_cw_coef, float p_ccw_coef, float p_duration) {}
92+
virtual void damper_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {}
93+
virtual void inertia_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {}
94+
virtual void stop_force_feedback() {}
8895
};
8996

9097
static constexpr int32_t JOYPADS_MAX = 16;
@@ -367,6 +374,13 @@ class Input : public Object {
367374
void stop_joy_vibration(int p_device);
368375
void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0);
369376

377+
void constant_force_feedback(int p_device, float p_force, float p_duration);
378+
void spring_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration);
379+
void friction_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_duration);
380+
void damper_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration);
381+
void inertia_force_feedback(int p_device, float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration);
382+
void stop_force_feedback(int p_device);
383+
370384
void set_mouse_position(const Point2 &p_posf);
371385

372386
void action_press(const StringName &p_action, float p_strength = 1.f);

drivers/sdl/joypad_sdl.cpp

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ JoypadSDL *JoypadSDL::get_singleton() {
7575
Error JoypadSDL::initialize() {
7676
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
7777
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
78-
ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD), FAILED, SDL_GetError());
78+
ERR_FAIL_COND_V_MSG(!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC), FAILED, SDL_GetError());
7979

8080
// Add Godot's mapping database from memory
8181
int i = 0;
@@ -169,6 +169,7 @@ void JoypadSDL::process_events() {
169169
joypads[joy_id].sdl_instance_idx = sdl_event.jdevice.which;
170170
joypads[joy_id].supports_force_feedback = SDL_GetBooleanProperty(propertiesID, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, false);
171171
joypads[joy_id].guid = StringName(String(guid));
172+
joypads[joy_id].setup_haptic_effect();
172173

173174
sdl_instance_id_to_joypad_id.insert(sdl_event.jdevice.which, joy_id);
174175

@@ -280,6 +281,11 @@ void JoypadSDL::close_joypad(int p_pad_idx) {
280281
joypads[p_pad_idx].attached = false;
281282
sdl_instance_id_to_joypad_id.erase(sdl_instance_idx);
282283

284+
if (joypads[p_pad_idx].haptic) {
285+
SDL_DestroyHapticEffect(joypads[p_pad_idx].haptic, joypads[p_pad_idx].haptic_effect_id);
286+
SDL_CloseHaptic(joypads[p_pad_idx].haptic);
287+
}
288+
283289
if (SDL_IsGamepad(sdl_instance_idx)) {
284290
SDL_Gamepad *gamepad = SDL_GetGamepadFromID(sdl_instance_idx);
285291
SDL_CloseGamepad(gamepad);
@@ -302,6 +308,204 @@ bool JoypadSDL::Joypad::set_joy_light(const Color &p_color) {
302308
return SDL_SetJoystickLED(get_sdl_joystick(), linear.get_r8(), linear.get_g8(), linear.get_b8());
303309
}
304310

311+
void JoypadSDL::Joypad::constant_force_feedback(float p_force, float p_duration) {
312+
if (!haptic) {
313+
#ifdef DEBUG_ENABLED
314+
print_line("Input.constant_force_feedback(): not haptic.");
315+
#endif
316+
return;
317+
}
318+
319+
SDL_HapticEffect effect;
320+
if ((SDL_GetHapticFeatures(haptic) & SDL_HAPTIC_CONSTANT) == 0) {
321+
#ifdef DEBUG_ENABLED
322+
print_line("Input.constant_force_feedback(): unsupported.");
323+
#endif
324+
return;
325+
}
326+
327+
if (haptic_effect_id) {
328+
SDL_DestroyHapticEffect(haptic, haptic_effect_id);
329+
}
330+
331+
SDL_memset(&effect, 0, sizeof(SDL_HapticEffect)); // 0 is safe default
332+
effect.type = SDL_HAPTIC_CONSTANT;
333+
effect.constant.direction.type = SDL_HAPTIC_CARTESIAN;
334+
effect.constant.direction.dir[0] = 1;
335+
effect.constant.direction.dir[1] = 0;
336+
effect.constant.length = (Uint32)(p_duration * 1000);
337+
if (effect.constant.length == 0) {
338+
effect.constant.length = SDL_HAPTIC_INFINITY;
339+
}
340+
effect.constant.level = SDL_MAX_SINT16 * SDL_clamp(p_force, -1.0f, 1.0f);
341+
342+
haptic_effect_id = SDL_CreateHapticEffect(haptic, &effect);
343+
SDL_RunHapticEffect(haptic, haptic_effect_id, 1);
344+
}
345+
346+
void JoypadSDL::Joypad::spring_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {
347+
if (!haptic) {
348+
#ifdef DEBUG_ENABLED
349+
print_line("Input.spring_force_feedback(): not haptic.");
350+
#endif
351+
return;
352+
}
353+
354+
SDL_HapticEffect effect;
355+
if ((SDL_GetHapticFeatures(haptic) & SDL_HAPTIC_SPRING) == 0) {
356+
#ifdef DEBUG_ENABLED
357+
print_line("Input.spring_force_feedback(): unsupported.");
358+
#endif
359+
return;
360+
}
361+
362+
if (haptic_effect_id) {
363+
SDL_DestroyHapticEffect(haptic, haptic_effect_id);
364+
}
365+
366+
SDL_memset(&effect, 0, sizeof(SDL_HapticEffect)); // 0 is safe default
367+
effect.type = SDL_HAPTIC_SPRING;
368+
effect.condition.direction.type = SDL_HAPTIC_CARTESIAN;
369+
effect.condition.direction.dir[0] = 1;
370+
effect.condition.direction.dir[1] = 0;
371+
effect.condition.right_coeff[0] = p_cw_coef;
372+
effect.condition.left_coeff[0] = p_ccw_coef;
373+
// TODO: What is "offset"?
374+
effect.condition.length = (Uint32)(p_duration * 1000);
375+
if (effect.condition.length == 0) {
376+
effect.condition.length = SDL_HAPTIC_INFINITY;
377+
}
378+
379+
haptic_effect_id = SDL_CreateHapticEffect(haptic, &effect);
380+
SDL_RunHapticEffect(haptic, haptic_effect_id, 1);
381+
}
382+
383+
void JoypadSDL::Joypad::friction_force_feedback(float p_cw_coef, float p_ccw_coef, float p_duration) {
384+
if (!haptic) {
385+
#ifdef DEBUG_ENABLED
386+
print_line("Input.friction_force_feedback(): not haptic.");
387+
#endif
388+
return;
389+
}
390+
391+
SDL_HapticEffect effect;
392+
if ((SDL_GetHapticFeatures(haptic) & SDL_HAPTIC_FRICTION) == 0) {
393+
#ifdef DEBUG_ENABLED
394+
print_line("Input.friction_force_feedback(): unsupported.");
395+
#endif
396+
return;
397+
}
398+
399+
if (haptic_effect_id) {
400+
SDL_DestroyHapticEffect(haptic, haptic_effect_id);
401+
}
402+
403+
SDL_memset(&effect, 0, sizeof(SDL_HapticEffect)); // 0 is safe default
404+
effect.type = SDL_HAPTIC_FRICTION;
405+
effect.condition.direction.type = SDL_HAPTIC_CARTESIAN;
406+
effect.condition.direction.dir[0] = 1;
407+
effect.condition.direction.dir[1] = 0;
408+
effect.condition.right_coeff[0] = p_cw_coef;
409+
effect.condition.left_coeff[0] = p_ccw_coef;
410+
// TODO: What is "offset"?
411+
effect.condition.length = (Uint32)(p_duration * 1000);
412+
if (effect.condition.length == 0) {
413+
effect.condition.length = SDL_HAPTIC_INFINITY;
414+
}
415+
416+
haptic_effect_id = SDL_CreateHapticEffect(haptic, &effect);
417+
SDL_RunHapticEffect(haptic, haptic_effect_id, 1);
418+
}
419+
420+
void JoypadSDL::Joypad::damper_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {
421+
if (!haptic) {
422+
#ifdef DEBUG_ENABLED
423+
print_line("Input.damper_force_feedback(): not haptic.");
424+
#endif
425+
return;
426+
}
427+
428+
SDL_HapticEffect effect;
429+
if ((SDL_GetHapticFeatures(haptic) & SDL_HAPTIC_DAMPER) == 0) {
430+
#ifdef DEBUG_ENABLED
431+
print_line("Input.damper_force_feedback(): unsupported.");
432+
#endif
433+
return;
434+
}
435+
436+
if (haptic_effect_id) {
437+
SDL_DestroyHapticEffect(haptic, haptic_effect_id);
438+
}
439+
440+
SDL_memset(&effect, 0, sizeof(SDL_HapticEffect)); // 0 is safe default
441+
effect.type = SDL_HAPTIC_DAMPER;
442+
effect.condition.direction.type = SDL_HAPTIC_CARTESIAN;
443+
effect.condition.direction.dir[0] = 1;
444+
effect.condition.direction.dir[1] = 0;
445+
effect.condition.right_coeff[0] = p_cw_coef;
446+
effect.condition.left_coeff[0] = p_ccw_coef;
447+
// TODO: What is "offset"?
448+
effect.condition.length = (Uint32)(p_duration * 1000);
449+
if (effect.condition.length == 0) {
450+
effect.condition.length = SDL_HAPTIC_INFINITY;
451+
}
452+
453+
haptic_effect_id = SDL_CreateHapticEffect(haptic, &effect);
454+
SDL_RunHapticEffect(haptic, haptic_effect_id, 1);
455+
}
456+
457+
void JoypadSDL::Joypad::inertia_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) {
458+
if (!haptic) {
459+
#ifdef DEBUG_ENABLED
460+
print_line("Input.inertia_force_feedback(): not haptic.");
461+
#endif
462+
return;
463+
}
464+
465+
SDL_HapticEffect effect;
466+
if ((SDL_GetHapticFeatures(haptic) & SDL_HAPTIC_INERTIA) == 0) {
467+
#ifdef DEBUG_ENABLED
468+
print_line("Input.inertia_force_feedback(): unsupported.");
469+
#endif
470+
return;
471+
}
472+
473+
if (haptic_effect_id) {
474+
SDL_DestroyHapticEffect(haptic, haptic_effect_id);
475+
}
476+
477+
SDL_memset(&effect, 0, sizeof(SDL_HapticEffect)); // 0 is safe default
478+
effect.type = SDL_HAPTIC_INERTIA;
479+
effect.condition.direction.type = SDL_HAPTIC_CARTESIAN;
480+
effect.condition.direction.dir[0] = 1;
481+
effect.condition.direction.dir[1] = 0;
482+
effect.condition.right_coeff[0] = p_cw_coef;
483+
effect.condition.left_coeff[0] = p_ccw_coef;
484+
// TODO: What is "offset"?
485+
effect.condition.length = (Uint32)(p_duration * 1000);
486+
if (effect.condition.length == 0) {
487+
effect.condition.length = SDL_HAPTIC_INFINITY;
488+
}
489+
490+
haptic_effect_id = SDL_CreateHapticEffect(haptic, &effect);
491+
SDL_RunHapticEffect(haptic, haptic_effect_id, 1);
492+
}
493+
494+
void JoypadSDL::Joypad::stop_force_feedback() {
495+
if (!haptic) {
496+
#ifdef DEBUG_ENABLED
497+
print_line("Input.stop_force_feedback(): not haptic.");
498+
#endif
499+
return;
500+
}
501+
502+
if (haptic_effect_id) {
503+
SDL_StopHapticEffect(haptic, haptic_effect_id);
504+
SDL_DestroyHapticEffect(haptic, haptic_effect_id);
505+
haptic_effect_id = 0;
506+
}
507+
}
508+
305509
SDL_Joystick *JoypadSDL::Joypad::get_sdl_joystick() const {
306510
return SDL_GetJoystickFromID(sdl_instance_idx);
307511
}
@@ -310,4 +514,21 @@ SDL_Gamepad *JoypadSDL::Joypad::get_sdl_gamepad() const {
310514
return SDL_GetGamepadFromID(sdl_instance_idx);
311515
}
312516

517+
void JoypadSDL::Joypad::setup_haptic_effect() {
518+
haptic = SDL_OpenHapticFromJoystick(get_sdl_joystick());
519+
if (!haptic) {
520+
return;
521+
}
522+
SDL_HapticEffect effect;
523+
SDL_memset(&effect, 0, sizeof(SDL_HapticEffect)); // 0 is safe default
524+
effect.type = SDL_HAPTIC_CONSTANT;
525+
effect.constant.direction.type = SDL_HAPTIC_CARTESIAN;
526+
effect.constant.direction.dir[0] = 1;
527+
effect.constant.direction.dir[1] = 0;
528+
effect.constant.length = 0;
529+
effect.constant.level = 0;
530+
531+
haptic_effect_id = SDL_CreateHapticEffect(haptic, &effect);
532+
}
533+
313534
#endif // SDL_ENABLED

drivers/sdl/joypad_sdl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
typedef uint32_t SDL_JoystickID;
3737
typedef struct SDL_Joystick SDL_Joystick;
3838
typedef struct SDL_Gamepad SDL_Gamepad;
39+
typedef struct SDL_Haptic SDL_Haptic;
3940

4041
class JoypadSDL {
4142
public:
@@ -57,12 +58,22 @@ class JoypadSDL {
5758

5859
bool supports_force_feedback = false;
5960
uint64_t ff_effect_timestamp = 0;
61+
SDL_Haptic *haptic = nullptr;
62+
int haptic_effect_id = 0;
6063

6164
virtual bool has_joy_light() const override;
6265
virtual bool set_joy_light(const Color &p_color) override;
6366

67+
virtual void constant_force_feedback(float p_force, float p_duration) override;
68+
virtual void spring_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) override;
69+
virtual void friction_force_feedback(float p_cw_coef, float p_ccw_coef, float p_duration) override;
70+
virtual void damper_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) override;
71+
virtual void inertia_force_feedback(float p_cw_coef, float p_ccw_coef, float p_offset, float p_duration) override;
72+
virtual void stop_force_feedback() override;
73+
6474
SDL_Joystick *get_sdl_joystick() const;
6575
SDL_Gamepad *get_sdl_gamepad() const;
76+
void setup_haptic_effect();
6677
};
6778

6879
static JoypadSDL *singleton;

0 commit comments

Comments
 (0)