XRInputSource.cpp (14942B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/XRInputSource.h" 8 9 #include "VRDisplayClient.h" 10 #include "XRInputSpace.h" 11 #include "XRNativeOriginTracker.h" 12 #include "XRNativeOriginViewer.h" 13 #include "mozilla/dom/Gamepad.h" 14 #include "mozilla/dom/GamepadManager.h" 15 #include "mozilla/dom/XRInputSourceEvent.h" 16 17 namespace mozilla::dom { 18 19 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(XRInputSource, mParent, mTargetRaySpace, 20 mGripSpace, mGamepad) 21 22 // Follow the controller profile ids from 23 // https://github.com/immersive-web/webxr-input-profiles. 24 nsTArray<nsString> GetInputSourceProfile(gfx::VRControllerType aType) { 25 nsTArray<nsString> profile; 26 nsString id; 27 28 switch (aType) { 29 case gfx::VRControllerType::HTCVive: 30 id.AssignLiteral("htc-vive"); 31 profile.AppendElement(id); 32 id.AssignLiteral("generic-trigger-squeeze-touchpad"); 33 profile.AppendElement(id); 34 break; 35 case gfx::VRControllerType::HTCViveCosmos: 36 id.AssignLiteral("htc-vive-cosmos"); 37 profile.AppendElement(id); 38 id.AssignLiteral("generic-trigger-squeeze-thumbstick"); 39 profile.AppendElement(id); 40 break; 41 case gfx::VRControllerType::HTCViveFocus: 42 id.AssignLiteral("htc-vive-focus"); 43 profile.AppendElement(id); 44 id.AssignLiteral("generic-trigger-touchpad"); 45 profile.AppendElement(id); 46 break; 47 case gfx::VRControllerType::HTCViveFocusPlus: 48 id.AssignLiteral("htc-vive-focus-plus"); 49 profile.AppendElement(id); 50 id.AssignLiteral("generic-trigger-squeeze-touchpad"); 51 profile.AppendElement(id); 52 break; 53 case gfx::VRControllerType::MSMR: 54 id.AssignLiteral("microsoft-mixed-reality"); 55 profile.AppendElement(id); 56 id.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick"); 57 profile.AppendElement(id); 58 break; 59 case gfx::VRControllerType::ValveIndex: 60 id.AssignLiteral("valve-index"); 61 profile.AppendElement(id); 62 id.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick"); 63 profile.AppendElement(id); 64 break; 65 case gfx::VRControllerType::OculusGo: 66 id.AssignLiteral("oculus-go"); 67 profile.AppendElement(id); 68 id.AssignLiteral("generic-trigger-touchpad"); 69 profile.AppendElement(id); 70 break; 71 case gfx::VRControllerType::OculusTouch: 72 id.AssignLiteral("oculus-touch"); 73 profile.AppendElement(id); 74 id.AssignLiteral("generic-trigger-squeeze-thumbstick"); 75 profile.AppendElement(id); 76 break; 77 case gfx::VRControllerType::OculusTouch2: 78 id.AssignLiteral("oculus-touch-v2"); 79 profile.AppendElement(id); 80 id.AssignLiteral("oculus-touch"); 81 profile.AppendElement(id); 82 id.AssignLiteral("generic-trigger-squeeze-thumbstick"); 83 profile.AppendElement(id); 84 break; 85 case gfx::VRControllerType::OculusTouch3: 86 id.AssignLiteral("oculus-touch-v3"); 87 profile.AppendElement(id); 88 id.AssignLiteral("oculus-touch-v2"); 89 profile.AppendElement(id); 90 id.AssignLiteral("oculus-touch"); 91 profile.AppendElement(id); 92 id.AssignLiteral("generic-trigger-squeeze-thumbstick"); 93 profile.AppendElement(id); 94 break; 95 case gfx::VRControllerType::PicoGaze: 96 id.AssignLiteral("pico-gaze"); 97 profile.AppendElement(id); 98 id.AssignLiteral("generic-button"); 99 profile.AppendElement(id); 100 break; 101 case gfx::VRControllerType::PicoG2: 102 id.AssignLiteral("pico-g2"); 103 profile.AppendElement(id); 104 id.AssignLiteral("generic-trigger-touchpad"); 105 profile.AppendElement(id); 106 break; 107 case gfx::VRControllerType::PicoNeo2: 108 id.AssignLiteral("pico-neo2"); 109 profile.AppendElement(id); 110 id.AssignLiteral("generic-trigger-squeeze-thumbstick"); 111 profile.AppendElement(id); 112 break; 113 default: 114 NS_WARNING("Unsupported XR input source profile.\n"); 115 break; 116 } 117 return profile; 118 } 119 120 XRInputSource::XRInputSource(nsISupports* aParent) 121 : mParent(aParent), 122 mGamepad(nullptr), 123 mIndex(-1), 124 mSelectAction(ActionState::ActionState_Released), 125 mSqueezeAction(ActionState::ActionState_Released) {} 126 127 XRInputSource::~XRInputSource() { 128 mTargetRaySpace = nullptr; 129 mGripSpace = nullptr; 130 mGamepad = nullptr; 131 } 132 133 JSObject* XRInputSource::WrapObject(JSContext* aCx, 134 JS::Handle<JSObject*> aGivenProto) { 135 return XRInputSource_Binding::Wrap(aCx, this, aGivenProto); 136 } 137 138 XRHandedness XRInputSource::Handedness() { return mHandedness; } 139 140 XRTargetRayMode XRInputSource::TargetRayMode() { return mTargetRayMode; } 141 142 XRSpace* XRInputSource::TargetRaySpace() { return mTargetRaySpace; } 143 144 XRSpace* XRInputSource::GetGripSpace() { return mGripSpace; } 145 146 void XRInputSource::GetProfiles(nsTArray<nsString>& aResult) { 147 aResult = mProfiles.Clone(); 148 } 149 150 Gamepad* XRInputSource::GetGamepad() { return mGamepad; } 151 152 void XRInputSource::Setup(XRSession* aSession, uint32_t aIndex) { 153 MOZ_ASSERT(aSession); 154 gfx::VRDisplayClient* displayClient = aSession->GetDisplayClient(); 155 if (!displayClient) { 156 MOZ_ASSERT(displayClient); 157 return; 158 } 159 const gfx::VRDisplayInfo& displayInfo = displayClient->GetDisplayInfo(); 160 const gfx::VRControllerState& controllerState = 161 displayInfo.mControllerState[aIndex]; 162 MOZ_ASSERT(controllerState.controllerName[0] != '\0'); 163 164 mProfiles = GetInputSourceProfile(controllerState.type); 165 mHandedness = XRHandedness::None; 166 switch (controllerState.hand) { 167 case GamepadHand::_empty: 168 mHandedness = XRHandedness::None; 169 break; 170 case GamepadHand::Left: 171 mHandedness = XRHandedness::Left; 172 break; 173 case GamepadHand::Right: 174 mHandedness = XRHandedness::Right; 175 break; 176 default: 177 MOZ_ASSERT(false && "Unknown GamepadHand type."); 178 break; 179 } 180 181 RefPtr<XRNativeOrigin> nativeOriginTargetRay = nullptr; 182 mTargetRayMode = XRTargetRayMode::Tracked_pointer; 183 switch (controllerState.targetRayMode) { 184 case gfx::TargetRayMode::Gaze: 185 mTargetRayMode = XRTargetRayMode::Gaze; 186 nativeOriginTargetRay = new XRNativeOriginViewer(displayClient); 187 break; 188 case gfx::TargetRayMode::TrackedPointer: 189 mTargetRayMode = XRTargetRayMode::Tracked_pointer; 190 // We use weak pointers of poses in XRNativeOriginTracker to sync their 191 // data internally. 192 nativeOriginTargetRay = 193 new XRNativeOriginTracker(&controllerState.targetRayPose); 194 break; 195 case gfx::TargetRayMode::Screen: 196 mTargetRayMode = XRTargetRayMode::Screen; 197 break; 198 default: 199 MOZ_ASSERT(false && "Undefined TargetRayMode type."); 200 break; 201 } 202 203 mTargetRaySpace = new XRInputSpace(aSession->GetParentObject(), aSession, 204 nativeOriginTargetRay, aIndex); 205 206 const uint32_t gamepadHandleValue = 207 displayInfo.mDisplayID * gfx::kVRControllerMaxCount + aIndex; 208 209 const GamepadHandle gamepadHandle{gamepadHandleValue, GamepadHandleKind::VR}; 210 211 mGamepad = new Gamepad(mParent, NS_ConvertASCIItoUTF16(""), -1, gamepadHandle, 212 GamepadMappingType::Xr_standard, controllerState.hand, 213 controllerState.numButtons, controllerState.numAxes, 214 controllerState.numHaptics, 0, 0); 215 mIndex = aIndex; 216 217 if (!mGripSpace) { 218 CreateGripSpace(aSession, controllerState); 219 } 220 } 221 222 void XRInputSource::SetGamepadIsConnected(bool aConnected, 223 XRSession* aSession) { 224 mGamepad->SetConnected(aConnected); 225 MOZ_ASSERT(aSession); 226 227 if (!aConnected) { 228 if (mSelectAction != ActionState::ActionState_Released) { 229 DispatchEvent(u"selectend"_ns, aSession); 230 mSelectAction = ActionState::ActionState_Released; 231 } 232 if (mSqueezeAction != ActionState::ActionState_Released) { 233 DispatchEvent(u"squeezeend"_ns, aSession); 234 mSqueezeAction = ActionState::ActionState_Released; 235 } 236 } 237 } 238 239 void XRInputSource::Update(XRSession* aSession) { 240 MOZ_ASSERT(aSession && mIndex >= 0 && mGamepad); 241 242 gfx::VRDisplayClient* displayClient = aSession->GetDisplayClient(); 243 if (!displayClient) { 244 MOZ_ASSERT(displayClient); 245 return; 246 } 247 const gfx::VRDisplayInfo& displayInfo = displayClient->GetDisplayInfo(); 248 const gfx::VRControllerState& controllerState = 249 displayInfo.mControllerState[mIndex]; 250 MOZ_ASSERT(controllerState.controllerName[0] != '\0'); 251 252 // OculusVR and OpenVR controllers need to wait until 253 // update functions to assign GamepadCapabilityFlags::Cap_GripSpacePosition 254 // flag. 255 if (!mGripSpace) { 256 CreateGripSpace(aSession, controllerState); 257 } 258 259 // Update button values. 260 nsTArray<RefPtr<GamepadButton>> buttons; 261 mGamepad->GetButtons(buttons); 262 for (uint32_t i = 0; i < buttons.Length(); ++i) { 263 const bool pressed = controllerState.buttonPressed & (1ULL << i); 264 const bool touched = controllerState.buttonTouched & (1ULL << i); 265 266 if (buttons[i]->Pressed() != pressed || buttons[i]->Touched() != touched || 267 buttons[i]->Value() != controllerState.triggerValue[i]) { 268 mGamepad->SetButton(i, pressed, touched, controllerState.triggerValue[i]); 269 } 270 } 271 // Update axis values. 272 nsTArray<double> axes; 273 mGamepad->GetAxes(axes); 274 for (uint32_t i = 0; i < axes.Length(); ++i) { 275 if (axes[i] != controllerState.axisValue[i]) { 276 mGamepad->SetAxis(i, controllerState.axisValue[i]); 277 } 278 } 279 280 // We define 0.85f and 0.15f based on our current finding 281 // for better experience, we can adjust these values if we need. 282 const float completeThreshold = 0.90f; 283 const float startThreshold = 0.85f; 284 const float endThreshold = 0.15f; 285 const uint32_t selectIndex = 0; 286 const uint32_t squeezeIndex = 1; 287 288 // Checking selectstart, select, selectend 289 if (buttons.Length() > selectIndex) { 290 if (controllerState.selectActionStartFrameId > 291 controllerState.selectActionStopFrameId) { 292 if (mSelectAction == ActionState::ActionState_Released && 293 controllerState.triggerValue[selectIndex] > endThreshold) { 294 DispatchEvent(u"selectstart"_ns, aSession); 295 mSelectAction = ActionState::ActionState_Pressing; 296 } else if (mSelectAction == ActionState::ActionState_Pressing && 297 controllerState.triggerValue[selectIndex] > 298 completeThreshold) { 299 mSelectAction = ActionState::ActionState_Pressed; 300 } else if (mSelectAction == ActionState::ActionState_Pressed && 301 controllerState.triggerValue[selectIndex] < startThreshold) { 302 DispatchEvent(u"select"_ns, aSession); 303 mSelectAction = ActionState::ActionState_Releasing; 304 } else if (mSelectAction <= ActionState::ActionState_Releasing && 305 controllerState.triggerValue[selectIndex] < endThreshold) { 306 // For a select btn which only has pressed and unpressed status. 307 if (mSelectAction == ActionState::ActionState_Pressed) { 308 DispatchEvent(u"select"_ns, aSession); 309 } 310 DispatchEvent(u"selectend"_ns, aSession); 311 mSelectAction = ActionState::ActionState_Released; 312 } 313 } else if (mSelectAction <= ActionState::ActionState_Releasing) { 314 // For a select btn which only has pressed and unpressed status. 315 if (mSelectAction == ActionState::ActionState_Pressed) { 316 DispatchEvent(u"select"_ns, aSession); 317 } 318 DispatchEvent(u"selectend"_ns, aSession); 319 mSelectAction = ActionState::ActionState_Released; 320 } 321 } 322 323 // Checking squeezestart, squeeze, squeezeend 324 if (buttons.Length() > squeezeIndex) { 325 if (controllerState.squeezeActionStartFrameId > 326 controllerState.squeezeActionStopFrameId) { 327 if (mSqueezeAction == ActionState::ActionState_Released && 328 controllerState.triggerValue[squeezeIndex] > endThreshold) { 329 DispatchEvent(u"squeezestart"_ns, aSession); 330 mSqueezeAction = ActionState::ActionState_Pressing; 331 } else if (mSqueezeAction == ActionState::ActionState_Pressing && 332 controllerState.triggerValue[squeezeIndex] > 333 completeThreshold) { 334 mSqueezeAction = ActionState::ActionState_Pressed; 335 } else if (mSqueezeAction == ActionState::ActionState_Pressed && 336 controllerState.triggerValue[squeezeIndex] < startThreshold) { 337 DispatchEvent(u"squeeze"_ns, aSession); 338 mSqueezeAction = ActionState::ActionState_Releasing; 339 } else if (mSqueezeAction <= ActionState::ActionState_Releasing && 340 controllerState.triggerValue[squeezeIndex] < endThreshold) { 341 // For a squeeze btn which only has pressed and unpressed status. 342 if (mSqueezeAction == ActionState::ActionState_Pressed) { 343 DispatchEvent(u"squeeze"_ns, aSession); 344 } 345 DispatchEvent(u"squeezeend"_ns, aSession); 346 mSqueezeAction = ActionState::ActionState_Released; 347 } 348 } else if (mSqueezeAction <= ActionState::ActionState_Releasing) { 349 // For a squeeze btn which only has pressed and unpressed status. 350 if (mSqueezeAction == ActionState::ActionState_Pressed) { 351 DispatchEvent(u"squeeze"_ns, aSession); 352 } 353 DispatchEvent(u"squeezeend"_ns, aSession); 354 mSqueezeAction = ActionState::ActionState_Released; 355 } 356 } 357 } 358 359 int32_t XRInputSource::GetIndex() { return mIndex; } 360 361 void XRInputSource::DispatchEvent(const nsAString& aEvent, 362 XRSession* aSession) { 363 if (!GetParentObject() || !aSession) { 364 return; 365 } 366 // Create a XRFrame for its callbacks 367 RefPtr<XRFrame> frame = new XRFrame(GetParentObject(), aSession); 368 frame->StartInputSourceEvent(); 369 370 XRInputSourceEventInit init; 371 init.mBubbles = false; 372 init.mCancelable = false; 373 init.mFrame = frame; 374 init.mInputSource = this; 375 376 RefPtr<XRInputSourceEvent> event = 377 XRInputSourceEvent::Constructor(aSession, aEvent, init); 378 379 event->SetTrusted(true); 380 aSession->DispatchEvent(*event); 381 frame->EndInputSourceEvent(); 382 } 383 384 void XRInputSource::CreateGripSpace( 385 XRSession* aSession, const gfx::VRControllerState& controllerState) { 386 MOZ_ASSERT(!mGripSpace); 387 MOZ_ASSERT(aSession && mIndex >= 0 && mGamepad); 388 if (mTargetRayMode == XRTargetRayMode::Tracked_pointer && 389 controllerState.flags & GamepadCapabilityFlags::Cap_GripSpacePosition) { 390 RefPtr<XRNativeOrigin> nativeOriginGrip = nullptr; 391 nativeOriginGrip = new XRNativeOriginTracker(&controllerState.pose); 392 mGripSpace = new XRInputSpace(aSession->GetParentObject(), aSession, 393 nativeOriginGrip, mIndex); 394 } else { 395 mGripSpace = nullptr; 396 } 397 } 398 399 } // namespace mozilla::dom