Skip to content

Commit 77579f9

Browse files
committed
Merge pull request #111679 from Nintorch/joypad-motion-sensors
Add support for joypad motion sensors
2 parents 33296bd + a3eb202 commit 77579f9

File tree

10 files changed

+1940
-0
lines changed

10 files changed

+1940
-0
lines changed

COPYRIGHT.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,11 @@ Comment: The FreeType Project
310310
Copyright: 1996-2025, David Turner, Robert Wilhelm, and Werner Lemberg.
311311
License: FTL
312312

313+
Files: thirdparty/gamepadmotionhelpers/*
314+
Comment: GamepadMotionHelpers
315+
Copyright: 2020-2023, Julian "Jibb" Smart
316+
License: Expat
317+
313318
Files: thirdparty/glad/*
314319
Comment: glad
315320
Copyright: 2013-2022, David Herberth

core/input/input.cpp

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
#include "core/os/thread.h"
4141
#endif
4242

43+
#include "thirdparty/gamepadmotionhelpers/GamepadMotion.hpp"
44+
45+
#define STANDARD_GRAVITY 9.80665f
46+
4347
static const char *_joy_buttons[(size_t)JoyButton::SDL_MAX] = {
4448
"a",
4549
"b",
@@ -147,6 +151,20 @@ void Input::_bind_methods() {
147151
ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer);
148152
ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer);
149153
ClassDB::bind_method(D_METHOD("get_gyroscope"), &Input::get_gyroscope);
154+
ClassDB::bind_method(D_METHOD("get_joy_accelerometer", "device"), &Input::get_joy_accelerometer);
155+
ClassDB::bind_method(D_METHOD("get_joy_gravity", "device"), &Input::get_joy_gravity);
156+
ClassDB::bind_method(D_METHOD("get_joy_gyroscope", "device"), &Input::get_joy_gyroscope);
157+
ClassDB::bind_method(D_METHOD("get_joy_motion_sensors_rate", "device"), &Input::get_joy_motion_sensors_rate);
158+
ClassDB::bind_method(D_METHOD("is_joy_motion_sensors_enabled", "device"), &Input::is_joy_motion_sensors_enabled);
159+
ClassDB::bind_method(D_METHOD("set_joy_motion_sensors_enabled", "device", "enable"), &Input::set_joy_motion_sensors_enabled);
160+
ClassDB::bind_method(D_METHOD("has_joy_motion_sensors", "device"), &Input::has_joy_motion_sensors);
161+
ClassDB::bind_method(D_METHOD("start_joy_motion_sensors_calibration", "device"), &Input::start_joy_motion_sensors_calibration);
162+
ClassDB::bind_method(D_METHOD("stop_joy_motion_sensors_calibration", "device"), &Input::stop_joy_motion_sensors_calibration);
163+
ClassDB::bind_method(D_METHOD("clear_joy_motion_sensors_calibration", "device"), &Input::clear_joy_motion_sensors_calibration);
164+
ClassDB::bind_method(D_METHOD("get_joy_motion_sensors_calibration", "device"), &Input::get_joy_motion_sensors_calibration);
165+
ClassDB::bind_method(D_METHOD("set_joy_motion_sensors_calibration", "device", "calibration_info"), &Input::set_joy_motion_sensors_calibration);
166+
ClassDB::bind_method(D_METHOD("is_joy_motion_sensors_calibrated", "device"), &Input::is_joy_motion_sensors_calibrated);
167+
ClassDB::bind_method(D_METHOD("is_joy_motion_sensors_calibrating", "device"), &Input::is_joy_motion_sensors_calibrating);
150168
ClassDB::bind_method(D_METHOD("set_gravity", "value"), &Input::set_gravity);
151169
ClassDB::bind_method(D_METHOD("set_accelerometer", "value"), &Input::set_accelerometer);
152170
ClassDB::bind_method(D_METHOD("set_magnetometer", "value"), &Input::set_magnetometer);
@@ -684,6 +702,11 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
684702
for (int i = 0; i < (int)JoyAxis::MAX; i++) {
685703
set_joy_axis(p_idx, (JoyAxis)i, 0.0f);
686704
}
705+
MotionInfo *motion = joy_motion.getptr(p_idx);
706+
if (motion != nullptr && motion->gamepad_motion != nullptr) {
707+
delete motion->gamepad_motion;
708+
}
709+
joy_motion.erase(p_idx);
687710
}
688711
joy_names[p_idx] = js;
689712

@@ -1018,6 +1041,196 @@ bool Input::has_joy_light(int p_device) const {
10181041
return joypad && joypad->has_light;
10191042
}
10201043

1044+
Vector3 Input::get_joy_accelerometer(int p_device) const {
1045+
_THREAD_SAFE_METHOD_
1046+
const MotionInfo *motion = joy_motion.getptr(p_device);
1047+
if (motion == nullptr) {
1048+
return Vector3();
1049+
}
1050+
1051+
float joy_acceleration_data[3];
1052+
motion->gamepad_motion->GetProcessedAcceleration(joy_acceleration_data[0], joy_acceleration_data[1], joy_acceleration_data[2]);
1053+
Vector3 joy_acceleration(joy_acceleration_data[0], joy_acceleration_data[1], joy_acceleration_data[2]);
1054+
1055+
float joy_gravity_data[3];
1056+
motion->gamepad_motion->GetGravity(joy_gravity_data[0], joy_gravity_data[1], joy_gravity_data[2]);
1057+
Vector3 joy_gravity(joy_gravity_data[0], joy_gravity_data[1], joy_gravity_data[2]);
1058+
1059+
return (-joy_acceleration + joy_gravity) * STANDARD_GRAVITY;
1060+
}
1061+
1062+
Vector3 Input::get_joy_gravity(int p_device) const {
1063+
_THREAD_SAFE_METHOD_
1064+
const MotionInfo *motion = joy_motion.getptr(p_device);
1065+
if (motion == nullptr) {
1066+
return Vector3();
1067+
}
1068+
1069+
float joy_gravity_data[3];
1070+
motion->gamepad_motion->GetGravity(joy_gravity_data[0], joy_gravity_data[1], joy_gravity_data[2]);
1071+
Vector3 joy_gravity(joy_gravity_data[0], joy_gravity_data[1], joy_gravity_data[2]);
1072+
1073+
return joy_gravity.normalized() * STANDARD_GRAVITY;
1074+
}
1075+
1076+
Vector3 Input::get_joy_gyroscope(int p_device) const {
1077+
_THREAD_SAFE_METHOD_
1078+
const MotionInfo *motion = joy_motion.getptr(p_device);
1079+
if (motion == nullptr) {
1080+
return Vector3();
1081+
}
1082+
1083+
float joy_gyro_data[3];
1084+
motion->gamepad_motion->GetCalibratedGyro(joy_gyro_data[0], joy_gyro_data[1], joy_gyro_data[2]);
1085+
Vector3 joy_gyro(joy_gyro_data[0], joy_gyro_data[1], joy_gyro_data[2]);
1086+
1087+
return joy_gyro * M_PI / 180.0;
1088+
}
1089+
1090+
void Input::set_joy_motion_sensors_enabled(int p_device, bool p_enable) {
1091+
_THREAD_SAFE_METHOD_
1092+
Joypad *joypad = joy_names.getptr(p_device);
1093+
if (joypad == nullptr || joypad->features == nullptr) {
1094+
return;
1095+
}
1096+
MotionInfo *motion = joy_motion.getptr(p_device);
1097+
if (motion == nullptr) {
1098+
return;
1099+
}
1100+
joypad->features->set_joy_motion_sensors_enabled(p_enable);
1101+
motion->sensors_enabled = p_enable;
1102+
}
1103+
1104+
bool Input::is_joy_motion_sensors_enabled(int p_device) const {
1105+
_THREAD_SAFE_METHOD_
1106+
const MotionInfo *motion = joy_motion.getptr(p_device);
1107+
return motion != nullptr && motion->sensors_enabled;
1108+
}
1109+
1110+
bool Input::has_joy_motion_sensors(int p_device) const {
1111+
_THREAD_SAFE_METHOD_
1112+
return joy_motion.has(p_device);
1113+
}
1114+
1115+
float Input::get_joy_motion_sensors_rate(int p_device) const {
1116+
_THREAD_SAFE_METHOD_
1117+
const MotionInfo *motion = joy_motion.getptr(p_device);
1118+
if (motion == nullptr) {
1119+
return 0.0f;
1120+
}
1121+
return motion->sensor_data_rate;
1122+
}
1123+
1124+
void Input::start_joy_motion_sensors_calibration(int p_device) {
1125+
_THREAD_SAFE_METHOD_
1126+
MotionInfo *motion = joy_motion.getptr(p_device);
1127+
if (motion == nullptr) {
1128+
return;
1129+
}
1130+
1131+
ERR_FAIL_COND_MSG(!motion->sensors_enabled, "Motion sensors are not enabled on the joypad.");
1132+
ERR_FAIL_COND_MSG(motion->calibrating, "Calibration already in progress.");
1133+
1134+
motion->gamepad_motion->ResetContinuousCalibration();
1135+
motion->gamepad_motion->StartContinuousCalibration();
1136+
1137+
motion->calibrating = true;
1138+
motion->calibrated = false;
1139+
}
1140+
1141+
void Input::stop_joy_motion_sensors_calibration(int p_device) {
1142+
_THREAD_SAFE_METHOD_
1143+
MotionInfo *motion = joy_motion.getptr(p_device);
1144+
if (motion == nullptr) {
1145+
return;
1146+
}
1147+
1148+
ERR_FAIL_COND_MSG(!motion->sensors_enabled, "Motion sensors are not enabled on the joypad.");
1149+
ERR_FAIL_COND_MSG(!motion->calibrating, "Calibration hasn't been started.");
1150+
1151+
motion->gamepad_motion->PauseContinuousCalibration();
1152+
1153+
motion->calibrating = false;
1154+
motion->calibrated = true;
1155+
}
1156+
1157+
void Input::clear_joy_motion_sensors_calibration(int p_device) {
1158+
_THREAD_SAFE_METHOD_
1159+
MotionInfo *motion = joy_motion.getptr(p_device);
1160+
if (motion == nullptr) {
1161+
return;
1162+
}
1163+
1164+
// Calibration might be in progress and the developer or the user might want to reset it,
1165+
// so no need to stop the calibration.
1166+
1167+
motion->gamepad_motion->ResetContinuousCalibration();
1168+
}
1169+
1170+
Dictionary Input::get_joy_motion_sensors_calibration(int p_device) const {
1171+
_THREAD_SAFE_METHOD_
1172+
const MotionInfo *motion = joy_motion.getptr(p_device);
1173+
if (motion == nullptr) {
1174+
return Dictionary();
1175+
}
1176+
1177+
if (!motion->calibrated) {
1178+
return Dictionary();
1179+
}
1180+
1181+
float joy_gyro_offset_data[3];
1182+
motion->gamepad_motion->GetCalibrationOffset(joy_gyro_offset_data[0], joy_gyro_offset_data[1], joy_gyro_offset_data[2]);
1183+
Vector3 joy_gyro_offset(joy_gyro_offset_data[0], joy_gyro_offset_data[1], joy_gyro_offset_data[2]);
1184+
1185+
Dictionary result;
1186+
result["gyroscope_offset"] = joy_gyro_offset * M_PI / 180.0;
1187+
return result;
1188+
}
1189+
1190+
void Input::set_joy_motion_sensors_calibration(int p_device, const Dictionary &p_calibration_info) {
1191+
_THREAD_SAFE_METHOD_
1192+
MotionInfo *motion = joy_motion.getptr(p_device);
1193+
if (motion == nullptr) {
1194+
return;
1195+
}
1196+
1197+
ERR_FAIL_COND_MSG(motion->calibrating, "Calibration is currently in progress.");
1198+
1199+
Vector3 gyro_offset = p_calibration_info.get("gyroscope_offset", Vector3()).operator Vector3() * 180.0 / M_PI;
1200+
1201+
motion->gamepad_motion->SetCalibrationOffset(gyro_offset.x, gyro_offset.y, gyro_offset.z, 1);
1202+
motion->calibrating = false;
1203+
motion->calibrated = true;
1204+
}
1205+
1206+
bool Input::is_joy_motion_sensors_calibrating(int p_device) const {
1207+
_THREAD_SAFE_METHOD_
1208+
const MotionInfo *motion = joy_motion.getptr(p_device);
1209+
if (motion == nullptr) {
1210+
return false;
1211+
}
1212+
return motion->calibrating;
1213+
}
1214+
1215+
bool Input::is_joy_motion_sensors_calibrated(int p_device) const {
1216+
_THREAD_SAFE_METHOD_
1217+
const MotionInfo *motion = joy_motion.getptr(p_device);
1218+
if (motion == nullptr) {
1219+
return false;
1220+
}
1221+
return motion->calibrated;
1222+
}
1223+
1224+
void Input::set_joy_motion_sensors_rate(int p_device, float p_rate) {
1225+
_THREAD_SAFE_METHOD_
1226+
MotionInfo *motion = joy_motion.getptr(p_device);
1227+
if (motion == nullptr) {
1228+
return;
1229+
}
1230+
1231+
motion->sensor_data_rate = p_rate;
1232+
}
1233+
10211234
void Input::start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration) {
10221235
_THREAD_SAFE_METHOD_
10231236
if (p_weak_magnitude < 0.f || p_weak_magnitude > 1.f || p_strong_magnitude < 0.f || p_strong_magnitude > 1.f) {
@@ -1469,6 +1682,22 @@ void Input::joy_hat(int p_device, BitField<HatMask> p_val) {
14691682
joy_names[p_device].hat_current = (int)p_val;
14701683
}
14711684

1685+
void Input::joy_motion_sensors(int p_device, const Vector3 &p_accelerometer, const Vector3 &p_gyroscope) {
1686+
_THREAD_SAFE_METHOD_
1687+
// TODO: events
1688+
MotionInfo *motion = joy_motion.getptr(p_device);
1689+
if (motion == nullptr) {
1690+
return;
1691+
}
1692+
1693+
Vector3 gyro_degrees = p_gyroscope * 180.0 / M_PI;
1694+
Vector3 accel_g = -p_accelerometer / STANDARD_GRAVITY;
1695+
uint64_t new_timestamp = OS::get_singleton()->get_ticks_msec();
1696+
float delta_time = (new_timestamp - motion->last_timestamp) / 1000.0f;
1697+
motion->last_timestamp = new_timestamp;
1698+
motion->gamepad_motion->ProcessMotion(gyro_degrees.x, gyro_degrees.y, gyro_degrees.z, accel_g.x, accel_g.y, accel_g.z, delta_time);
1699+
}
1700+
14721701
void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed) {
14731702
Ref<InputEventJoypadButton> ievent;
14741703
ievent.instantiate();
@@ -1520,6 +1749,16 @@ void Input::_update_joypad_features(int p_device) {
15201749
if (joypad->features->has_joy_light()) {
15211750
joypad->has_light = true;
15221751
}
1752+
if (joypad->features->has_joy_motion_sensors()) {
1753+
MotionInfo &motion = joy_motion[p_device];
1754+
1755+
if (!motion.gamepad_motion) {
1756+
motion.gamepad_motion = new GamepadMotion();
1757+
} else {
1758+
motion.gamepad_motion->Reset();
1759+
}
1760+
motion.last_timestamp = OS::get_singleton()->get_ticks_msec();
1761+
}
15231762
}
15241763

15251764
Input::JoyEvent Input::_get_mapped_button_event(const JoyDeviceMapping &mapping, JoyButton p_button) {

core/input/input.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
#include "core/templates/rb_set.h"
3939
#include "core/variant/typed_array.h"
4040

41+
class GamepadMotion;
42+
4143
class Input : public Object {
4244
GDCLASS(Input, Object);
4345
_THREAD_SAFE_CLASS_
@@ -85,6 +87,9 @@ class Input : public Object {
8587

8688
virtual bool has_joy_light() const { return false; }
8789
virtual void set_joy_light(const Color &p_color) {}
90+
91+
virtual bool has_joy_motion_sensors() const { return false; }
92+
virtual void set_joy_motion_sensors_enabled(bool p_enable) {}
8893
};
8994

9095
static constexpr int32_t JOYPADS_MAX = 16;
@@ -157,6 +162,23 @@ class Input : public Object {
157162

158163
HashMap<int, VibrationInfo> joy_vibration;
159164

165+
struct MotionInfo {
166+
bool sensors_enabled : 1;
167+
bool calibrating : 1;
168+
bool calibrated : 1;
169+
float sensor_data_rate = 0.0f;
170+
uint64_t last_timestamp = 0;
171+
GamepadMotion *gamepad_motion = nullptr;
172+
173+
MotionInfo() {
174+
sensors_enabled = false;
175+
calibrating = false;
176+
calibrated = false;
177+
}
178+
};
179+
180+
HashMap<int, MotionInfo> joy_motion;
181+
160182
struct VelocityTrack {
161183
uint64_t last_tick = 0;
162184
Vector2 velocity;
@@ -363,6 +385,28 @@ class Input : public Object {
363385
void set_joy_light(int p_device, const Color &p_color);
364386
bool has_joy_light(int p_device) const;
365387

388+
Vector3 get_joy_accelerometer(int p_device) const;
389+
Vector3 get_joy_gravity(int p_device) const;
390+
Vector3 get_joy_gyroscope(int p_device) const;
391+
392+
void set_joy_motion_sensors_enabled(int p_device, bool p_enable);
393+
bool is_joy_motion_sensors_enabled(int p_device) const;
394+
395+
bool has_joy_motion_sensors(int p_device) const;
396+
float get_joy_motion_sensors_rate(int p_device) const;
397+
398+
void start_joy_motion_sensors_calibration(int p_device);
399+
void stop_joy_motion_sensors_calibration(int p_device);
400+
void clear_joy_motion_sensors_calibration(int p_device);
401+
402+
Dictionary get_joy_motion_sensors_calibration(int p_device) const;
403+
void set_joy_motion_sensors_calibration(int p_device, const Dictionary &p_calibration_info);
404+
405+
bool is_joy_motion_sensors_calibrating(int p_device) const;
406+
bool is_joy_motion_sensors_calibrated(int p_device) const;
407+
408+
void set_joy_motion_sensors_rate(int p_device, float p_rate);
409+
366410
void start_joy_vibration(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration = 0);
367411
void stop_joy_vibration(int p_device);
368412
void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0);
@@ -388,6 +432,7 @@ class Input : public Object {
388432
void joy_button(int p_device, JoyButton p_button, bool p_pressed);
389433
void joy_axis(int p_device, JoyAxis p_axis, float p_value);
390434
void joy_hat(int p_device, BitField<HatMask> p_val);
435+
void joy_motion_sensors(int p_device, const Vector3 &p_accelerometer, const Vector3 &p_gyroscope);
391436

392437
void add_joy_mapping(const String &p_mapping, bool p_update_existing = false);
393438
void remove_joy_mapping(const String &p_guid);

0 commit comments

Comments
 (0)