1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "device/vr/openvr/openvr_gamepad_helper.h"
6 #include "device/vr/openvr/openvr_api_wrapper.h"
7
8 #include <memory>
9 #include <unordered_set>
10
11 #include "base/compiler_specific.h"
12 #include "base/logging.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "device/gamepad/public/cpp/gamepads.h"
17 #include "device/vr/util/gamepad_builder.h"
18 #include "device/vr/util/xr_standard_gamepad_builder.h"
19 #include "device/vr/vr_device.h"
20 #include "third_party/openvr/src/headers/openvr.h"
21 #include "ui/gfx/transform.h"
22 #include "ui/gfx/transform_util.h"
23
24 namespace device {
25
26 namespace {
27
28 constexpr double kJoystickDeadzone = 0.16;
29
TryGetGamepadButton(const vr::VRControllerState_t & controller_state,uint64_t supported_buttons,vr::EVRButtonId button_id,GamepadButton * button)30 bool TryGetGamepadButton(const vr::VRControllerState_t& controller_state,
31 uint64_t supported_buttons,
32 vr::EVRButtonId button_id,
33 GamepadButton* button) {
34 uint64_t button_mask = vr::ButtonMaskFromId(button_id);
35 if ((supported_buttons & button_mask) != 0) {
36 bool button_pressed = (controller_state.ulButtonPressed & button_mask) != 0;
37 bool button_touched = (controller_state.ulButtonTouched & button_mask) != 0;
38 button->touched = button_touched || button_pressed;
39 button->pressed = button_pressed;
40 button->value = button_pressed ? 1.0 : 0.0;
41 return true;
42 }
43
44 return false;
45 }
46
GetAxisId(uint32_t axis_offset)47 vr::EVRButtonId GetAxisId(uint32_t axis_offset) {
48 return static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + axis_offset);
49 }
50
GetAxesButtons(vr::IVRSystem * vr_system,const vr::VRControllerState_t & controller_state,uint64_t supported_buttons,uint32_t controller_id)51 std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> GetAxesButtons(
52 vr::IVRSystem* vr_system,
53 const vr::VRControllerState_t& controller_state,
54 uint64_t supported_buttons,
55 uint32_t controller_id) {
56 std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> button_data_map;
57
58 for (uint32_t j = 0; j < vr::k_unControllerStateAxisCount; ++j) {
59 int32_t axis_type = vr_system->GetInt32TrackedDeviceProperty(
60 controller_id,
61 static_cast<vr::TrackedDeviceProperty>(vr::Prop_Axis0Type_Int32 + j));
62
63 GamepadBuilder::ButtonData button_data;
64
65 // Invert the y axis because -1 is up in the Gamepad API but down in OpenVR.
66 double x_axis = controller_state.rAxis[j].x;
67 double y_axis = -controller_state.rAxis[j].y;
68
69 if (axis_type == vr::k_eControllerAxis_Joystick) {
70 button_data.type = GamepadBuilder::ButtonData::Type::kThumbstick;
71
72 // We only want to apply the deadzone to joysticks, since various
73 // runtimes may not have already done that, but touchpads should
74 // be fine.
75 x_axis = std::fabs(x_axis) < kJoystickDeadzone ? 0 : x_axis;
76 y_axis = std::fabs(y_axis) < kJoystickDeadzone ? 0 : y_axis;
77 } else if (axis_type == vr::k_eControllerAxis_TrackPad) {
78 button_data.type = GamepadBuilder::ButtonData::Type::kTouchpad;
79 }
80
81 switch (axis_type) {
82 case vr::k_eControllerAxis_Joystick:
83 case vr::k_eControllerAxis_TrackPad: {
84 button_data.x_axis = x_axis;
85 button_data.y_axis = y_axis;
86 vr::EVRButtonId button_id = GetAxisId(j);
87
88 // Even if the button associated with the axis isn't supported, if we
89 // have valid axis data, we should still send that up. Since the spec
90 // expects buttons with axes, then we will add a dummy button to match
91 // the axes.
92 GamepadButton button;
93 if (TryGetGamepadButton(controller_state, supported_buttons, button_id,
94 &button)) {
95 button_data.touched = button.touched;
96 button_data.pressed = button.pressed;
97 button_data.value = button.value;
98 } else {
99 button_data.pressed = false;
100 button_data.value = 0.0;
101 button_data.touched =
102 (std::fabs(x_axis) > 0 || std::fabs(y_axis) > 0);
103 }
104
105 button_data_map[button_id] = button_data;
106 } break;
107 case vr::k_eControllerAxis_Trigger: {
108 GamepadButton button;
109 GamepadBuilder::ButtonData button_data;
110 vr::EVRButtonId button_id = GetAxisId(j);
111 if (TryGetGamepadButton(controller_state, supported_buttons, button_id,
112 &button)) {
113 button_data.touched = button.touched;
114 button_data.pressed = button.pressed;
115 button_data.value = x_axis;
116 button_data_map[button_id] = button_data;
117 }
118 } break;
119 }
120 }
121
122 return button_data_map;
123 }
124
125 constexpr std::array<vr::EVRButtonId, 5> kWebXRButtonOrder = {
126 vr::k_EButton_A, vr::k_EButton_DPad_Left, vr::k_EButton_DPad_Up,
127 vr::k_EButton_DPad_Right, vr::k_EButton_DPad_Down,
128 };
129
130 // To make sure this string fits the requirements of the WebXR spec, separate
131 // words/tokens are separated by "-" instead of whitespace and convert it to
132 // lowercase.
FixupProfileString(const std::string & name)133 std::string FixupProfileString(const std::string& name) {
134 std::vector<std::string> tokens =
135 base::SplitString(name, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
136 base::SPLIT_WANT_NONEMPTY);
137 std::string result = base::JoinString(tokens, "-");
138 return base::ToLowerASCII(result);
139 }
140
141 } // namespace
142
143 // Helper classes and WebXR Getters
144 class OpenVRGamepadBuilder : public XRStandardGamepadBuilder {
145 public:
146 enum class AxesRequirement {
147 kOptional = 0,
148 kRequireBoth = 1,
149 };
150
OpenVRGamepadBuilder(vr::IVRSystem * vr_system,uint32_t controller_id,vr::VRControllerState_t controller_state,mojom::XRHandedness handedness)151 OpenVRGamepadBuilder(vr::IVRSystem* vr_system,
152 uint32_t controller_id,
153 vr::VRControllerState_t controller_state,
154 mojom::XRHandedness handedness)
155 : XRStandardGamepadBuilder(handedness),
156 controller_state_(controller_state) {
157 supported_buttons_ = vr_system->GetUint64TrackedDeviceProperty(
158 controller_id, vr::Prop_SupportedButtons_Uint64);
159
160 axes_data_ = GetAxesButtons(vr_system, controller_state_,
161 supported_buttons_, controller_id);
162
163 base::Optional<GamepadBuilder::ButtonData> primary_button =
164 TryGetAxesOrTriggerButton(vr::k_EButton_SteamVR_Trigger);
165
166 if (!primary_button) {
167 return;
168 }
169
170 SetPrimaryButton(primary_button.value());
171
172 base::Optional<GamepadButton> secondary_button =
173 TryGetButton(vr::k_EButton_Grip);
174 if (secondary_button) {
175 SetSecondaryButton(secondary_button.value());
176 }
177
178 base::Optional<GamepadBuilder::ButtonData> touchpad_data =
179 TryGetNextUnusedButtonOfType(
180 GamepadBuilder::ButtonData::Type::kTouchpad);
181 if (touchpad_data) {
182 SetTouchpadData(touchpad_data.value());
183 }
184
185 base::Optional<GamepadBuilder::ButtonData> thumbstick_data =
186 TryGetNextUnusedButtonOfType(
187 GamepadBuilder::ButtonData::Type::kThumbstick);
188 if (thumbstick_data) {
189 SetThumbstickData(thumbstick_data.value());
190 }
191
192 // Now that all of the xr-standard reserved buttons have been filled in, we
193 // add the rest of the buttons in order of decreasing importance.
194 // First add regular buttons.
195 for (const auto& id : kWebXRButtonOrder) {
196 base::Optional<GamepadButton> button = TryGetButton(id);
197 if (button) {
198 AddOptionalButtonData(button.value());
199 }
200 }
201
202 // Finally, add any remaining axis buttons (triggers/josysticks/touchpads)
203 AddRemainingTriggersAndAxes();
204
205 // Find out the model and manufacturer names in case the caller wants this
206 // information for the input profiles array.
207 std::string model =
208 GetOpenVRString(vr_system, vr::Prop_ModelNumber_String, controller_id);
209 std::string manufacturer = GetOpenVRString(
210 vr_system, vr::Prop_ManufacturerName_String, controller_id);
211
212 UpdateProfiles(manufacturer, model);
213 }
214
215 ~OpenVRGamepadBuilder() override = default;
216
GetProfiles() const217 std::vector<std::string> GetProfiles() const { return profiles_; }
218
219 private:
UpdateProfiles(const std::string & manufacturer,const std::string & model)220 void UpdateProfiles(const std::string& manufacturer,
221 const std::string& model) {
222 // Per the WebXR spec, the first entry in the profiles array should be the
223 // most specific one.
224 std::string name =
225 FixupProfileString(manufacturer) + "-" + FixupProfileString(model);
226 profiles_.push_back(name);
227
228 // Also record information about what this controller actually does in a
229 // more general sense. The controller is guaranteed to at least have a
230 // trigger if we get here.
231 std::string capabilities = "generic-trigger";
232 if (HasSecondaryButton()) {
233 capabilities += "-squeeze";
234 }
235 if (HasTouchpad()) {
236 capabilities += "-touchpad";
237 }
238 if (HasThumbstick()) {
239 capabilities += "-thumbstick";
240 }
241 profiles_.push_back(capabilities);
242 }
243
TryGetAxesOrTriggerButton(vr::EVRButtonId button_id,AxesRequirement requirement=AxesRequirement::kOptional)244 base::Optional<GamepadBuilder::ButtonData> TryGetAxesOrTriggerButton(
245 vr::EVRButtonId button_id,
246 AxesRequirement requirement = AxesRequirement::kOptional) {
247 if (!IsInAxesData(button_id))
248 return base::nullopt;
249
250 bool require_axes = (requirement == AxesRequirement::kRequireBoth);
251 if (require_axes &&
252 axes_data_[button_id].type == GamepadBuilder::ButtonData::Type::kButton)
253 return base::nullopt;
254
255 used_axes_.insert(button_id);
256 return axes_data_[button_id];
257 }
258
TryGetNextUnusedButtonOfType(GamepadBuilder::ButtonData::Type type)259 base::Optional<GamepadBuilder::ButtonData> TryGetNextUnusedButtonOfType(
260 GamepadBuilder::ButtonData::Type type) {
261 for (const auto& axes_data_pair : axes_data_) {
262 vr::EVRButtonId button_id = axes_data_pair.first;
263 if (IsUsed(button_id))
264 continue;
265
266 if (axes_data_pair.second.type != type)
267 continue;
268
269 return TryGetAxesOrTriggerButton(button_id,
270 AxesRequirement::kRequireBoth);
271 }
272
273 return base::nullopt;
274 }
275
TryGetButton(vr::EVRButtonId button_id)276 base::Optional<GamepadButton> TryGetButton(vr::EVRButtonId button_id) {
277 GamepadButton button;
278 if (TryGetGamepadButton(controller_state_, supported_buttons_, button_id,
279 &button)) {
280 return button;
281 }
282
283 return base::nullopt;
284 }
285
286 // This will add any remaining unused values from axes_data to the gamepad.
287 // Returns a bool indicating whether any additional axes were added.
AddRemainingTriggersAndAxes()288 void AddRemainingTriggersAndAxes() {
289 for (const auto& axes_data_pair : axes_data_) {
290 if (!IsUsed(axes_data_pair.first)) {
291 AddOptionalButtonData(axes_data_pair.second);
292 }
293 }
294 }
295
IsUsed(vr::EVRButtonId button_id)296 bool IsUsed(vr::EVRButtonId button_id) {
297 auto it = used_axes_.find(button_id);
298 return it != used_axes_.end();
299 }
300
IsInAxesData(vr::EVRButtonId button_id)301 bool IsInAxesData(vr::EVRButtonId button_id) {
302 auto it = axes_data_.find(button_id);
303 return it != axes_data_.end();
304 }
305
306 const vr::VRControllerState_t controller_state_;
307 uint64_t supported_buttons_;
308 std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> axes_data_;
309 std::unordered_set<vr::EVRButtonId> used_axes_;
310 std::vector<std::string> profiles_;
311
312 DISALLOW_COPY_AND_ASSIGN(OpenVRGamepadBuilder);
313 };
314
315 OpenVRInputSourceData::OpenVRInputSourceData() = default;
316 OpenVRInputSourceData::~OpenVRInputSourceData() = default;
317 OpenVRInputSourceData::OpenVRInputSourceData(
318 const OpenVRInputSourceData& other) = default;
319
GetXRInputSourceData(vr::IVRSystem * vr_system,uint32_t controller_id,vr::VRControllerState_t controller_state,mojom::XRHandedness handedness)320 OpenVRInputSourceData OpenVRGamepadHelper::GetXRInputSourceData(
321 vr::IVRSystem* vr_system,
322 uint32_t controller_id,
323 vr::VRControllerState_t controller_state,
324 mojom::XRHandedness handedness) {
325 OpenVRGamepadBuilder builder(vr_system, controller_id, controller_state,
326 handedness);
327
328 OpenVRInputSourceData data;
329 data.gamepad = builder.GetGamepad();
330 data.profiles = builder.GetProfiles();
331 return data;
332 }
333
334 } // namespace device
335