Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

@ -0,0 +1,142 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection
{
/// <summary>
/// Test if hand joint is inside generic collider and updates its active state
/// based on that test. We could trigger-based testing, but if the hand disappears
/// during one frame, we will not get a trigger exit event (which means we require
/// manual testing in Update anyway to accomodate that edge case).
/// </summary>
public class ColliderContainsHandJointActiveState : MonoBehaviour, IActiveState
{
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
private IHand Hand;
[SerializeField]
private Collider[] _entryColliders;
[SerializeField]
private Collider[] _exitColliders;
[SerializeField]
private HandJointId _jointToTest = HandJointId.HandWristRoot;
public bool Active { get; private set; }
private bool _active = false;
protected virtual void Awake()
{
Hand = _hand as IHand;
Active = false;
}
protected virtual void Start()
{
this.AssertField(Hand, nameof(Hand));
this.AssertCollectionField(_entryColliders, nameof(_entryColliders));
this.AssertCollectionField(_exitColliders, nameof(_exitColliders));
}
protected virtual void Update()
{
if (Hand.GetJointPose(_jointToTest, out Pose jointPose))
{
Active = JointPassesTests(jointPose);
}
else
{
Active = false;
}
}
private bool JointPassesTests(Pose jointPose)
{
bool passesCollisionTest;
if (_active)
{
passesCollisionTest = IsPointWithinColliders(jointPose.position,
_exitColliders);
}
else
{
passesCollisionTest = IsPointWithinColliders(jointPose.position,
_entryColliders);
}
_active = passesCollisionTest;
return passesCollisionTest;
}
private bool IsPointWithinColliders(Vector3 point, Collider[] colliders)
{
foreach (var collider in colliders)
{
if (!Collisions.IsPointWithinCollider(point, collider))
{
return false;
}
}
return true;
}
#region Inject
public void InjectAllColliderContainsHandJointActiveState(IHand hand, Collider[] entryColliders,
Collider[] exitColliders, HandJointId jointToTest)
{
InjectHand(hand);
InjectEntryColliders(entryColliders);
InjectExitColliders(exitColliders);
InjectJointToTest(jointToTest);
}
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
public void InjectEntryColliders(Collider[] entryColliders)
{
_entryColliders = entryColliders;
}
public void InjectExitColliders(Collider[] exitColliders)
{
_exitColliders = exitColliders;
}
public void InjectJointToTest(HandJointId jointToTest)
{
_jointToTest = jointToTest;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: af496e475135e134aa6a3ad7e0109882
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7098ce5b7bdb8734980e21c523861a71
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,62 @@
/*
* 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.
*/
using Oculus.Interaction.DebugTree;
using System;
using System.Collections.Generic;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class ActiveStateDebugTree : DebugTree<IActiveState>
{
public ActiveStateDebugTree(IActiveState root) : base(root)
{
}
private static Dictionary<Type, IActiveStateModel> _models =
new Dictionary<Type, IActiveStateModel>();
public static void RegisterModel<TType>(IActiveStateModel stateModel)
where TType : class, IActiveState
{
Type key = typeof(TType);
if (_models.ContainsKey(key))
{
_models[key] = stateModel;
}
else
{
_models.Add(key, stateModel);
}
}
protected override bool TryGetChildren(IActiveState node, out IEnumerable<IActiveState> children)
{
if (_models.TryGetValue(node.GetType(), out IActiveStateModel model)
&& model != null)
{
children = model.GetChildren(node);
return true;
}
children = null;
return false;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98347ac4d9a23ac4c84b5eea4c0291d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,57 @@
/*
* 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.
*/
using Oculus.Interaction.DebugTree;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class ActiveStateDebugTreeUI : DebugTreeUI<IActiveState>
{
[Tooltip("The IActiveState to debug.")]
[SerializeField, Interface(typeof(IActiveState))]
private UnityEngine.Object _activeState;
[Tooltip("The node prefab which will be used to build the visual tree.")]
[SerializeField, Interface(typeof(INodeUI<IActiveState>))]
private UnityEngine.Component _nodePrefab;
protected override IActiveState Value
{
get => _activeState as IActiveState;
}
protected override INodeUI<IActiveState> NodePrefab
{
get => _nodePrefab as INodeUI<IActiveState>;
}
protected override DebugTree<IActiveState> InstantiateTree(IActiveState value)
{
return new ActiveStateDebugTree(value);
}
protected override string TitleForValue(IActiveState value)
{
Object obj = value as Object;
return obj != null ? obj.name : "";
}
}
}

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 8ff3d6159be4e004f8ae25b5fcf864bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _activeState: {instanceID: 0}
- _nodePrefab: {fileID: 4289178980685552619, guid: bcb4af79d88c4af4a90fb6e19de13e70,
type: 3}
- _topLevel: {instanceID: 0}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,80 @@
/*
* 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.
*/
using UnityEngine;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class ActiveStateDebugVisual : MonoBehaviour
{
[Tooltip("The IActiveState to debug.")]
[SerializeField, Interface(typeof(IActiveState))]
private UnityEngine.Object _activeState;
private IActiveState ActiveState { get; set; }
[Tooltip("The renderer used for the color change.")]
[SerializeField]
private Renderer _target;
[Tooltip("The renderer will be set to this color " +
"when ActiveState is inactive.")]
[SerializeField]
private Color _normalColor = Color.red;
[Tooltip("The renderer will be set to this color " +
"when ActiveState is active.")]
[SerializeField]
private Color _activeColor = Color.green;
private Material _material;
private bool _lastActiveValue = false;
protected virtual void Awake()
{
ActiveState = _activeState as IActiveState;
this.AssertField(ActiveState, nameof(ActiveState));
this.AssertField(_target, nameof(_target));
_material = _target.material;
SetMaterialColor(_lastActiveValue ? _activeColor : _normalColor);
}
private void OnDestroy()
{
Destroy(_material);
}
protected virtual void Update()
{
bool isActive = ActiveState.Active;
if (_lastActiveValue != isActive)
{
SetMaterialColor(isActive ? _activeColor : _normalColor);
_lastActiveValue = isActive;
}
}
private void SetMaterialColor(Color activeColor)
{
_material.color = activeColor;
_target.enabled = _material.color.a > 0.0f;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38d73b2d0c0ee504587e0237ec474ca4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
/*
* 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.
*/
using System.Collections.Generic;
using System.Linq;
namespace Oculus.Interaction.PoseDetection.Debug
{
public interface IActiveStateModel
{
IEnumerable<IActiveState> GetChildren(IActiveState activeState);
}
public abstract class ActiveStateModel<TActiveState> : IActiveStateModel
where TActiveState : class, IActiveState
{
public IEnumerable<IActiveState> GetChildren(IActiveState activeState)
{
if (activeState is TActiveState type)
{
return GetChildren(type);
}
return Enumerable.Empty<IActiveState>();
}
protected abstract IEnumerable<IActiveState> GetChildren(TActiveState activeState);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ed4598c08239c94fa439c2921b27349
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,93 @@
/*
* 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.
*/
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.Assertions;
using Oculus.Interaction.DebugTree;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class ActiveStateNodeUIHorizontal : MonoBehaviour, INodeUI<IActiveState>
{
[SerializeField]
private RectTransform _childArea;
[SerializeField]
private RectTransform _connectingLine;
[SerializeField]
private TextMeshProUGUI _label;
[SerializeField]
private Image _activeImage;
[SerializeField]
private Color _activeColor = Color.green;
[SerializeField]
private Color _inactiveColor = Color.red;
private const string OBJNAME_FORMAT = "<color=#dddddd><size=85%>{0}</size></color>";
public RectTransform ChildArea => _childArea;
private ITreeNode<IActiveState> _boundNode;
private bool _isRoot = false;
private bool _isDuplicate = false;
public void Bind(ITreeNode<IActiveState> node, bool isRoot, bool isDuplicate)
{
Assert.IsNotNull(node);
_isRoot = isRoot;
_isDuplicate = isDuplicate;
_boundNode = node;
_label.text = GetLabelText(node);
}
protected virtual void Start()
{
this.AssertField(_childArea, nameof(_childArea));
this.AssertField(_connectingLine, nameof(_connectingLine));
this.AssertField(_activeImage, nameof(_activeImage));
this.AssertField(_label, nameof(_label));
}
protected virtual void Update()
{
_activeImage.color = _boundNode.Value.Active ? _activeColor : _inactiveColor;
_childArea.gameObject.SetActive(_childArea.childCount > 0);
_connectingLine.gameObject.SetActive(!_isRoot);
}
private string GetLabelText(ITreeNode<IActiveState> node)
{
string label = _isDuplicate ? "<i>" : "";
if (node.Value is UnityEngine.Object obj)
{
label += obj.name + System.Environment.NewLine;
}
label += string.Format(OBJNAME_FORMAT, node.Value.GetType().Name);
return label;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4bf41478a1af76e48b69aa772f7b81fd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,93 @@
/*
* 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.
*/
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.Assertions;
using Oculus.Interaction.DebugTree;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class ActiveStateNodeUIVertical : MonoBehaviour, INodeUI<IActiveState>
{
[SerializeField]
private RectTransform _childArea;
[SerializeField]
private RectTransform _connectingLine;
[SerializeField]
private TextMeshProUGUI _label;
[SerializeField]
private Image _activeImage;
[SerializeField]
private Color _activeColor = Color.green;
[SerializeField]
private Color _inactiveColor = Color.red;
private const string OBJNAME_FORMAT = "<color=#dddddd><size=85%>{0}</size></color>";
public RectTransform ChildArea => _childArea;
private ITreeNode<IActiveState> _boundNode;
private bool _isRoot = false;
private bool _isDuplicate = false;
public void Bind(ITreeNode<IActiveState> node, bool isRoot, bool isDuplicate)
{
Assert.IsNotNull(node);
_isRoot = isRoot;
_isDuplicate = isDuplicate;
_boundNode = node;
_label.text = GetLabelText(node);
}
protected virtual void Start()
{
this.AssertField(_childArea, nameof(_childArea));
this.AssertField(_connectingLine, nameof(_connectingLine));
this.AssertField(_activeImage, nameof(_activeImage));
this.AssertField(_label, nameof(_label));
}
protected virtual void Update()
{
_activeImage.color = _boundNode.Value.Active ? _activeColor : _inactiveColor;
_childArea.gameObject.SetActive(_childArea.childCount > 0);
_connectingLine.gameObject.SetActive(!_isRoot);
}
private string GetLabelText(ITreeNode<IActiveState> node)
{
string label = _isDuplicate ? "<i>" : "";
if (node.Value is UnityEngine.Object obj)
{
label += obj.name + " - ";
}
label += string.Format(OBJNAME_FORMAT, node.Value.GetType().Name);
return label;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10fca570a88ae124dbdb5dfda85fbbf6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,105 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using TMPro;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class FingerFeatureDebugVisual : MonoBehaviour
{
[SerializeField]
private Renderer _target;
[SerializeField]
private Color _normalColor = Color.red;
[SerializeField]
private Color _activeColor = Color.green;
[SerializeField]
private TextMeshPro _targetText;
private IFingerFeatureStateProvider _fingerFeatureState;
private Material _material;
private bool _lastActiveValue;
private HandFinger _handFinger;
private ShapeRecognizer.FingerFeatureConfig _featureConfig;
private bool _initialized;
protected virtual void Awake()
{
_material = _target.material;
this.AssertField(_material, nameof(_material));
this.AssertField(_targetText, nameof(_targetText));
_material.color = _lastActiveValue ? _activeColor : _normalColor;
}
protected virtual void OnDestroy()
{
Destroy(_material);
}
public void Initialize(HandFinger handFinger,
ShapeRecognizer.FingerFeatureConfig config,
IFingerFeatureStateProvider fingerFeatureState)
{
_initialized = true;
_handFinger = handFinger;
_featureConfig = config;
_fingerFeatureState = fingerFeatureState;
}
protected virtual void Update()
{
if (!_initialized)
{
return;
}
FingerFeature feature = _featureConfig.Feature;
bool isActive = false;
if (_fingerFeatureState.GetCurrentState(_handFinger, feature,
out string currentState))
{
float? featureVal = _fingerFeatureState.GetFeatureValue(_handFinger, feature);
isActive = _fingerFeatureState.IsStateActive(_handFinger, feature, _featureConfig.Mode, _featureConfig.State);
string featureValStr = featureVal.HasValue ? featureVal.Value.ToString("F2") : "--";
_targetText.text = $"{_handFinger} {feature}" + $"{currentState} ({featureValStr})";
}
else
{
_targetText.text = $"{_handFinger} {feature}\n";
}
if (isActive != _lastActiveValue)
{
_material.color = isActive ? _activeColor : _normalColor;
_lastActiveValue = isActive;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89e8f8c35caba0c4abef1bdd8890c640
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,146 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class FingerFeatureSkeletalDebugVisual : MonoBehaviour
{
[SerializeField]
private FingerFeatureStateProvider _fingerFeatureStateProvider;
[SerializeField]
private LineRenderer _lineRenderer;
[SerializeField]
private Color _normalColor = Color.red;
[SerializeField]
private Color _activeColor = Color.green;
[SerializeField]
private float _lineWidth = 0.005f;
private IHand _hand;
private bool _lastFeatureActiveValue = false;
private IReadOnlyList<HandJointId> _jointsCovered = null;
private HandFinger _finger;
private ShapeRecognizer.FingerFeatureConfig _fingerFeatureConfig;
private bool _initializedPositions;
private bool _initialized;
protected virtual void Awake()
{
this.AssertField(_lineRenderer, nameof(_lineRenderer));
UpdateFeatureActiveValueAndVisual(false);
}
private void UpdateFeatureActiveValueAndVisual(bool newValue)
{
var colorToUse = newValue ? _activeColor : _normalColor;
_lineRenderer.startColor = colorToUse;
_lineRenderer.endColor = colorToUse;
_lastFeatureActiveValue = newValue;
}
public void Initialize(
IHand hand,
HandFinger finger,
ShapeRecognizer.FingerFeatureConfig fingerFeatureConfig)
{
_hand = hand;
_initialized = true;
this.AssertField(_fingerFeatureStateProvider, nameof(_fingerFeatureStateProvider));
var featureValueProvider = _fingerFeatureStateProvider.GetValueProvider(finger);
_jointsCovered = featureValueProvider.GetJointsAffected(
finger,
fingerFeatureConfig.Feature);
_finger = finger;
_fingerFeatureConfig = fingerFeatureConfig;
_initializedPositions = false;
}
protected virtual void Update()
{
if (!_initialized || !_hand.IsTrackedDataValid)
{
ToggleLineRendererEnableState(false);
return;
}
ToggleLineRendererEnableState(true);
UpdateDebugSkeletonLineRendererJoints();
UpdateFeatureActiveValue();
}
private void ToggleLineRendererEnableState(bool enableState)
{
if (_lineRenderer.enabled == enableState)
{
return;
}
_lineRenderer.enabled = enableState;
}
private void UpdateDebugSkeletonLineRendererJoints()
{
if (!_initializedPositions)
{
_lineRenderer.positionCount = _jointsCovered.Count;
_initializedPositions = true;
}
if (Mathf.Abs(_lineRenderer.startWidth - _lineWidth) > Mathf.Epsilon)
{
_lineRenderer.startWidth = _lineWidth;
_lineRenderer.endWidth = _lineWidth;
}
int numJoints = _jointsCovered.Count;
for (int i = 0; i < numJoints; i++)
{
if (_hand.GetJointPose(_jointsCovered[i], out Pose jointPose))
{
_lineRenderer.SetPosition(i, jointPose.position);
}
}
}
private void UpdateFeatureActiveValue()
{
bool isActive = _fingerFeatureStateProvider.IsStateActive(_finger, _fingerFeatureConfig.Feature,
_fingerFeatureConfig.Mode, _fingerFeatureConfig.State);
if (isActive != _lastFeatureActiveValue)
{
UpdateFeatureActiveValueAndVisual(isActive);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df12566059c64f34a8985d7383779198
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,154 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class HandShapeDebugVisual : MonoBehaviour
{
[SerializeField, Interface(typeof(IFingerFeatureStateProvider))]
private UnityEngine.Object _fingerFeatureStateProvider;
private IFingerFeatureStateProvider FingerFeatureStateProvider;
[SerializeField]
private ShapeRecognizerActiveState _shapeRecognizerActiveState;
[SerializeField]
private Renderer _target;
[SerializeField]
private Color _normalColor = Color.red;
[SerializeField]
private Color _activeColor = Color.green;
[SerializeField]
private GameObject _fingerFeatureDebugVisualPrefab;
[SerializeField]
private Transform _fingerFeatureParent;
[SerializeField]
private Vector3 _fingerSpacingVec = new Vector3(0.0f, -1.0f, 0.0f);
[SerializeField]
private Vector3 _fingerFeatureSpacingVec = new Vector3(1.0f, 0.0f, 0.0f);
[SerializeField]
private Vector3 _fingerFeatureDebugLocalScale = new Vector3(0.3f, 0.3f, 0.3f);
[SerializeField]
private TextMeshPro _targetText;
private Material _material;
private bool _lastActiveValue = false;
protected virtual void Awake()
{
FingerFeatureStateProvider = _fingerFeatureStateProvider as IFingerFeatureStateProvider;
this.AssertField(_shapeRecognizerActiveState, nameof(_shapeRecognizerActiveState));
this.AssertField(_target, nameof(_target));
this.AssertField(_fingerFeatureDebugVisualPrefab, nameof(_fingerFeatureDebugVisualPrefab));
this.AssertField(_targetText, nameof(_targetText));
_material = _target.material;
_material.color = _lastActiveValue ? _activeColor : _normalColor;
if (_fingerFeatureParent == null)
{
_fingerFeatureParent = transform;
}
}
protected virtual void Start()
{
this.AssertField(FingerFeatureStateProvider, nameof(FingerFeatureStateProvider));
Vector3 fingerOffset = Vector3.zero;
var statesByFinger = AllFeatureStates()
.GroupBy(s => s.Item1)
.Select(group => new
{
HandFinger = group.Key,
FingerFeatures = group.SelectMany(item => item.Item2)
});
foreach (var g in statesByFinger)
{
Vector3 fingerDebugFeatureTotalDisp = fingerOffset;
foreach (var config in g.FingerFeatures)
{
var fingerFeatureDebugVisInst = Instantiate(_fingerFeatureDebugVisualPrefab, _fingerFeatureParent);
var debugVisComp = fingerFeatureDebugVisInst.GetComponent<FingerFeatureDebugVisual>();
debugVisComp.Initialize(g.HandFinger, config, FingerFeatureStateProvider);
var debugVisTransform = debugVisComp.transform;
debugVisTransform.localScale = _fingerFeatureDebugLocalScale;
debugVisTransform.localRotation = Quaternion.identity;
debugVisTransform.localPosition = fingerDebugFeatureTotalDisp;
fingerDebugFeatureTotalDisp += _fingerFeatureSpacingVec;
}
fingerOffset += _fingerSpacingVec;
}
string shapeNames = "";
foreach (ShapeRecognizer shapeRecognizer in _shapeRecognizerActiveState.Shapes)
{
shapeNames += shapeRecognizer.ShapeName;
}
_targetText.text = $"{_shapeRecognizerActiveState.Handedness} Hand: {shapeNames} ";
}
private IEnumerable<ValueTuple<HandFinger, IReadOnlyList<ShapeRecognizer.FingerFeatureConfig>>> AllFeatureStates()
{
foreach (ShapeRecognizer shapeRecognizer in _shapeRecognizerActiveState.Shapes)
{
foreach (var handFingerConfigs in shapeRecognizer.GetFingerFeatureConfigs())
{
yield return handFingerConfigs;
}
}
}
protected virtual void OnDestroy()
{
Destroy(_material);
}
protected virtual void Update()
{
bool isActive = _shapeRecognizerActiveState.Active;
if (_lastActiveValue != isActive)
{
_material.color = isActive ? _activeColor : _normalColor;
_lastActiveValue = isActive;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53451bc1e24308a478c0a120ed9eae00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,84 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class HandShapeSkeletalDebugVisual : MonoBehaviour
{
[SerializeField]
private ShapeRecognizerActiveState _shapeRecognizerActiveState;
[SerializeField]
private GameObject _fingerFeatureDebugVisualPrefab;
protected virtual void Awake()
{
this.AssertField(_shapeRecognizerActiveState, nameof(_shapeRecognizerActiveState));
this.AssertField(_fingerFeatureDebugVisualPrefab, nameof(_fingerFeatureDebugVisualPrefab));
}
protected virtual void Start()
{
var statesByFinger = AllFeatureStates()
.GroupBy(s => s.Item1)
.Select(group => new
{
HandFinger = group.Key,
FingerFeatures = group.SelectMany(item => item.Item2)
});
foreach (var g in statesByFinger)
{
foreach (var feature in g.FingerFeatures)
{
var boneDebugObject = Instantiate(_fingerFeatureDebugVisualPrefab);
var skeletalComp = boneDebugObject.GetComponent<FingerFeatureSkeletalDebugVisual>();
skeletalComp.Initialize(_shapeRecognizerActiveState.Hand, g.HandFinger, feature);
var debugVisTransform = boneDebugObject.transform;
debugVisTransform.parent = this.transform;
debugVisTransform.localScale = Vector3.one;
debugVisTransform.localRotation = Quaternion.identity;
debugVisTransform.localPosition = Vector3.zero;
}
}
}
private IEnumerable<ValueTuple<HandFinger, IReadOnlyList<ShapeRecognizer.FingerFeatureConfig>>> AllFeatureStates()
{
foreach (ShapeRecognizer shapeRecognizer in _shapeRecognizerActiveState.Shapes)
{
foreach (var handFingerConfigs in shapeRecognizer.GetFingerFeatureConfigs())
{
yield return handFingerConfigs;
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 86fda5ce347f35d44a5c6faee65c6679
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,136 @@
/*
* 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.
*/
using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class JointRotationDebugVisual : MonoBehaviour
{
[SerializeField]
private JointRotationActiveState _jointRotation;
[SerializeField]
private Material _lineRendererMaterial;
[SerializeField]
private float _rendererLineWidth = 0.005f;
[SerializeField]
private float _rendererLineLength = 0.1f;
private List<LineRenderer> _lineRenderers;
private int _enabledRendererCount;
protected bool _started = false;
protected virtual void Awake()
{
_lineRenderers = new List<LineRenderer>();
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(_jointRotation, nameof(_jointRotation));
this.AssertField(_lineRendererMaterial, nameof(_lineRendererMaterial));
this.EndStart(ref _started);
}
protected virtual void OnDisable()
{
if (_started)
{
ResetLines();
}
}
protected virtual void Update()
{
ResetLines();
foreach (var config in _jointRotation.FeatureConfigs)
{
if (_jointRotation.Hand.GetJointPose(config.Feature, out Pose jointPose) &&
_jointRotation.FeatureStates.TryGetValue(config, out var state))
{
DrawDebugLine(jointPose.position, state.TargetAxis, state.Amount);
}
}
}
private void DrawDebugLine(Vector3 jointPos, Vector3 direction, float amount)
{
Vector3 fullLength = direction.normalized * _rendererLineLength;
bool metThreshold = amount >= 1f;
if (metThreshold)
{
AddLine(jointPos, jointPos + fullLength, Color.green);
}
else
{
Vector3 breakpoint = Vector3.Lerp(jointPos, jointPos + fullLength, amount);
AddLine(jointPos, breakpoint, Color.yellow);
AddLine(breakpoint, jointPos + fullLength, Color.red);
}
}
private void ResetLines()
{
foreach (var lineRenderer in _lineRenderers)
{
if (lineRenderer != null)
{
lineRenderer.enabled = false;
}
}
_enabledRendererCount = 0;
}
private void AddLine(Vector3 start, Vector3 end, Color color)
{
LineRenderer lineRenderer;
if (_enabledRendererCount == _lineRenderers.Count)
{
lineRenderer = new GameObject().AddComponent<LineRenderer>();
lineRenderer.startWidth = _rendererLineWidth;
lineRenderer.endWidth = _rendererLineWidth;
lineRenderer.positionCount = 2;
lineRenderer.material = _lineRendererMaterial;
_lineRenderers.Add(lineRenderer);
}
else
{
lineRenderer = _lineRenderers[_enabledRendererCount];
}
_enabledRendererCount++;
lineRenderer.enabled = true;
lineRenderer.SetPosition(0, start);
lineRenderer.SetPosition(1, end);
lineRenderer.startColor = color;
lineRenderer.endColor = color;
}
}
}

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: c4aa7ca7fb2a2214ead03b478fe55005
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _jointRotation: {instanceID: 0}
- _lineRendererMaterial: {fileID: 10306, guid: 0000000000000000f000000000000000,
type: 0}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,136 @@
/*
* 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.
*/
using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class JointVelocityDebugVisual : MonoBehaviour
{
[SerializeField]
private JointVelocityActiveState _jointVelocity;
[SerializeField]
private Material _lineRendererMaterial;
[SerializeField]
private float _rendererLineWidth = 0.005f;
[SerializeField]
private float _rendererLineLength = 0.1f;
private List<LineRenderer> _lineRenderers;
private int _enabledRendererCount;
protected bool _started = false;
protected virtual void Awake()
{
_lineRenderers = new List<LineRenderer>();
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(_jointVelocity, nameof(_jointVelocity));
this.AssertField(_lineRendererMaterial, nameof(_lineRendererMaterial));
this.EndStart(ref _started);
}
protected virtual void OnDisable()
{
if (_started)
{
ResetLines();
}
}
protected virtual void Update()
{
ResetLines();
foreach (var config in _jointVelocity.FeatureConfigs)
{
if (_jointVelocity.Hand.GetJointPose(config.Feature, out Pose jointPose) &&
_jointVelocity.FeatureStates.TryGetValue(config, out var state))
{
DrawDebugLine(jointPose.position, state.TargetVector, state.Amount);
}
}
}
private void DrawDebugLine(Vector3 jointPos, Vector3 direction, float amount)
{
Vector3 fullLength = direction.normalized * _rendererLineLength;
bool metThreshold = amount >= 1f;
if (metThreshold)
{
AddLine(jointPos, jointPos + fullLength, Color.green);
}
else
{
Vector3 breakpoint = Vector3.Lerp(jointPos, jointPos + fullLength, amount);
AddLine(jointPos, breakpoint, Color.yellow);
AddLine(breakpoint, jointPos + fullLength, Color.red);
}
}
private void ResetLines()
{
foreach (var lineRenderer in _lineRenderers)
{
if (lineRenderer != null)
{
lineRenderer.enabled = false;
}
}
_enabledRendererCount = 0;
}
private void AddLine(Vector3 start, Vector3 end, Color color)
{
LineRenderer lineRenderer;
if (_enabledRendererCount == _lineRenderers.Count)
{
lineRenderer = new GameObject().AddComponent<LineRenderer>();
lineRenderer.startWidth = _rendererLineWidth;
lineRenderer.endWidth = _rendererLineWidth;
lineRenderer.positionCount = 2;
lineRenderer.material = _lineRendererMaterial;
_lineRenderers.Add(lineRenderer);
}
else
{
lineRenderer = _lineRenderers[_enabledRendererCount];
}
_enabledRendererCount++;
lineRenderer.enabled = true;
lineRenderer.SetPosition(0, start);
lineRenderer.SetPosition(1, end);
lineRenderer.startColor = color;
lineRenderer.endColor = color;
}
}
}

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ad4c672f99cfab247af90b55917b90f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _jointVelocity: {instanceID: 0}
- _lineRendererMaterial: {fileID: 10306, guid: 0000000000000000f000000000000000,
type: 0}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,118 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using TMPro;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class TransformFeatureDebugVisual : MonoBehaviour
{
[SerializeField]
private Renderer _target;
[SerializeField]
private Color _normalColor = Color.red;
[SerializeField]
private Color _activeColor = Color.green;
[SerializeField]
private TextMeshPro _targetText;
private TransformFeatureStateProvider _transformFeatureStateProvider;
private TransformRecognizerActiveState _transformRecognizerActiveState;
private Material _material;
private bool _lastActiveValue;
private TransformFeatureConfig _targetConfig;
private bool _initialized;
private Handedness _handedness;
protected virtual void Awake()
{
_material = _target.material;
this.AssertField(_material, nameof(_material));
this.AssertField(_targetText, nameof(_targetText));
_material.color = _lastActiveValue ? _activeColor : _normalColor;
}
private void OnDestroy()
{
Destroy(_material);
}
public void Initialize(
Handedness handedness,
TransformFeatureConfig targetConfig,
TransformFeatureStateProvider transformFeatureStateProvider,
TransformRecognizerActiveState transformActiveState)
{
_handedness = handedness;
_initialized = true;
_transformFeatureStateProvider = transformFeatureStateProvider;
_transformRecognizerActiveState = transformActiveState;
_targetConfig = targetConfig;
}
protected virtual void Update()
{
if (!_initialized)
{
return;
}
bool isActive = false;
TransformFeature feature = _targetConfig.Feature;
if (_transformFeatureStateProvider.GetCurrentState(
_transformRecognizerActiveState.TransformConfig,
feature,
out string currentState))
{
float? featureVal = _transformFeatureStateProvider.GetFeatureValue(
_transformRecognizerActiveState.TransformConfig, feature);
isActive = _transformFeatureStateProvider.IsStateActive(
_transformRecognizerActiveState.TransformConfig,
feature,
_targetConfig.Mode,
_targetConfig.State);
string featureValStr = featureVal.HasValue ? featureVal.Value.ToString("F2") : "--";
_targetText.text = $"{feature}\n" +
$"{currentState} ({featureValStr})";
}
else
{
_targetText.text = $"{feature}\n";
}
if (isActive != _lastActiveValue)
{
_material.color = isActive ? _activeColor : _normalColor;
_lastActiveValue = isActive;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9390f6f5772c6d942a736437d338b667
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,69 @@
/*
* 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.
*/
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class TransformFeatureVectorDebugParentVisual : MonoBehaviour
{
[SerializeField]
private TransformRecognizerActiveState _transformRecognizerActiveState;
[SerializeField]
private GameObject _vectorVisualPrefab;
public void GetTransformFeatureVectorAndWristPos(TransformFeature feature,
bool isHandVector, ref Vector3? featureVec, ref Vector3? wristPos)
{
_transformRecognizerActiveState.GetFeatureVectorAndWristPos(feature, isHandVector,
ref featureVec, ref wristPos);
}
protected virtual void Awake()
{
this.AssertField(_transformRecognizerActiveState, nameof(_transformRecognizerActiveState));
this.AssertField(_vectorVisualPrefab, nameof(_vectorVisualPrefab));
}
protected virtual void Start()
{
var featureConfigs = _transformRecognizerActiveState.FeatureConfigs;
foreach (var featureConfig in featureConfigs)
{
var feature = featureConfig.Feature;
CreateVectorDebugView(feature, false);
CreateVectorDebugView(feature, true);
}
}
private void CreateVectorDebugView(TransformFeature feature, bool trackingHandVector)
{
var featureDebugVis = Instantiate(_vectorVisualPrefab, this.transform);
var debugVisComp = featureDebugVis.GetComponent<TransformFeatureVectorDebugVisual>();
debugVisComp.Initialize(feature, trackingHandVector, this, trackingHandVector ?
Color.blue : Color.black);
var debugVisTransform = debugVisComp.transform;
debugVisTransform.localRotation = Quaternion.identity;
debugVisTransform.localPosition = Vector3.zero;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8cd27ccbb9a942e47a1b2bfd53f957e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,101 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class TransformFeatureVectorDebugVisual : MonoBehaviour
{
public IHand Hand { get; private set; }
[SerializeField]
private LineRenderer _lineRenderer;
[SerializeField]
private float _lineWidth = 0.005f;
[SerializeField]
private float _lineScale = 0.1f;
private bool _isInitialized = false;
private TransformFeature _feature;
private TransformFeatureVectorDebugParentVisual _parent;
private bool _trackingHandVector = false;
protected virtual void Awake()
{
this.AssertField(_lineRenderer, nameof(_lineRenderer));
_lineRenderer.enabled = false;
}
public void Initialize(TransformFeature feature,
bool trackingHandVector,
TransformFeatureVectorDebugParentVisual parent,
Color lineColor)
{
_isInitialized = true;
_lineRenderer.enabled = true;
_lineRenderer.positionCount = 2;
_lineRenderer.startColor = lineColor;
_lineRenderer.endColor = lineColor;
_feature = feature;
_trackingHandVector = trackingHandVector;
_parent = parent;
}
protected virtual void Update()
{
if (!_isInitialized)
{
return;
}
Vector3? featureVec = null;
Vector3? wristPos = null;
_parent.GetTransformFeatureVectorAndWristPos(_feature,
_trackingHandVector, ref featureVec, ref wristPos);
if (featureVec == null || wristPos == null)
{
if (_lineRenderer.enabled)
{
_lineRenderer.enabled = false;
}
return;
}
if (!_lineRenderer.enabled)
{
_lineRenderer.enabled = true;
}
if (Mathf.Abs(_lineRenderer.startWidth - _lineWidth) > Mathf.Epsilon)
{
_lineRenderer.startWidth = _lineWidth;
_lineRenderer.endWidth = _lineWidth;
}
_lineRenderer.SetPosition(0, wristPos.Value);
_lineRenderer.SetPosition(1, wristPos.Value + _lineScale*featureVec.Value);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5b3b9e0179019c46a27bcc4ed7916ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,140 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using TMPro;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Debug
{
public class TransformRecognizerDebugVisual : MonoBehaviour
{
[SerializeField]
private Hand _hand;
[SerializeField]
private TransformFeatureStateProvider _transformFeatureStateProvider;
[SerializeField]
private TransformRecognizerActiveState _transformRecognizerActiveState;
[SerializeField]
private Renderer _target;
[SerializeField]
private Color _normalColor = Color.red;
[SerializeField]
private Color _activeColor = Color.green;
[SerializeField]
private GameObject _transformFeatureDebugVisualPrefab;
[SerializeField]
private Transform _debugVisualParent;
[SerializeField]
private Vector3 _featureSpacingVec = new Vector3(1.0f, 0.0f, 0.0f);
[SerializeField]
private Vector3 _featureDebugLocalScale = new Vector3(0.3f, 0.3f, 0.3f);
[SerializeField]
private TextMeshPro _targetText;
private Material _material;
private bool _lastActiveValue = false;
protected virtual void Awake()
{
this.AssertField(_hand, nameof(_hand));
this.AssertField(_transformRecognizerActiveState, nameof(_transformRecognizerActiveState));
this.AssertField(_target, nameof(_target));
this.AssertField(_transformFeatureDebugVisualPrefab, nameof(_transformFeatureDebugVisualPrefab));
this.AssertField(_targetText, nameof(_targetText));
_material = _target.material;
_material.color = _lastActiveValue ? _activeColor : _normalColor;
if (_debugVisualParent == null)
{
_debugVisualParent = transform;
}
}
protected virtual void Start()
{
Vector3 totalDisp = Vector3.zero;
string shapeNames = "";
this.AssertField(_transformFeatureStateProvider, nameof(_transformFeatureStateProvider));
var featureConfigs = _transformRecognizerActiveState.FeatureConfigs;
foreach (var featureConfig in featureConfigs)
{
var featureDebugVis = Instantiate(_transformFeatureDebugVisualPrefab, _debugVisualParent);
var debugVisComp = featureDebugVis.GetComponent<TransformFeatureDebugVisual>();
debugVisComp.Initialize(_transformRecognizerActiveState.Hand.Handedness,
featureConfig,
_transformFeatureStateProvider,
_transformRecognizerActiveState);
var debugVisTransform = debugVisComp.transform;
debugVisTransform.localScale = _featureDebugLocalScale;
debugVisTransform.localRotation = Quaternion.identity;
debugVisTransform.localPosition = totalDisp;
totalDisp += _featureSpacingVec;
if (!String.IsNullOrEmpty(shapeNames)) { shapeNames += "\n "; }
shapeNames += $"{featureConfig.Mode} {featureConfig.State} ({_transformRecognizerActiveState.Hand.Handedness})";
}
_targetText.text = $"{shapeNames}";
}
private void OnDestroy()
{
Destroy(_material);
}
private bool AllActive()
{
if (!_transformRecognizerActiveState.Active)
{
return false;
}
return true;
}
protected virtual void Update()
{
bool isActive = AllActive();
if (_lastActiveValue != isActive)
{
_material.color = isActive ? _activeColor : _normalColor;
_lastActiveValue = isActive;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aca13d9f89c0c904a8d62bde280f5b52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,159 @@
/*
* 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.
*/
namespace Oculus.Interaction.PoseDetection
{
using FingerFeatureConfig = ShapeRecognizer.FingerFeatureConfig;
public class FeatureConfigBuilder
{
public class BuildCondition<TBuildState>
{
private readonly BuildStateDelegate _buildStateFn;
public delegate TBuildState BuildStateDelegate(FeatureStateActiveMode mode);
public BuildCondition(BuildStateDelegate buildStateFn)
{
_buildStateFn = buildStateFn;
}
public TBuildState Is => _buildStateFn(FeatureStateActiveMode.Is);
public TBuildState IsNot => _buildStateFn(FeatureStateActiveMode.IsNot);
}
}
public class FingerFeatureConfigBuilder : FeatureConfigBuilder
{
public static BuildCondition<OpenCloseStateBuilder> Curl { get; } =
new BuildCondition<OpenCloseStateBuilder>(mode => new OpenCloseStateBuilder(mode, FingerFeature.Curl));
public static BuildCondition<OpenCloseStateBuilder> Flexion { get; } =
new BuildCondition<OpenCloseStateBuilder>(mode => new OpenCloseStateBuilder(mode, FingerFeature.Flexion));
public static BuildCondition<AbductionStateBuilder> Abduction { get; } =
new BuildCondition<AbductionStateBuilder>(mode => new AbductionStateBuilder(mode));
public static BuildCondition<OppositionStateBuilder> Opposition { get; } =
new BuildCondition<OppositionStateBuilder>(mode => new OppositionStateBuilder(mode));
public class OpenCloseStateBuilder
{
private readonly FeatureStateActiveMode _mode;
private readonly FingerFeature _fingerFeature;
private readonly FeatureStateDescription[] _states;
public OpenCloseStateBuilder(FeatureStateActiveMode featureStateActiveMode,
FingerFeature fingerFeature)
{
_mode = featureStateActiveMode;
_fingerFeature = fingerFeature;
_states = FingerFeatureProperties.FeatureDescriptions[_fingerFeature].FeatureStates;
}
public FingerFeatureConfig Open =>
new FingerFeatureConfig { Feature = _fingerFeature, Mode = _mode, State = _states[0].Id };
public FingerFeatureConfig Neutral =>
new FingerFeatureConfig { Feature = _fingerFeature, Mode = _mode, State = _states[1].Id };
public FingerFeatureConfig Closed =>
new FingerFeatureConfig { Feature = _fingerFeature, Mode = _mode, State = _states[2].Id };
}
public class AbductionStateBuilder
{
private readonly FeatureStateActiveMode _mode;
public AbductionStateBuilder(FeatureStateActiveMode mode)
{
_mode = mode;
}
public FingerFeatureConfig None =>
new FingerFeatureConfig { Feature = FingerFeature.Abduction, Mode = _mode, State = FingerFeatureProperties.AbductionFeatureStates[0].Id };
public FingerFeatureConfig Closed =>
new FingerFeatureConfig { Feature = FingerFeature.Abduction, Mode = _mode, State = FingerFeatureProperties.AbductionFeatureStates[1].Id };
public FingerFeatureConfig Open =>
new FingerFeatureConfig { Feature = FingerFeature.Abduction, Mode = _mode, State = FingerFeatureProperties.AbductionFeatureStates[2].Id };
}
public class OppositionStateBuilder
{
private readonly FeatureStateActiveMode _mode;
public OppositionStateBuilder(FeatureStateActiveMode mode)
{
_mode = mode;
}
public FingerFeatureConfig Touching =>
new FingerFeatureConfig { Feature = FingerFeature.Opposition, Mode = _mode, State = FingerFeatureProperties.OppositionFeatureStates[0].Id };
public FingerFeatureConfig Near =>
new FingerFeatureConfig { Feature = FingerFeature.Opposition, Mode = _mode, State = FingerFeatureProperties.OppositionFeatureStates[1].Id };
public FingerFeatureConfig None =>
new FingerFeatureConfig { Feature = FingerFeature.Opposition, Mode = _mode, State = FingerFeatureProperties.OppositionFeatureStates[2].Id };
}
}
public class TransformFeatureConfigBuilder : FeatureConfigBuilder
{
public static BuildCondition<TrueFalseStateBuilder> WristUp { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.WristUp));
public static BuildCondition<TrueFalseStateBuilder> WristDown { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.WristDown));
public static BuildCondition<TrueFalseStateBuilder> PalmDown { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.PalmDown));
public static BuildCondition<TrueFalseStateBuilder> PalmUp { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.PalmUp));
public static BuildCondition<TrueFalseStateBuilder> PalmTowardsFace { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.PalmTowardsFace));
public static BuildCondition<TrueFalseStateBuilder> PalmAwayFromFace { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.PalmAwayFromFace));
public static BuildCondition<TrueFalseStateBuilder> FingersUp { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.FingersUp));
public static BuildCondition<TrueFalseStateBuilder> FingersDown { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.FingersDown));
public static BuildCondition<TrueFalseStateBuilder> PinchClear { get; } =
new BuildCondition<TrueFalseStateBuilder>(mode => new TrueFalseStateBuilder(mode, TransformFeature.PinchClear));
public class TrueFalseStateBuilder
{
private readonly FeatureStateActiveMode _mode;
private readonly TransformFeature _transformFeature;
private readonly FeatureStateDescription[] _states;
public TrueFalseStateBuilder(FeatureStateActiveMode featureStateActiveMode,
TransformFeature transformFeature)
{
_mode = featureStateActiveMode;
_transformFeature = transformFeature;
_states = TransformFeatureProperties.FeatureDescriptions[_transformFeature].FeatureStates;
}
public TransformFeatureConfig Open =>
new TransformFeatureConfig { Feature = _transformFeature, Mode = _mode, State = _states[0].Id };
public TransformFeatureConfig Closed =>
new TransformFeatureConfig { Feature = _transformFeature, Mode = _mode, State = _states[1].Id };
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1bb5c328f4204486ad98f3f962154e07
timeCreated: 1633634969

View File

@ -0,0 +1,60 @@
/*
* 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.
*/
namespace Oculus.Interaction.PoseDetection
{
public class FeatureStateDescription
{
public FeatureStateDescription(string id, string name)
{
Id = id;
Name = name;
}
public string Id { get; }
public string Name { get; }
}
public class FeatureDescription
{
public FeatureDescription(string shortDescription, string description,
float minValueHint, float maxValueHint,
FeatureStateDescription[] featureStates)
{
ShortDescription = shortDescription;
Description = description;
MinValueHint = minValueHint;
MaxValueHint = maxValueHint;
FeatureStates = featureStates;
}
public string ShortDescription { get; }
public string Description { get; }
public float MinValueHint { get; }
public float MaxValueHint { get; }
/// <summary>
/// A hint to the editor on which feature states to provide by default, and in which order
/// they should appear.
/// The underlying system will accept other ranges; this is just for the UI.
/// </summary>
public FeatureStateDescription[] FeatureStates { get; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 53118e1a377b4b6685ca776f1d23ca3b
timeCreated: 1633632912

View File

@ -0,0 +1,313 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection
{
public enum FeatureStateActiveMode {
Is,
IsNot,
}
[Serializable]
public abstract class FeatureConfigBase<TFeature>
{
[SerializeField]
private FeatureStateActiveMode _mode;
[SerializeField]
private TFeature _feature;
[SerializeField]
private string _state;
public FeatureStateActiveMode Mode
{
get => _mode;
set { _mode = value; }
}
public TFeature Feature
{
get => _feature;
set { _feature = value; }
}
public string State
{
get => _state;
set { _state = value; }
}
}
/// <summary>
/// A helper class that keeps track of the current state of features, quantized into
/// corresponding FeatureStates.
/// </summary>
/// <typeparam name="TFeature">
/// An enum containing all features that can be tracked.
/// </typeparam>
/// <typeparam name="TFeatureState">
/// An enum of all the possible states of each member of the <see cref="TFeature"/> param.
/// The name of each member of this enum must be prefixed with one of the values of TFeature.
/// </typeparam>
public class FeatureStateProvider<TFeature, TFeatureState>
where TFeature : unmanaged, Enum
where TFeatureState : IEquatable<TFeatureState>
{
/// <summary>
/// This should be updated with current value of the input data frameId. It is used to
/// determine if values need to be recalculated.
/// </summary>
public int LastUpdatedFrameId { get; set; }
private struct FeatureStateSnapshot
{
public bool HasCurrentState;
public TFeatureState State;
public TFeatureState DesiredState;
public int LastUpdatedFrameId;
public double DesiredStateEntryTime;
}
// A map of Map of (int)Feature => current state
private FeatureStateSnapshot[] _featureToCurrentState;
// A map of Map of (int)Feature => threshold configuration
private IFeatureStateThresholds<TFeature, TFeatureState>[] _featureToThresholds;
private readonly Func<TFeature, float?> _valueReader;
private readonly Func<TFeature, int> _featureToInt;
private readonly Func<float> _timeProvider;
#region Lookup Helpers
private int EnumToInt(TFeature value) => _featureToInt(value);
private static readonly TFeature[] FeatureEnumValues = (TFeature[])Enum.GetValues(typeof(TFeature));
private IFeatureThresholds<TFeature,TFeatureState> _featureThresholds;
#endregion
public FeatureStateProvider(Func<TFeature, float?> valueReader,
Func<TFeature, int> featureToInt,
Func<float> timeProvider)
{
_valueReader = valueReader;
_featureToInt = featureToInt;
_timeProvider = timeProvider;
}
public void InitializeThresholds(IFeatureThresholds<TFeature, TFeatureState> featureThresholds)
{
_featureThresholds = featureThresholds;
_featureToThresholds = ValidateFeatureThresholds(featureThresholds.FeatureStateThresholds);
InitializeStates();
}
public IFeatureStateThresholds<TFeature, TFeatureState>[] ValidateFeatureThresholds(
IReadOnlyList<IFeatureStateThresholds<TFeature, TFeatureState>> featureStateThresholdsList)
{
var featureToFeatureStateThresholds =
new IFeatureStateThresholds<TFeature, TFeatureState>[Enum.GetNames(typeof(TFeature)).Length];
foreach (var featureStateThresholds in featureStateThresholdsList)
{
var featureIdx = EnumToInt(featureStateThresholds.Feature);
featureToFeatureStateThresholds[featureIdx] = featureStateThresholds;
// Just check that the thresholds are set correctly.
for (var index = 0; index < featureStateThresholds.Thresholds.Count; index++)
{
var featureStateThreshold = featureStateThresholds.Thresholds[index];
if (featureStateThreshold.ToFirstWhenBelow >
featureStateThreshold.ToSecondWhenAbove)
{
Assert.IsTrue(false,
$"Feature {featureStateThresholds.Feature} threshold at index {index}: ToFirstWhenBelow should be less than ToSecondWhenAbove.");
}
}
}
for (int i = 0; i < featureToFeatureStateThresholds.Length; i++)
{
if (featureToFeatureStateThresholds[i] == null)
{
Assert.IsNotNull(featureToFeatureStateThresholds[i],
$"StateThresholds does not contain an entry for feature with value {i}");
}
}
return featureToFeatureStateThresholds;
}
private void InitializeStates()
{
// Set up current state
_featureToCurrentState = new FeatureStateSnapshot[FeatureEnumValues.Length];
foreach (TFeature feature in FeatureEnumValues)
{
int featureIdx = EnumToInt(feature);
// Set default state.
ref var currentState = ref _featureToCurrentState[featureIdx];
currentState.State = default;
currentState.DesiredState = default;
currentState.DesiredStateEntryTime = 0;
}
}
private ref IFeatureStateThresholds<TFeature, TFeatureState> GetFeatureThresholds(TFeature feature)
{
Assert.IsNotNull(_featureToThresholds, "Must call InitializeThresholds() before querying state");
return ref _featureToThresholds[EnumToInt(feature)];
}
public TFeatureState GetCurrentFeatureState(TFeature feature)
{
Assert.IsNotNull(_featureToThresholds, "Must call InitializeThresholds() before querying state");
ref var currentState = ref _featureToCurrentState[EnumToInt(feature)];
if (currentState.LastUpdatedFrameId == LastUpdatedFrameId)
{
return currentState.State;
}
// Reads the raw value
float? value = _valueReader(feature);
if (!value.HasValue)
{
return currentState.State;
}
// Hand data changed since this was last queried.
currentState.LastUpdatedFrameId = LastUpdatedFrameId;
// Determine which state we should transition to based on the thresholds, and previous state.
var featureStateThresholds = GetFeatureThresholds(feature).Thresholds;
TFeatureState desiredState;
if (!currentState.HasCurrentState)
{
desiredState = ReadDesiredState(value.Value, featureStateThresholds);
}
else
{
desiredState = ReadDesiredState(value.Value, featureStateThresholds,
currentState.State);
}
// If this is the same as the current state, do nothing.
if (desiredState.Equals(currentState.State))
{
return currentState.State;
}
// If the desired state is different from the previous frame, reset the timer
var currentTime = _timeProvider();
if (!desiredState.Equals(currentState.DesiredState))
{
currentState.DesiredStateEntryTime = currentTime;
currentState.DesiredState = desiredState;
}
// If the time in the desired state has exceeded the threshold, update the actual
// state.
if (currentState.DesiredStateEntryTime + _featureThresholds.MinTimeInState <= currentTime)
{
currentState.HasCurrentState = true;
currentState.State = desiredState;
}
return currentState.State;
}
private TFeatureState ReadDesiredState(float value,
IReadOnlyList<IFeatureStateThreshold<TFeatureState>> featureStateThresholds,
TFeatureState previousState)
{
// Run it through the threshold calculation.
var currentFeatureState = previousState;
for (int i = 0; i < featureStateThresholds.Count; ++i)
{
var featureStateThreshold = featureStateThresholds[i];
if (currentFeatureState.Equals(featureStateThreshold.FirstState) &&
value > featureStateThreshold.ToSecondWhenAbove)
{
// In the first state and exceeded the threshold to enter the second state.
return featureStateThreshold.SecondState;
}
if (currentFeatureState.Equals(featureStateThreshold.SecondState) &&
value < featureStateThreshold.ToFirstWhenBelow)
{
// In the second state and exceeded the threshold to enter the first state.
return featureStateThreshold.FirstState;
}
}
return previousState;
}
private TFeatureState ReadDesiredState(float value,
IReadOnlyList<IFeatureStateThreshold<TFeatureState>> featureStateThresholds)
{
// Run it through the threshold calculation.
TFeatureState currentFeatureState = default;
for (int i = 0; i < featureStateThresholds.Count; ++i)
{
var featureStateThreshold = featureStateThresholds[i];
if (value <= featureStateThreshold.ToSecondWhenAbove)
{
currentFeatureState = featureStateThreshold.FirstState;
break;
}
currentFeatureState = featureStateThreshold.SecondState;
}
return currentFeatureState;
}
public void ReadTouchedFeatureStates()
{
Assert.IsNotNull(_featureToThresholds, "Must call InitializeThresholds() before querying state");
for (var featureIdx = 0;
featureIdx < _featureToCurrentState.Length;
featureIdx++)
{
ref FeatureStateSnapshot stateSnapshot =
ref _featureToCurrentState[featureIdx];
if (stateSnapshot.LastUpdatedFrameId == 0)
{
// This state has never been queried via IsStateActive, so don't
// bother updating it.
continue;
}
// Force evaluation with this new frame Id.
GetCurrentFeatureState(FeatureEnumValues[featureIdx]);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b2b0882e924a4f52b3cebb599b3fa0d1
timeCreated: 1628024315

View File

@ -0,0 +1,113 @@
/*
* 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.
*/
using System.Collections.Generic;
namespace Oculus.Interaction.PoseDetection
{
public static class FingerFeatureProperties
{
public static readonly FeatureStateDescription[] CurlFeatureStates =
{
new FeatureStateDescription("0", "Open"),
new FeatureStateDescription("1", "Neutral"),
new FeatureStateDescription("2", "Closed"),
};
public static readonly FeatureStateDescription[] FlexionFeatureStates =
{
new FeatureStateDescription("3", "Open"),
new FeatureStateDescription("4", "Neutral"),
new FeatureStateDescription("5", "Closed"),
};
public static readonly FeatureStateDescription[] AbductionFeatureStates =
{
new FeatureStateDescription("6", "None"),
new FeatureStateDescription("7", "Closed"),
new FeatureStateDescription("8", "Open"),
};
public static readonly FeatureStateDescription[] OppositionFeatureStates =
{
new FeatureStateDescription("9", "Touching"),
new FeatureStateDescription("10", "Near"),
new FeatureStateDescription("11", "None"),
};
public static IReadOnlyDictionary<FingerFeature, FeatureDescription> FeatureDescriptions
{
get;
} =
new Dictionary<FingerFeature, FeatureDescription>
{
[FingerFeature.Curl] = new FeatureDescription(FeatureCurlShortHelpText,
FeatureCurlDetailHelpText,
180,
260,
CurlFeatureStates),
[FingerFeature.Flexion] = new FeatureDescription(
FeatureFlexionShortHelpText,
FeatureFlexionDetailHelpText,
180,
260,
FlexionFeatureStates),
[FingerFeature.Abduction] = new FeatureDescription(
FeatureAbductionShortHelpText,
FeatureAbductionDetailHelpText,
8,
90,
AbductionFeatureStates),
[FingerFeature.Opposition] = new FeatureDescription(
FeatureOppositionShortHelpText,
FeatureOppositionDetailHelpText,
0,
0.2f,
OppositionFeatureStates)
};
public const string FeatureCurlShortHelpText =
"Convex angle (in degrees) representing the top 2 joints of the fingers. Angle increases as finger curl becomes closed.";
public const string FeatureCurlDetailHelpText =
"Calculated from the average of the convex angles formed by the 2 bones connected to Joint 2, and 2 bones connected to Joint 3.\n" +
"Values above 180 Positive show a curled state, while values below 180 represent hyper-extension.";
public const string FeatureFlexionShortHelpText =
"Convex angle (in degrees) of joint 1 of the finger. Angle increases as finger flexion becomes closed.";
public const string FeatureFlexionDetailHelpText =
"Calculated from the angle between the bones connected to finger Joint 1 around the Z axis of the joint.\n" +
"For fingers, joint 1 is commonly known as the 'Knuckle'; but for the thumb it is alongside the wrist.\n" +
"Values above 180 Positive show a curled state, while values below 180 represent hyper-extension." +
"upwards from the palm.";
public const string FeatureAbductionShortHelpText =
"Angle (in degrees) between the given finger, and the next finger towards the pinkie.";
public const string FeatureAbductionDetailHelpText =
"Zero value implies that the two fingers are parallel.\n" +
"Positive angles indicate that the fingertips are spread apart.\n" +
"Small negative angles are possible, and indicate that the finger is pressed up against the next finger.";
public const string FeatureOppositionShortHelpText =
"Distance between the tip of the given finger and the tip of the thumb.\n" +
"Calculated tracking space, with a 1.0 hand scale.";
public const string FeatureOppositionDetailHelpText =
"Positive values indicate that the fingertips are spread apart.\n" +
"Negative values are not possible.";
public const string FeatureStateThresholdMidpointHelpText = "The angle at which a state will transition from A > B (or B > A)";
public const string FeatureStateThresholdWidthHelpText =
"How far the angle must exceed the midpoint until the transition can occur. " +
"This is to prevent rapid flickering at transition edges.";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ef4951587e241c79251e9b88839be0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,307 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.PoseDetection
{
internal class FingerFeatureStateDictionary
{
struct HandFingerState
{
public FeatureStateProvider<FingerFeature, string> StateProvider;
}
private readonly HandFingerState[] _fingerState = new HandFingerState[Constants.NUM_FINGERS];
public void InitializeFinger(HandFinger finger,
FeatureStateProvider<FingerFeature, string> stateProvider)
{
_fingerState[(int)finger] = new HandFingerState
{
StateProvider = stateProvider
};
}
public FeatureStateProvider<FingerFeature, string> GetStateProvider(HandFinger finger)
{
return _fingerState[(int)finger].StateProvider;
}
}
public interface IFingerFeatureStateProvider
{
bool GetCurrentState(HandFinger finger, FingerFeature fingerFeature, out string currentState);
bool IsStateActive(HandFinger finger, FingerFeature feature, FeatureStateActiveMode mode, string stateId);
float? GetFeatureValue(HandFinger finger, FingerFeature fingerFeature);
}
/// <summary>
/// Interprets finger feature values using <see cref="FingerShapes"/> and uses
/// the given <see cref="FingerFeatureStateThresholds"/> to quantize these values into states.
/// To avoid rapid fluctuations at the edges of two states, this class uses the calculated
/// feature state from the previous frame and the given state thresholds to apply a buffer
/// between state transition edges.
/// </summary>
public class FingerFeatureStateProvider : MonoBehaviour, IFingerFeatureStateProvider
{
[SerializeField, Interface(typeof(IHand))]
[Tooltip("Data source used to retrieve finger bone rotations.")]
private UnityEngine.Object _hand;
public IHand Hand { get; private set; }
[Serializable]
public struct FingerStateThresholds
{
[Tooltip("Which finger the state thresholds apply to.")]
public HandFinger Finger;
[Tooltip("State threshold configuration")]
public FingerFeatureStateThresholds StateThresholds;
}
[SerializeField]
[Tooltip("Contains state transition threasholds for each finger. " +
"Must contain 5 entries (one for each finger). " +
"Each finger must exist in the list exactly once.")]
private List<FingerStateThresholds> _fingerStateThresholds;
[Header("Advanced Settings")]
[SerializeField]
[Tooltip("If true, disables proactive evaluation of any FingerFeature that has been " +
"queried at least once. This will force lazy-evaluation of state within calls " +
"to IsStateActive, which means you must call IsStateActive for each feature manually " +
"each frame to avoid missing transitions between states.")]
private bool _disableProactiveEvaluation;
protected bool _started = false;
private FingerFeatureStateDictionary _state;
Func<float> _timeProvider;
public static FingerShapes DefaultFingerShapes { get; } = new FingerShapes();
private FingerShapes _fingerShapes = DefaultFingerShapes;
private ReadOnlyHandJointPoses _handJointPoses;
#region Unity Lifecycle Methods
protected virtual void Awake()
{
Hand = _hand as IHand;
_state = new FingerFeatureStateDictionary();
_handJointPoses = ReadOnlyHandJointPoses.Empty;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hand, nameof(Hand));
if (_timeProvider == null)
{
_timeProvider = () => Time.time;
}
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Hand.WhenHandUpdated += HandDataAvailable;
ReadStateThresholds();
}
}
protected virtual void OnDisable()
{
if (_started)
{
Hand.WhenHandUpdated -= HandDataAvailable;
_handJointPoses = ReadOnlyHandJointPoses.Empty;
}
}
#endregion
private void ReadStateThresholds()
{
this.AssertCollectionField(_fingerStateThresholds, nameof(_fingerStateThresholds));
this.AssertField(_timeProvider, nameof(_timeProvider));
this.AssertIsTrue(Constants.NUM_FINGERS == _fingerStateThresholds.Count,
$"The{AssertUtils.Nicify(nameof(_fingerStateThresholds))} count must be equal to {Constants.NUM_FINGERS}.");
HandFingerFlags seenFingers = HandFingerFlags.None;
foreach (FingerStateThresholds fingerStateThresholds in _fingerStateThresholds)
{
seenFingers |= HandFingerUtils.ToFlags(fingerStateThresholds.Finger);
HandFinger finger = fingerStateThresholds.Finger;
var featureStateProvider = _state.GetStateProvider(finger);
if (featureStateProvider == null)
{
featureStateProvider =
new FeatureStateProvider<FingerFeature, string>(
feature => GetFeatureValue(finger, feature),
feature => (int)feature,
_timeProvider);
_state.InitializeFinger(fingerStateThresholds.Finger,
featureStateProvider);
}
featureStateProvider.InitializeThresholds(fingerStateThresholds.StateThresholds);
}
this.AssertIsTrue(seenFingers == HandFingerFlags.All,
$"The {AssertUtils.Nicify(nameof(_fingerStateThresholds))} is missing some fingers.");
}
private void HandDataAvailable()
{
int frameId = Hand.CurrentDataVersion;
if (!Hand.GetJointPosesFromWrist(out _handJointPoses))
{
return;
}
// Update the frameId of all state providers to mark data as dirty. If
// proactiveEvaluation is enabled, also read the state of any feature that has been
// touched, which will force it to evaluate.
if (!_disableProactiveEvaluation)
{
for (var fingerIdx = 0; fingerIdx < Constants.NUM_FINGERS; ++fingerIdx)
{
var featureStateProvider = _state.GetStateProvider((HandFinger)fingerIdx);
featureStateProvider.LastUpdatedFrameId = frameId;
featureStateProvider.ReadTouchedFeatureStates();
}
}
else
{
for (var fingerIdx = 0; fingerIdx < Constants.NUM_FINGERS; ++fingerIdx)
{
_state.GetStateProvider((HandFinger)fingerIdx).LastUpdatedFrameId =
frameId;
}
}
}
public bool GetCurrentState(HandFinger finger, FingerFeature fingerFeature, out string currentState)
{
if (!IsDataValid())
{
currentState = default;
return false;
}
else
{
currentState = GetCurrentFingerFeatureState(finger, fingerFeature);
return currentState != default;
}
}
private string GetCurrentFingerFeatureState(HandFinger finger, FingerFeature fingerFeature)
{
return _state.GetStateProvider(finger).GetCurrentFeatureState(fingerFeature);
}
/// <summary>
/// Returns the current value of the feature. If the finger joints are not populated with
/// valid data (for instance, due to a disconnected hand), the method will return NaN.
/// </summary>
public float? GetFeatureValue(HandFinger finger, FingerFeature fingerFeature)
{
if (!IsDataValid())
{
return null;
}
return _fingerShapes.GetValue(finger, fingerFeature, Hand);
}
private bool IsDataValid()
{
return _handJointPoses.Count > 0;
}
public FingerShapes GetValueProvider(HandFinger finger)
{
return _fingerShapes;
}
public bool IsStateActive(HandFinger finger, FingerFeature feature, FeatureStateActiveMode mode, string stateId)
{
var currentState = GetCurrentFingerFeatureState(finger, feature);
switch (mode)
{
case FeatureStateActiveMode.Is:
return currentState == stateId;
case FeatureStateActiveMode.IsNot:
return currentState != stateId;
default:
return false;
}
}
#region Inject
public void InjectAllFingerFeatureStateProvider(IHand hand, List<FingerStateThresholds> fingerStateThresholds, FingerShapes fingerShapes,
bool disableProactiveEvaluation)
{
InjectHand(hand);
InjectFingerStateThresholds(fingerStateThresholds);
InjectFingerShapes(fingerShapes);
InjectDisableProactiveEvaluation(disableProactiveEvaluation);
}
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
public void InjectFingerStateThresholds(List<FingerStateThresholds> fingerStateThresholds)
{
_fingerStateThresholds = fingerStateThresholds;
}
public void InjectFingerShapes(FingerShapes fingerShapes)
{
_fingerShapes = fingerShapes;
}
public void InjectDisableProactiveEvaluation(bool disableProactiveEvaluation)
{
_disableProactiveEvaluation = disableProactiveEvaluation;
}
public void InjectOptionalTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
#endregion
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b942c16a6d6a4edaad7c18c7d5762cdf
timeCreated: 1627755300

View File

@ -0,0 +1,78 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using Oculus.Interaction.PoseDetection;
using UnityEngine;
namespace Oculus.Interaction
{
/// <summary>
/// FingerFeatureStateProviderRef is a utility component that delegates all of its IFingerFeatureStateProvider implementation
/// to the provided FingerFeatureStateProvider object.
/// </summary>
public class FingerFeatureStateProviderRef : MonoBehaviour, IFingerFeatureStateProvider
{
[SerializeField, Interface(typeof(IFingerFeatureStateProvider))]
private UnityEngine.Object _fingerFeatureStateProvider;
public IFingerFeatureStateProvider FingerFeatureStateProvider { get; private set; }
protected virtual void Awake()
{
FingerFeatureStateProvider = _fingerFeatureStateProvider as IFingerFeatureStateProvider;
}
protected virtual void Start()
{
this.AssertField(FingerFeatureStateProvider, nameof(FingerFeatureStateProvider));
}
public bool GetCurrentState(HandFinger finger, FingerFeature fingerFeature, out string currentState)
{
return FingerFeatureStateProvider.GetCurrentState(finger, fingerFeature, out currentState);
}
public bool IsStateActive(HandFinger finger, FingerFeature feature, FeatureStateActiveMode mode,
string stateId)
{
return FingerFeatureStateProvider.IsStateActive(finger, feature, mode, stateId);
}
public float? GetFeatureValue(HandFinger finger, FingerFeature fingerFeature)
{
return FingerFeatureStateProvider.GetFeatureValue(finger, fingerFeature);
}
#region Inject
public void InjectAllFingerFeatureStateProviderRef(IFingerFeatureStateProvider fingerFeatureStateProvider)
{
InjectFingerFeatureStateProvider(fingerFeatureStateProvider);
}
public void InjectFingerFeatureStateProvider(IFingerFeatureStateProvider fingerFeatureStateProvider)
{
_fingerFeatureStateProvider = fingerFeatureStateProvider as UnityEngine.Object;
FingerFeatureStateProvider = fingerFeatureStateProvider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 870d37507a21e5e42877dca0f61dcd28
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,118 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection
{
[Serializable]
public class FingerFeatureStateThreshold : IFeatureStateThreshold<string>
{
public FingerFeatureStateThreshold() { }
public FingerFeatureStateThreshold(float thresholdMidpoint,
float thresholdWidth,
string firstState,
string secondState)
{
_thresholdMidpoint = thresholdMidpoint;
_thresholdWidth = thresholdWidth;
_firstState = firstState;
_secondState = secondState;
}
[SerializeField]
[Tooltip(FingerFeatureProperties.FeatureStateThresholdMidpointHelpText)]
private float _thresholdMidpoint;
[SerializeField]
[Tooltip(FingerFeatureProperties.FeatureStateThresholdWidthHelpText)]
private float _thresholdWidth;
[SerializeField]
[Tooltip("State to transition to when value passes below the threshold")]
private string _firstState;
[SerializeField]
[Tooltip("State to transition to when value passes above the threshold")]
private string _secondState;
public float ThresholdMidpoint => _thresholdMidpoint;
public float ThresholdWidth => _thresholdWidth;
public float ToFirstWhenBelow => _thresholdMidpoint - _thresholdWidth * 0.5f;
public float ToSecondWhenAbove => _thresholdMidpoint + _thresholdWidth * 0.5f;
public string FirstState => _firstState;
public string SecondState => _secondState;
}
[Serializable]
public class FingerFeatureThresholds : IFeatureStateThresholds<FingerFeature, string>
{
public FingerFeatureThresholds() { }
public FingerFeatureThresholds(FingerFeature feature,
IEnumerable<FingerFeatureStateThreshold> thresholds)
{
_feature = feature;
_thresholds = new List<FingerFeatureStateThreshold>(thresholds);
}
[SerializeField]
[Tooltip("Which feature this collection of thresholds controls. " +
"Each feature should exist at most once.")]
private FingerFeature _feature;
[SerializeField]
[Tooltip("List of state transitions, with thresold settings. " +
"The entries in this list must be in ascending order, based on their 'midpoint' values.")]
private List<FingerFeatureStateThreshold> _thresholds;
public FingerFeature Feature => _feature;
public IReadOnlyList<IFeatureStateThreshold<string>> Thresholds => _thresholds;
}
/// <summary>
/// A configuration class that contains a list of threshold boundaries
/// </summary>
[CreateAssetMenu(menuName = "Oculus/Interaction/SDK/Pose Detection/Finger Thresholds")]
public class FingerFeatureStateThresholds : ScriptableObject,
IFeatureThresholds<FingerFeature, string>
{
[SerializeField]
[Tooltip("List of all supported finger features, along with the state entry/exit thresholds.")]
private List<FingerFeatureThresholds> _featureThresholds;
[SerializeField]
[Tooltip("Length of time that the finger must be in the new state before the feature " +
"state provider will use the new value.")]
private double _minTimeInState;
public void Construct(List<FingerFeatureThresholds> featureThresholds,
double minTimeInState)
{
_featureThresholds = featureThresholds;
_minTimeInState = minTimeInState;
}
public IReadOnlyList<IFeatureStateThresholds<FingerFeature, string>>
FeatureStateThresholds => _featureThresholds;
public double MinTimeInState => _minTimeInState;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c5af6ab9d1bd4a47970ad151090281f3
timeCreated: 1627757407

View File

@ -0,0 +1,257 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection
{
public enum FingerFeature
{
[Tooltip(FingerFeatureProperties.FeatureCurlShortHelpText)]
Curl,
[Tooltip(FingerFeatureProperties.FeatureFlexionShortHelpText)]
Flexion,
[Tooltip(FingerFeatureProperties.FeatureAbductionShortHelpText)]
Abduction,
[Tooltip(FingerFeatureProperties.FeatureOppositionShortHelpText)]
Opposition
}
public class FingerShapes
{
#region Joints Visualization Mappings
private static readonly HandJointId[][] CURL_LINE_JOINTS =
{
new [] {HandJointId.HandThumb2, HandJointId.HandThumb3, HandJointId.HandThumbTip},
new [] {HandJointId.HandIndex2, HandJointId.HandIndex3, HandJointId.HandIndexTip},
new [] {HandJointId.HandMiddle2, HandJointId.HandMiddle3, HandJointId.HandMiddleTip},
new [] {HandJointId.HandRing2, HandJointId.HandRing3, HandJointId.HandRingTip},
new [] {HandJointId.HandPinky2, HandJointId.HandPinky3, HandJointId.HandPinkyTip}
};
private static readonly HandJointId[][] FLEXION_LINE_JOINTS =
{
new [] {HandJointId.HandThumb1, HandJointId.HandThumb2, HandJointId.HandThumb3},
new [] {HandJointId.HandIndex1, HandJointId.HandIndex2, HandJointId.HandIndex3},
new [] {HandJointId.HandMiddle1, HandJointId.HandMiddle2, HandJointId.HandMiddle3},
new [] {HandJointId.HandRing1, HandJointId.HandRing2, HandJointId.HandRing3},
new [] {HandJointId.HandPinky1, HandJointId.HandPinky2, HandJointId.HandPinky3}
};
private static readonly HandJointId[][] ABDUCTION_LINE_JOINTS =
{
new [] {HandJointId.HandThumbTip, HandJointId.HandThumb1, HandJointId.HandIndex1, HandJointId.HandIndexTip},
new [] {HandJointId.HandIndexTip, HandJointId.HandIndex1, HandJointId.HandMiddle1, HandJointId.HandMiddleTip},
new [] {HandJointId.HandMiddleTip, HandJointId.HandMiddle1, HandJointId.HandRing1, HandJointId.HandRingTip},
new [] {HandJointId.HandRingTip, HandJointId.HandRing1, HandJointId.HandPinky1, HandJointId.HandPinkyTip},
Array.Empty<HandJointId>()
};
private static readonly HandJointId[][] OPPOSITION_LINE_JOINTS =
{
Array.Empty<HandJointId>(),
new [] {HandJointId.HandThumbTip, HandJointId.HandIndexTip},
new [] {HandJointId.HandThumbTip, HandJointId.HandMiddleTip},
new [] {HandJointId.HandThumbTip, HandJointId.HandRingTip},
new [] {HandJointId.HandThumbTip, HandJointId.HandPinkyTip},
};
#endregion
#region Joint Calculation Mappings
private static readonly HandJointId[][] CURL_ANGLE_JOINTS =
{
new[]
{
HandJointId.HandThumb1, HandJointId.HandThumb2, HandJointId.HandThumb3,
HandJointId.HandThumbTip
},
new[]
{
HandJointId.HandIndex1, HandJointId.HandIndex2, HandJointId.HandIndex3,
HandJointId.HandIndexTip
},
new[]
{
HandJointId.HandMiddle1, HandJointId.HandMiddle2, HandJointId.HandMiddle3,
HandJointId.HandMiddleTip
},
new[]
{
HandJointId.HandRing1, HandJointId.HandRing2, HandJointId.HandRing3,
HandJointId.HandRingTip
},
new[]
{
HandJointId.HandPinky1, HandJointId.HandPinky2, HandJointId.HandPinky3,
HandJointId.HandPinkyTip
}
};
private static readonly HandJointId[] KNUCKLE_JOINTS =
{
HandJointId.HandThumb2,
HandJointId.HandIndex1,
HandJointId.HandMiddle1,
HandJointId.HandRing1,
HandJointId.HandPinky1
};
#endregion
public virtual float GetValue(HandFinger finger, FingerFeature feature, IHand hand)
{
switch (feature)
{
case FingerFeature.Curl:
return GetCurlValue(finger, hand);
case FingerFeature.Flexion:
return GetFlexionValue(finger, hand);
case FingerFeature.Abduction:
return GetAbductionValue(finger, hand);
case FingerFeature.Opposition:
return GetOppositionValue(finger, hand);
default:
return 0.0f;
}
}
private static float PosesCurlValue(Pose p0, Pose p1, Pose p2)
{
Vector3 bone1 = p0.position - p1.position;
Vector3 bone2 = p2.position - p1.position;
float angle = Vector3.SignedAngle(bone1, bone2, p1.forward * -1f);
if (angle < 0f) angle += 360f;
return angle;
}
public static float PosesListCurlValue(Pose[] poses)
{
float angleSum = 0;
for (int i = 0; i < poses.Length - 2; i++)
{
angleSum += PosesCurlValue(poses[i], poses[i + 1], poses[i + 2]);
}
return angleSum;
}
protected float JointsCurlValue(HandJointId[] joints, IHand hand)
{
if (!hand.GetJointPosesFromWrist(out ReadOnlyHandJointPoses poses))
{
return 0.0f;
}
float angleSum = 0;
for (int i = 0; i < joints.Length - 2; i++)
{
angleSum += PosesCurlValue(poses[(int)joints[i]],
poses[(int)joints[i + 1]],
poses[(int)joints[i + 2]]);
}
return angleSum;
}
public float GetCurlValue(HandFinger finger, IHand hand)
{
HandJointId[] handJointIds = CURL_ANGLE_JOINTS[(int)finger];
return JointsCurlValue(handJointIds, hand) / (handJointIds.Length - 2);
}
public float GetFlexionValue(HandFinger finger, IHand hand)
{
if (!hand.GetJointPosesFromWrist(out ReadOnlyHandJointPoses poses))
{
return 0.0f;
}
HandJointId knuckle = KNUCKLE_JOINTS[(int)finger];
Vector3 handDir = Vector3.up;
Vector3 fingerDir = Vector3.ProjectOnPlane(poses[knuckle].up, Vector3.forward);
return 180f + Vector3.SignedAngle(handDir, fingerDir, Vector3.back);
}
public float GetAbductionValue(HandFinger finger, IHand hand)
{
if (finger == HandFinger.Pinky
|| !hand.GetJointPosesFromWrist(out ReadOnlyHandJointPoses poses))
{
return 0.0f;
}
HandFinger nextFinger = finger + 1;
Vector3 fingerProximal = poses[HandJointUtils.GetHandFingerProximal(finger)].position;
Vector3 proximalMidpoint = Vector3.Lerp(
fingerProximal,
poses[HandJointUtils.GetHandFingerProximal(nextFinger)].position,
0.5f);
Vector3 normal1;
if (finger == HandFinger.Thumb)
{
normal1 = poses[HandJointUtils.GetHandFingerTip(finger)].position -
fingerProximal;
}
else
{
normal1 = poses[HandJointUtils.GetHandFingerTip(finger)].position -
proximalMidpoint;
}
Vector3 normal2 = poses[HandJointUtils.GetHandFingerTip(nextFinger)].position -
proximalMidpoint;
Vector3 axis = Vector3.Cross(normal1, normal2);
return Vector3.SignedAngle(normal1, normal2, axis);
}
public float GetOppositionValue(HandFinger finger, IHand hand)
{
if (finger == HandFinger.Thumb
|| !hand.GetJointPosesFromWrist(out ReadOnlyHandJointPoses poses))
{
return 0.0f;
}
Vector3 pos1 = poses[HandJointUtils.GetHandFingerTip(finger)].position;
Vector3 pos2 = poses[HandJointId.HandThumbTip].position;
return Vector3.Magnitude(pos1 - pos2);
}
public virtual IReadOnlyList<HandJointId> GetJointsAffected(HandFinger finger, FingerFeature feature)
{
switch (feature)
{
case FingerFeature.Curl:
return CURL_LINE_JOINTS[(int)finger];
case FingerFeature.Flexion:
return FLEXION_LINE_JOINTS[(int)finger];
case FingerFeature.Abduction:
return ABDUCTION_LINE_JOINTS[(int)finger];
case FingerFeature.Opposition:
return OPPOSITION_LINE_JOINTS[(int)finger];
default:
return null;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b35099b781e8a9a449c26d6df610d496
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,150 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection
{
public class HmdOffset : MonoBehaviour
{
[SerializeField, Interface(typeof(IHmd))]
private UnityEngine.Object _hmd;
private IHmd Hmd;
[SerializeField]
private Vector3 _offsetTranslation = Vector3.zero;
[SerializeField]
private Vector3 _offsetRotation = Vector3.zero;
[SerializeField]
private bool _disablePitchFromSource = false;
[SerializeField]
private bool _disableYawFromSource = false;
[SerializeField]
private bool _disableRollFromSource = false;
protected bool _started;
protected virtual void Awake()
{
Hmd = _hmd as IHmd;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hmd, nameof(Hmd));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Hmd.WhenUpdated += HandleHmdUpdated;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Hmd.WhenUpdated -= HandleHmdUpdated;
}
}
protected virtual void HandleHmdUpdated()
{
if (!Hmd.TryGetRootPose(out Pose centerEyePose))
{
return;
}
var centerEyePosition = centerEyePose.position;
var centerEyeRotation = centerEyePose.rotation;
var eulerAngles = centerEyeRotation.eulerAngles;
var pitch = Quaternion.Euler(new Vector3(eulerAngles.x, 0.0f, 0.0f));
var yaw = Quaternion.Euler(new Vector3(0.0f, eulerAngles.y, 0.0f));
var roll = Quaternion.Euler(new Vector3(0.0f, 0.0f, eulerAngles.z));
var finalSourceRotation = Quaternion.identity;
if (!_disableYawFromSource)
{
finalSourceRotation *= yaw;
}
if (!_disablePitchFromSource)
{
finalSourceRotation *= pitch;
}
if (!_disableRollFromSource)
{
finalSourceRotation *= roll;
}
var totalRotation = finalSourceRotation * Quaternion.Euler(_offsetRotation);
transform.position = centerEyePosition +
totalRotation * _offsetTranslation;
transform.rotation = totalRotation;
}
#region Inject
public void InjectAllHmdOffset(IHmd hmd)
{
InjectHmd(hmd);
}
public void InjectHmd(IHmd hmd)
{
_hmd = hmd as UnityEngine.Object;
Hmd = hmd;
}
public void InjectOptionalOffsetTranslation(Vector3 val)
{
_offsetTranslation = val;
}
public void InjectOptionalOffsetRotation(Vector3 val)
{
_offsetRotation = val;
}
public void InjectOptionalDisablePitchFromSource(bool val)
{
_disablePitchFromSource = val;
}
public void InjectOptionalDisableYawFromSource(bool val)
{
_disableYawFromSource = val;
}
public void InjectOptionalDisableRollFromSource(bool val)
{
_disableRollFromSource = val;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8310d4698426ea24e923c8a77fe0f3a0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,30 @@
/*
* 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.
*/
namespace Oculus.Interaction.PoseDetection
{
public interface IFeatureStateThreshold<TFeatureState>
{
float ToFirstWhenBelow {get;}
float ToSecondWhenAbove {get;}
TFeatureState FirstState {get;}
TFeatureState SecondState {get;}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6152072e57cc4c7c87b467b1237dc8e0
timeCreated: 1628024382

View File

@ -0,0 +1,30 @@
/*
* 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.
*/
using System.Collections.Generic;
namespace Oculus.Interaction.PoseDetection
{
public interface IFeatureStateThresholds<TFeature, TFeatureState>
{
TFeature Feature {get;}
IReadOnlyList<IFeatureStateThreshold<TFeatureState>> Thresholds {get;}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a32c5568211a4c8086cbf588996b654b
timeCreated: 1628024369

View File

@ -0,0 +1,35 @@
/*
* 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.
*/
using System.Collections.Generic;
namespace Oculus.Interaction.PoseDetection
{
public interface IFeatureThresholds<TFeature, TFeatureState>
{
IReadOnlyList<IFeatureStateThresholds<TFeature, TFeatureState>>
FeatureStateThresholds
{
get;
}
double MinTimeInState { get; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b736c28bc3ad4a639641c5c8f0fbd88d
timeCreated: 1628622856

View File

@ -0,0 +1,224 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection
{
public class JointDeltaConfig
{
public readonly int InstanceID;
public readonly IEnumerable<HandJointId> JointIDs;
public JointDeltaConfig(int instanceID, IEnumerable<HandJointId> jointIDs)
{
InstanceID = instanceID;
JointIDs = jointIDs;
}
}
public interface IJointDeltaProvider
{
bool GetPositionDelta(HandJointId joint, out Vector3 delta);
bool GetRotationDelta(HandJointId joint, out Quaternion delta);
void RegisterConfig(JointDeltaConfig config);
void UnRegisterConfig(JointDeltaConfig config);
}
public class JointDeltaProvider : MonoBehaviour, IJointDeltaProvider
{
private class PoseData
{
public bool IsValid = false;
public Pose Pose = Pose.identity;
}
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
private IHand Hand;
private Dictionary<HandJointId, PoseData[]> _poseDataCache =
new Dictionary<HandJointId, PoseData[]>();
private HashSet<HandJointId> _trackedJoints =
new HashSet<HandJointId>();
private Dictionary<int, List<HandJointId>> _requestors =
new Dictionary<int, List<HandJointId>>();
private int PrevDataIndex => 1 - CurDataIndex;
private int CurDataIndex = 0;
private int _lastUpdateDataVersion;
protected bool _started = false;
/// <summary>
/// Get the delta position between the previous pose and current pose
/// </summary>
/// <param name="joint">The joint for which to retrieve data</param>
/// <param name="delta">The position delta between poses in world space</param>
/// <returns>True if data available</returns>
public bool GetPositionDelta(HandJointId joint, out Vector3 delta)
{
UpdateData();
PoseData prevPose = _poseDataCache[joint][PrevDataIndex];
PoseData curPose = _poseDataCache[joint][CurDataIndex];
if (!prevPose.IsValid || !curPose.IsValid)
{
delta = Vector3.zero;
return false;
}
delta = curPose.Pose.position - prevPose.Pose.position;
return true;
}
/// <summary>
/// Get the delta rotation between the previous pose and current pose
/// </summary>
/// <param name="joint">The joint for which to retrieve data</param>
/// <param name="delta">The rotation delta between poses in world space</param>
/// <returns>True if data available</returns>
public bool GetRotationDelta(HandJointId joint, out Quaternion delta)
{
UpdateData();
PoseData prevPose = _poseDataCache[joint][PrevDataIndex];
PoseData curPose = _poseDataCache[joint][CurDataIndex];
if (!prevPose.IsValid || !curPose.IsValid)
{
delta = Quaternion.identity;
return false;
}
delta = curPose.Pose.rotation * Quaternion.Inverse(prevPose.Pose.rotation);
return true;
}
/// <summary>
/// Get the previous frame's pose
/// </summary>
/// <param name="joint">The joint for which to retrieve data</param>
/// <param name="pose">The previous pose</param>
/// <returns>True if data available</returns>
public bool GetPrevJointPose(HandJointId joint, out Pose pose)
{
UpdateData();
PoseData poseData = _poseDataCache[joint][PrevDataIndex];
pose = poseData.Pose;
return poseData.IsValid;
}
public void RegisterConfig(JointDeltaConfig config)
{
bool containsKeyAlready = _requestors.ContainsKey(config.InstanceID);
this.AssertIsTrue(!containsKeyAlready,
$"Trying to register multiple configs with the same id");
_requestors.Add(config.InstanceID, new List<HandJointId>(config.JointIDs));
// Check if any new joints added, if so then add to cache
foreach (var joint in config.JointIDs)
{
if (!_poseDataCache.ContainsKey(joint))
{
_poseDataCache.Add(joint, new PoseData[2]
{ new PoseData(), new PoseData() });
// New joint tracked, so write current data
PoseData toWrite = _poseDataCache[joint][CurDataIndex];
toWrite.IsValid = Hand.GetJointPose(joint, out toWrite.Pose);
}
}
}
public void UnRegisterConfig(JointDeltaConfig config)
{
_requestors.Remove(config.InstanceID);
}
protected virtual void Awake()
{
Hand = _hand as IHand;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hand, nameof(Hand));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Hand.WhenHandUpdated += UpdateData;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Hand.WhenHandUpdated -= UpdateData;
}
}
private void UpdateData()
{
if (Hand.CurrentDataVersion <= _lastUpdateDataVersion)
{
return;
}
_lastUpdateDataVersion = Hand.CurrentDataVersion;
// Swap read and write indices each data version
CurDataIndex = 1 - CurDataIndex;
// Only fetch pose data for currently tracked joints
_trackedJoints.Clear();
foreach (var key in _requestors.Keys)
{
IList<HandJointId> joints = _requestors[key];
_trackedJoints.UnionWithNonAlloc(joints);
}
// Fetch pose data for tracked joints, and
// invalidate data for untracked joints
foreach (var joint in _poseDataCache.Keys)
{
PoseData toWrite = _poseDataCache[joint][CurDataIndex];
toWrite.IsValid = _trackedJoints.Contains(joint) &&
Hand.GetJointPose(joint, out toWrite.Pose);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 645a8b39ed56176499370c5bfaf13d63
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,81 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using Oculus.Interaction.PoseDetection;
using UnityEngine;
namespace Oculus.Interaction
{
/// <summary>
/// JointDeltaProviderRef is a utility component that delegates all of its IJointDeltaProvider implementation
/// to the provided JointDeltaProvider object.
/// </summary>
public class JointDeltaProviderRef : MonoBehaviour, IJointDeltaProvider
{
[SerializeField, Interface(typeof(IJointDeltaProvider))]
private UnityEngine.Object _jointDeltaProvider;
public IJointDeltaProvider JointDeltaProvider { get; private set; }
protected virtual void Awake()
{
JointDeltaProvider = _jointDeltaProvider as IJointDeltaProvider;
}
protected virtual void Start()
{
this.AssertField(JointDeltaProvider, nameof(JointDeltaProvider));
}
public bool GetPositionDelta(HandJointId joint, out Vector3 delta)
{
return JointDeltaProvider.GetPositionDelta(joint, out delta);
}
public bool GetRotationDelta(HandJointId joint, out Quaternion delta)
{
return JointDeltaProvider.GetRotationDelta(joint, out delta);
}
public void RegisterConfig(JointDeltaConfig config)
{
JointDeltaProvider.RegisterConfig(config);
}
public void UnRegisterConfig(JointDeltaConfig config)
{
JointDeltaProvider.UnRegisterConfig(config);
}
#region Inject
public void InjectAllJointDeltaProviderRef(IJointDeltaProvider jointDeltaProvider)
{
InjectJointDeltaProvider(jointDeltaProvider);
}
public void InjectJointDeltaProvider(IJointDeltaProvider jointDeltaProvider)
{
_jointDeltaProvider = jointDeltaProvider as UnityEngine.Object;
JointDeltaProvider = jointDeltaProvider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 71d26e05b18b0994dacc3c0257325d33
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,196 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection
{
/// <summary>
/// This component tracks the distance between two hand joints and reports
/// <see cref="IActiveState.Active"/> when distance is under a provided threshold.
/// </summary>
public class JointDistanceActiveState : MonoBehaviour, IActiveState
{
[Tooltip("The IHand that JointIdA will be sourced from.")]
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _handA;
private IHand HandA;
[Tooltip("The joint of HandA to use for distance check.")]
[SerializeField]
private HandJointId _jointIdA;
[Tooltip("The IHand that JointIdB will be sourced from.")]
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _handB;
private IHand HandB;
[Tooltip("The joint of HandB to use for distance check.")]
[SerializeField]
private HandJointId _jointIdB;
[Tooltip("The ActiveState will become Active when joints are " +
"within this distance from each other.")]
[SerializeField]
private float _distance = 0.05f;
[Tooltip("The distance value will be modified by this width " +
"to create differing enter/exit thresholds. Used to prevent " +
"chattering at the threshold edge.")]
[SerializeField]
private float _thresholdWidth = 0.02f;
[Tooltip("A new state must be maintaned for at least this " +
"many seconds before the Active property changes.")]
[SerializeField]
private float _minTimeInState = 0.05f;
public bool Active
{
get
{
if (!isActiveAndEnabled)
{
return false;
}
UpdateActiveState();
return _activeState;
}
}
private bool _activeState = false;
private bool _internalState = false;
private float _lastStateChangeTime = 0f;
private int _lastStateUpdateFrame = 0;
protected virtual void Awake()
{
HandA = _handA as IHand;
HandB = _handB as IHand;
}
protected virtual void Start()
{
this.AssertField(HandA, nameof(HandA));
this.AssertField(HandB, nameof(HandB));
}
protected virtual void Update()
{
UpdateActiveState();
}
private void UpdateActiveState()
{
if (Time.frameCount <= _lastStateUpdateFrame)
{
return;
}
_lastStateUpdateFrame = Time.frameCount;
bool newState = JointDistanceWithinThreshold();
if (newState != _internalState)
{
_internalState = newState;
_lastStateChangeTime = Time.unscaledTime;
}
if (Time.unscaledTime - _lastStateChangeTime >= _minTimeInState)
{
_activeState = _internalState;
}
}
private bool JointDistanceWithinThreshold()
{
if (HandA.GetJointPose(_jointIdA, out Pose poseA) &&
HandB.GetJointPose(_jointIdB, out Pose poseB))
{
float threshold = _internalState ?
_distance + _thresholdWidth * 0.5f :
_distance - _thresholdWidth * 0.5f;
return Vector3.Distance(poseA.position, poseB.position) <= threshold;
}
else
{
return false;
}
}
#if UNITY_EDITOR
protected virtual void OnValidate()
{
_distance = Mathf.Max(_distance, 0f);
_minTimeInState = Mathf.Max(_minTimeInState, 0f);
_thresholdWidth = Mathf.Max(_thresholdWidth, 0f);
}
#endif
#region Inject
public void InjectAllJointDistanceActiveState(IHand handA, HandJointId jointIdA, IHand handB, HandJointId jointIdB)
{
InjectHandA(handA);
InjectJointIdA(jointIdA);
InjectHandB(handB);
InjectJointIdB(jointIdB);
}
public void InjectHandA(IHand handA)
{
_handA = handA as UnityEngine.Object;
HandA = handA;
}
public void InjectJointIdA(HandJointId jointIdA)
{
_jointIdA = jointIdA;
}
public void InjectHandB(IHand handB)
{
_handB = handB as UnityEngine.Object;
HandB = handB;
}
public void InjectJointIdB(HandJointId jointIdB)
{
_jointIdB = jointIdB;
}
public void InjectOptionalDistance(float val)
{
_distance = val;
}
public void InjectOptionalThresholdWidth(float val)
{
_thresholdWidth = val;
}
public void InjectOptionalMinTimeInState(float val)
{
_minTimeInState = val;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 06fe84cad95e40a428c3ca4346ad3afd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,388 @@
/*
* 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.
*/
using System;
using Oculus.Interaction.Input;
using UnityEngine;
using System.Collections.Generic;
namespace Oculus.Interaction.PoseDetection
{
public class JointRotationActiveState : MonoBehaviour, IActiveState
{
public enum RelativeTo
{
Hand = 0,
World = 1,
}
public enum WorldAxis
{
PositiveX = 0,
NegativeX = 1,
PositiveY = 2,
NegativeY = 3,
PositiveZ = 4,
NegativeZ = 5,
}
public enum HandAxis
{
Pronation = 0,
Supination = 1,
RadialDeviation = 2,
UlnarDeviation = 3,
Extension = 4,
Flexion = 5,
}
[Serializable]
public struct JointRotationFeatureState
{
/// <summary>
/// The world target euler angles for a
/// <see cref="JointRotationFeatureConfig"/>
/// </summary>
public readonly Vector3 TargetAxis;
/// <summary>
/// The normalized joint rotation along the target
/// axis relative to <see cref="_degreesPerSecond"/>
/// </summary>
public readonly float Amount;
public JointRotationFeatureState(Vector3 targetAxis, float amount)
{
TargetAxis = targetAxis;
Amount = amount;
}
}
[Serializable]
public class JointRotationFeatureConfigList
{
[SerializeField]
private List<JointRotationFeatureConfig> _values;
public List<JointRotationFeatureConfig> Values => _values;
}
[Serializable]
public class JointRotationFeatureConfig : FeatureConfigBase<HandJointId>
{
[Tooltip("The detection axis will be in this coordinate space.")]
[SerializeField]
private RelativeTo _relativeTo = RelativeTo.Hand;
[Tooltip("The world axis used for detection.")]
[SerializeField]
private WorldAxis _worldAxis = WorldAxis.PositiveZ;
[Tooltip("The axis of the hand root pose used for detection.")]
[SerializeField]
private HandAxis _handAxis = HandAxis.RadialDeviation;
public RelativeTo RelativeTo => _relativeTo;
public WorldAxis WorldAxis => _worldAxis;
public HandAxis HandAxis => _handAxis;
}
[Tooltip("Provided joints will be sourced from this IHand.")]
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
public IHand Hand { get; private set; }
[Tooltip("JointDeltaProvider caches joint deltas to avoid " +
"unnecessary recomputing of deltas.")]
[SerializeField, Interface(typeof(IJointDeltaProvider))]
private UnityEngine.Object _jointDeltaProvider;
[SerializeField]
private JointRotationFeatureConfigList _featureConfigs;
[Tooltip("The angular velocity used for the detection " +
"threshold, in degrees per second.")]
[SerializeField, Min(0)]
private float _degreesPerSecond = 120f;
[Tooltip("The degrees per second value will be modified by this width " +
"to create differing enter/exit thresholds. Used to prevent " +
"chattering at the threshold edge.")]
[SerializeField, Min(0)]
private float _thresholdWidth = 30f;
[Tooltip("A new state must be maintaned for at least this " +
"many seconds before the Active property changes.")]
[SerializeField, Min(0)]
private float _minTimeInState = 0.05f;
public bool Active
{
get
{
if (!isActiveAndEnabled)
{
return false;
}
UpdateActiveState();
return _activeState;
}
}
public IReadOnlyList<JointRotationFeatureConfig> FeatureConfigs =>
_featureConfigs.Values;
public IReadOnlyDictionary<JointRotationFeatureConfig, JointRotationFeatureState> FeatureStates =>
_featureStates;
private Dictionary<JointRotationFeatureConfig, JointRotationFeatureState> _featureStates =
new Dictionary<JointRotationFeatureConfig, JointRotationFeatureState>();
private JointDeltaConfig _jointDeltaConfig;
private IJointDeltaProvider JointDeltaProvider;
private Func<float> _timeProvider;
private int _lastStateUpdateFrame;
private float _lastStateChangeTime;
private float _lastUpdateTime;
private bool _internalState;
private bool _activeState;
protected bool _started = false;
protected virtual void Awake()
{
Hand = _hand as IHand;
JointDeltaProvider = _jointDeltaProvider as IJointDeltaProvider;
_timeProvider = () => Time.time;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hand, nameof(Hand));
this.AssertField(JointDeltaProvider, nameof(JointDeltaProvider));
this.AssertCollectionField(FeatureConfigs, nameof(FeatureConfigs));
this.AssertField(_timeProvider, nameof(_timeProvider));
IList<HandJointId> allTrackedJoints = new List<HandJointId>();
foreach (var config in FeatureConfigs)
{
allTrackedJoints.Add(config.Feature);
_featureStates.Add(config, new JointRotationFeatureState());
}
_jointDeltaConfig = new JointDeltaConfig(GetInstanceID(), allTrackedJoints);
_lastUpdateTime = _timeProvider();
this.EndStart(ref _started);
}
private bool CheckAllJointRotations()
{
bool result = true;
float deltaTime = _timeProvider() - _lastUpdateTime;
float threshold = _internalState ?
_degreesPerSecond + _thresholdWidth * 0.5f :
_degreesPerSecond - _thresholdWidth * 0.5f;
threshold *= deltaTime;
foreach (var config in FeatureConfigs)
{
if (Hand.GetRootPose(out Pose rootPose) &&
Hand.GetJointPose(config.Feature, out Pose curPose) &&
JointDeltaProvider.GetRotationDelta(
config.Feature, out Quaternion worldDeltaRotation))
{
Vector3 rotDeltaEuler = worldDeltaRotation.eulerAngles;
for (int i = 0; i < 3; ++i)
{
while (rotDeltaEuler[i] > 180)
{
rotDeltaEuler[i] -= 360;
}
while (rotDeltaEuler[i] < -180)
{
rotDeltaEuler[i] += 360;
}
}
Vector3 worldTargetRotation =
GetWorldTargetRotation(rootPose, config);
float rotationOnTargetAxis =
Vector3.Dot(rotDeltaEuler, worldTargetRotation);
_featureStates[config] = new JointRotationFeatureState(
worldTargetRotation,
threshold > 0 ?
Mathf.Clamp01(rotationOnTargetAxis / threshold) :
1);
bool rotationExceedsThreshold = rotationOnTargetAxis > threshold;
result &= rotationExceedsThreshold;
}
else
{
result = false;
}
}
return result;
}
protected virtual void Update()
{
UpdateActiveState();
}
protected virtual void OnEnable()
{
if (_started)
{
JointDeltaProvider.RegisterConfig(_jointDeltaConfig);
}
}
protected virtual void OnDisable()
{
if (_started)
{
JointDeltaProvider.UnRegisterConfig(_jointDeltaConfig);
}
}
private void UpdateActiveState()
{
if (Time.frameCount <= _lastStateUpdateFrame)
{
return;
}
_lastStateUpdateFrame = Time.frameCount;
bool newState = CheckAllJointRotations();
if (newState != _internalState)
{
_internalState = newState;
_lastStateChangeTime = _timeProvider();
}
if (_timeProvider() - _lastStateChangeTime >= _minTimeInState)
{
_activeState = _internalState;
}
_lastUpdateTime = _timeProvider();
}
private Vector3 GetWorldTargetRotation(Pose rootPose, JointRotationFeatureConfig config)
{
switch (config.RelativeTo)
{
default:
case RelativeTo.Hand:
return GetHandAxisVector(config.HandAxis, rootPose);
case RelativeTo.World:
return GetWorldAxisVector(config.WorldAxis);
}
}
private Vector3 GetWorldAxisVector(WorldAxis axis)
{
switch (axis)
{
default:
case WorldAxis.PositiveX:
return Vector3.right;
case WorldAxis.NegativeX:
return Vector3.left;
case WorldAxis.PositiveY:
return Vector3.up;
case WorldAxis.NegativeY:
return Vector3.down;
case WorldAxis.PositiveZ:
return Vector3.forward;
case WorldAxis.NegativeZ:
return Vector3.back;
}
}
private Vector3 GetHandAxisVector(HandAxis axis, Pose rootPose)
{
switch (axis)
{
case HandAxis.Pronation:
return rootPose.rotation * Vector3.left;
case HandAxis.Supination:
return rootPose.rotation * Vector3.right;
case HandAxis.RadialDeviation:
return rootPose.rotation * Vector3.down;
case HandAxis.UlnarDeviation:
return rootPose.rotation * Vector3.up;
case HandAxis.Extension:
return rootPose.rotation * Vector3.back;
case HandAxis.Flexion:
return rootPose.rotation * Vector3.forward;
default:
return Vector3.zero;
}
}
#region Inject
public void InjectAllJointRotationActiveState(JointRotationFeatureConfigList featureConfigs,
IHand hand, IJointDeltaProvider jointDeltaProvider)
{
InjectFeatureConfigList(featureConfigs);
InjectHand(hand);
InjectJointDeltaProvider(jointDeltaProvider);
}
public void InjectFeatureConfigList(JointRotationFeatureConfigList featureConfigs)
{
_featureConfigs = featureConfigs;
}
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
public void InjectJointDeltaProvider(IJointDeltaProvider jointDeltaProvider)
{
JointDeltaProvider = jointDeltaProvider;
_jointDeltaProvider = jointDeltaProvider as UnityEngine.Object;
}
public void InjectOptionalTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d74f6b51a9292b9409651b36d6d9aad7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,461 @@
/*
* 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.
*/
using System;
using Oculus.Interaction.Input;
using UnityEngine;
using UnityEngine.Assertions;
using System.Collections.Generic;
namespace Oculus.Interaction.PoseDetection
{
public class JointVelocityActiveState : MonoBehaviour, IActiveState
{
public enum RelativeTo
{
Hand = 0,
World = 1,
Head = 2,
}
public enum WorldAxis
{
PositiveX = 0,
NegativeX = 1,
PositiveY = 2,
NegativeY = 3,
PositiveZ = 4,
NegativeZ = 5,
}
public enum HeadAxis
{
HeadForward = 0,
HeadBackward = 1,
HeadUp = 2,
HeadDown = 3,
HeadLeft = 4,
HeadRight = 5,
}
public enum HandAxis
{
PalmForward = 0,
PalmBackward = 1,
WristUp = 2,
WristDown = 3,
WristForward = 4,
WristBackward = 5,
}
[Serializable]
public struct JointVelocityFeatureState
{
/// <summary>
/// The world target vector for a
/// <see cref="JointVelocityFeatureConfig"/>
/// </summary>
public readonly Vector3 TargetVector;
/// <summary>
/// The normalized joint velocity along the target
/// vector relative to <see cref="_minVelocity"/>
/// </summary>
public readonly float Amount;
public JointVelocityFeatureState(Vector3 targetVector, float velocity)
{
TargetVector = targetVector;
Amount = velocity;
}
}
[Serializable]
public class JointVelocityFeatureConfigList
{
[SerializeField]
private List<JointVelocityFeatureConfig> _values;
public List<JointVelocityFeatureConfig> Values => _values;
}
[Serializable]
public class JointVelocityFeatureConfig : FeatureConfigBase<HandJointId>
{
[Tooltip("The detection axis will be in this coordinate space.")]
[SerializeField]
private RelativeTo _relativeTo = RelativeTo.Hand;
[Tooltip("The world axis used for detection.")]
[SerializeField]
private WorldAxis _worldAxis = WorldAxis.PositiveZ;
[Tooltip("The axis of the hand root pose used for detection.")]
[SerializeField]
private HandAxis _handAxis = HandAxis.WristForward;
[Tooltip("The axis of the head pose used for detection.")]
[SerializeField]
private HeadAxis _headAxis = HeadAxis.HeadForward;
public RelativeTo RelativeTo => _relativeTo;
public WorldAxis WorldAxis => _worldAxis;
public HandAxis HandAxis => _handAxis;
public HeadAxis HeadAxis => _headAxis;
}
[Tooltip("Provided joints will be sourced from this IHand.")]
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
public IHand Hand { get; private set; }
[Tooltip("JointDeltaProvider caches joint deltas to avoid " +
"unnecessary recomputing of deltas.")]
[SerializeField, Interface(typeof(IJointDeltaProvider))]
private UnityEngine.Object _jointDeltaProvider;
public IJointDeltaProvider JointDeltaProvider { get; private set; }
[Tooltip("Reference to the Hmd providing the HeadAxis pose.")]
[SerializeField, Optional, Interface(typeof(IHmd))]
private UnityEngine.Object _hmd;
public IHmd Hmd { get; private set; }
[SerializeField]
private JointVelocityFeatureConfigList _featureConfigs;
[Tooltip("The velocity used for the detection " +
"threshold, in units per second.")]
[SerializeField, Min(0)]
private float _minVelocity = 0.5f;
[Tooltip("The min velocity value will be modified by this width " +
"to create differing enter/exit thresholds. Used to prevent " +
"chattering at the threshold edge.")]
[SerializeField, Min(0)]
private float _thresholdWidth = 0.02f;
[Tooltip("A new state must be maintaned for at least this " +
"many seconds before the Active property changes.")]
[SerializeField, Min(0)]
private float _minTimeInState = 0.05f;
public bool Active
{
get
{
if (!isActiveAndEnabled)
{
return false;
}
UpdateActiveState();
return _activeState;
}
}
public IReadOnlyList<JointVelocityFeatureConfig> FeatureConfigs =>
_featureConfigs.Values;
public IReadOnlyDictionary<JointVelocityFeatureConfig, JointVelocityFeatureState> FeatureStates =>
_featureStates;
private Dictionary<JointVelocityFeatureConfig, JointVelocityFeatureState> _featureStates =
new Dictionary<JointVelocityFeatureConfig, JointVelocityFeatureState>();
private JointDeltaConfig _jointDeltaConfig;
private Func<float> _timeProvider;
private int _lastStateUpdateFrame;
private float _lastStateChangeTime;
private float _lastUpdateTime;
private bool _internalState;
private bool _activeState;
protected bool _started = false;
protected virtual void Awake()
{
Hand = _hand as IHand;
JointDeltaProvider = _jointDeltaProvider as IJointDeltaProvider;
_timeProvider = () => Time.time;
if (_hmd != null)
{
Hmd = _hmd as IHmd;
}
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hand, nameof(Hand));
this.AssertField(JointDeltaProvider, nameof(JointDeltaProvider));
this.AssertField(_jointDeltaProvider, nameof(_jointDeltaProvider));
this.AssertField(_timeProvider, nameof(_timeProvider));
IList<HandJointId> allTrackedJoints = new List<HandJointId>();
foreach (var config in FeatureConfigs)
{
allTrackedJoints.Add(config.Feature);
_featureStates.Add(config, new JointVelocityFeatureState());
Assert.IsTrue(config.RelativeTo != RelativeTo.Head || Hmd != null);
this.AssertIsTrue(config.RelativeTo != RelativeTo.Head || Hmd != null,
$"One of the {AssertUtils.Nicify(nameof(FeatureConfigs))} is not relative to the head or the {nameof(Hmd)}");
}
_jointDeltaConfig = new JointDeltaConfig(GetInstanceID(), allTrackedJoints);
_lastUpdateTime = _timeProvider();
this.EndStart(ref _started);
}
private bool CheckAllJointVelocities()
{
bool result = true;
float deltaTime = _timeProvider() - _lastUpdateTime;
float threshold = _internalState ?
_minVelocity + _thresholdWidth * 0.5f :
_minVelocity - _thresholdWidth * 0.5f;
threshold *= deltaTime;
foreach (var config in FeatureConfigs)
{
if (Hand.GetRootPose(out Pose rootPose) &&
Hand.GetJointPose(config.Feature, out Pose curPose) &&
JointDeltaProvider.GetPositionDelta(
config.Feature, out Vector3 worldDeltaDirection))
{
Vector3 worldTargetDirection = GetWorldTargetVector(rootPose, config);
float velocityAlongTargetAxis =
Vector3.Dot(worldDeltaDirection, worldTargetDirection);
_featureStates[config] = new JointVelocityFeatureState(
worldTargetDirection,
threshold > 0 ?
Mathf.Clamp01(velocityAlongTargetAxis / threshold) :
1);
bool velocityExceedsThreshold = velocityAlongTargetAxis > threshold;
result &= velocityExceedsThreshold;
}
else
{
result = false;
}
}
return result;
}
protected virtual void Update()
{
UpdateActiveState();
}
protected virtual void OnEnable()
{
if (_started)
{
JointDeltaProvider.RegisterConfig(_jointDeltaConfig);
}
}
protected virtual void OnDisable()
{
if (_started)
{
JointDeltaProvider.UnRegisterConfig(_jointDeltaConfig);
}
}
private void UpdateActiveState()
{
if (Time.frameCount <= _lastStateUpdateFrame)
{
return;
}
_lastStateUpdateFrame = Time.frameCount;
bool newState = CheckAllJointVelocities();
if (newState != _internalState)
{
_internalState = newState;
_lastStateChangeTime = _timeProvider();
}
if (_timeProvider() - _lastStateChangeTime >= _minTimeInState)
{
_activeState = _internalState;
}
_lastUpdateTime = _timeProvider();
}
private Vector3 GetWorldTargetVector(Pose rootPose, JointVelocityFeatureConfig config)
{
switch (config.RelativeTo)
{
default:
case RelativeTo.Hand:
return GetHandAxisVector(config.HandAxis, rootPose);
case RelativeTo.World:
return GetWorldAxisVector(config.WorldAxis);
case RelativeTo.Head:
return GetHeadAxisVector(config.HeadAxis);
}
}
private Vector3 GetWorldAxisVector(WorldAxis axis)
{
switch (axis)
{
default:
case WorldAxis.PositiveX:
return Vector3.right;
case WorldAxis.NegativeX:
return Vector3.left;
case WorldAxis.PositiveY:
return Vector3.up;
case WorldAxis.NegativeY:
return Vector3.down;
case WorldAxis.PositiveZ:
return Vector3.forward;
case WorldAxis.NegativeZ:
return Vector3.back;
}
}
private Vector3 GetHandAxisVector(HandAxis axis, Pose rootPose)
{
Vector3 result;
switch (axis)
{
case HandAxis.PalmForward:
result = Hand.Handedness == Handedness.Left ?
rootPose.up : -1.0f * rootPose.up;
break;
case HandAxis.PalmBackward:
result = Hand.Handedness == Handedness.Left ?
-1.0f * rootPose.up : rootPose.up;
break;
case HandAxis.WristUp:
result = Hand.Handedness == Handedness.Left ?
rootPose.forward : -1.0f * rootPose.forward;
break;
case HandAxis.WristDown:
result = Hand.Handedness == Handedness.Left ?
-1.0f * rootPose.forward : rootPose.forward;
break;
case HandAxis.WristForward:
result = Hand.Handedness == Handedness.Left ?
rootPose.right : -1.0f * rootPose.right;
break;
case HandAxis.WristBackward:
result = Hand.Handedness == Handedness.Left ?
-1.0f * rootPose.right : rootPose.right;
break;
default:
result = Vector3.zero;
break;
}
return result;
}
private Vector3 GetHeadAxisVector(HeadAxis axis)
{
Hmd.TryGetRootPose(out Pose headPose);
Vector3 result;
switch (axis)
{
case HeadAxis.HeadForward:
result = headPose.forward;
break;
case HeadAxis.HeadBackward:
result = -headPose.forward;
break;
case HeadAxis.HeadUp:
result = headPose.up;
break;
case HeadAxis.HeadDown:
result = -headPose.up;
break;
case HeadAxis.HeadRight:
result = headPose.right;
break;
case HeadAxis.HeadLeft:
result = -headPose.right;
break;
default:
result = Vector3.zero;
break;
}
return result;
}
#region Inject
public void InjectAllJointVelocityActiveState(JointVelocityFeatureConfigList featureConfigs,
IHand hand, IJointDeltaProvider jointDeltaProvider)
{
InjectFeatureConfigList(featureConfigs);
InjectHand(hand);
InjectJointDeltaProvider(jointDeltaProvider);
}
public void InjectFeatureConfigList(JointVelocityFeatureConfigList featureConfigs)
{
_featureConfigs = featureConfigs;
}
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
public void InjectJointDeltaProvider(IJointDeltaProvider jointDeltaProvider)
{
JointDeltaProvider = jointDeltaProvider;
_jointDeltaProvider = jointDeltaProvider as UnityEngine.Object;
}
public void InjectOptionalTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
public void InjectOptionalHmd(IHmd hmd)
{
_hmd = hmd as UnityEngine.Object;
Hmd = hmd;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 91629c29fa7084f4bb4cd2da4084251f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,308 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using Oculus.Interaction.PoseDetection.Debug;
using System.Linq;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection
{
/// <summary>
/// Chains together a number of IActiveStates into a sequence.
/// The Sequence._stepsToActivate field contains an optional list of IActiveState's which must be 'activated' in
/// order.
/// The sequence can progress from Step N to N + 1 when: MinActiveTime <= "Time step N active for" <= MaxStepTime, and:
/// Step N just became inactive OR
/// Step N is the last step OR
/// Step N+1 is active
///
/// Note that once the sequence has moved on to the next step, the previous step does not need to remain active.
/// Each step has three fields:
/// ActiveState: The IActiveState that is used to determine if the conditions of this step are fulfilled
/// MinActiveTime: How long (in seconds) the IActiveState of this step must be contiguously active before moving
/// on to the next step. If the ActiveState drops out of being active for even a single frame
/// the countdown is reset.
/// MaxStepTime: If the elapsed time that the sequence is spent waiting for this step to reach its MinActiveTime
/// exceeds this value then the whole sequence is reset back to the beginning.
///
/// Once all steps are complete the Sequence.Active becomes true. It will remain true as long as RemainActiveWhile
/// is true. If _remainActiveCooldown > 0, Sequence.Active will remain active even after RemainActiveWhile becomes
/// false until the cooldown timer is met. The timer is reset if RemainActiveWhile becomes true again.
/// </summary>
public class Sequence : MonoBehaviour, IActiveState
{
[Serializable]
public class ActivationStep
{
[Tooltip("The IActiveState that is used to determine if the conditions of this step are fulfilled.")]
[SerializeField, Interface(typeof(IActiveState))]
private UnityEngine.Object _activeState;
public IActiveState ActiveState { get; private set; }
[SerializeField]
[Tooltip("This step must be consistently active for this amount of time before continuing to the next step.")]
private float _minActiveTime;
public float MinActiveTime => _minActiveTime;
[SerializeField]
[Tooltip(
"Maximum time that can be spent waiting for this step to complete, before the whole sequence is abandoned. " +
"This value must be greater than minActiveTime, or zero. This value is ignored if zero, and for the first step in the list.")]
private float _maxStepTime;
public float MaxStepTime => _maxStepTime;
public ActivationStep()
{
}
public ActivationStep(IActiveState activeState, float minActiveTime, float maxStepTime)
{
ActiveState = activeState;
_minActiveTime = minActiveTime;
_maxStepTime = maxStepTime;
}
public void Start()
{
if (ActiveState == null)
{
ActiveState = _activeState as IActiveState;
}
Assert.IsNotNull(ActiveState);
}
}
[Tooltip("The sequence will step through these ActivationSteps one at a " +
"time, advancing when each step becomes Active. Once all steps are active, " +
"the sequence itself will become Active.")]
[SerializeField, Optional]
private ActivationStep[] _stepsToActivate;
[Tooltip("Once the sequence is active, it will remain active as long as " +
"this IActiveState is Active.")]
[SerializeField, Optional, Interface(typeof(IActiveState))]
private UnityEngine.Object _remainActiveWhile;
[Tooltip("Sequence will not become inactive until RemainActiveWhile has " +
"been inactive for at least this many seconds.")]
[SerializeField, Optional]
private float _remainActiveCooldown;
private IActiveState RemainActiveWhile { get; set; }
/// <summary>
/// Returns the index of the step in <see cref="_stepsToActivate"/> whose conditions are
/// waiting to be activated.
/// If <see cref="Active"/> is true, this value will be set to the
/// size of <see cref="_stepsToActivate"/>.
/// If <see cref="_stepsToActivate"/> has no steps, this property will be 0.
/// </summary>
public int CurrentActivationStep { get; private set; }
private float _currentStepActivatedTime;
private float _stepFailedTime;
private bool _currentStepWasActive;
Func<float> _timeProvider;
private float _cooldownExceededTime;
private bool _wasRemainActive;
#region Unity Lifecycle
protected virtual void Awake()
{
RemainActiveWhile = _remainActiveWhile as IActiveState;
ResetState();
}
protected virtual void Start()
{
if (_timeProvider == null)
{
_timeProvider = () => Time.time;
}
if (_stepsToActivate == null)
{
_stepsToActivate = Array.Empty<ActivationStep>();
}
foreach (var step in _stepsToActivate)
{
step.Start();
}
}
protected virtual void Update()
{
var time = _timeProvider();
if (Active)
{
// Test for active, if RemainActiveWhile is set.
bool shouldBeActive = RemainActiveWhile != null && RemainActiveWhile.Active;
if (!shouldBeActive)
{
if (_wasRemainActive)
{
_cooldownExceededTime = time + _remainActiveCooldown;
}
if (_cooldownExceededTime <= time)
{
Active = false;
}
}
_wasRemainActive = shouldBeActive;
// No longer active; start activation condition at the beginning
if (!Active)
{
ResetState();
}
return;
}
if (CurrentActivationStep < _stepsToActivate.Length)
{
var currentStep = _stepsToActivate[CurrentActivationStep];
if (time > _stepFailedTime && CurrentActivationStep > 0 && currentStep.MaxStepTime > 0.0f)
{
// Failed to activate before max time limit reached. Start from the beginning.
ResetState();
}
bool currentStepIsActive = currentStep.ActiveState.Active;
if (currentStepIsActive)
{
if (!_currentStepWasActive)
{
// Step wasn't active, but now it is! Start the timer until next step can
// be entered.
_currentStepActivatedTime = time + currentStep.MinActiveTime;
}
}
if (time >= _currentStepActivatedTime && _currentStepWasActive)
{
// Time constraint met. Go to next step if either:
// - this step just became inactive OR
// - this is the last step OR
// - the next step is active
var nextStepIndex = CurrentActivationStep + 1;
bool thisStepCondition = !currentStepIsActive;
bool nextStepCondition = (nextStepIndex == _stepsToActivate.Length) ||
_stepsToActivate[nextStepIndex].ActiveState.Active;
if (thisStepCondition || nextStepCondition)
{
EnterNextStep(time);
}
}
_currentStepWasActive = currentStepIsActive;
}
else if (RemainActiveWhile != null)
{
Active = RemainActiveWhile.Active;
}
}
private void EnterNextStep(float time)
{
CurrentActivationStep++;
_currentStepWasActive = false;
if (CurrentActivationStep < _stepsToActivate.Length)
{
var currentStep = _stepsToActivate[CurrentActivationStep];
_stepFailedTime = time + currentStep.MaxStepTime;
return;
}
// This was the last step. Activate.
Active = true;
// In case there is no RemainActiveWhile condition, start the cooldown
// timer
_cooldownExceededTime = time + _remainActiveCooldown;
// Activate native component
NativeMethods.isdk_NativeComponent_Activate(0x5365717565446574);
}
private void ResetState()
{
CurrentActivationStep = 0;
_currentStepWasActive = false;
_currentStepActivatedTime = 0.0f;
}
#endregion
public bool Active { get; private set; }
static Sequence()
{
ActiveStateDebugTree.RegisterModel<Sequence>(new DebugModel());
}
private class DebugModel : ActiveStateModel<Sequence>
{
protected override IEnumerable<IActiveState> GetChildren(Sequence activeState)
{
List<IActiveState> children = new List<IActiveState>();
children.AddRange(activeState._stepsToActivate.Select(step => step.ActiveState));
children.Add(activeState.RemainActiveWhile);
return children.Where(c => c != null);
}
}
#region Inject
public void InjectOptionalStepsToActivate(ActivationStep[] stepsToActivate)
{
_stepsToActivate = stepsToActivate;
}
public void InjectOptionalRemainActiveWhile(IActiveState activeState)
{
_remainActiveWhile = activeState as UnityEngine.Object;
RemainActiveWhile = activeState;
}
public void InjectOptionalTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e7ab4178b1f98e40bc6baf2176709df
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,97 @@
/*
* 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.
*/
using System.Collections.Generic;
using Oculus.Interaction.PoseDetection.Debug;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection
{
public class SequenceActiveState : MonoBehaviour, IActiveState
{
[Tooltip("The Sequence that will drive this component.")]
[SerializeField]
private Sequence _sequence;
[Tooltip("If true, this ActiveState will become Active as soon " +
"as the first sequence step becomes Active.")]
[SerializeField]
private bool _activateIfStepsStarted;
[Tooltip("If true, this ActiveState will be active when " +
"the supplied Sequence is Active.")]
[SerializeField]
private bool _activateIfStepsComplete = true;
protected virtual void Start()
{
this.AssertField(_sequence, nameof(_sequence));
}
public bool Active
{
get
{
return (_activateIfStepsStarted && _sequence.CurrentActivationStep > 0 && !_sequence.Active) ||
(_activateIfStepsComplete && _sequence.Active);
}
}
static SequenceActiveState()
{
ActiveStateDebugTree.RegisterModel<SequenceActiveState>(new DebugModel());
}
private class DebugModel : ActiveStateModel<SequenceActiveState>
{
protected override IEnumerable<IActiveState> GetChildren(SequenceActiveState activeState)
{
return new[] { activeState._sequence };
}
}
#region Inject
public void InjectAllSequenceActiveState(Sequence sequence,
bool activateIfStepsStarted, bool activateIfStepsComplete)
{
InjectSequence(sequence);
InjectActivateIfStepsStarted(activateIfStepsStarted);
InjectActivateIfStepsComplete(activateIfStepsComplete);
}
public void InjectSequence(Sequence sequence)
{
_sequence = sequence;
}
public void InjectActivateIfStepsStarted(bool activateIfStepsStarted)
{
_activateIfStepsStarted = activateIfStepsStarted;
}
public void InjectActivateIfStepsComplete(bool activateIfStepsComplete)
{
_activateIfStepsComplete = activateIfStepsComplete;
}
#endregion
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d2a8d5cf844b463aabaed6d6db3da8c0
timeCreated: 1634670079

View File

@ -0,0 +1,162 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection
{
[CreateAssetMenu(menuName = "Oculus/Interaction/SDK/Pose Detection/Shape")]
public class ShapeRecognizer : ScriptableObject
{
[Serializable]
public class FingerFeatureConfigList
{
[SerializeField]
private List<FingerFeatureConfig> _value;
public IReadOnlyList<FingerFeatureConfig> Value => _value;
public FingerFeatureConfigList() { }
public FingerFeatureConfigList(List<FingerFeatureConfig> value)
{
_value = value;
}
}
[Serializable]
public class FingerFeatureConfig : FeatureConfigBase<FingerFeature>
{
}
[SerializeField]
private string _shapeName;
[SerializeField]
private FingerFeatureConfigList _thumbFeatureConfigs = new FingerFeatureConfigList();
[SerializeField]
private FingerFeatureConfigList _indexFeatureConfigs = new FingerFeatureConfigList();
[SerializeField]
private FingerFeatureConfigList _middleFeatureConfigs = new FingerFeatureConfigList();
[SerializeField]
private FingerFeatureConfigList _ringFeatureConfigs = new FingerFeatureConfigList();
[SerializeField]
private FingerFeatureConfigList _pinkyFeatureConfigs = new FingerFeatureConfigList();
public IReadOnlyList<FingerFeatureConfig> ThumbFeatureConfigs => _thumbFeatureConfigs.Value;
public IReadOnlyList<FingerFeatureConfig> IndexFeatureConfigs => _indexFeatureConfigs.Value;
public IReadOnlyList<FingerFeatureConfig> MiddleFeatureConfigs => _middleFeatureConfigs.Value;
public IReadOnlyList<FingerFeatureConfig> RingFeatureConfigs => _ringFeatureConfigs.Value;
public IReadOnlyList<FingerFeatureConfig> PinkyFeatureConfigs => _pinkyFeatureConfigs.Value;
public string ShapeName => _shapeName;
public IReadOnlyList<FingerFeatureConfig> GetFingerFeatureConfigs(HandFinger finger)
{
switch (finger)
{
case HandFinger.Thumb:
return ThumbFeatureConfigs;
case HandFinger.Index:
return IndexFeatureConfigs;
case HandFinger.Middle:
return MiddleFeatureConfigs;
case HandFinger.Ring:
return RingFeatureConfigs;
case HandFinger.Pinky:
return PinkyFeatureConfigs;
default:
throw new ArgumentException("must be a HandFinger enum value",
nameof(finger));
}
}
public IEnumerable<ValueTuple<HandFinger, IReadOnlyList<FingerFeatureConfig>>>
GetFingerFeatureConfigs()
{
for (var fingerIdx = 0; fingerIdx < Constants.NUM_FINGERS; ++fingerIdx)
{
HandFinger finger = (HandFinger)fingerIdx;
var configs = GetFingerFeatureConfigs(finger);
if (configs.Count == 0)
{
continue;
}
yield return new ValueTuple<HandFinger, IReadOnlyList<FingerFeatureConfig>>(finger,
configs);
}
}
#region Inject
public void InjectAllShapeRecognizer(IDictionary<HandFinger, FingerFeatureConfig[]> fingerFeatureConfigs)
{
FingerFeatureConfigList ReadFeatureConfigs(HandFinger finger)
{
if (!fingerFeatureConfigs.TryGetValue(finger, out FingerFeatureConfig[] configs))
{
configs = Array.Empty<FingerFeatureConfig>();
}
return new FingerFeatureConfigList(new List<FingerFeatureConfig>(configs));
}
_thumbFeatureConfigs = ReadFeatureConfigs(HandFinger.Thumb);
_indexFeatureConfigs = ReadFeatureConfigs(HandFinger.Index);
_middleFeatureConfigs = ReadFeatureConfigs(HandFinger.Middle);
_ringFeatureConfigs = ReadFeatureConfigs(HandFinger.Ring);
_pinkyFeatureConfigs = ReadFeatureConfigs(HandFinger.Pinky);
}
public void InjectThumbFeatureConfigs(FingerFeatureConfig[] configs)
{
_thumbFeatureConfigs = new FingerFeatureConfigList(new List<FingerFeatureConfig>(configs));
}
public void InjectIndexFeatureConfigs(FingerFeatureConfig[] configs)
{
_indexFeatureConfigs = new FingerFeatureConfigList(new List<FingerFeatureConfig>(configs));
}
public void InjectMiddleFeatureConfigs(FingerFeatureConfig[] configs)
{
_middleFeatureConfigs = new FingerFeatureConfigList(new List<FingerFeatureConfig>(configs));
}
public void InjectRingFeatureConfigs(FingerFeatureConfig[] configs)
{
_ringFeatureConfigs = new FingerFeatureConfigList(new List<FingerFeatureConfig>(configs));
}
public void InjectPinkyFeatureConfigs(FingerFeatureConfig[] configs)
{
_pinkyFeatureConfigs = new FingerFeatureConfigList(new List<FingerFeatureConfig>(configs));
}
public void InjectShapeName(string shapeName)
{
_shapeName = shapeName;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cfe1df7ed391a24fbe2a2d275e81b06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,165 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection
{
public class ShapeRecognizerActiveState : MonoBehaviour, IActiveState
{
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
public IHand Hand { get; private set; }
[SerializeField, Interface(typeof(IFingerFeatureStateProvider))]
private UnityEngine.Object _fingerFeatureStateProvider;
protected IFingerFeatureStateProvider FingerFeatureStateProvider;
[SerializeField]
private ShapeRecognizer[] _shapes;
public IReadOnlyList<ShapeRecognizer> Shapes => _shapes;
public Handedness Handedness => Hand.Handedness;
struct FingerFeatureStateUsage
{
public HandFinger handFinger;
public ShapeRecognizer.FingerFeatureConfig config;
}
private List<FingerFeatureStateUsage> _allFingerStates = new List<FingerFeatureStateUsage>();
// keeps track of native state
private bool _nativeActive = false;
protected virtual void Awake()
{
Hand = _hand as IHand;
FingerFeatureStateProvider = _fingerFeatureStateProvider as IFingerFeatureStateProvider;
}
protected virtual void Start()
{
this.AssertField(Hand, nameof(Hand));
this.AssertField(FingerFeatureStateProvider, nameof(FingerFeatureStateProvider));
this.AssertCollectionField(_shapes, nameof(_shapes));
_allFingerStates = FlattenUsedFeatures();
// Warm up the proactive evaluation
InitStateProvider();
}
private void InitStateProvider()
{
foreach (FingerFeatureStateUsage state in _allFingerStates)
{
FingerFeatureStateProvider.GetCurrentState(state.handFinger, state.config.Feature, out _);
}
}
private List<FingerFeatureStateUsage> FlattenUsedFeatures()
{
var fingerFeatureStateUsages = new List<FingerFeatureStateUsage>();
foreach (var sr in _shapes)
{
int configCount = 0;
for (var fingerIdx = 0; fingerIdx < Constants.NUM_FINGERS; ++fingerIdx)
{
var handFinger = (HandFinger)fingerIdx;
foreach (var config in sr.GetFingerFeatureConfigs(handFinger))
{
++configCount;
fingerFeatureStateUsages.Add(new FingerFeatureStateUsage()
{
handFinger = handFinger, config = config
});
}
}
// If this assertion is hit, open the ScriptableObject in the Unity Inspector
// and ensure that it has at least one valid condition.
Assert.IsTrue(configCount > 0, $"Shape {sr.ShapeName} has no valid conditions.");
}
return fingerFeatureStateUsages;
}
public bool Active
{
get
{
if (!isActiveAndEnabled || _allFingerStates.Count == 0)
{
return (_nativeActive = false);
}
foreach (FingerFeatureStateUsage stateUsage in _allFingerStates)
{
if (!FingerFeatureStateProvider.IsStateActive(stateUsage.handFinger,
stateUsage.config.Feature, stateUsage.config.Mode, stateUsage.config.State))
{
return (_nativeActive = false);
}
}
if (!_nativeActive)
{
// Activate native component
int result = NativeMethods.isdk_NativeComponent_Activate(0x48506f7365446574);
this.AssertIsTrue(result == NativeMethods.IsdkSuccess, "Unable to Activate native recognizer!");
}
return (_nativeActive = true);
}
}
#region Inject
public void InjectAllShapeRecognizerActiveState(IHand hand,
IFingerFeatureStateProvider fingerFeatureStateProvider,
ShapeRecognizer[] shapes)
{
InjectHand(hand);
InjectFingerFeatureStateProvider(fingerFeatureStateProvider);
InjectShapes(shapes);
}
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
public void InjectFingerFeatureStateProvider(IFingerFeatureStateProvider fingerFeatureStateProvider)
{
_fingerFeatureStateProvider = fingerFeatureStateProvider as UnityEngine.Object;
FingerFeatureStateProvider = fingerFeatureStateProvider;
}
public void InjectShapes(ShapeRecognizer[] shapes)
{
_shapes = shapes;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08f7cfb1f9629da4494ac0840f3a3cfd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,67 @@
/*
* 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.
*/
using System.Collections.Generic;
namespace Oculus.Interaction.PoseDetection
{
public static class TransformFeatureProperties
{
public static IReadOnlyDictionary<TransformFeature, FeatureDescription> FeatureDescriptions
{
get;
} = CreateFeatureDescriptions();
private static IReadOnlyDictionary<TransformFeature, FeatureDescription> CreateFeatureDescriptions()
{
int startIndex = 0;
return new Dictionary<TransformFeature, FeatureDescription>
{
[TransformFeature.WristUp] = CreateDesc(ref startIndex),
[TransformFeature.WristDown] = CreateDesc(ref startIndex),
[TransformFeature.PalmDown] = CreateDesc(ref startIndex),
[TransformFeature.PalmUp] = CreateDesc(ref startIndex),
[TransformFeature.PalmTowardsFace] = CreateDesc(ref startIndex),
[TransformFeature.PalmAwayFromFace] = CreateDesc(ref startIndex),
[TransformFeature.FingersUp] = CreateDesc(ref startIndex),
[TransformFeature.FingersDown] = CreateDesc(ref startIndex),
[TransformFeature.PinchClear] = CreateDesc(ref startIndex),
};
}
private static FeatureDescription CreateDesc(ref int startIndex)
{
var desc = new FeatureDescription("", "", 0, 180,
new[]
{
new FeatureStateDescription((startIndex).ToString(), "True"),
// to support legacy data (which had a 3rd intermediary step), need to skip 1.
new FeatureStateDescription((startIndex + 2).ToString(), "False")
});
startIndex += 3;
return desc;
}
public const string FeatureStateThresholdMidpointHelpText = "The value at which a state will transition from A > B (or B > A)";
public const string FeatureStateThresholdWidthHelpText =
"How far the transform value must exceed the midpoint until the transition can occur. " +
"This is to prevent rapid flickering at transition edges.";
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8d8fbcf2f8f5475fa39c11050b34cdab
timeCreated: 1631578210

View File

@ -0,0 +1,385 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.PoseDetection
{
public enum UpVectorType
{
Head,
Tracking,
World
}
[Serializable]
public class TransformConfig
{
public TransformConfig()
{
PositionOffset = Vector3.zero;
RotationOffset = Vector3.zero;
UpVectorType = UpVectorType.Head;
FeatureThresholds = null;
InstanceId = 0;
}
// Position offset relative to the reference transform.
public Vector3 PositionOffset;
// Rotational offset relative to the reference transform.
public Vector3 RotationOffset;
public UpVectorType UpVectorType;
public TransformFeatureStateThresholds FeatureThresholds;
// set via component that uses this class
public int InstanceId { get; set; }
}
public class TransformJointData
{
public bool IsValid;
public Handedness Handedness;
public Pose CenterEyePose, WristPose;
public Vector3 TrackingSystemUp;
public Vector3 TrackingSystemForward;
}
internal class TransformFeatureStateCollection
{
public class TransformStateInfo
{
public TransformStateInfo(TransformConfig transformConfig,
FeatureStateProvider<TransformFeature, string> stateProvider)
{
Config = transformConfig;
StateProvider = stateProvider;
}
public TransformConfig Config;
public FeatureStateProvider<TransformFeature, string> StateProvider;
}
private Dictionary<int, TransformStateInfo> _idToTransformStateInfo =
new Dictionary<int, TransformStateInfo>();
public void RegisterConfig(TransformConfig transformConfig, TransformJointData jointData,
Func<float> timeProvider)
{
bool containsKeyAlready = _idToTransformStateInfo.ContainsKey(transformConfig.InstanceId);
Assert.IsFalse(containsKeyAlready,
"Trying to register multiple configs with the same id into " +
"TransformFeatureStateCollection.");
var featureStateProvider = new FeatureStateProvider<TransformFeature, string>
// note that jointData and transformConfig are reference types (classes), because they can change
// during run time
((feature) => TransformFeatureValueProvider.GetValue(feature, jointData, transformConfig),
feature => (int)feature,
timeProvider);
TransformStateInfo newTransfState = new TransformStateInfo(transformConfig, featureStateProvider);
featureStateProvider.InitializeThresholds(transformConfig.FeatureThresholds);
_idToTransformStateInfo.Add(transformConfig.InstanceId, newTransfState);
}
public void UnRegisterConfig(TransformConfig transformConfig)
{
_idToTransformStateInfo.Remove(transformConfig.InstanceId);
}
public FeatureStateProvider<TransformFeature, string> GetStateProvider(
TransformConfig transformConfig)
{
return _idToTransformStateInfo[transformConfig.InstanceId].StateProvider;
}
public void SetConfig(int configId, TransformConfig config)
{
_idToTransformStateInfo[configId].Config = config;
}
public TransformConfig GetConfig(int configId)
{
return _idToTransformStateInfo[configId].Config;
}
public void UpdateFeatureStates(int lastUpdatedFrameId,
bool disableProactiveEvaluation)
{
foreach (var transformStateInfo in _idToTransformStateInfo.Values)
{
var featureStateProvider = transformStateInfo.StateProvider;
if (!disableProactiveEvaluation)
{
featureStateProvider.LastUpdatedFrameId = lastUpdatedFrameId;
featureStateProvider.ReadTouchedFeatureStates();
}
else
{
featureStateProvider.LastUpdatedFrameId = lastUpdatedFrameId;
}
}
}
}
public interface ITransformFeatureStateProvider
{
bool IsStateActive(TransformConfig config, TransformFeature feature,
FeatureStateActiveMode mode, string stateId);
bool GetCurrentState(TransformConfig config, TransformFeature transformFeature,
out string currentState);
void RegisterConfig(TransformConfig transformConfig);
void UnRegisterConfig(TransformConfig transformConfig);
void GetFeatureVectorAndWristPos(TransformConfig config,
TransformFeature transformFeature, bool isHandVector, ref Vector3? featureVec,
ref Vector3? wristPos);
}
/// <summary>
/// Interprets transform feature values from a <see cref="TransformFeatureValueProvider"/>
/// and uses the given <see cref="TransformFeatureStateThresholds"/> to quantize
/// these values into states. To avoid rapid fluctuations at the edges
/// of two states, this classes uses the calculated feature states from the previous
/// frame and the given state thresholds to apply a buffer between
/// state transition edges.
/// </summary>
public class TransformFeatureStateProvider : MonoBehaviour, ITransformFeatureStateProvider
{
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
public IHand Hand { get; private set; }
[SerializeField, Interface(typeof(IHmd))]
private UnityEngine.Object _hmd;
public IHmd Hmd { get; private set; }
[SerializeField, Interface(typeof(ITrackingToWorldTransformer))]
private UnityEngine.Object _trackingToWorldTransformer;
public ITrackingToWorldTransformer TrackingToWorldTransformer { get; private set; }
[Header("Advanced Settings")]
[SerializeField]
[Tooltip("If true, disables proactive evaluation of any TransformFeature that has been " +
"queried at least once. This will force lazy-evaluation of state within calls " +
"to IsStateActive, which means you must do so each frame to avoid missing " +
"transitions between states.")]
private bool _disableProactiveEvaluation;
private TransformJointData _jointData = new TransformJointData();
private TransformFeatureStateCollection _transformFeatureStateCollection;
private Func<float> _timeProvider;
protected bool _started = false;
protected virtual void Awake()
{
Hand = _hand as IHand;
Hmd = _hmd as IHmd;
TrackingToWorldTransformer = _trackingToWorldTransformer as ITrackingToWorldTransformer;
_transformFeatureStateCollection = new TransformFeatureStateCollection();
_timeProvider = () => Time.time;
}
public void RegisterConfig(TransformConfig transformConfig)
{
//Register time provider indirectly in case reference changes
Func<float> getTime = () => _timeProvider();
_transformFeatureStateCollection.RegisterConfig(transformConfig, _jointData, getTime);
}
public void UnRegisterConfig(TransformConfig transformConfig)
{
_transformFeatureStateCollection.UnRegisterConfig(transformConfig);
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hand, nameof(Hand));
this.AssertField(Hmd, nameof(Hmd));
this.AssertField(_timeProvider, nameof(_timeProvider));
this.AssertField(TrackingToWorldTransformer, nameof(TrackingToWorldTransformer));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Hand.WhenHandUpdated += HandDataAvailable;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Hand.WhenHandUpdated -= HandDataAvailable;
}
}
private void HandDataAvailable()
{
UpdateJointData();
UpdateStateForHand();
}
private void UpdateJointData()
{
_jointData.IsValid = Hand.GetRootPose(out _jointData.WristPose) &&
Hmd.TryGetRootPose(out _jointData.CenterEyePose);
if (!_jointData.IsValid)
{
return;
}
_jointData.Handedness = Hand.Handedness;
_jointData.TrackingSystemUp = TrackingToWorldTransformer.Transform.up;
_jointData.TrackingSystemForward = TrackingToWorldTransformer.Transform.forward;
}
private void UpdateStateForHand()
{
// Update the frameId of all state providers to mark data as dirty. If
// proactiveEvaluation is enabled, also read the state of any feature that has been
// touched, which will force it to evaluate.
_transformFeatureStateCollection.UpdateFeatureStates(
Hand.CurrentDataVersion,
_disableProactiveEvaluation);
}
public bool IsHandDataValid()
{
return _jointData.IsValid;
}
public bool IsStateActive(TransformConfig config, TransformFeature feature, FeatureStateActiveMode mode, string stateId)
{
var currentState = GetCurrentFeatureState(config, feature);
switch (mode)
{
case FeatureStateActiveMode.Is:
return currentState == stateId;
case FeatureStateActiveMode.IsNot:
return currentState != stateId;
default:
return false;
}
}
private string GetCurrentFeatureState(TransformConfig config,
TransformFeature feature)
{
return _transformFeatureStateCollection.GetStateProvider(config).
GetCurrentFeatureState(feature);
}
public bool GetCurrentState(TransformConfig config, TransformFeature transformFeature,
out string currentState)
{
if (!IsHandDataValid())
{
currentState = default;
return false;
}
currentState = GetCurrentFeatureState(config, transformFeature);
return currentState != default;
}
/// <summary>
/// Returns the current value of the feature. If the hand joints are not populated with
/// valid data (for instance, due to a disconnected hand), the method will return null;
/// </summary>
public float? GetFeatureValue(TransformConfig config,
TransformFeature transformFeature)
{
if (!IsHandDataValid())
{
return null;
}
return TransformFeatureValueProvider.GetValue(transformFeature,
_jointData, config);
}
public void GetFeatureVectorAndWristPos(TransformConfig config,
TransformFeature transformFeature, bool isHandVector, ref Vector3? featureVec,
ref Vector3? wristPos)
{
featureVec = null;
wristPos = null;
if (!IsHandDataValid())
{
return;
}
featureVec = isHandVector ?
TransformFeatureValueProvider.GetHandVectorForFeature(transformFeature,
_jointData, in config) :
TransformFeatureValueProvider.GetTargetVectorForFeature(transformFeature,
_jointData, in config);
wristPos = _jointData.WristPose.position;
}
#region Inject
public void InjectAllTransformFeatureStateProvider(IHand hand, IHmd hmd, bool disableProactiveEvaluation)
{
InjectHand(hand);
InjectHmd(hmd);
_disableProactiveEvaluation = disableProactiveEvaluation;
}
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
public void InjectHmd(IHmd hand)
{
_hmd = hand as UnityEngine.Object;
Hmd = hand;
}
public void InjectDisableProactiveEvaluation(bool disabled)
{
_disableProactiveEvaluation = disabled;
}
public void InjectOptionalTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bffe606b408599b4fad696ddc889a943
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using Oculus.Interaction.PoseDetection;
using UnityEngine;
namespace Oculus.Interaction
{
/// <summary>
/// TransformFeatureStateProviderRef is a utility component that delegates all of its ITransformFeatureStateProvider implementation
/// to the provided TransformFeatureStateProvider object.
/// </summary>
public class TransformFeatureStateProviderRef : MonoBehaviour, ITransformFeatureStateProvider
{
[SerializeField, Interface(typeof(ITransformFeatureStateProvider))]
private UnityEngine.Object _transformFeatureStateProvider;
public ITransformFeatureStateProvider TransformFeatureStateProvider { get; private set; }
protected virtual void Awake()
{
TransformFeatureStateProvider = _transformFeatureStateProvider as ITransformFeatureStateProvider;
}
protected virtual void Start()
{
this.AssertField(TransformFeatureStateProvider, nameof(TransformFeatureStateProvider));
}
public bool IsStateActive(TransformConfig config, TransformFeature feature, FeatureStateActiveMode mode,
string stateId)
{
return TransformFeatureStateProvider.IsStateActive(config, feature, mode, stateId);
}
public bool GetCurrentState(TransformConfig config, TransformFeature transformFeature,
out string currentState)
{
return TransformFeatureStateProvider.GetCurrentState(config, transformFeature, out currentState);
}
public void RegisterConfig(TransformConfig transformConfig)
{
TransformFeatureStateProvider.RegisterConfig(transformConfig);
}
public void UnRegisterConfig(TransformConfig transformConfig)
{
TransformFeatureStateProvider.UnRegisterConfig(transformConfig);
}
public void GetFeatureVectorAndWristPos(TransformConfig config, TransformFeature transformFeature,
bool isHandVector, ref Vector3? featureVec, ref Vector3? wristPos)
{
TransformFeatureStateProvider.GetFeatureVectorAndWristPos(config, transformFeature, isHandVector, ref featureVec, ref wristPos);
}
#region Inject
public void InjectAllTransformFeatureStateProviderRef(ITransformFeatureStateProvider transformFeatureStateProvider)
{
InjectTransformFeatureStateProvider(transformFeatureStateProvider);
}
public void InjectTransformFeatureStateProvider(ITransformFeatureStateProvider transformFeatureStateProvider)
{
_transformFeatureStateProvider = transformFeatureStateProvider as UnityEngine.Object;
TransformFeatureStateProvider = transformFeatureStateProvider;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e9485329571269f41ba05b864b35f1cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,132 @@
/*
* 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.
*/
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection
{
[Serializable]
public class TransformFeatureStateThreshold : IFeatureStateThreshold<string>
{
public TransformFeatureStateThreshold()
{
}
public TransformFeatureStateThreshold(
float thresholdMidpoint,
float thresholdWidth,
string firstState,
string secondState)
{
_thresholdMidpoint = thresholdMidpoint;
_thresholdWidth = thresholdWidth;
_firstState = firstState;
_secondState = secondState;
}
[SerializeField]
[Tooltip(TransformFeatureProperties.FeatureStateThresholdMidpointHelpText)]
private float _thresholdMidpoint;
[SerializeField]
[Tooltip(TransformFeatureProperties.FeatureStateThresholdWidthHelpText)]
private float _thresholdWidth;
[SerializeField]
[Tooltip("State to transition to when value passes below the threshold")]
private string _firstState;
[SerializeField]
[Tooltip("State to transition to when value passes above the threshold")]
private string _secondState;
public float ToFirstWhenBelow => _thresholdMidpoint - _thresholdWidth * 0.5f;
public float ToSecondWhenAbove => _thresholdMidpoint + _thresholdWidth * 0.5f;
public string FirstState => _firstState;
public string SecondState => _secondState;
}
[Serializable]
public class TransformFeatureThresholds : IFeatureStateThresholds<TransformFeature,
string>
{
public TransformFeatureThresholds() { }
public TransformFeatureThresholds(TransformFeature featureTransform,
IEnumerable<TransformFeatureStateThreshold> thresholds)
{
_feature = featureTransform;
_thresholds = new List<TransformFeatureStateThreshold>(thresholds);
}
[SerializeField]
[Tooltip("Which feature this collection of thresholds controls. " +
"Each feature should exist at most once.")]
private TransformFeature _feature;
[SerializeField]
[Tooltip("List of state transitions, with thresold settings. " +
"The entries in this list must be in ascending order, based on their 'midpoint' values.")]
private List<TransformFeatureStateThreshold> _thresholds;
[SerializeField]
[Tooltip("Length of time that the transform must be in the new state before the feature " +
"state provider will use the new value.")]
private double _minTimeInState;
public TransformFeature Feature => _feature;
public IReadOnlyList<IFeatureStateThreshold<string>>
Thresholds => _thresholds;
public double MinTimeInState => _minTimeInState;
}
[CreateAssetMenu(menuName = "Oculus/Interaction/SDK/Pose Detection/Transform Thresholds")]
public class TransformFeatureStateThresholds : ScriptableObject,
IFeatureThresholds<TransformFeature, string>
{
[SerializeField]
[Tooltip("List of all supported transform features, along with the state entry/exit thresholds.")]
private List<TransformFeatureThresholds> _featureThresholds;
[SerializeField]
[Tooltip("Length of time that the transform must be in the new state before the feature " +
"state provider will use the new value.")]
private double _minTimeInState;
public void Construct(List<TransformFeatureThresholds> featureThresholds,
double minTimeInState)
{
_featureThresholds = featureThresholds;
_minTimeInState = minTimeInState;
}
public IReadOnlyList<IFeatureStateThresholds<TransformFeature, string>>
FeatureStateThresholds => _featureThresholds;
public double MinTimeInState => _minTimeInState;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac666f23650b94b4ea891467ab3677e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,337 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection
{
public enum TransformFeature
{
WristUp,
WristDown,
PalmDown,
PalmUp,
PalmTowardsFace,
PalmAwayFromFace,
FingersUp,
FingersDown,
PinchClear
};
public class TransformFeatureValueProvider
{
public struct TransformProperties
{
public TransformProperties(Pose centerEyePos,
Pose wristPose,
Handedness handedness,
Vector3 trackingSystemUp,
Vector3 trackingSystemForward)
{
CenterEyePose = centerEyePos;
WristPose = wristPose;
Handedness = handedness;
TrackingSystemUp = trackingSystemUp;
TrackingSystemForward = trackingSystemForward;
}
public readonly Pose CenterEyePose;
public readonly Pose WristPose;
public readonly Handedness Handedness;
public readonly Vector3 TrackingSystemUp;
public readonly Vector3 TrackingSystemForward;
}
public static float GetValue(TransformFeature transformFeature, TransformJointData transformJointData,
TransformConfig transformConfig)
{
TransformProperties transformProps =
new TransformProperties(transformJointData.CenterEyePose, transformJointData.WristPose,
transformJointData.Handedness, transformJointData.TrackingSystemUp,
transformJointData.TrackingSystemForward);
switch (transformFeature)
{
case TransformFeature.WristDown:
return GetWristDownValue(in transformProps, in transformConfig);
case TransformFeature.WristUp:
return GetWristUpValue(in transformProps, in transformConfig);
case TransformFeature.PalmDown:
return GetPalmDownValue(in transformProps, in transformConfig);
case TransformFeature.PalmUp:
return GetPalmUpValue(in transformProps, in transformConfig);
case TransformFeature.PalmTowardsFace:
return GetPalmTowardsFaceValue(in transformProps, in transformConfig);
case TransformFeature.PalmAwayFromFace:
return GetPalmAwayFromFaceValue(in transformProps, in transformConfig);
case TransformFeature.FingersUp:
return GetFingersUpValue(in transformProps, in transformConfig);
case TransformFeature.FingersDown:
return GetFingersDownValue(in transformProps, in transformConfig);
case TransformFeature.PinchClear:
default:
return GetPinchClearValue(in transformProps, in transformConfig);
}
}
public static Vector3 GetHandVectorForFeature(TransformFeature transformFeature,
in TransformJointData transformJointData,
in TransformConfig transformConfig)
{
TransformProperties transformProps =
new TransformProperties(transformJointData.CenterEyePose, transformJointData.WristPose,
transformJointData.Handedness, transformJointData.TrackingSystemUp,
transformJointData.TrackingSystemForward);
return GetHandVectorForFeature(transformFeature, in transformProps, in transformConfig);
}
private static Vector3 GetHandVectorForFeature(TransformFeature transformFeature,
in TransformProperties transformProps,
in TransformConfig transformConfig)
{
Vector3 handVector = Vector3.zero;
switch (transformFeature)
{
case TransformFeature.WristDown:
case TransformFeature.WristUp:
handVector = transformProps.Handedness == Handedness.Left ?
transformProps.WristPose.forward :
-1.0f * transformProps.WristPose.forward;
break;
case TransformFeature.PalmDown:
case TransformFeature.PalmUp:
case TransformFeature.PalmTowardsFace:
case TransformFeature.PalmAwayFromFace:
handVector = transformProps.Handedness == Handedness.Left ?
transformProps.WristPose.up : -1.0f * transformProps.WristPose.up;
break;
case TransformFeature.FingersUp:
case TransformFeature.FingersDown:
handVector = transformProps.Handedness == Handedness.Left ?
transformProps.WristPose.right : -1.0f * transformProps.WristPose.right;
break;
case TransformFeature.PinchClear:
default:
handVector = transformProps.Handedness == Handedness.Left ?
transformProps.WristPose.forward : -1.0f * transformProps.WristPose.forward;
break;
}
return handVector;
}
public static Vector3 GetTargetVectorForFeature(TransformFeature transformFeature,
in TransformJointData transformJointData,
in TransformConfig transformConfig)
{
TransformProperties transformProps =
new TransformProperties(transformJointData.CenterEyePose, transformJointData.WristPose,
transformJointData.Handedness, transformJointData.TrackingSystemUp,
transformJointData.TrackingSystemForward);
return GetTargetVectorForFeature(transformFeature, in transformProps, in transformConfig);
}
private static Vector3 GetTargetVectorForFeature(TransformFeature transformFeature,
in TransformProperties transformProps,
in TransformConfig transformConfig)
{
Vector3 targetVector = Vector3.zero;
switch (transformFeature)
{
case TransformFeature.WristDown:
case TransformFeature.PalmDown:
case TransformFeature.FingersDown:
targetVector = OffsetVectorWithRotation(transformProps,
GetVerticalVector(transformProps.CenterEyePose,
transformProps.TrackingSystemUp, false,
in transformConfig),
in transformConfig);
break;
case TransformFeature.WristUp:
case TransformFeature.PalmUp:
case TransformFeature.FingersUp:
targetVector = OffsetVectorWithRotation(transformProps,
GetVerticalVector(transformProps.CenterEyePose,
transformProps.TrackingSystemUp, true,
in transformConfig),
in transformConfig);
break;
case TransformFeature.PalmTowardsFace:
targetVector = OffsetVectorWithRotation(transformProps,
-1.0f * transformProps.CenterEyePose.forward,
in transformConfig);
break;
case TransformFeature.PalmAwayFromFace:
targetVector = OffsetVectorWithRotation(transformProps,
transformProps.CenterEyePose.forward,
in transformConfig);
break;
case TransformFeature.PinchClear:
targetVector = OffsetVectorWithRotation(transformProps,
-1.0f * transformProps.CenterEyePose.forward,
in transformConfig);
break;
default:
break;
}
return targetVector;
}
private static float GetWristDownValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.WristDown,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.WristDown,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetWristUpValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.WristUp,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.WristUp,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetPalmDownValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.PalmDown,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.PalmDown,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetPalmUpValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.PalmUp,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.PalmUp,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetPalmTowardsFaceValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.PalmTowardsFace,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.PalmTowardsFace,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetPalmAwayFromFaceValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.PalmAwayFromFace,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.PalmAwayFromFace,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetFingersUpValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.FingersUp,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.FingersUp,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetFingersDownValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.FingersDown,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.FingersDown,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static float GetPinchClearValue(in TransformProperties transformProps,
in TransformConfig transformConfig)
{
var handVector = GetHandVectorForFeature(TransformFeature.PinchClear,
in transformProps,
in transformConfig);
var targetVector = GetTargetVectorForFeature(TransformFeature.PinchClear,
in transformProps, in transformConfig);
return Vector3.Angle(handVector, targetVector);
}
private static Vector3 GetVerticalVector(in Pose centerEyePose,
in Vector3 trackingSystemUp,
bool isUp,
in TransformConfig transformConfig)
{
switch (transformConfig.UpVectorType)
{
case UpVectorType.Head:
return isUp ? centerEyePose.up : -1.0f * centerEyePose.up;
case UpVectorType.Tracking:
return isUp ? trackingSystemUp : -1.0f * trackingSystemUp;
case UpVectorType.World:
default:
return isUp ? Vector3.up : Vector3.down;
}
}
private static Vector3 OffsetVectorWithRotation(in TransformProperties transformProps,
in Vector3 originalVector,
in TransformConfig transformConfig)
{
Quaternion baseRotation;
switch (transformConfig.UpVectorType)
{
case UpVectorType.Head:
baseRotation = transformProps.CenterEyePose.rotation;
break;
case UpVectorType.Tracking:
baseRotation =
Quaternion.LookRotation(transformProps.TrackingSystemForward,
transformProps.TrackingSystemUp);
break;
case UpVectorType.World:
default:
baseRotation = Quaternion.identity;
break;
}
Quaternion offset = Quaternion.Euler(transformConfig.RotationOffset);
return baseRotation * offset * Quaternion.Inverse(baseRotation) * originalVector;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 849e24afed5709e4a82857a19ce08501
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,183 @@
/*
* 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.
*/
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.PoseDetection
{
[Serializable]
public class TransformFeatureConfigList
{
[SerializeField]
private List<TransformFeatureConfig> _values;
public List<TransformFeatureConfig> Values => _values;
}
[Serializable]
public class TransformFeatureConfig : FeatureConfigBase<TransformFeature>
{
}
public class TransformRecognizerActiveState : MonoBehaviour, IActiveState
{
[SerializeField, Interface(typeof(IHand))]
private UnityEngine.Object _hand;
public IHand Hand { get; private set; }
[SerializeField, Interface(typeof(ITransformFeatureStateProvider))]
private UnityEngine.Object _transformFeatureStateProvider;
protected ITransformFeatureStateProvider TransformFeatureStateProvider;
[SerializeField]
private TransformFeatureConfigList _transformFeatureConfigs;
[SerializeField]
[Tooltip("State provider uses this to determine the state of features during real time, so" +
" edit at runtime at your own risk.")]
private TransformConfig _transformConfig;
public IReadOnlyList<TransformFeatureConfig> FeatureConfigs => _transformFeatureConfigs.Values;
public TransformConfig TransformConfig => _transformConfig;
protected bool _started = false;
protected virtual void Awake()
{
Hand = _hand as IHand;
TransformFeatureStateProvider =
_transformFeatureStateProvider as ITransformFeatureStateProvider;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Hand, nameof(Hand));
this.AssertField(TransformFeatureStateProvider, nameof(TransformFeatureStateProvider));
this.AssertField(_transformFeatureConfigs, nameof(_transformFeatureConfigs));
this.AssertField(_transformConfig, nameof(_transformConfig));
_transformConfig.InstanceId = GetInstanceID();
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
TransformFeatureStateProvider.RegisterConfig(_transformConfig);
// Warm up the proactive evaluation
InitStateProvider();
}
}
protected virtual void OnDisable()
{
if (_started)
{
TransformFeatureStateProvider.UnRegisterConfig(_transformConfig);
}
}
private void InitStateProvider()
{
foreach(var featureConfig in FeatureConfigs)
{
TransformFeatureStateProvider.GetCurrentState(_transformConfig, featureConfig.Feature, out _);
}
}
public void GetFeatureVectorAndWristPos(TransformFeature feature, bool isHandVector,
ref Vector3? featureVec, ref Vector3? wristPos)
{
TransformFeatureStateProvider.GetFeatureVectorAndWristPos(
TransformConfig, feature, isHandVector, ref featureVec, ref wristPos);
}
public bool Active
{
get
{
if (!isActiveAndEnabled)
{
return false;
}
foreach(var featureConfig in FeatureConfigs)
{
if (! TransformFeatureStateProvider.IsStateActive(
_transformConfig,
featureConfig.Feature,
featureConfig.Mode,
featureConfig.State))
{
return false;
}
}
return true;
}
}
#region Inject
public void InjectAllTransformRecognizerActiveState(IHand hand,
ITransformFeatureStateProvider transformFeatureStateProvider,
TransformFeatureConfigList transformFeatureList,
TransformConfig transformConfig)
{
InjectHand(hand);
InjectTransformFeatureStateProvider(transformFeatureStateProvider);
InjectTransformFeatureList(transformFeatureList);
InjectTransformConfig(transformConfig);
}
public void InjectHand(IHand hand)
{
_hand = hand as UnityEngine.Object;
Hand = hand;
}
public void InjectTransformFeatureStateProvider(ITransformFeatureStateProvider transformFeatureStateProvider)
{
TransformFeatureStateProvider = transformFeatureStateProvider;
_transformFeatureStateProvider = transformFeatureStateProvider as UnityEngine.Object;
}
public void InjectTransformFeatureList(TransformFeatureConfigList transformFeatureList)
{
_transformFeatureConfigs = transformFeatureList;
}
public void InjectTransformConfig(TransformConfig transformConfig)
{
_transformConfig = transformConfig;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6f9440d09721c849864899e8986e219
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: