Initialer Upload neues Unity-Projekt
This commit is contained in:
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af496e475135e134aa6a3ad7e0109882
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7098ce5b7bdb8734980e21c523861a71
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98347ac4d9a23ac4c84b5eea4c0291d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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 : "";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38d73b2d0c0ee504587e0237ec474ca4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ed4598c08239c94fa439c2921b27349
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bf41478a1af76e48b69aa772f7b81fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10fca570a88ae124dbdb5dfda85fbbf6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89e8f8c35caba0c4abef1bdd8890c640
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df12566059c64f34a8985d7383779198
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53451bc1e24308a478c0a120ed9eae00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86fda5ce347f35d44a5c6faee65c6679
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9390f6f5772c6d942a736437d338b667
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cd27ccbb9a942e47a1b2bfd53f957e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5b3b9e0179019c46a27bcc4ed7916ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aca13d9f89c0c904a8d62bde280f5b52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bb5c328f4204486ad98f3f962154e07
|
||||
timeCreated: 1633634969
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53118e1a377b4b6685ca776f1d23ca3b
|
||||
timeCreated: 1633632912
|
||||
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2b0882e924a4f52b3cebb599b3fa0d1
|
||||
timeCreated: 1628024315
|
||||
@ -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.";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ef4951587e241c79251e9b88839be0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b942c16a6d6a4edaad7c18c7d5762cdf
|
||||
timeCreated: 1627755300
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 870d37507a21e5e42877dca0f61dcd28
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5af6ab9d1bd4a47970ad151090281f3
|
||||
timeCreated: 1627757407
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b35099b781e8a9a449c26d6df610d496
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8310d4698426ea24e923c8a77fe0f3a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6152072e57cc4c7c87b467b1237dc8e0
|
||||
timeCreated: 1628024382
|
||||
@ -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;}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a32c5568211a4c8086cbf588996b654b
|
||||
timeCreated: 1628024369
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b736c28bc3ad4a639641c5c8f0fbd88d
|
||||
timeCreated: 1628622856
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 645a8b39ed56176499370c5bfaf13d63
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71d26e05b18b0994dacc3c0257325d33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06fe84cad95e40a428c3ca4346ad3afd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d74f6b51a9292b9409651b36d6d9aad7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91629c29fa7084f4bb4cd2da4084251f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e7ab4178b1f98e40bc6baf2176709df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2a8d5cf844b463aabaed6d6db3da8c0
|
||||
timeCreated: 1634670079
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cfe1df7ed391a24fbe2a2d275e81b06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08f7cfb1f9629da4494ac0840f3a3cfd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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.";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d8fbcf2f8f5475fa39c11050b34cdab
|
||||
timeCreated: 1631578210
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bffe606b408599b4fad696ddc889a943
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9485329571269f41ba05b864b35f1cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac666f23650b94b4ea891467ab3677e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 849e24afed5709e4a82857a19ce08501
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6f9440d09721c849864899e8986e219
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user