/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using Leap.Unity.Query;
using UnityEngine;
namespace Leap.Unity
{
///
/// Allows you to add to a capped-size ring buffer of Ts and, when full, compute the
/// buffer's average change over time. DeltaBuffer without type parameters supports
/// Vector3s; DeltaFloatBuffer supports floats, and DeltaQuaternionBuffer supports
/// Quaternion rotations.
///
/// To support other types, subclass DeltaBuffer with your sample type and average
/// change type (in many cases the these are the same) and implement the Delta()
/// function to compute the average change of samples currently in the buffer.
///
public abstract class DeltaBuffer : IIndexable
{
protected struct ValueTimePair
{
public SampleType value;
public float time;
}
public DeltaBuffer(int bufferSize)
{
_buffer = new RingBuffer(bufferSize);
}
protected RingBuffer _buffer;
public int Count { get { return _buffer.Count; } }
public bool IsFull { get { return _buffer.IsFull; } }
public bool IsEmpty { get { return _buffer.IsEmpty; } }
public int Capacity { get { return _buffer.Capacity; } }
public SampleType this[int idx]
{
get { return _buffer[idx].value; }
}
public void Clear() { _buffer.Clear(); }
public void Add(SampleType sample, float sampleTime)
{
if (!IsEmpty && sampleTime == GetLatestTime())
{
SetLatest(sample, sampleTime);
return;
}
_buffer.Add(new ValueTimePair { value = sample, time = sampleTime });
}
public SampleType Get(int idx)
{
return _buffer.Get(idx).value;
}
public SampleType GetLatest()
{
return Get(Count - 1);
}
public void Set(int idx, SampleType sample, float sampleTime)
{
_buffer.Set(idx, new ValueTimePair { value = sample, time = sampleTime });
}
public void SetLatest(SampleType sample, float sampleTime)
{
if (Count == 0) Set(0, sample, sampleTime);
else Set(Count - 1, sample, sampleTime);
}
public float GetTime(int idx)
{
return _buffer.Get(idx).time;
}
public float GetLatestTime()
{
return _buffer.Get(Count - 1).time;
}
///
/// Returns the average change between each sample per unit time.
///
/// If the buffer is empty, you should return the identity for your derivative type.
///
public abstract DerivativeType Delta();
#region foreach Support
public IndexableEnumerator GetEnumerator()
{
return new IndexableEnumerator(this);
}
#endregion
}
///
/// A ring buffer of Vector3s with a Delta() function that computes the buffer's
/// average change over time.
///
/// The larger the buffer, the more stable but also delayed the resulting average
/// change over time. A buffer size of 5 is a good start for 60-90 Hz updates.
///
public class DeltaBuffer : DeltaBuffer
{
public DeltaBuffer(int bufferSize) : base(bufferSize) { }
///
/// Returns the average change between each sample per unit time, or zero if the
/// buffer contains one or fewer elements.
///
/// The larger the buffer, the more stable but also delayed the resulting average
/// change over time. A buffer size of 5 is a good start for 60-90 Hz updates.
///
public override Vector3 Delta()
{
if (Count <= 1) { return Vector3.zero; }
Vector3 deltaPerTimeSum = Vector3.zero;
for (int i = 0; i < Count - 1; i++)
{
deltaPerTimeSum += (Get(i + 1) - Get(i)) / (GetTime(i + 1) - GetTime(i));
}
return deltaPerTimeSum / (Count - 1);
}
}
///
/// A ring buffer of floats with a Delta() function that computes the buffer's
/// average change over time. Delta() will return zero if the buffer contains one
/// or fewer samples.
///
/// The larger the buffer, the more stable but also delayed the resulting average
/// change over time. A buffer size of 5 is a good start for 60-90 Hz updates.
///
public class DeltaFloatBuffer : DeltaBuffer
{
public DeltaFloatBuffer(int bufferSize) : base(bufferSize) { }
///
/// Returns the average change between each sample per unit time, or zero if the
/// buffer is empty.
///
public override float Delta()
{
if (Count <= 1) { return 0f; }
float deltaPerTimeSum = 0f;
for (int i = 0; i < Count - 1; i++)
{
deltaPerTimeSum += (Get(i + 1) - Get(i)) / (GetTime(i + 1) - GetTime(i));
}
return deltaPerTimeSum / (Count - 1);
}
}
///
/// A ring buffer of Quaternions with a Delta() function that computes the buffer's
/// average change over time as an angle-axis vector. Returns Vector3.zero if the
/// buffer contains one or fewer samples.
///
/// The larger the buffer, the more stable but also delayed the resulting average
/// change over time. A buffer size of 5 is a good start for 60-90 Hz updates.
///
public class DeltaQuaternionBuffer : DeltaBuffer
{
public DeltaQuaternionBuffer(int bufferSize) : base(bufferSize) { }
///
/// Returns the average angular velocity of Quaternions in the buffer as an
/// angle-axis vector, or zero if the buffer is empty.
///
public override Vector3 Delta()
{
if (Count <= 1) return Vector3.zero;
var deltaSum = Vector3.zero;
for (int i = 0; i < Count - 1; i++)
{
var sample0 = _buffer.Get(i);
var sample1 = _buffer.Get(i + 1);
var r0 = sample0.value;
var t0 = sample0.time;
var r1 = sample1.value;
var t1 = sample1.time;
var delta = (r1.From(r0)).ToAngleAxisVector();
var deltaTime = t1.From(t0);
deltaSum += delta / deltaTime;
}
return deltaSum / (Count - 1);
}
}
}