Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
371 changes: 371 additions & 0 deletions COPYRIGHT.txt

Large diffs are not rendered by default.

286 changes: 286 additions & 0 deletions core/input/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "core/config/project_settings.h"
#include "core/input/default_controller_mappings.h"
#include "core/input/input_dualsense.h"
#include "core/input/input_map.h"
#include "core/os/os.h"

Expand Down Expand Up @@ -143,6 +144,15 @@ void Input::_bind_methods() {
ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0));
ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration);
ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0));
ClassDB::bind_method(D_METHOD("has_joy_adaptive_triggers", "device"), &Input::has_joy_adaptive_triggers);
ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_off", "device", "axis"), &Input::joy_adaptive_triggers_off);
ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_feedback", "device", "axis", "position", "strength"), &Input::joy_adaptive_triggers_feedback);
ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_weapon", "device", "axis", "start_position", "end_position", "strength"), &Input::joy_adaptive_triggers_weapon);
ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_vibration", "device", "axis", "position", "frequency", "amplitude"), &Input::joy_adaptive_triggers_vibration);
ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_multi_feedback", "device", "axis", "strengths"), &Input::joy_adaptive_triggers_multi_feedback);
ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_slope_feedback", "device", "axis", "start_position", "end_position", "start_strength", "end_strength"), &Input::joy_adaptive_triggers_slope_feedback);
ClassDB::bind_method(D_METHOD("joy_adaptive_triggers_multi_vibration", "device", "axis", "frequency", "amplitudes"), &Input::joy_adaptive_triggers_multi_vibration);
ClassDB::bind_method(D_METHOD("send_joy_packet", "device", "packet"), &Input::send_joy_packet);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might need to make this function not exposed at the moment, because it might be superseded by exposing HIDAPI later.

ClassDB::bind_method(D_METHOD("get_gravity"), &Input::get_gravity);
ClassDB::bind_method(D_METHOD("get_accelerometer"), &Input::get_accelerometer);
ClassDB::bind_method(D_METHOD("get_magnetometer"), &Input::get_magnetometer);
Expand Down Expand Up @@ -1043,6 +1053,282 @@ void Input::vibrate_handheld(int p_duration_ms, float p_amplitude) {
OS::get_singleton()->vibrate_handheld(p_duration_ms, p_amplitude);
}

#define DUALSENSE_BOTH_TRIGGERS (JoyAxis)((int)JoyAxis::TRIGGER_LEFT + (int)JoyAxis::TRIGGER_RIGHT)
#define DUALSENSE_CHECK_TRIGGER \
Joypad *joypad = joy_names.getptr(p_device); \
ERR_FAIL_NULL_V_MSG(joypad, false, "Joypad not connected."); \
ERR_FAIL_NULL_V_MSG(joypad->features, false, "Adaptive triggers are not supported on this platform."); \
ERR_FAIL_COND_V_MSG(p_axis != JoyAxis::TRIGGER_LEFT && p_axis != JoyAxis::TRIGGER_RIGHT && p_axis != DUALSENSE_BOTH_TRIGGERS, false, \
"Invalid trigger axis, please specify JOY_AXIS_TRIGGER_LEFT, JOY_AXIS_TRIGGER_RIGHT, or JOY_AXIS_TRIGGER_LEFT + JOY_AXIS_TRIGGER_RIGHT."); \
if (!joypad->features->has_joy_adaptive_triggers()) { \
return false; \
}

#define DUALSENSE_SET_UP_TRIGGER_EFFECT \
DS5SetStateData state; \
uint8_t *values = state.LeftTriggerFFB; \
memset(&state, 0, sizeof(state)); \
if (p_axis == JoyAxis::TRIGGER_LEFT) { \
state.AllowLeftTriggerFFB = 1; \
} else if (p_axis == JoyAxis::TRIGGER_RIGHT) { \
values = state.RightTriggerFFB; \
state.AllowRightTriggerFFB = 1; \
} else { \
state.AllowLeftTriggerFFB = 1; \
state.AllowRightTriggerFFB = 1; \
}

#define DUALSENSE_SEND_EFFECT \
if (p_axis == DUALSENSE_BOTH_TRIGGERS) { \
memcpy(state.RightTriggerFFB, state.LeftTriggerFFB, sizeof(state.LeftTriggerFFB)); \
} \
return joypad->features->send_joy_packet(&state, sizeof(state));

bool Input::has_joy_adaptive_triggers(int p_device) const {
const Joypad *joypad = joy_names.getptr(p_device);
if (joypad == nullptr || joypad->features == nullptr) {
return false;
}
return joypad->features->has_joy_adaptive_triggers();
}

// Credit for the values-generating code: Copyright (c) 2021-2022 John "Nielk1" Klein
// https://gist.github.com/Nielk1/6d54cc2c00d2201ccb8c2720ad7538db

bool Input::joy_adaptive_triggers_off(int p_device, JoyAxis p_axis) {
_THREAD_SAFE_METHOD_
DUALSENSE_CHECK_TRIGGER;
DUALSENSE_SET_UP_TRIGGER_EFFECT;
values[0] = 0x05;
DUALSENSE_SEND_EFFECT;
}

bool Input::joy_adaptive_triggers_feedback(int p_device, JoyAxis p_axis, int p_position, int p_strength) {
if (p_strength == 0) {
return joy_adaptive_triggers_off(p_device, p_axis);
}

_THREAD_SAFE_METHOD_
DUALSENSE_CHECK_TRIGGER;

ERR_FAIL_COND_V_MSG(p_position < 0 || p_position > 9, false, R"(The value of "position" must be between 0 and 9.)");
ERR_FAIL_COND_V_MSG(p_strength < 0 || p_strength > 8, false, R"(The value of "strength" must be between 0 and 9.)");

DUALSENSE_SET_UP_TRIGGER_EFFECT;

uint8_t force_value = (uint8_t)((p_strength - 1) & 0x07);
uint32_t force_zones = 0;
uint16_t active_zones = 0;
for (int i = p_position; i < 10; i++) {
force_zones |= (uint32_t)(force_value << (3 * i));
active_zones |= (uint16_t)(1 << i);
}

values[0] = 0x21;
values[1] = (uint8_t)((active_zones >> 0) & 0xFF);
values[2] = (uint8_t)((active_zones >> 8) & 0xFF);
values[3] = (uint8_t)((force_zones >> 0) & 0xFF);
values[4] = (uint8_t)((force_zones >> 8) & 0xFF);
values[5] = (uint8_t)((force_zones >> 16) & 0xFF);
values[6] = (uint8_t)((force_zones >> 24) & 0xFF);

DUALSENSE_SEND_EFFECT;
}

bool Input::joy_adaptive_triggers_weapon(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_strength) {
if (p_strength == 0) {
return joy_adaptive_triggers_off(p_device, p_axis);
}

_THREAD_SAFE_METHOD_
DUALSENSE_CHECK_TRIGGER;

ERR_FAIL_COND_V_MSG(p_start_position < 2 || p_start_position > 7, false,
R"(The value of "start_position" must be between 2 and 7.)");
ERR_FAIL_COND_V_MSG(p_end_position <= p_start_position || p_end_position > 8, false,
R"(The value of "end_position" must be between "start_position" + 1 and 8.)");
ERR_FAIL_COND_V_MSG(p_strength < 0 || p_strength > 8, false,
R"(The value of "strength" must be between 0 and 8.)");

DUALSENSE_SET_UP_TRIGGER_EFFECT;

uint16_t start_and_stop_zones = (uint16_t)((1 << p_start_position) | (1 << p_end_position));

values[0] = 0x25;
values[1] = (uint8_t)((start_and_stop_zones >> 0) & 0xFF);
values[2] = (uint8_t)((start_and_stop_zones >> 8) & 0xFF);
values[3] = (uint8_t)(p_strength - 1);

DUALSENSE_SEND_EFFECT;
}

bool Input::joy_adaptive_triggers_vibration(int p_device, JoyAxis p_axis, int p_position, int p_frequency, int p_amplitude) {
if (p_amplitude == 0 || p_frequency == 0) {
return joy_adaptive_triggers_off(p_device, p_axis);
}

_THREAD_SAFE_METHOD_
DUALSENSE_CHECK_TRIGGER;

ERR_FAIL_COND_V_MSG(p_position < 0 || p_position > 9, false, R"(The value of "position" must be between 0 and 9.)");
ERR_FAIL_COND_V_MSG(p_amplitude < 0 || p_amplitude > 8, false, R"(The value of "amplitude" must be between 0 and 8.)");
ERR_FAIL_COND_V_MSG(p_frequency < 0 || p_frequency > 255, false, R"(The value of "frequency" must be between 0 and 255.)");

DUALSENSE_SET_UP_TRIGGER_EFFECT;

uint8_t strength_value = (uint8_t)((p_amplitude - 1) & 0x7);
uint32_t amplitude_zones = 0;
uint16_t active_zones = 0;

for (int i = p_position; i < 10; i++) {
amplitude_zones |= (uint32_t)(strength_value << (3 * i));
active_zones |= (uint16_t)(1 << i);
}

values[0] = 0x26;
values[1] = (uint8_t)((active_zones >> 0) & 0xFF);
values[2] = (uint8_t)((active_zones >> 8) & 0xFF);
values[3] = (uint8_t)((amplitude_zones >> 0) & 0xFF);
values[4] = (uint8_t)((amplitude_zones >> 8) & 0xFF);
values[5] = (uint8_t)((amplitude_zones >> 16) & 0xFF);
values[6] = (uint8_t)((amplitude_zones >> 24) & 0xFF);
values[9] = (uint8_t)p_frequency;

DUALSENSE_SEND_EFFECT;
}

bool Input::joy_adaptive_triggers_multi_feedback(int p_device, JoyAxis p_axis, const PackedInt32Array &p_strengths) {
ERR_FAIL_COND_V_MSG(p_strengths.size() != 10, false, R"(The "strengths" parameter array must have 10 elements.)");

bool have_positive_values = false;
for (int i = 0; i < 10; i++) {
if (p_strengths.get(i) > 0) {
have_positive_values = true;
break;
}
}
if (!have_positive_values) {
return joy_adaptive_triggers_off(p_device, p_axis);
}

_THREAD_SAFE_METHOD_
DUALSENSE_CHECK_TRIGGER;
DUALSENSE_SET_UP_TRIGGER_EFFECT;

uint32_t force_zones = 0;
uint16_t active_zones = 0;

for (int i = 0; i < 10; i++) {
int strength = p_strengths[i];
ERR_FAIL_COND_V_MSG(strength < 0 || strength > 8, false,
vformat(R"(The value of "strengths[%d]" must be between 0 and 8.)", i));

if (strength > 0) {
uint8_t force_value = (uint8_t)((strength - 1) & 0x07);
force_zones |= (uint32_t)(force_value << (3 * i));
active_zones |= (uint16_t)(1 << i);
}
}

values[0] = 0x21;
values[1] = (uint8_t)((active_zones >> 0) & 0xFF);
values[2] = (uint8_t)((active_zones >> 8) & 0xFF);
values[3] = (uint8_t)((force_zones >> 0) & 0xFF);
values[4] = (uint8_t)((force_zones >> 8) & 0xFF);
values[5] = (uint8_t)((force_zones >> 16) & 0xFF);
values[6] = (uint8_t)((force_zones >> 24) & 0xFF);

DUALSENSE_SEND_EFFECT;
}

bool Input::joy_adaptive_triggers_slope_feedback(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_start_strength, int p_end_strength) {
//_THREAD_SAFE_METHOD_ // joy_adaptive_triggers_multi_feedback already handles this
DUALSENSE_CHECK_TRIGGER;

ERR_FAIL_COND_V_MSG(p_start_position < 0 || p_start_position > p_end_position, false,
R"(The value of "start_position" must be between 0 and "end_position".)");
ERR_FAIL_COND_V_MSG(p_end_position <= p_start_position || p_end_position > 9, false,
R"(The value of "end_position" must be between "start_position" + 1 and 9.)");
ERR_FAIL_COND_V_MSG(p_start_strength < 1 || p_start_position > 8, false,
R"(The value of "start_strength" must be between 1 and 8.)");
ERR_FAIL_COND_V_MSG(p_end_strength < 1 || p_end_strength > 8, false,
R"(The value of "end_strength" must be between 1 and 8.)");

PackedInt32Array strengths;
float slope = 1.0f * (p_end_strength - p_start_strength) / (p_end_position - p_start_position);
for (int i = 0; i < 10; i++) {
if (i < p_start_position) {
strengths.append(0);
} else if (i <= p_end_position) {
strengths.append((uint8_t)round(p_start_strength + slope * (i - p_start_position)));
} else {
strengths.append(p_end_strength);
}
}

return joy_adaptive_triggers_multi_feedback(p_device, p_axis, strengths);
}

bool Input::joy_adaptive_triggers_multi_vibration(int p_device, JoyAxis p_axis, int p_frequency, const PackedInt32Array &p_amplitudes) {
ERR_FAIL_COND_V_MSG(p_frequency < 0 || p_frequency > 255, false, R"(The value of "frequency" must be between 0 and 255.)");
ERR_FAIL_COND_V_MSG(p_amplitudes.size() != 10, false, R"(The "amplitudes" parameter array must have 10 elements.)");

bool have_positive_values = false;
for (int i = 0; i < 10; i++) {
if (p_amplitudes.get(i) > 0) {
have_positive_values = true;
break;
}
}
if (!have_positive_values || p_frequency == 0) {
return joy_adaptive_triggers_off(p_device, p_axis);
}

_THREAD_SAFE_METHOD_
DUALSENSE_CHECK_TRIGGER;
DUALSENSE_SET_UP_TRIGGER_EFFECT;

uint32_t strength_zones = 0;
uint16_t active_zones = 0;

for (int i = 0; i < 10; i++) {
int amplitude = p_amplitudes[i];
ERR_FAIL_COND_V_MSG(amplitude < 0 || amplitude > 8, false,
vformat(R"(The value of "amplitude[%d]" must be between 0 and 8.)", i));
if (amplitude > 0) {
uint8_t strength_value = (uint8_t)((amplitude - 1) * 0x07);
strength_zones |= (uint32_t)(strength_value << (3 * i));
active_zones |= (uint16_t)(1 << i);
}
}

values[0] = 0x26;
values[1] = (uint8_t)((active_zones >> 0) & 0xFF);
values[2] = (uint8_t)((active_zones >> 8) & 0xFF);
values[3] = (uint8_t)((strength_zones >> 0) & 0xFF);
values[4] = (uint8_t)((strength_zones >> 8) & 0xFF);
values[5] = (uint8_t)((strength_zones >> 16) & 0xFF);
values[6] = (uint8_t)((strength_zones >> 24) & 0xFF);
values[9] = (uint8_t)p_frequency;

DUALSENSE_SEND_EFFECT;
}

bool Input::send_joy_packet(int p_device, const PackedByteArray &p_packet) {
_THREAD_SAFE_METHOD_

Joypad *joypad = joy_names.getptr(p_device);
ERR_FAIL_NULL_V_MSG(joypad, false, "Joypad not connected.");
ERR_FAIL_NULL_V_MSG(joypad->features, false, "Sending custom joypad packets is not supported on this platform.");

if (p_packet.is_empty()) {
WARN_PRINT("Packet size is 0. Skipping sending the packet.");
return false;
}

return joypad->features->send_joy_packet(p_packet.ptr(), p_packet.size());
}

void Input::set_gravity(const Vector3 &p_gravity) {
_THREAD_SAFE_METHOD_

Expand Down
13 changes: 13 additions & 0 deletions core/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class Input : public Object {

virtual bool has_joy_light() const { return false; }
virtual bool set_joy_light(const Color &p_color) { return false; }
virtual bool has_joy_adaptive_triggers() const { return false; }
virtual bool send_joy_packet(const void *p_data, int p_size) { return false; }
};

static constexpr int32_t JOYPADS_MAX = 16;
Expand Down Expand Up @@ -367,6 +369,17 @@ class Input : public Object {
void stop_joy_vibration(int p_device);
void vibrate_handheld(int p_duration_ms = 500, float p_amplitude = -1.0);

bool has_joy_adaptive_triggers(int p_device) const;
bool joy_adaptive_triggers_off(int p_device, JoyAxis p_axis);
bool joy_adaptive_triggers_feedback(int p_device, JoyAxis p_axis, int p_position, int p_strength);
bool joy_adaptive_triggers_weapon(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_strength);
bool joy_adaptive_triggers_vibration(int p_device, JoyAxis p_axis, int p_position, int p_frequency, int p_amplitude);
bool joy_adaptive_triggers_multi_feedback(int p_device, JoyAxis p_axis, const PackedInt32Array &p_strengths);
bool joy_adaptive_triggers_slope_feedback(int p_device, JoyAxis p_axis, int p_start_position, int p_end_position, int p_start_strength, int p_end_strength);
bool joy_adaptive_triggers_multi_vibration(int p_device, JoyAxis p_axis, int p_frequency, const PackedInt32Array &p_amplitudes);

bool send_joy_packet(int p_device, const PackedByteArray &p_packet);

void set_mouse_position(const Point2 &p_posf);

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