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