summaryrefslogtreecommitdiff
path: root/chromium/device/vr/openxr/openxr_controller.cc
blob: 3d4faaf137dab31d2d54755d79612c74451b4644 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/vr/openxr/openxr_controller.h"

#include <stdint.h>

#include "base/check.h"
#include "base/notreached.h"
#include "base/stl_util.h"
#include "device/gamepad/public/cpp/gamepads.h"
#include "device/vr/openxr/openxr_util.h"
#include "device/vr/util/xr_standard_gamepad_builder.h"
#include "ui/gfx/geometry/quaternion.h"
#include "ui/gfx/transform_util.h"

namespace device {

namespace {

const char* GetStringFromType(OpenXrHandednessType type) {
  switch (type) {
    case OpenXrHandednessType::kLeft:
      return "left";
    case OpenXrHandednessType::kRight:
      return "right";
    case OpenXrHandednessType::kCount:
      NOTREACHED();
      return "";
  }
}

std::string GetTopLevelUserPath(OpenXrHandednessType type) {
  return std::string("/user/hand/") + GetStringFromType(type);
}

}  // namespace

OpenXrController::OpenXrController()
    : description_(nullptr),
      type_(OpenXrHandednessType::kCount),  // COUNT refers to invalid.
      instance_(XR_NULL_HANDLE),
      session_(XR_NULL_HANDLE),
      action_set_(XR_NULL_HANDLE),
      grip_pose_action_{XR_NULL_HANDLE},
      grip_pose_space_(XR_NULL_HANDLE),
      pointer_pose_action_(XR_NULL_HANDLE),
      pointer_pose_space_(XR_NULL_HANDLE),
      interaction_profile_(OpenXrInteractionProfileType::kCount) {}

OpenXrController::~OpenXrController() {
  // We don't need to destroy all of the actions because destroying an
  // action set destroys all actions that are part of the set.

  if (action_set_ != XR_NULL_HANDLE) {
    xrDestroyActionSet(action_set_);
  }
  if (grip_pose_space_ != XR_NULL_HANDLE) {
    xrDestroySpace(grip_pose_space_);
  }
  if (pointer_pose_space_ != XR_NULL_HANDLE) {
    xrDestroySpace(pointer_pose_space_);
  }
}
XrResult OpenXrController::Initialize(
    OpenXrHandednessType type,
    XrInstance instance,
    XrSession session,
    const OpenXRPathHelper* path_helper,
    std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings) {
  DCHECK(bindings);
  type_ = type;
  instance_ = instance;
  session_ = session;
  path_helper_ = path_helper;

  std::string action_set_name =
      std::string(GetStringFromType(type_)) + "_action_set";

  XrActionSetCreateInfo action_set_create_info = {
      XR_TYPE_ACTION_SET_CREATE_INFO};

  errno_t error = strcpy_s(action_set_create_info.actionSetName,
                           base::size(action_set_create_info.actionSetName),
                           action_set_name.c_str());
  DCHECK(!error);
  error = strcpy_s(action_set_create_info.localizedActionSetName,
                   base::size(action_set_create_info.localizedActionSetName),
                   action_set_name.c_str());
  DCHECK(!error);

  RETURN_IF_XR_FAILED(
      xrCreateActionSet(instance_, &action_set_create_info, &action_set_));

  RETURN_IF_XR_FAILED(InitializeControllerActions());

  SuggestBindings(bindings);
  RETURN_IF_XR_FAILED(InitializeControllerSpaces());

  return XR_SUCCESS;
}

XrResult OpenXrController::InitializeControllerActions() {
  RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kTrigger));
  RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kSqueeze));
  RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kTrackpad));
  RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kThumbstick));
  RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kThumbrest));
  RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kButton1));
  RETURN_IF_XR_FAILED(CreateActionsForButton(OpenXrButtonType::kButton2));

  const std::string type_string = GetStringFromType(type_);
  const std::string name_prefix = type_string + "_controller_";
  // Axis Actions
  RETURN_IF_XR_FAILED(
      CreateAction(XR_ACTION_TYPE_VECTOR2F_INPUT, name_prefix + "trackpad_axis",
                   &(axis_action_map_[OpenXrAxisType::kTrackpad])));
  RETURN_IF_XR_FAILED(CreateAction(
      XR_ACTION_TYPE_VECTOR2F_INPUT, name_prefix + "thumbstick_axis",
      &(axis_action_map_[OpenXrAxisType::kThumbstick])));

  // Generic Pose Actions
  RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_POSE_INPUT,
                                   name_prefix + "grip_pose",
                                   &grip_pose_action_));
  RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_POSE_INPUT,
                                   name_prefix + "aim_pose",
                                   &pointer_pose_action_));

  return XR_SUCCESS;
}

XrResult OpenXrController::SuggestBindings(
    std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings) const {
  const std::string binding_prefix = GetTopLevelUserPath(type_);

  for (auto interaction_profile : kOpenXrControllerInteractionProfiles) {
    XrPath interaction_profile_path =
        path_helper_->GetInteractionProfileXrPath(interaction_profile.type);
    RETURN_IF_XR_FAILED(SuggestActionBinding(bindings, interaction_profile_path,
                                             grip_pose_action_,
                                             binding_prefix + "/input/grip"));
    RETURN_IF_XR_FAILED(SuggestActionBinding(bindings, interaction_profile_path,
                                             pointer_pose_action_,
                                             binding_prefix + "/input/aim"));

    const OpenXrButtonPathMap* button_maps;
    size_t button_map_size;
    switch (type_) {
      case OpenXrHandednessType::kLeft:
        button_maps = interaction_profile.left_button_maps;
        button_map_size = interaction_profile.left_button_map_size;
        break;
      case OpenXrHandednessType::kRight:
        button_maps = interaction_profile.right_button_maps;
        button_map_size = interaction_profile.right_button_map_size;
        break;
      case OpenXrHandednessType::kCount:
        NOTREACHED() << "Controller can only be left or right";
        return XR_ERROR_VALIDATION_FAILURE;
    }

    for (size_t button_map_index = 0; button_map_index < button_map_size;
         button_map_index++) {
      const OpenXrButtonPathMap& cur_button_map = button_maps[button_map_index];
      OpenXrButtonType button_type = cur_button_map.type;
      for (size_t action_map_index = 0;
           action_map_index < cur_button_map.action_map_size;
           action_map_index++) {
        const OpenXrButtonActionPathMap& cur_action_map =
            cur_button_map.action_maps[action_map_index];
        RETURN_IF_XR_FAILED(SuggestActionBinding(
            bindings, interaction_profile_path,
            button_action_map_.at(button_type).at(cur_action_map.type),
            binding_prefix + cur_action_map.path));
      }
    }

    for (size_t axis_map_index = 0;
         axis_map_index < interaction_profile.axis_map_size; axis_map_index++) {
      const OpenXrAxisPathMap& cur_axis_map =
          interaction_profile.axis_maps[axis_map_index];
      RETURN_IF_XR_FAILED(
          SuggestActionBinding(bindings, interaction_profile_path,
                               axis_action_map_.at(cur_axis_map.type),
                               binding_prefix + cur_axis_map.path));
    }
  }

  return XR_SUCCESS;
}

XrResult OpenXrController::InitializeControllerSpaces() {
  RETURN_IF_XR_FAILED(CreateActionSpace(grip_pose_action_, &grip_pose_space_));

  RETURN_IF_XR_FAILED(
      CreateActionSpace(pointer_pose_action_, &pointer_pose_space_));

  return XR_SUCCESS;
}

uint32_t OpenXrController::GetId() const {
  return static_cast<uint32_t>(type_);
}

device::mojom::XRHandedness OpenXrController::GetHandness() const {
  switch (type_) {
    case OpenXrHandednessType::kLeft:
      return device::mojom::XRHandedness::LEFT;
    case OpenXrHandednessType::kRight:
      return device::mojom::XRHandedness::RIGHT;
    case OpenXrHandednessType::kCount:
      // LEFT controller and RIGHT controller are currently the only supported
      // controllers. In the future, other controllers such as sound (which
      // does not have a handedness) will be added here.
      NOTREACHED();
      return device::mojom::XRHandedness::NONE;
  }
}

mojom::XRInputSourceDescriptionPtr OpenXrController::GetDescription(
    XrTime predicted_display_time) {
  // Description only need to be set once unless interaction profiles changes.
  if (!description_) {
    // UpdateInteractionProfile() can not be called inside Initialize() function
    // because XrGetCurrentInteractionProfile can't be called before
    // xrSuggestInteractionProfileBindings getting called.
    if (XR_FAILED(UpdateInteractionProfile())) {
      return nullptr;
    }
    description_ = device::mojom::XRInputSourceDescription::New();
    description_->handedness = GetHandness();
    description_->target_ray_mode = device::mojom::XRTargetRayMode::POINTING;
    description_->profiles =
        path_helper_->GetInputProfiles(interaction_profile_);
  }

  if (!description_->input_from_pointer) {
    description_->input_from_pointer =
        GetPointerFromGripTransform(predicted_display_time);
  }

  return description_.Clone();
}

base::Optional<GamepadButton> OpenXrController::GetButton(
    OpenXrButtonType type) const {
  GamepadButton ret;
  // Button should at least have one of the three actions;
  bool has_value = false;

  DCHECK(button_action_map_.count(type) == 1);
  auto button = button_action_map_.at(type);
  XrActionStateBoolean press_state_bool = {XR_TYPE_ACTION_STATE_BOOLEAN};
  if (XR_SUCCEEDED(QueryState(button[OpenXrButtonActionType::kPress],
                              &press_state_bool)) &&
      press_state_bool.isActive) {
    ret.pressed = press_state_bool.currentState;
    has_value = true;
  } else {
    ret.pressed = false;
  }

  XrActionStateBoolean touch_state_bool = {XR_TYPE_ACTION_STATE_BOOLEAN};
  if (XR_SUCCEEDED(QueryState(button[OpenXrButtonActionType::kTouch],
                              &touch_state_bool)) &&
      touch_state_bool.isActive) {
    ret.touched = touch_state_bool.currentState;
    has_value = true;
  } else {
    ret.touched = ret.pressed;
  }

  XrActionStateFloat value_state_float = {XR_TYPE_ACTION_STATE_FLOAT};
  if (XR_SUCCEEDED(QueryState(button[OpenXrButtonActionType::kValue],
                              &value_state_float)) &&
      value_state_float.isActive) {
    ret.value = value_state_float.currentState;
    has_value = true;
  } else {
    ret.value = ret.pressed ? 1.0 : 0.0;
  }

  if (!has_value) {
    return base::nullopt;
  }

  return ret;
}

std::vector<double> OpenXrController::GetAxis(OpenXrAxisType type) const {
  XrActionStateVector2f axis_state_v2f = {XR_TYPE_ACTION_STATE_VECTOR2F};
  if (XR_FAILED(QueryState(axis_action_map_.at(type), &axis_state_v2f)) ||
      !axis_state_v2f.isActive) {
    return {};
  }

  return {axis_state_v2f.currentState.x, axis_state_v2f.currentState.y};
}

XrResult OpenXrController::UpdateInteractionProfile() {
  XrPath top_level_user_path;

  std::string top_level_user_path_string = GetTopLevelUserPath(type_);
  RETURN_IF_XR_FAILED(xrStringToPath(
      instance_, top_level_user_path_string.c_str(), &top_level_user_path));

  XrInteractionProfileState interaction_profile_state = {
      XR_TYPE_INTERACTION_PROFILE_STATE};
  RETURN_IF_XR_FAILED(xrGetCurrentInteractionProfile(
      session_, top_level_user_path, &interaction_profile_state));
  interaction_profile_ = path_helper_->GetInputProfileType(
      interaction_profile_state.interactionProfile);

  if (description_) {
    // TODO(crbug.com/1006072):
    // Query USB vendor and product ID From OpenXR.
    description_->profiles =
        path_helper_->GetInputProfiles(interaction_profile_);
  }
  return XR_SUCCESS;
}

base::Optional<gfx::Transform> OpenXrController::GetMojoFromGripTransform(
    XrTime predicted_display_time,
    XrSpace local_space,
    bool* emulated_position) const {
  return GetTransformFromSpaces(predicted_display_time, grip_pose_space_,
                                local_space, emulated_position);
}

base::Optional<gfx::Transform> OpenXrController::GetPointerFromGripTransform(
    XrTime predicted_display_time) const {
  bool emulated_position;
  return GetTransformFromSpaces(predicted_display_time, pointer_pose_space_,
                                grip_pose_space_, &emulated_position);
}

base::Optional<gfx::Transform> OpenXrController::GetTransformFromSpaces(
    XrTime predicted_display_time,
    XrSpace target,
    XrSpace origin,
    bool* emulated_position) const {
  XrSpaceLocation location = {XR_TYPE_SPACE_LOCATION};
  // emulated_position indicates when there is a fallback from a fully-tracked
  // (i.e. 6DOF) type case to some form of orientation-only type tracking
  // (i.e. 3DOF/IMU type sensors)
  // Thus we have to make sure orientation is tracked.
  // Valid Bit only indicates it's either tracked or emulated, we have to check
  // for XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT to make sure orientation is
  // tracked.
  if (FAILED(
          xrLocateSpace(target, origin, predicted_display_time, &location)) ||
      !(location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_TRACKED_BIT) ||
      !(location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT)) {
    return base::nullopt;
  }

  // Convert the orientation and translation given by runtime into a
  // transformation matrix.
  gfx::DecomposedTransform decomp;
  decomp.quaternion =
      gfx::Quaternion(location.pose.orientation.x, location.pose.orientation.y,
                      location.pose.orientation.z, location.pose.orientation.w);
  decomp.translate[0] = location.pose.position.x;
  decomp.translate[1] = location.pose.position.y;
  decomp.translate[2] = location.pose.position.z;

  *emulated_position = true;
  if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) {
    *emulated_position = false;
  }

  return gfx::ComposeTransform(decomp);
}

XrResult OpenXrController::CreateActionsForButton(
    OpenXrButtonType button_type) {
  const std::string type_string = GetStringFromType(type_);
  std::string name_prefix = type_string + "_controller_";

  switch (button_type) {
    case OpenXrButtonType::kTrigger:
      name_prefix += "trigger_";
      break;
    case OpenXrButtonType::kSqueeze:
      name_prefix += "squeeze_";
      break;
    case OpenXrButtonType::kTrackpad:
      name_prefix += "trackpad_";
      break;
    case OpenXrButtonType::kThumbstick:
      name_prefix += "thumbstick_";
      break;
    case OpenXrButtonType::kThumbrest:
      name_prefix += "thumbrest_";
      break;
    case OpenXrButtonType::kButton1:
      name_prefix += "upper_button_";
      break;
    case OpenXrButtonType::kButton2:
      name_prefix += "lower_button_";
      break;
  }

  std::unordered_map<OpenXrButtonActionType, XrAction>& cur_button =
      button_action_map_[button_type];
  XrAction new_action;
  RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_BOOLEAN_INPUT,
                                   name_prefix + "button_press", &new_action));
  cur_button[OpenXrButtonActionType::kPress] = new_action;
  RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_FLOAT_INPUT,
                                   name_prefix + "button_value", &new_action));
  cur_button[OpenXrButtonActionType::kValue] = new_action;
  RETURN_IF_XR_FAILED(CreateAction(XR_ACTION_TYPE_BOOLEAN_INPUT,
                                   name_prefix + "button_touch", &new_action));
  cur_button[OpenXrButtonActionType::kTouch] = new_action;
  return XR_SUCCESS;
}

XrResult OpenXrController::CreateAction(XrActionType type,
                                        const std::string& action_name,
                                        XrAction* action) {
  DCHECK(action);
  XrActionCreateInfo action_create_info = {XR_TYPE_ACTION_CREATE_INFO};
  action_create_info.actionType = type;

  errno_t error =
      strcpy_s(action_create_info.actionName,
               base::size(action_create_info.actionName), action_name.data());
  DCHECK(error == 0);
  error = strcpy_s(action_create_info.localizedActionName,
                   base::size(action_create_info.localizedActionName),
                   action_name.data());
  DCHECK(error == 0);
  return xrCreateAction(action_set_, &action_create_info, action);
}

XrResult OpenXrController::SuggestActionBinding(
    std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings,
    XrPath interaction_profile_path,
    XrAction action,
    std::string binding_string) const {
  XrPath binding_path;
  // make sure all actions we try to suggest binding are initialized.
  DCHECK(action != XR_NULL_HANDLE);
  RETURN_IF_XR_FAILED(
      xrStringToPath(instance_, binding_string.c_str(), &binding_path));
  (*bindings)[interaction_profile_path].push_back({action, binding_path});

  return XR_SUCCESS;
}

XrResult OpenXrController::CreateActionSpace(XrAction action, XrSpace* space) {
  DCHECK(space);
  XrActionSpaceCreateInfo action_space_create_info = {};
  action_space_create_info.type = XR_TYPE_ACTION_SPACE_CREATE_INFO;
  action_space_create_info.action = action;
  action_space_create_info.subactionPath = XR_NULL_PATH;
  action_space_create_info.poseInActionSpace = PoseIdentity();
  return xrCreateActionSpace(session_, &action_space_create_info, space);
}

}  // namespace device