diff options
Diffstat (limited to 'dom/vr/XRInputSource.cpp')
-rw-r--r-- | dom/vr/XRInputSource.cpp | 400 |
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 |