Forum Discussion
lufinkey
3 years agoProtege
Randomly large velocities from standard velocity calculator, but only in APK
I keep having extremely large values coming from the StandardVelocityCalculator that don't make sense with how the hand is moved. At first i thought it was tangential velocity influence, but turning ...
lufinkey
2 years agoProtege
Here's the custom "SimpleVelocityCalculator". The only remaining issue with it is that the poses are currently stored in world space, so any teleport will immediately make velocity really large. In our game, users don't need to hold anything while teleporting, so we just haven't gotten around to fixing it. But there's a small TODO in the code with a bit more detail on the fix that it needs (let me know if you have any questions on that and feel free to share if you make the fix). We also call ResetVelocityHistory in InteractableSelected in our custom "HandGrabInteractor" implementation, but you could probably do this within an event callback if you're using the oculus HandGrabInteractor:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Oculus.Interaction;
using Oculus.Interaction.Throw;
public class SimpleVelocityCalculator: MonoBehaviour, IVelocityCalculator {
public IPoseInputDevice poseInputDevice {
get => __poseInputDevice;
set {
if (__poseInputDevice != value) {
__poseInputDevice = value;
_poseInputDevice = value as MonoBehaviour;
_previousInputPose = GetInputDevicePose();
}
}
}
[SerializeField, Interface(typeof(IPoseInputDevice))]
private MonoBehaviour _poseInputDevice;
private IPoseInputDevice __poseInputDevice;
public float maxVelocityChange {
get => _maxVelocityChange;
set => _maxVelocityChange = value;
}
[SerializeField]
private float _maxVelocityChange = 10.0f;
public float maxAngularVelocityChange {
get => _maxAngularVelocityChange;
set => _maxAngularVelocityChange = value;
}
[SerializeField]
private float _maxAngularVelocityChange = 20.0f;
// IMPORTANT TODO!! store poses relative to a tracking space root, to prevent large velocities when teleporting (and then convert velocity vector to world space in CalculateThrowVelocity)
private Pose? _previousInputPose = null;
private Pose _previousValidInputPose = Pose.identity;
private Vector3 _linearVelocity = Vector3.zero;
private Vector3 _angularVelocity = Vector3.zero;
private List<ReleaseVelocityInformation> _lastThrowVelocities = new List<ReleaseVelocityInformation>();
#region Unity Events
protected void Awake() {
__poseInputDevice = _poseInputDevice as IPoseInputDevice;
}
protected void Update() {
UpdateVelocities();
}
#endregion
#region IVelocityCalculator
float IVelocityCalculator.UpdateFrequency => Mathf.Epsilon;
void IVelocityCalculator.SetUpdateFrequency(float frequency) {
Debug.LogError("Update frequency cannot be modified on SimpleVelocityCalculator");
}
public event System.Action<List<ReleaseVelocityInformation>> WhenThrowVelocitiesChanged = delegate { };
public event System.Action<ReleaseVelocityInformation> WhenNewSampleAvailable = delegate { };
IReadOnlyList<ReleaseVelocityInformation> IVelocityCalculator.LastThrowVelocities() => _lastThrowVelocities;
public ReleaseVelocityInformation CalculateThrowVelocity(Transform objectThrown) {
_lastThrowVelocities.Clear();
ReleaseVelocityInformation releaseInfo = new ReleaseVelocityInformation(
_linearVelocity,
_angularVelocity,
origin: _previousValidInputPose.position,
isSelectedVelocity: true);
_lastThrowVelocities.Add(releaseInfo);
ResetVelocityHistory();
WhenThrowVelocitiesChanged(_lastThrowVelocities);
return releaseInfo;
}
#endregion
public Pose? GetInputDevicePose() {
var inputDevice = this.poseInputDevice;
if (inputDevice == null || !inputDevice.IsInputValid || !inputDevice.IsHighConfidence || !inputDevice.GetRootPose(out Pose referencePose)) {
return null;
}
return referencePose;
}
public void ResetVelocityHistory() {
_previousInputPose = null;
_linearVelocity = Vector3.zero;
_angularVelocity = Vector3.zero;
}
private void UpdateVelocities() {
var newInputPose = GetInputDevicePose();
var prevInputPose = _previousInputPose;
_previousInputPose = newInputPose;
if (newInputPose != null) {
_previousValidInputPose = newInputPose.Value;
}
if (newInputPose == null || prevInputPose == null) {
return;
}
var newPoseVal = newInputPose.Value;
var prevPoseVal = prevInputPose.Value;
var positionDelta = (newPoseVal.position - prevPoseVal.position);
var rotationDelta = FromToRotationGlobal(prevPoseVal.rotation, newPoseVal.rotation);
Vector3 velocityTarget = (positionDelta / Time.deltaTime);
if (!float.IsNaN(velocityTarget.x)) {
_linearVelocity = Vector3.MoveTowards(_linearVelocity, velocityTarget, _maxVelocityChange);
}
rotationDelta.ToAngleAxis(out float angle, out Vector3 axis);
angle = NormalizeSignedAngle(angle);
if (angle != 0) {
Vector3 angularTarget = angle * axis;
if (!float.IsNaN(angularTarget.x)) {
angularTarget = (angularTarget / Time.deltaTime);
_angularVelocity = Vector3.MoveTowards(_angularVelocity, angularTarget, _maxAngularVelocityChange);
}
}
var newReleaseInfo = new ReleaseVelocityInformation(_linearVelocity, _angularVelocity, newPoseVal.position);
WhenNewSampleAvailable.Invoke(newReleaseInfo);
}
private static Quaternion FromToRotationGlobal(Quaternion from, Quaternion to) {
return to * Quaternion.Inverse(from);
}
public static Vector3 NormalizeSignedAngles(Vector3 euler) {
euler.x = NormalizeSignedAngle(euler.x);
euler.y = NormalizeSignedAngle(euler.y);
euler.z = NormalizeSignedAngle(euler.z);
return euler;
}
public static float NormalizeSignedAngle(float angle) {
angle %= 360.0f;
while (angle > 180.0f) {
angle -= 360.0f;
}
while (angle < -180.0f) {
angle += 360.0f;
}
return angle;
}
}- simonmersus2 years agoExplorer
Hey, thanks for the script, it helped me implement a velocity tracker for hand tracking that's working pretty smotthly. I have a question, how do you handle spikes in velocity when tracking is lost ? I tried to implement a frame buffer to discard the first frame after tracking is back but i'm still getting spikes after that (worst scenario is when I'm occluding one of my hand and it is jumping over the place)
- lufinkey2 years agoProtege
Within this code? Using Vector3.MoveTowards with a max change in velocity might be what you're looking for
- lufinkey2 years agoProtege
I do also use a nullable for the last tracked pose, so if there's any loss in tracking, it'll set it to null and won't use it to calculate the velocity when tracking reconnects
Quick Links
- Horizon Developer Support
- Quest User Forums
- Troubleshooting Forum for problems with a game or app
- Quest Support for problems with your device
Other Meta Support
Related Content
- 2 years ago
- 9 months ago
- 9 months ago
- 8 months ago