/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #if USE_OPENXR using Oculus.Interaction.Input; using System; using System.Linq; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.XR; using UnityEngine.XR; namespace Oculus.Interaction.UnityXR { public class FromUnityXRControllerDataSource : DataSource { [Header("Shared Configuration")] [SerializeField] private Handedness _handedness; [SerializeField, Interface(typeof(ITrackingToWorldTransformer))] private UnityEngine.Object _trackingToWorldTransformer; private ITrackingToWorldTransformer TrackingToWorldTransformer; private static string ControllerActionMap = $@"{{ ""maps"": [ {{ ""name"": ""XRController"", ""actions"": [ {{ ""name"": ""PrimaryButton"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/primaryButton"" }} ] }}, {{ ""name"": ""PrimaryTouch"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/primaryTouched"" }} ] }}, {{ ""name"": ""SecondaryButton"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/secondaryButton"" }} ] }}, {{ ""name"": ""SecondaryTouch"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/secondaryTouched"" }} ] }}, {{ ""name"": ""GripButton"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/gripPressed"" }} ] }}, {{ ""name"": ""TriggerButton"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/triggerPressed"" }} ] }}, {{ ""name"": ""TriggerTouch"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/triggerTouched"" }} ] }}, {{ ""name"": ""MenuButton"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/menu"" }} ] }}, {{ ""name"": ""Primary2DAxisClick"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/thumbstickClicked"" }} ] }}, {{ ""name"": ""Primary2DAxisTouch"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/thumbstickTouched"" }} ] }}, {{ ""name"": ""Thumbrest"", ""expectedControlLayout"": ""Integer"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/thumbrestTouched"" }} ] }}, {{ ""name"": ""Trigger"", ""expectedControlLayout"": ""Axis1D"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/trigger"" }} ] }}, {{ ""name"": ""Grip"", ""expectedControlLayout"": ""Axis1D"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/grip"" }} ] }}, {{ ""name"": ""Primary2DAxis"", ""expectedControlLayout"": ""Axis2D"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/thumbstick"" }} ] }}, {{ ""name"": ""Secondary2DAxis"", ""expectedControlLayout"": ""Axis2D"", ""bindings"": [ {{ }} ] }}, {{ ""name"": ""RootPose"", ""expectedControlLayout"": ""Pose"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/devicePose"" }} ] }}, {{ ""name"": ""PointerPose"", ""expectedControlLayout"": ""Pose"", ""bindings"": [ {{ ""path"":""{{LeftHand}}/pointer"" }} ] }} ] }} ]}}"; [SerializeField] private InputActionMap _leftHandControllerBindings = InputActionMap.FromJson(ControllerActionMap).FirstOrDefault(); [SerializeField] private InputActionMap _rightHandControllerBindings = InputActionMap.FromJson(ControllerActionMap.Replace("{LeftHand}", "{RightHand}")).FirstOrDefault(); private readonly ControllerDataAsset _dataAsset = new(); private readonly ControllerDataSourceConfig _config = new(); private static readonly Quaternion OpenXRToOVRLeftRotTipInverted = Quaternion.Inverse( Quaternion.AngleAxis(90, Vector3.forward)); private static readonly Quaternion OpenXRToOVRRightRotTipInverted = Quaternion.Inverse( Quaternion.AngleAxis(180, Vector3.right) * Quaternion.AngleAxis(-90, Vector3.forward)); private static readonly Func DefaultFrameCountProvider = () => Time.frameCount; private Func _frameCountProvider = DefaultFrameCountProvider; private int _lastRequiredUpdate; public void SetTimeFrameCountProvider(Func frameCountProvider) { frameCountProvider ??= DefaultFrameCountProvider; _frameCountProvider = frameCountProvider; } private void Awake() { TrackingToWorldTransformer = _trackingToWorldTransformer as ITrackingToWorldTransformer; UpdateConfig(); InputActionMap map = (_handedness == Handedness.Left)? _leftHandControllerBindings : _rightHandControllerBindings; // TODO - source from meta handaim _dataAsset.IsDominantHand = _handedness != Handedness.Left; foreach (var usageName in Enum.GetNames(typeof(ControllerButtonUsage))) { if (usageName == ControllerButtonUsage.None.ToString()) continue; var usage = Enum.Parse(usageName); map[usageName].started += _ => _dataAsset.Input.SetButton(usage, true); map[usageName].canceled += _ => _dataAsset.Input.SetButton(usage, false); } foreach (var usageName in Enum.GetNames(typeof(ControllerAxis1DUsage))) { if (usageName == ControllerAxis1DUsage.None.ToString()) continue; map[usageName].performed += context => _dataAsset.Input.SetAxis1D(Enum.Parse(usageName), context.ReadValue()); map[usageName].canceled += context => _dataAsset.Input.SetAxis1D(Enum.Parse(usageName), 0); } foreach (var usageName in Enum.GetNames(typeof(ControllerAxis2DUsage))) { if (usageName == ControllerAxis2DUsage.None.ToString()) continue; map[usageName].performed += context => _dataAsset.Input.SetAxis2D(Enum.Parse(usageName), context.ReadValue()); map[usageName].canceled += context => _dataAsset.Input.SetAxis2D(Enum.Parse(usageName), Vector2.zero); } map[nameof(_dataAsset.RootPose)].performed += context => { var poseState = context.ReadValue(); _dataAsset.RootPose = new Pose(poseState.position, poseState.rotation); _dataAsset.RootPose = FlipZ(_dataAsset.RootPose); _dataAsset.RootPose.rotation *= (_dataAsset.Config.Handedness == Handedness.Left) ? OpenXRToOVRLeftRotTipInverted : OpenXRToOVRRightRotTipInverted; _dataAsset.RootPose = FlipZ(_dataAsset.RootPose); _dataAsset.RootPoseOrigin = PoseOrigin.RawTrackedPose; _dataAsset.IsTracked = poseState.trackingState.HasFlag(InputTrackingState.Position) && poseState.trackingState.HasFlag(InputTrackingState.Rotation); _dataAsset.IsDataValid = _dataAsset.IsTracked; _dataAsset.IsConnected = _dataAsset.IsTracked; var frameCount = _frameCountProvider.Invoke(); if (_lastRequiredUpdate != frameCount) { _lastRequiredUpdate = frameCount; MarkInputDataRequiresUpdate(); } }; map[nameof(_dataAsset.RootPose)].canceled += _ => { _dataAsset.RootPoseOrigin = PoseOrigin.None; }; map[nameof(_dataAsset.PointerPose)].performed += context => { var poseState = context.ReadValue(); _dataAsset.PointerPose = new Pose(poseState.position, poseState.rotation); _dataAsset.PointerPoseOrigin = PoseOrigin.RawTrackedPose; }; map[nameof(_dataAsset.PointerPose)].canceled += _ => { _dataAsset.PointerPoseOrigin = PoseOrigin.None; }; map.Enable(); } private static Quaternion FlipZ(Quaternion q) { return new Quaternion() { x = -q.x, y = -q.y, z = q.z, w = q.w }; } public static Pose FlipZ(Pose p) { p.rotation = FlipZ(p.rotation); p.position = new Vector3() { x = p.position.x, y = p.position.y, z = -p.position.z }; return p; } protected override void Start() { this.BeginStart(ref _started, () => base.Start()); this.AssertField(TrackingToWorldTransformer, nameof(TrackingToWorldTransformer)); UpdateConfig(); this.EndStart(ref _started); } protected override void UpdateData() { } private void UpdateConfig() { _config.Handedness = _handedness; _config.TrackingToWorldTransformer = TrackingToWorldTransformer; _dataAsset.Config = _config; } protected override ControllerDataAsset DataAsset { get => _dataAsset; } #region Inject public void InjectHandedness(Handedness handedness) { _handedness = handedness; } public void InjectTrackingToWorldTransformer(ITrackingToWorldTransformer trackingToWorldTransformer) { _trackingToWorldTransformer = trackingToWorldTransformer as UnityEngine.Object; TrackingToWorldTransformer = trackingToWorldTransformer; } #endregion } } #endif // USE_OPENXR