summaryrefslogtreecommitdiffstats
path: root/dom/vr/XRInputSource.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/vr/XRInputSource.cpp')
-rw-r--r--dom/vr/XRInputSource.cpp400
1 files changed, 400 insertions, 0 deletions
diff --git a/dom/vr/XRInputSource.cpp b/dom/vr/XRInputSource.cpp
new file mode 100644
index 0000000000..8cf2849d1c
--- /dev/null
+++ b/dom/vr/XRInputSource.cpp
@@ -0,0 +1,400 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/XRInputSource.h"
+#include "mozilla/dom/XRInputSourceEvent.h"
+#include "XRNativeOriginViewer.h"
+#include "XRNativeOriginTracker.h"
+#include "XRInputSpace.h"
+#include "VRDisplayClient.h"
+
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadManager.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(XRInputSource, mParent, mTargetRaySpace,
+ mGripSpace, mGamepad)
+
+// Follow the controller profile ids from
+// https://github.com/immersive-web/webxr-input-profiles.
+nsTArray<nsString> GetInputSourceProfile(gfx::VRControllerType aType) {
+ nsTArray<nsString> profile;
+ nsString id;
+
+ switch (aType) {
+ case gfx::VRControllerType::HTCVive:
+ id.AssignLiteral("htc-vive");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-touchpad");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::HTCViveCosmos:
+ id.AssignLiteral("htc-vive-cosmos");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-thumbstick");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::HTCViveFocus:
+ id.AssignLiteral("htc-vive-focus");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-touchpad");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::HTCViveFocusPlus:
+ id.AssignLiteral("htc-vive-focus-plus");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-touchpad");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::MSMR:
+ id.AssignLiteral("microsoft-mixed-reality");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::ValveIndex:
+ id.AssignLiteral("valve-index");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-touchpad-thumbstick");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::OculusGo:
+ id.AssignLiteral("oculus-go");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-touchpad");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::OculusTouch:
+ id.AssignLiteral("oculus-touch");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-thumbstick");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::OculusTouch2:
+ id.AssignLiteral("oculus-touch-v2");
+ profile.AppendElement(id);
+ id.AssignLiteral("oculus-touch");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-thumbstick");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::OculusTouch3:
+ id.AssignLiteral("oculus-touch-v3");
+ profile.AppendElement(id);
+ id.AssignLiteral("oculus-touch-v2");
+ profile.AppendElement(id);
+ id.AssignLiteral("oculus-touch");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-thumbstick");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::PicoGaze:
+ id.AssignLiteral("pico-gaze");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-button");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::PicoG2:
+ id.AssignLiteral("pico-g2");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-touchpad");
+ profile.AppendElement(id);
+ break;
+ case gfx::VRControllerType::PicoNeo2:
+ id.AssignLiteral("pico-neo2");
+ profile.AppendElement(id);
+ id.AssignLiteral("generic-trigger-squeeze-thumbstick");
+ profile.AppendElement(id);
+ break;
+ default:
+ NS_WARNING("Unsupported XR input source profile.\n");
+ break;
+ }
+ return profile;
+}
+
+XRInputSource::XRInputSource(nsISupports* aParent)
+ : mParent(aParent),
+ mGamepad(nullptr),
+ mIndex(-1),
+ mSelectAction(ActionState::ActionState_Released),
+ mSqueezeAction(ActionState::ActionState_Released) {}
+
+XRInputSource::~XRInputSource() {
+ mTargetRaySpace = nullptr;
+ mGripSpace = nullptr;
+ mGamepad = nullptr;
+}
+
+JSObject* XRInputSource::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return XRInputSource_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+XRHandedness XRInputSource::Handedness() { return mHandedness; }
+
+XRTargetRayMode XRInputSource::TargetRayMode() { return mTargetRayMode; }
+
+XRSpace* XRInputSource::TargetRaySpace() { return mTargetRaySpace; }
+
+XRSpace* XRInputSource::GetGripSpace() { return mGripSpace; }
+
+void XRInputSource::GetProfiles(nsTArray<nsString>& aResult) {
+ aResult = mProfiles.Clone();
+}
+
+Gamepad* XRInputSource::GetGamepad() { return mGamepad; }
+
+void XRInputSource::Setup(XRSession* aSession, uint32_t aIndex) {
+ MOZ_ASSERT(aSession);
+ gfx::VRDisplayClient* displayClient = aSession->GetDisplayClient();
+ if (!displayClient) {
+ MOZ_ASSERT(displayClient);
+ return;
+ }
+ const gfx::VRDisplayInfo& displayInfo = displayClient->GetDisplayInfo();
+ const gfx::VRControllerState& controllerState =
+ displayInfo.mControllerState[aIndex];
+ MOZ_ASSERT(controllerState.controllerName[0] != '\0');
+
+ mProfiles = GetInputSourceProfile(controllerState.type);
+ mHandedness = XRHandedness::None;
+ switch (controllerState.hand) {
+ case GamepadHand::_empty:
+ mHandedness = XRHandedness::None;
+ break;
+ case GamepadHand::Left:
+ mHandedness = XRHandedness::Left;
+ break;
+ case GamepadHand::Right:
+ mHandedness = XRHandedness::Right;
+ break;
+ default:
+ MOZ_ASSERT(false && "Unknown GamepadHand type.");
+ break;
+ }
+
+ RefPtr<XRNativeOrigin> nativeOriginTargetRay = nullptr;
+ mTargetRayMode = XRTargetRayMode::Tracked_pointer;
+ switch (controllerState.targetRayMode) {
+ case gfx::TargetRayMode::Gaze:
+ mTargetRayMode = XRTargetRayMode::Gaze;
+ nativeOriginTargetRay = new XRNativeOriginViewer(displayClient);
+ break;
+ case gfx::TargetRayMode::TrackedPointer:
+ mTargetRayMode = XRTargetRayMode::Tracked_pointer;
+ // We use weak pointers of poses in XRNativeOriginTracker to sync their
+ // data internally.
+ nativeOriginTargetRay =
+ new XRNativeOriginTracker(&controllerState.targetRayPose);
+ break;
+ case gfx::TargetRayMode::Screen:
+ mTargetRayMode = XRTargetRayMode::Screen;
+ break;
+ default:
+ MOZ_ASSERT(false && "Undefined TargetRayMode type.");
+ break;
+ }
+
+ mTargetRaySpace = new XRInputSpace(aSession->GetParentObject(), aSession,
+ nativeOriginTargetRay, aIndex);
+
+ const uint32_t gamepadHandleValue =
+ displayInfo.mDisplayID * gfx::kVRControllerMaxCount + aIndex;
+
+ const GamepadHandle gamepadHandle{gamepadHandleValue, GamepadHandleKind::VR};
+
+ mGamepad =
+ new Gamepad(mParent, NS_ConvertASCIItoUTF16(""), -1, gamepadHandle,
+ GamepadMappingType::Xr_standard, controllerState.hand,
+ displayInfo.mDisplayID, controllerState.numButtons,
+ controllerState.numAxes, controllerState.numHaptics, 0, 0);
+ mIndex = aIndex;
+
+ if (!mGripSpace) {
+ CreateGripSpace(aSession, controllerState);
+ }
+}
+
+void XRInputSource::SetGamepadIsConnected(bool aConnected,
+ XRSession* aSession) {
+ mGamepad->SetConnected(aConnected);
+ MOZ_ASSERT(aSession);
+
+ if (!aConnected) {
+ if (mSelectAction != ActionState::ActionState_Released) {
+ DispatchEvent(u"selectend"_ns, aSession);
+ mSelectAction = ActionState::ActionState_Released;
+ }
+ if (mSqueezeAction != ActionState::ActionState_Released) {
+ DispatchEvent(u"squeezeend"_ns, aSession);
+ mSqueezeAction = ActionState::ActionState_Released;
+ }
+ }
+}
+
+void XRInputSource::Update(XRSession* aSession) {
+ MOZ_ASSERT(aSession && mIndex >= 0 && mGamepad);
+
+ gfx::VRDisplayClient* displayClient = aSession->GetDisplayClient();
+ if (!displayClient) {
+ MOZ_ASSERT(displayClient);
+ return;
+ }
+ const gfx::VRDisplayInfo& displayInfo = displayClient->GetDisplayInfo();
+ const gfx::VRControllerState& controllerState =
+ displayInfo.mControllerState[mIndex];
+ MOZ_ASSERT(controllerState.controllerName[0] != '\0');
+
+ // OculusVR and OpenVR controllers need to wait until
+ // update functions to assign GamepadCapabilityFlags::Cap_GripSpacePosition
+ // flag.
+ if (!mGripSpace) {
+ CreateGripSpace(aSession, controllerState);
+ }
+
+ // Update button values.
+ nsTArray<RefPtr<GamepadButton>> buttons;
+ mGamepad->GetButtons(buttons);
+ for (uint32_t i = 0; i < buttons.Length(); ++i) {
+ const bool pressed = controllerState.buttonPressed & (1ULL << i);
+ const bool touched = controllerState.buttonTouched & (1ULL << i);
+
+ if (buttons[i]->Pressed() != pressed || buttons[i]->Touched() != touched ||
+ buttons[i]->Value() != controllerState.triggerValue[i]) {
+ mGamepad->SetButton(i, pressed, touched, controllerState.triggerValue[i]);
+ }
+ }
+ // Update axis values.
+ nsTArray<double> axes;
+ mGamepad->GetAxes(axes);
+ for (uint32_t i = 0; i < axes.Length(); ++i) {
+ if (axes[i] != controllerState.axisValue[i]) {
+ mGamepad->SetAxis(i, controllerState.axisValue[i]);
+ }
+ }
+
+ // We define 0.85f and 0.15f based on our current finding
+ // for better experience, we can adjust these values if we need.
+ const float completeThreshold = 0.90f;
+ const float startThreshold = 0.85f;
+ const float endThreshold = 0.15f;
+ const uint32_t selectIndex = 0;
+ const uint32_t squeezeIndex = 1;
+
+ // Checking selectstart, select, selectend
+ if (buttons.Length() > selectIndex) {
+ if (controllerState.selectActionStartFrameId >
+ controllerState.selectActionStopFrameId) {
+ if (mSelectAction == ActionState::ActionState_Released &&
+ controllerState.triggerValue[selectIndex] > endThreshold) {
+ DispatchEvent(u"selectstart"_ns, aSession);
+ mSelectAction = ActionState::ActionState_Pressing;
+ } else if (mSelectAction == ActionState::ActionState_Pressing &&
+ controllerState.triggerValue[selectIndex] >
+ completeThreshold) {
+ mSelectAction = ActionState::ActionState_Pressed;
+ } else if (mSelectAction == ActionState::ActionState_Pressed &&
+ controllerState.triggerValue[selectIndex] < startThreshold) {
+ DispatchEvent(u"select"_ns, aSession);
+ mSelectAction = ActionState::ActionState_Releasing;
+ } else if (mSelectAction <= ActionState::ActionState_Releasing &&
+ controllerState.triggerValue[selectIndex] < endThreshold) {
+ // For a select btn which only has pressed and unpressed status.
+ if (mSelectAction == ActionState::ActionState_Pressed) {
+ DispatchEvent(u"select"_ns, aSession);
+ }
+ DispatchEvent(u"selectend"_ns, aSession);
+ mSelectAction = ActionState::ActionState_Released;
+ }
+ } else if (mSelectAction <= ActionState::ActionState_Releasing) {
+ // For a select btn which only has pressed and unpressed status.
+ if (mSelectAction == ActionState::ActionState_Pressed) {
+ DispatchEvent(u"select"_ns, aSession);
+ }
+ DispatchEvent(u"selectend"_ns, aSession);
+ mSelectAction = ActionState::ActionState_Released;
+ }
+ }
+
+ // Checking squeezestart, squeeze, squeezeend
+ if (buttons.Length() > squeezeIndex) {
+ if (controllerState.squeezeActionStartFrameId >
+ controllerState.squeezeActionStopFrameId) {
+ if (mSqueezeAction == ActionState::ActionState_Released &&
+ controllerState.triggerValue[squeezeIndex] > endThreshold) {
+ DispatchEvent(u"squeezestart"_ns, aSession);
+ mSqueezeAction = ActionState::ActionState_Pressing;
+ } else if (mSqueezeAction == ActionState::ActionState_Pressing &&
+ controllerState.triggerValue[squeezeIndex] >
+ completeThreshold) {
+ mSqueezeAction = ActionState::ActionState_Pressed;
+ } else if (mSqueezeAction == ActionState::ActionState_Pressed &&
+ controllerState.triggerValue[squeezeIndex] < startThreshold) {
+ DispatchEvent(u"squeeze"_ns, aSession);
+ mSqueezeAction = ActionState::ActionState_Releasing;
+ } else if (mSqueezeAction <= ActionState::ActionState_Releasing &&
+ controllerState.triggerValue[squeezeIndex] < endThreshold) {
+ // For a squeeze btn which only has pressed and unpressed status.
+ if (mSqueezeAction == ActionState::ActionState_Pressed) {
+ DispatchEvent(u"squeeze"_ns, aSession);
+ }
+ DispatchEvent(u"squeezeend"_ns, aSession);
+ mSqueezeAction = ActionState::ActionState_Released;
+ }
+ } else if (mSqueezeAction <= ActionState::ActionState_Releasing) {
+ // For a squeeze btn which only has pressed and unpressed status.
+ if (mSqueezeAction == ActionState::ActionState_Pressed) {
+ DispatchEvent(u"squeeze"_ns, aSession);
+ }
+ DispatchEvent(u"squeezeend"_ns, aSession);
+ mSqueezeAction = ActionState::ActionState_Released;
+ }
+ }
+}
+
+int32_t XRInputSource::GetIndex() { return mIndex; }
+
+void XRInputSource::DispatchEvent(const nsAString& aEvent,
+ XRSession* aSession) {
+ if (!GetParentObject() || !aSession) {
+ return;
+ }
+ // Create a XRFrame for its callbacks
+ RefPtr<XRFrame> frame = new XRFrame(GetParentObject(), aSession);
+ frame->StartInputSourceEvent();
+
+ XRInputSourceEventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+ init.mFrame = frame;
+ init.mInputSource = this;
+
+ RefPtr<XRInputSourceEvent> event =
+ XRInputSourceEvent::Constructor(aSession, aEvent, init);
+
+ event->SetTrusted(true);
+ aSession->DispatchEvent(*event);
+ frame->EndInputSourceEvent();
+}
+
+void XRInputSource::CreateGripSpace(
+ XRSession* aSession, const gfx::VRControllerState& controllerState) {
+ MOZ_ASSERT(!mGripSpace);
+ MOZ_ASSERT(aSession && mIndex >= 0 && mGamepad);
+ if (mTargetRayMode == XRTargetRayMode::Tracked_pointer &&
+ controllerState.flags & GamepadCapabilityFlags::Cap_GripSpacePosition) {
+ RefPtr<XRNativeOrigin> nativeOriginGrip = nullptr;
+ nativeOriginGrip = new XRNativeOriginTracker(&controllerState.pose);
+ mGripSpace = new XRInputSpace(aSession->GetParentObject(), aSession,
+ nativeOriginGrip, mIndex);
+ } else {
+ mGripSpace = nullptr;
+ }
+}
+
+} // namespace mozilla::dom