clean project
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.Throw
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides pose information for a controller.
|
||||
/// </summary>
|
||||
public class ControllerPoseInputDevice : MonoBehaviour, IPoseInputDevice
|
||||
{
|
||||
[SerializeField, Interface(typeof(IController))]
|
||||
private MonoBehaviour _controller;
|
||||
public IController Controller { get; private set; }
|
||||
[SerializeField]
|
||||
private Transform _trackingSpaceTransform;
|
||||
|
||||
public bool IsInputValid =>
|
||||
Controller.IsConnected &&
|
||||
Controller.IsPoseValid;
|
||||
|
||||
public bool IsHighConfidence => IsInputValid;
|
||||
|
||||
public bool GetRootPose(out Pose pose)
|
||||
{
|
||||
pose = new Pose();
|
||||
if (!IsInputValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Controller.TryGetPose(out pose))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Controller = _controller as IController;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(_controller);
|
||||
Assert.IsNotNull(_trackingSpaceTransform);
|
||||
}
|
||||
|
||||
public (Vector3, Vector3) GetExternalVelocities()
|
||||
{
|
||||
return (Vector3.zero, Vector3.zero);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllControllerPoseInputDevice(
|
||||
IController controller,
|
||||
Transform trackingSpaceTransform)
|
||||
{
|
||||
InjectController(controller);
|
||||
InjectTrackingSpaceTransform(trackingSpaceTransform);
|
||||
}
|
||||
|
||||
public void InjectController(IController controller)
|
||||
{
|
||||
_controller = controller as MonoBehaviour;
|
||||
Controller = controller;
|
||||
}
|
||||
|
||||
public void InjectTrackingSpaceTransform(Transform trackingSpaceTransform)
|
||||
{
|
||||
_trackingSpaceTransform = trackingSpaceTransform;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05e1160b52479574a9092dd2fef44bfe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,284 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.Throw
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides pose information for a hand.
|
||||
/// </summary>
|
||||
public class HandPoseInputDevice : MonoBehaviour, IPoseInputDevice
|
||||
{
|
||||
[SerializeField, Interface(typeof(IHand))]
|
||||
private MonoBehaviour _hand;
|
||||
public IHand Hand { get; private set; }
|
||||
|
||||
[SerializeField]
|
||||
private float _bufferLengthSeconds = 0.1f;
|
||||
[SerializeField]
|
||||
private float _sampleFrequency = 90.0f;
|
||||
|
||||
public float BufferLengthSeconds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _bufferLengthSeconds;
|
||||
}
|
||||
set
|
||||
{
|
||||
_bufferLengthSeconds = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float SampleFrequency
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sampleFrequency;
|
||||
}
|
||||
set
|
||||
{
|
||||
_sampleFrequency = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInputValid => Hand.IsTrackedDataValid;
|
||||
|
||||
public bool IsHighConfidence => Hand.IsHighConfidence;
|
||||
|
||||
private int _bufferSize = -1;
|
||||
|
||||
private class HandJointPoseMetaData
|
||||
{
|
||||
public HandJointPoseMetaData(HandFinger finger,
|
||||
HandJointId joint, int bufferLength)
|
||||
{
|
||||
Finger = finger;
|
||||
JointId = joint;
|
||||
Velocities = new List<Vector3>();
|
||||
_previousPosition = null;
|
||||
_lastWritePos = -1;
|
||||
_bufferLength = bufferLength;
|
||||
}
|
||||
|
||||
public void BufferNewValue(Pose newPose, float delta)
|
||||
{
|
||||
Vector3 newPosition = newPose.position;
|
||||
Vector3 newVelocity = _previousPosition.HasValue ?
|
||||
((newPosition - _previousPosition.Value) / delta) : Vector3.zero;
|
||||
int nextWritePos = (_lastWritePos < 0) ? 0 :
|
||||
(_lastWritePos + 1) % _bufferLength;
|
||||
if (Velocities.Count <= nextWritePos)
|
||||
{
|
||||
Velocities.Add(newVelocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
Velocities[nextWritePos] = newVelocity;
|
||||
}
|
||||
|
||||
_previousPosition = newPosition;
|
||||
_lastWritePos = nextWritePos;
|
||||
}
|
||||
|
||||
public Vector3 GetAverageVelocityVector()
|
||||
{
|
||||
Vector3 average = Vector3.zero;
|
||||
foreach (var speed in Velocities)
|
||||
{
|
||||
average += speed;
|
||||
}
|
||||
average /= Velocities.Count;
|
||||
return average;
|
||||
}
|
||||
|
||||
public void ResetSpeedsBuffer()
|
||||
{
|
||||
Velocities.Clear();
|
||||
_lastWritePos = -1;
|
||||
_previousPosition = null;
|
||||
}
|
||||
|
||||
public readonly HandFinger Finger;
|
||||
public readonly HandJointId JointId;
|
||||
public readonly List<Vector3> Velocities;
|
||||
|
||||
private Vector3? _previousPosition;
|
||||
private int _lastWritePos;
|
||||
private int _bufferLength;
|
||||
}
|
||||
|
||||
private HandJointPoseMetaData[] _jointPoseInfoArray = null;
|
||||
|
||||
public bool GetRootPose(out Pose pose)
|
||||
{
|
||||
pose = new Pose();
|
||||
if (!IsInputValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Hand.GetJointPose(HandJointId.HandWristRoot,
|
||||
out pose))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Pose palmOffset = new Pose();
|
||||
if (!Hand.GetPalmPoseLocal(out palmOffset))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
palmOffset.Postmultiply(pose);
|
||||
pose = palmOffset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Hand = _hand as IHand;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(_hand);
|
||||
_bufferSize = Mathf.CeilToInt(_bufferLengthSeconds
|
||||
* _sampleFrequency);
|
||||
}
|
||||
|
||||
protected virtual void LateUpdate()
|
||||
{
|
||||
BufferFingerVelocities();
|
||||
}
|
||||
|
||||
private void BufferFingerVelocities()
|
||||
{
|
||||
if (!IsInputValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
AllocateFingerBonesArrayIfNecessary();
|
||||
BufferFingerBoneVelocities();
|
||||
}
|
||||
|
||||
private void AllocateFingerBonesArrayIfNecessary()
|
||||
{
|
||||
if (_jointPoseInfoArray != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_jointPoseInfoArray = new[]
|
||||
{
|
||||
new HandJointPoseMetaData(HandFinger.Thumb,
|
||||
HandJointId.HandThumb3,
|
||||
_bufferSize),
|
||||
new HandJointPoseMetaData(HandFinger.Index,
|
||||
HandJointId.HandIndex3,
|
||||
_bufferSize),
|
||||
new HandJointPoseMetaData(HandFinger.Middle,
|
||||
HandJointId.HandMiddle3,
|
||||
_bufferSize),
|
||||
new HandJointPoseMetaData(HandFinger.Ring,
|
||||
HandJointId.HandRing3,
|
||||
_bufferSize),
|
||||
new HandJointPoseMetaData(HandFinger.Pinky,
|
||||
HandJointId.HandPinky3,
|
||||
_bufferSize)
|
||||
};
|
||||
}
|
||||
|
||||
private bool GetFingerIsHighConfidence(HandFinger handFinger)
|
||||
{
|
||||
return Hand.IsTrackedDataValid &&
|
||||
Hand.GetFingerIsHighConfidence(handFinger);
|
||||
}
|
||||
|
||||
private bool GetJointPose(HandJointId handJointId, out Pose pose)
|
||||
{
|
||||
pose = new Pose();
|
||||
if (!Hand.IsTrackedDataValid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Hand.GetJointPose(handJointId, out pose))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void BufferFingerBoneVelocities()
|
||||
{
|
||||
float deltaValue = Time.deltaTime;
|
||||
foreach (var jointPoseInfo in _jointPoseInfoArray)
|
||||
{
|
||||
if (!GetFingerIsHighConfidence(jointPoseInfo.Finger))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Pose jointPose;
|
||||
if (!GetJointPose(jointPoseInfo.JointId, out jointPose))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
jointPoseInfo.BufferNewValue(jointPose, deltaValue);
|
||||
}
|
||||
}
|
||||
|
||||
public (Vector3, Vector3) GetExternalVelocities()
|
||||
{
|
||||
if (_jointPoseInfoArray == null)
|
||||
{
|
||||
return (Vector3.zero, Vector3.zero);
|
||||
}
|
||||
|
||||
Vector3 averageVelocityAllFingers = Vector3.zero;
|
||||
foreach (var fingerMetaInfo in _jointPoseInfoArray)
|
||||
{
|
||||
averageVelocityAllFingers += fingerMetaInfo.GetAverageVelocityVector();
|
||||
}
|
||||
averageVelocityAllFingers /= _jointPoseInfoArray.Length;
|
||||
foreach (var item in _jointPoseInfoArray)
|
||||
{
|
||||
item.ResetSpeedsBuffer();
|
||||
}
|
||||
|
||||
return (averageVelocityAllFingers, Vector3.zero);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllHandPoseInputDevice(
|
||||
IHand hand)
|
||||
{
|
||||
InjectHand(hand);
|
||||
}
|
||||
|
||||
public void InjectHand(IHand hand)
|
||||
{
|
||||
_hand = hand as MonoBehaviour;
|
||||
Hand = hand;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fb913c9e194da6449a32f1bf3cc991b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.Throw
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface to input device that be used to drive mechanics like
|
||||
/// throwing (via velocity calculation).
|
||||
/// </summary>
|
||||
public interface IPoseInputDevice
|
||||
{
|
||||
bool IsInputValid { get; }
|
||||
|
||||
bool IsHighConfidence { get; }
|
||||
|
||||
bool GetRootPose(out Pose pose);
|
||||
|
||||
(Vector3, Vector3) GetExternalVelocities();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 977ecdca40477174eb47996d9c8d700d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.Throw
|
||||
{
|
||||
/// <summary>
|
||||
/// Transform information used to derive velocities.
|
||||
/// </summary>
|
||||
public struct TransformSample
|
||||
{
|
||||
public TransformSample(Vector3 position, Quaternion rotation, float time,
|
||||
int frameIndex)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
SampleTime = time;
|
||||
FrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
public static TransformSample Interpolate(TransformSample start,
|
||||
TransformSample fin, float time)
|
||||
{
|
||||
float alpha = Mathf.Clamp01(Mathf.InverseLerp(start.SampleTime,
|
||||
fin.SampleTime, time));
|
||||
|
||||
return new TransformSample(Vector3.Lerp(start.Position, fin.Position, alpha),
|
||||
Quaternion.Slerp(start.Rotation, fin.Rotation, alpha),
|
||||
time, (int)Mathf.Lerp((float)start.FrameIndex, (float)fin.FrameIndex, alpha));
|
||||
}
|
||||
|
||||
public readonly Vector3 Position;
|
||||
public readonly Quaternion Rotation;
|
||||
public readonly float SampleTime;
|
||||
public readonly int FrameIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information related to release velocities such as linear and
|
||||
/// angular.
|
||||
/// </summary>
|
||||
public struct ReleaseVelocityInformation
|
||||
{
|
||||
public Vector3 LinearVelocity;
|
||||
public Vector3 AngularVelocity;
|
||||
public Vector3 Origin;
|
||||
public bool IsSelectedVelocity;
|
||||
|
||||
public ReleaseVelocityInformation(Vector3 linearVelocity,
|
||||
Vector3 angularVelocity,
|
||||
Vector3 origin,
|
||||
bool isSelectedVelocity = false)
|
||||
{
|
||||
LinearVelocity = linearVelocity;
|
||||
AngularVelocity = angularVelocity;
|
||||
Origin = origin;
|
||||
IsSelectedVelocity = isSelectedVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to velocity calculator used to make throwing
|
||||
/// possible.
|
||||
/// </summary>
|
||||
public interface IVelocityCalculator
|
||||
{
|
||||
float UpdateFrequency { get; }
|
||||
|
||||
event Action<List<ReleaseVelocityInformation>> WhenThrowVelocitiesChanged;
|
||||
|
||||
event Action<ReleaseVelocityInformation> WhenNewSampleAvailable;
|
||||
|
||||
ReleaseVelocityInformation CalculateThrowVelocity(Transform objectThrown);
|
||||
|
||||
IReadOnlyList<ReleaseVelocityInformation> LastThrowVelocities();
|
||||
|
||||
void SetUpdateFrequency(float frequency);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f8fbc86c372ee34581f48a516c6e816
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,666 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.Throw
|
||||
{
|
||||
/// <summary>
|
||||
/// Velocity calculator that depends only on an IPoseInputDevice,
|
||||
/// which means its input agnostic.
|
||||
/// </summary>
|
||||
public class StandardVelocityCalculator : MonoBehaviour, IVelocityCalculator
|
||||
{
|
||||
[Serializable]
|
||||
public class BufferingParams
|
||||
{
|
||||
public float BufferLengthSeconds = 0.4f;
|
||||
public float SampleFrequency = 90.0f;
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
Assert.IsTrue(BufferLengthSeconds > 0.0f);
|
||||
Assert.IsTrue(SampleFrequency > 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private struct SamplePoseData
|
||||
{
|
||||
public readonly Pose TransformPose;
|
||||
public readonly Vector3 LinearVelocity;
|
||||
public readonly Vector3 AngularVelocity;
|
||||
public readonly float Time;
|
||||
|
||||
public SamplePoseData(Pose transformPose,
|
||||
Vector3 linearVelocity, Vector3 angularVelocity, float time)
|
||||
{
|
||||
TransformPose = transformPose;
|
||||
LinearVelocity = linearVelocity;
|
||||
AngularVelocity = angularVelocity;
|
||||
Time = time;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField, Interface(typeof(IPoseInputDevice))]
|
||||
private MonoBehaviour _throwInputDevice;
|
||||
public IPoseInputDevice ThrowInputDevice { get; private set; }
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("The reference position is the center of mass of the hand or controller." +
|
||||
" Use this in case the computed center of mass is not entirely correct.")]
|
||||
private Vector3 _referenceOffset = Vector3.zero;
|
||||
|
||||
[SerializeField, Tooltip("Related to buffering velocities, used for final release " +
|
||||
"velocity calculation.")]
|
||||
private BufferingParams _bufferingParams;
|
||||
|
||||
[SerializeField, Tooltip("Influence of latest velocities upon release.")]
|
||||
[Range(0.0f, 1.0f)]
|
||||
private float _instantVelocityInfluence = 1.0f;
|
||||
[SerializeField]
|
||||
[Range(0.0f, 1.0f), Tooltip("Influence of derived velocities trend upon release.")]
|
||||
private float _trendVelocityInfluence = 1.0f;
|
||||
[SerializeField]
|
||||
[Range(0.0f, 1.0f), Tooltip("Influence of tangential velcities upon release, which" +
|
||||
" can be affected by rotational motion.")]
|
||||
private float _tangentialVelocityInfluence = 1.0f;
|
||||
[SerializeField]
|
||||
[Range(0.0f, 1.0f), Tooltip("Influence of external velocities upon release. For hands, " +
|
||||
"this can include fingers.")]
|
||||
private float _fingerSpeedInfluence = 0.0f;
|
||||
|
||||
[SerializeField, Tooltip("Time of anticipated release. Hand tracking " +
|
||||
"might experience greater latency compared to controllers.")]
|
||||
private float _stepBackTime = 0.08f;
|
||||
|
||||
[SerializeField, Tooltip("Trend velocity uses a window of velocities, " +
|
||||
"assuming not too many of those velocities are zero. If they exceed a max percentage " +
|
||||
"then a last resort method is used.")]
|
||||
private float _maxPercentZeroSamplesTrendVeloc = 0.5f;
|
||||
|
||||
[SerializeField, Tooltip("Lower this number in case linear release velocity feels " +
|
||||
"too fast. It scales each linear velocity sample buffered.")]
|
||||
private float _linearVelocityScaleModifier = 0.8f;
|
||||
|
||||
public float UpdateFrequency => _updateFrequency;
|
||||
private float _updateFrequency = -1.0f;
|
||||
private float _updateLatency = -1.0f;
|
||||
private float _lastUpdateTime = -1.0f;
|
||||
|
||||
public Vector3 ReferenceOffset
|
||||
{
|
||||
get
|
||||
{
|
||||
return _referenceOffset;
|
||||
}
|
||||
set
|
||||
{
|
||||
_referenceOffset = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float InstantVelocityInfluence {
|
||||
get
|
||||
{
|
||||
return _instantVelocityInfluence;
|
||||
}
|
||||
set
|
||||
{
|
||||
_instantVelocityInfluence = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float TrendVelocityInfluence
|
||||
{
|
||||
get
|
||||
{
|
||||
return _trendVelocityInfluence;
|
||||
}
|
||||
set
|
||||
{
|
||||
_trendVelocityInfluence = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float TangentialVelocityInfluence
|
||||
{
|
||||
get
|
||||
{
|
||||
return _tangentialVelocityInfluence;
|
||||
}
|
||||
set
|
||||
{
|
||||
_tangentialVelocityInfluence = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float FingerSpeedInfluence
|
||||
{
|
||||
get
|
||||
{
|
||||
return _fingerSpeedInfluence;
|
||||
}
|
||||
set
|
||||
{
|
||||
_fingerSpeedInfluence = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float StepBackTime
|
||||
{
|
||||
get
|
||||
{
|
||||
return _stepBackTime;
|
||||
}
|
||||
set
|
||||
{
|
||||
_stepBackTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float MaxPercentZeroSamplesTrendVeloc
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxPercentZeroSamplesTrendVeloc;
|
||||
}
|
||||
set
|
||||
{
|
||||
_maxPercentZeroSamplesTrendVeloc = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float LinearVelocityScaleModifier
|
||||
{
|
||||
get
|
||||
{
|
||||
return _linearVelocityScaleModifier;
|
||||
}
|
||||
set
|
||||
{
|
||||
_linearVelocityScaleModifier = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 AddedInstantLinearVelocity { get; private set; }
|
||||
public Vector3 AddedTrendLinearVelocity { get; private set; }
|
||||
public Vector3 AddedTangentialLinearVelocity { get; private set; }
|
||||
|
||||
List<ReleaseVelocityInformation> _currentThrowVelocities = new List<ReleaseVelocityInformation>();
|
||||
|
||||
public event Action<List<ReleaseVelocityInformation>> WhenThrowVelocitiesChanged = delegate { };
|
||||
|
||||
public event Action<ReleaseVelocityInformation> WhenNewSampleAvailable = delegate { };
|
||||
|
||||
private Vector3 _linearVelocity;
|
||||
private Vector3 _angularVelocity;
|
||||
|
||||
private Vector3? _previousReferencePosition;
|
||||
private Quaternion? _previousReferenceRotation;
|
||||
private float _accumulatedDelta;
|
||||
|
||||
private List<SamplePoseData> _bufferedPoses = new List<SamplePoseData>();
|
||||
private int _lastWritePos = -1;
|
||||
private int _bufferSize = -1;
|
||||
|
||||
private List<SamplePoseData> _windowWithMovement = new List<SamplePoseData>();
|
||||
private List<SamplePoseData> _tempWindow = new List<SamplePoseData>();
|
||||
|
||||
private const float _TREND_DOT_THRESHOLD = 0.6f;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
ThrowInputDevice = _throwInputDevice as IPoseInputDevice;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(_bufferingParams);
|
||||
_bufferingParams.Validate();
|
||||
|
||||
_bufferSize = Mathf.CeilToInt(_bufferingParams.BufferLengthSeconds
|
||||
* _bufferingParams.SampleFrequency);
|
||||
_bufferedPoses.Capacity = _bufferSize;
|
||||
|
||||
Assert.IsNotNull(ThrowInputDevice);
|
||||
}
|
||||
|
||||
public ReleaseVelocityInformation CalculateThrowVelocity(Transform objectThrown)
|
||||
{
|
||||
Vector3 linearVelocity = Vector3.zero,
|
||||
angularVelocity = Vector3.zero;
|
||||
|
||||
IncludeInstantVelocities(ref linearVelocity, ref angularVelocity);
|
||||
|
||||
IncludeTrendVelocities(ref linearVelocity, ref angularVelocity);
|
||||
|
||||
IncludeTangentialInfluence(ref linearVelocity, objectThrown.position);
|
||||
|
||||
IncludeExternalVelocities(ref linearVelocity);
|
||||
|
||||
_currentThrowVelocities.Clear();
|
||||
// queue items in order from lastWritePos to earliest sample
|
||||
int numPoses = _bufferedPoses.Count;
|
||||
for (int readPos = _lastWritePos, itemsRead = 0;
|
||||
itemsRead < numPoses; readPos--, itemsRead++)
|
||||
{
|
||||
if (readPos < 0)
|
||||
{
|
||||
readPos = numPoses - 1;
|
||||
}
|
||||
var item = _bufferedPoses[readPos];
|
||||
ReleaseVelocityInformation newSample = new ReleaseVelocityInformation(
|
||||
item.LinearVelocity,
|
||||
item.AngularVelocity,
|
||||
item.TransformPose.position,
|
||||
false);
|
||||
_currentThrowVelocities.Add(newSample);
|
||||
}
|
||||
ReleaseVelocityInformation newVelocity = new ReleaseVelocityInformation(linearVelocity,
|
||||
angularVelocity,
|
||||
_previousReferencePosition.HasValue ? _previousReferencePosition.Value : Vector3.zero,
|
||||
true);
|
||||
_currentThrowVelocities.Add(newVelocity);
|
||||
WhenThrowVelocitiesChanged(_currentThrowVelocities);
|
||||
|
||||
_bufferedPoses.Clear();
|
||||
_lastWritePos = -1;
|
||||
return newVelocity;
|
||||
}
|
||||
|
||||
private void IncludeInstantVelocities(ref Vector3 linearVelocity,
|
||||
ref Vector3 angularVelocity)
|
||||
{
|
||||
Vector3 instantLinearVelocity = Vector3.zero,
|
||||
instantAngularVelocity = Vector3.zero;
|
||||
IncludeEstimatedReleaseVelocities(ref instantLinearVelocity,
|
||||
ref instantAngularVelocity);
|
||||
|
||||
AddedInstantLinearVelocity = instantLinearVelocity * _instantVelocityInfluence;
|
||||
linearVelocity += AddedInstantLinearVelocity;
|
||||
angularVelocity += instantAngularVelocity * _instantVelocityInfluence;
|
||||
}
|
||||
|
||||
private void IncludeEstimatedReleaseVelocities(ref Vector3 linearVelocity,
|
||||
ref Vector3 angularVelocity)
|
||||
{
|
||||
linearVelocity = _linearVelocity;
|
||||
angularVelocity = _angularVelocity;
|
||||
|
||||
if (_stepBackTime < Mathf.Epsilon)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int beforeIndex, afterIndex;
|
||||
float lookupTime = Time.time - _stepBackTime;
|
||||
(beforeIndex, afterIndex) = FindPoseIndicesBasedOnTime(lookupTime);
|
||||
|
||||
if (beforeIndex < 0 || afterIndex < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var previousPoseData = _bufferedPoses[beforeIndex];
|
||||
var nextPoseData = _bufferedPoses[afterIndex];
|
||||
float previousTime = previousPoseData.Time;
|
||||
float nextTime = nextPoseData.Time;
|
||||
float t = (lookupTime - previousTime) / (nextTime - previousTime);
|
||||
|
||||
Vector3 lerpedVelocity = Vector3.Lerp(previousPoseData.LinearVelocity,
|
||||
nextPoseData.LinearVelocity, t);
|
||||
|
||||
Quaternion previousAngularVelocityQuat =
|
||||
VelocityCalculatorUtilMethods.AngularVelocityToQuat(previousPoseData.AngularVelocity);
|
||||
Quaternion nextAngularVelocityQuat =
|
||||
VelocityCalculatorUtilMethods.AngularVelocityToQuat(nextPoseData.AngularVelocity);
|
||||
Quaternion lerpedAngularVelocQuat = Quaternion.Slerp(previousAngularVelocityQuat,
|
||||
nextAngularVelocityQuat, t);
|
||||
Vector3 lerpedAngularVelocity = VelocityCalculatorUtilMethods.QuatToAngularVeloc(
|
||||
lerpedAngularVelocQuat);
|
||||
|
||||
linearVelocity = lerpedVelocity;
|
||||
angularVelocity = lerpedAngularVelocity;
|
||||
}
|
||||
|
||||
private void IncludeTrendVelocities(ref Vector3 linearVelocity,
|
||||
ref Vector3 angularVelocity)
|
||||
{
|
||||
Vector3 trendLinearVelocity, trendAngularVelocity;
|
||||
(trendLinearVelocity, trendAngularVelocity) = ComputeTrendVelocities();
|
||||
AddedTrendLinearVelocity = trendLinearVelocity * + _trendVelocityInfluence;
|
||||
linearVelocity += AddedTrendLinearVelocity;
|
||||
angularVelocity += trendLinearVelocity * _trendVelocityInfluence;
|
||||
}
|
||||
|
||||
private void IncludeTangentialInfluence(ref Vector3 linearVelocity, Vector3 interactablePosition)
|
||||
{
|
||||
var addedTangentialLinearVelocity = CalculateTangentialVector(interactablePosition);
|
||||
AddedTangentialLinearVelocity =
|
||||
addedTangentialLinearVelocity * _tangentialVelocityInfluence;
|
||||
linearVelocity += AddedTangentialLinearVelocity;
|
||||
}
|
||||
|
||||
private void IncludeExternalVelocities(ref Vector3 linearVelocity)
|
||||
{
|
||||
Vector3 extraLinearVelocity, extraAngularVelocity;
|
||||
(extraLinearVelocity, extraAngularVelocity) = ThrowInputDevice.GetExternalVelocities();
|
||||
float addedFingerSpeed = extraLinearVelocity.magnitude * _fingerSpeedInfluence;
|
||||
linearVelocity += linearVelocity.normalized * addedFingerSpeed;
|
||||
}
|
||||
|
||||
private (int, int) FindPoseIndicesBasedOnTime(float time)
|
||||
{
|
||||
int beforeIndex = -1, afterIndex = -1;
|
||||
|
||||
int numPoses = _bufferedPoses.Count;
|
||||
for (int i = 0; i < numPoses-1; i++)
|
||||
{
|
||||
var currPose = _bufferedPoses[i];
|
||||
var nextPose = _bufferedPoses[i + 1];
|
||||
if (currPose.Time < time && nextPose.Time > time)
|
||||
{
|
||||
beforeIndex = i;
|
||||
afterIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return (beforeIndex, afterIndex);
|
||||
}
|
||||
|
||||
private (Vector3, Vector3) ComputeTrendVelocities()
|
||||
{
|
||||
Vector3 trendLinearVelocity = Vector3.zero;
|
||||
Vector3 trendAngularVelocity = Vector3.zero;
|
||||
if (_bufferedPoses.Count == 0)
|
||||
{
|
||||
return (trendLinearVelocity, trendAngularVelocity);
|
||||
}
|
||||
|
||||
if (BufferedVelocitiesValid())
|
||||
{
|
||||
FindLargestWindowWithMovement();
|
||||
if (_windowWithMovement.Count == 0)
|
||||
{
|
||||
return (trendAngularVelocity, trendAngularVelocity);
|
||||
}
|
||||
foreach (var item in _windowWithMovement)
|
||||
{
|
||||
trendLinearVelocity += item.LinearVelocity;
|
||||
trendAngularVelocity += item.AngularVelocity;
|
||||
}
|
||||
trendLinearVelocity /= _bufferedPoses.Count;
|
||||
trendAngularVelocity /= _bufferedPoses.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
(trendLinearVelocity, trendAngularVelocity) =
|
||||
FindMostRecentBufferedSampleWithMovement();
|
||||
}
|
||||
|
||||
return (trendLinearVelocity, trendAngularVelocity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do we have enough buffered velocities to derive some sort of trend?
|
||||
/// If not, return false. This can happen when a user performs a very fast over or
|
||||
/// underhand throw where most velocities are zero.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool BufferedVelocitiesValid()
|
||||
{
|
||||
int numZeroVectors = 0;
|
||||
|
||||
foreach(var item in _bufferedPoses)
|
||||
{
|
||||
var velocityVector = item.LinearVelocity;
|
||||
if (velocityVector.sqrMagnitude < Mathf.Epsilon)
|
||||
{
|
||||
numZeroVectors++;
|
||||
}
|
||||
}
|
||||
|
||||
int numTotalVectors = _bufferedPoses.Count;
|
||||
float percentZero = (float)numZeroVectors / numTotalVectors;
|
||||
bool bufferedVelocitiesValid = percentZero > _maxPercentZeroSamplesTrendVeloc ?
|
||||
false : true;
|
||||
|
||||
return bufferedVelocitiesValid;
|
||||
}
|
||||
|
||||
private void FindLargestWindowWithMovement()
|
||||
{
|
||||
int numPoses = _bufferedPoses.Count;
|
||||
bool newWindowFound = false;
|
||||
_windowWithMovement.Clear();
|
||||
_tempWindow.Clear();
|
||||
Vector3 initialVector = Vector3.zero;
|
||||
|
||||
// start backwards from last sample
|
||||
for (int readPos = _lastWritePos, itemsRead = 0;
|
||||
itemsRead < numPoses; readPos--, itemsRead++)
|
||||
{
|
||||
if (readPos < 0)
|
||||
{
|
||||
readPos = numPoses - 1;
|
||||
}
|
||||
|
||||
var item = _bufferedPoses[readPos];
|
||||
bool currentItemHasMovement = item.LinearVelocity.sqrMagnitude > 0.0f;
|
||||
if (currentItemHasMovement)
|
||||
{
|
||||
if (!newWindowFound)
|
||||
{
|
||||
newWindowFound = true;
|
||||
_tempWindow.Clear();
|
||||
initialVector = item.LinearVelocity;
|
||||
}
|
||||
|
||||
// include vectors that are roughly the same direction as initial velocity
|
||||
if (Vector3.Dot(initialVector.normalized, item.LinearVelocity.normalized)
|
||||
> _TREND_DOT_THRESHOLD)
|
||||
{
|
||||
_tempWindow.Add(item);
|
||||
}
|
||||
}
|
||||
// end of window when we hit something with no speed
|
||||
else if (!currentItemHasMovement && newWindowFound)
|
||||
{
|
||||
newWindowFound = false;
|
||||
if (_tempWindow.Count > _windowWithMovement.Count)
|
||||
{
|
||||
TransferToDestBuffer(_tempWindow, _windowWithMovement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// in case window continues till end of buffer
|
||||
if (newWindowFound)
|
||||
{
|
||||
if (_tempWindow.Count > _windowWithMovement.Count)
|
||||
{
|
||||
TransferToDestBuffer(_tempWindow, _windowWithMovement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (Vector3, Vector3) FindMostRecentBufferedSampleWithMovement()
|
||||
{
|
||||
int numPoses = _bufferedPoses.Count;
|
||||
Vector3 linearVelocity = Vector3.zero;
|
||||
Vector3 angularVelocity = Vector3.zero;
|
||||
|
||||
for (int readPos = _lastWritePos, itemsRead = 0;
|
||||
itemsRead < numPoses; readPos--, itemsRead++)
|
||||
{
|
||||
if (readPos < 0)
|
||||
{
|
||||
readPos = numPoses - 1;
|
||||
}
|
||||
|
||||
var item = _bufferedPoses[readPos];
|
||||
var itemLinearVelocity = item.LinearVelocity;
|
||||
var itemAngularVelocity = item.AngularVelocity;
|
||||
if (itemLinearVelocity.sqrMagnitude > Mathf.Epsilon &&
|
||||
itemAngularVelocity.sqrMagnitude > Mathf.Epsilon)
|
||||
{
|
||||
linearVelocity = itemLinearVelocity;
|
||||
angularVelocity = itemAngularVelocity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (linearVelocity, angularVelocity);
|
||||
}
|
||||
|
||||
private void TransferToDestBuffer(List<SamplePoseData> source, List<SamplePoseData> dest)
|
||||
{
|
||||
dest.Clear();
|
||||
foreach (var sourceItem in source)
|
||||
{
|
||||
dest.Add(sourceItem);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 CalculateTangentialVector(Vector3 objectPosition)
|
||||
{
|
||||
if (_previousReferencePosition == null)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
float angularVelocityMag = _angularVelocity.magnitude;
|
||||
if (angularVelocityMag < Mathf.Epsilon)
|
||||
{
|
||||
return Vector3.zero;
|
||||
}
|
||||
|
||||
Vector3 centerOfMassToObject = objectPosition - _previousReferencePosition.Value;
|
||||
float radius = centerOfMassToObject.magnitude;
|
||||
Vector3 centerOfMassToObjectNorm = centerOfMassToObject.normalized;
|
||||
Vector3 axisOfRotation = _angularVelocity.normalized;
|
||||
Vector3 tangentialDirection = Vector3.Cross(axisOfRotation, centerOfMassToObjectNorm);
|
||||
// https://byjus.com/tangential-velocity-formula/
|
||||
return tangentialDirection * radius * angularVelocityMag;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ReleaseVelocityInformation> LastThrowVelocities()
|
||||
{
|
||||
return _currentThrowVelocities;
|
||||
}
|
||||
|
||||
public void SetUpdateFrequency(float frequency)
|
||||
{
|
||||
_updateFrequency = frequency;
|
||||
_updateLatency = 1.0f / _updateFrequency;
|
||||
}
|
||||
|
||||
protected virtual void LateUpdate()
|
||||
{
|
||||
if (_updateLatency > 0.0f && _lastUpdateTime > 0.0f &&
|
||||
(Time.time - _lastUpdateTime) < _updateLatency)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Pose referencePose;
|
||||
if (!ThrowInputDevice.IsInputValid || !ThrowInputDevice.IsHighConfidence ||
|
||||
!ThrowInputDevice.GetRootPose(out referencePose))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastUpdateTime = Time.time;
|
||||
referencePose = new Pose(
|
||||
_referenceOffset + referencePose.position,
|
||||
referencePose.rotation);
|
||||
UpdateVelocitiesAndBuffer(Time.deltaTime, referencePose);
|
||||
}
|
||||
|
||||
private void UpdateVelocitiesAndBuffer(float delta, Pose referencePose)
|
||||
{
|
||||
_accumulatedDelta += delta;
|
||||
|
||||
UpdateLatestVelocitiesAndPoseValues(referencePose, _accumulatedDelta);
|
||||
_accumulatedDelta = 0.0f;
|
||||
int nextWritePos = (_lastWritePos < 0) ? 0 :
|
||||
(_lastWritePos + 1) % _bufferSize;
|
||||
var newPose = new SamplePoseData(referencePose, _linearVelocity,
|
||||
_angularVelocity, Time.time);
|
||||
if (_bufferedPoses.Count <= nextWritePos)
|
||||
{
|
||||
_bufferedPoses.Add(newPose);
|
||||
}
|
||||
else
|
||||
{
|
||||
_bufferedPoses[nextWritePos] = newPose;
|
||||
}
|
||||
_lastWritePos = nextWritePos;
|
||||
}
|
||||
|
||||
private void UpdateLatestVelocitiesAndPoseValues(Pose referencePose, float delta)
|
||||
{
|
||||
(_linearVelocity, _angularVelocity) = GetLatestLinearAndAngularVelocities(
|
||||
referencePose, delta);
|
||||
_linearVelocity *= _linearVelocityScaleModifier;
|
||||
|
||||
WhenNewSampleAvailable(new ReleaseVelocityInformation(_linearVelocity, _angularVelocity,
|
||||
referencePose.position));
|
||||
|
||||
_previousReferencePosition = referencePose.position;
|
||||
_previousReferenceRotation = referencePose.rotation;
|
||||
}
|
||||
|
||||
private (Vector3, Vector3) GetLatestLinearAndAngularVelocities(Pose referencePose,
|
||||
float delta)
|
||||
{
|
||||
if (!_previousReferencePosition.HasValue || delta < Mathf.Epsilon)
|
||||
{
|
||||
return (Vector3.zero, Vector3.zero);
|
||||
}
|
||||
|
||||
Vector3 newLinearVelocity = (referencePose.position -
|
||||
_previousReferencePosition.Value) / delta;
|
||||
var newAngularVelocity = VelocityCalculatorUtilMethods.ToAngularVelocity(
|
||||
_previousReferenceRotation.Value,
|
||||
referencePose.rotation, delta);
|
||||
|
||||
return (newLinearVelocity, newAngularVelocity);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllStandardVelocityCalculator(
|
||||
IPoseInputDevice poseInputDevice,
|
||||
BufferingParams bufferingParams)
|
||||
{
|
||||
InjectPoseInputDevice(poseInputDevice);
|
||||
InjectBufferingParams(bufferingParams);
|
||||
}
|
||||
|
||||
public void InjectPoseInputDevice(IPoseInputDevice poseInputDevice)
|
||||
{
|
||||
_throwInputDevice = poseInputDevice as MonoBehaviour;
|
||||
ThrowInputDevice = poseInputDevice;
|
||||
}
|
||||
|
||||
public void InjectBufferingParams(BufferingParams bufferingParams)
|
||||
{
|
||||
_bufferingParams = bufferingParams;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc8862bea98fed5448c81a6a656d69cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user