VR4RoboticArm2/VR4RoboticArm/Library/PackageCache/com.meta.xr.sdk.interaction/Runtime/Scripts/PoseDetection/FeatureStateProvider.cs
IonutMocanu 48cccc22ad Main2
2025-09-08 11:13:29 +03:00

329 lines
13 KiB
C#

/*
* 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 generic provider that manages feature states through state collections in <see cref="FingerFeatureStateDictionary"/> and <see cref="TransformFeatureStateCollection"/>,
/// supporting the high-level providers <see cref="FingerFeatureStateProvider"/> and <see cref="TransformFeatureStateProvider"/>.
/// This provider implements hysteresis-based state management with time-based validation to ensure
/// stable and reliable feature state detection.
/// </summary>
/// <typeparam name="TFeature">An enum containing all trackable features</typeparam>
/// <typeparam name="TFeatureState">An enum of all possible states for each feature</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
/// <summary>
/// Initializes the provider with value reading, feature mapping, and time tracking functions.
/// </summary>
/// <param name="valueReader">Function that reads the current value of a feature</param>
/// <param name="featureToInt">Function that maps feature enum values to integer indices</param>
/// <param name="timeProvider">Function that provides the current time value</param>
public FeatureStateProvider(Func<TFeature, float?> valueReader,
Func<TFeature, int> featureToInt,
Func<float> timeProvider)
{
_valueReader = valueReader;
_featureToInt = featureToInt;
_timeProvider = timeProvider;
}
/// <summary>
/// Initializes the threshold configuration for all features.
/// </summary>
/// <param name="featureThresholds">Configuration defining state transition thresholds</param>
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)];
}
/// <summary>
/// Gets the current state of a specified feature, calculating and potentially updating it based on the latest feature value.
/// </summary>
/// <param name="feature">The feature to query</param>
/// <returns>The current state of the specified feature, which may or may not have changed this frame.</returns>
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]);
}
}
}
}