mixedreality/com.microsoft.mixedreality..../Core/Utilities/Physics/VectorRollingStatistics.cs

217 lines
7.5 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Physics
{
/// <summary>
/// Vector Statistics used in gaze stabilization.
/// </summary>
public class VectorRollingStatistics
{
/// <summary>
/// Current standard deviation of the positions of the vectors.
/// </summary>
public float CurrentStandardDeviation { get; private set; }
/// <summary>
/// Difference to standardDeviation when the latest sample was added.
/// </summary>
public float StandardDeviationDeltaAfterLatestSample { get; private set; }
/// <summary>
/// How many standard deviations the latest sample was away.
/// </summary>
public float StandardDeviationsAwayOfLatestSample { get; private set; }
/// <summary>
/// The average position.
/// </summary>
public Vector3 Average { get; private set; }
/// <summary>
/// The number of samples in the current set (may be 0 - maxSamples)
/// </summary>
public float ActualSampleCount { get; private set; }
/// <summary>
/// Keeps track of the index into the sample list for the rolling average.
/// </summary>
private int currentSampleIndex;
/// <summary>
/// An array of samples for calculating standard deviation.
/// </summary>
private Vector3[] samples;
/// <summary>
/// The sum of all of the samples.
/// </summary>
private Vector3 cumulativeFrame;
/// <summary>
/// The sum of all of the samples squared.
/// </summary>
private Vector3 cumulativeFrameSquared;
/// <summary>
/// The total number of samples taken.
/// </summary>
private int cumulativeFrameSamples;
/// <summary>
/// The maximum number of samples to include in
/// the average and standard deviation calculations.
/// </summary>
private int maxSamples;
/// <summary>
/// Initialize the rolling stats.
/// </summary>
public void Init(int sampleCount)
{
maxSamples = sampleCount;
samples = new Vector3[sampleCount];
Reset();
}
/// <summary>
/// Resets the stats to zero.
/// </summary>
public void Reset()
{
currentSampleIndex = 0;
ActualSampleCount = 0;
cumulativeFrame = Vector3.zero;
cumulativeFrameSquared = Vector3.zero;
cumulativeFrameSamples = 0;
CurrentStandardDeviation = 0.0f;
StandardDeviationDeltaAfterLatestSample = 0.0f;
StandardDeviationsAwayOfLatestSample = 0.0f;
Average = Vector3.zero;
if (samples != null)
{
for (int index = 0; index < samples.Length; index++)
{
samples[index] = Vector3.zero;
}
}
}
/// <summary>
/// Adds a new sample to the sample list and updates the stats.
/// </summary>
/// <param name="value">The new sample to add</param>
public void AddSample(Vector3 value)
{
if (maxSamples == 0)
{
return;
}
// remove the old sample from our accumulation
Vector3 oldSample = samples[currentSampleIndex];
// -- Below replaces operations:
// cumulativeFrame -= oldSample;
// cumulativeFrameSquared -= (oldSample.Mul(oldSample));
cumulativeFrame.x -= oldSample.x;
cumulativeFrame.y -= oldSample.y;
cumulativeFrame.z -= oldSample.z;
oldSample.x *= oldSample.x;
oldSample.y *= oldSample.y;
oldSample.z *= oldSample.z;
cumulativeFrameSquared.x -= oldSample.x;
cumulativeFrameSquared.y -= oldSample.y;
cumulativeFrameSquared.z -= oldSample.z;
// --
// Add the new sample to the accumulation
samples[currentSampleIndex] = value;
// -- Below replaces operations:
// cumulativeFrame += value;
// cumulativeFrameSquared += value.Mul(value);
cumulativeFrame.x += value.x;
cumulativeFrame.y += value.y;
cumulativeFrame.z += value.z;
Vector3 valueSquared = value;
valueSquared.x = value.x * value.x;
valueSquared.y = value.y * value.y;
valueSquared.z = value.z * value.z;
cumulativeFrameSquared.x += valueSquared.x;
cumulativeFrameSquared.y += valueSquared.y;
cumulativeFrameSquared.z += valueSquared.z;
// --
// Keep track of how many samples we have
cumulativeFrameSamples++;
ActualSampleCount = Mathf.Min(maxSamples, cumulativeFrameSamples);
// see how many standard deviations the current sample is from the previous average
// -- Below replaces operations:
// Vector3 deltaFromAverage = (Average - value);
Vector3 deltaFromAverage = Average;
deltaFromAverage.x -= value.x;
deltaFromAverage.y -= value.y;
deltaFromAverage.z -= value.z;
// --
float oldStandardDeviation = CurrentStandardDeviation;
// -- Below replaces operations:
// StandardDeviationsAwayOfLatestSample = oldStandardDeviation.Equals(0) ? 0 : (deltaFromAverage / oldStandardDeviation).magnitude;
if (oldStandardDeviation == 0)
{
StandardDeviationsAwayOfLatestSample = 0;
}
else
{
deltaFromAverage.x /= oldStandardDeviation;
deltaFromAverage.y /= oldStandardDeviation;
deltaFromAverage.z /= oldStandardDeviation;
StandardDeviationsAwayOfLatestSample = deltaFromAverage.magnitude;
}
// --
// And calculate new averages and standard deviations
// (note that calculating a standard deviation of a Vector3 might not
// be done properly, but the logic is working for the gaze stabilization scenario)
// -- Below replaces operations:
// Average = cumulativeFrame / ActualSampleCount;
// float newStandardDev = Mathf.Sqrt((cumulativeFrameSquared / ActualSampleCount - Average.Mul(Average)).magnitude);
Vector3 average = Average;
average.x = cumulativeFrame.x / ActualSampleCount;
average.y = cumulativeFrame.y / ActualSampleCount;
average.z = cumulativeFrame.z / ActualSampleCount;
Average = average;
Vector3 frmSqrDivSamples = cumulativeFrameSquared;
frmSqrDivSamples.x /= ActualSampleCount;
frmSqrDivSamples.y /= ActualSampleCount;
frmSqrDivSamples.z /= ActualSampleCount;
frmSqrDivSamples.x -= (average.x * average.x);
frmSqrDivSamples.y -= (average.y * average.y);
frmSqrDivSamples.z -= (average.z * average.z);
float newStandardDev = Mathf.Sqrt(frmSqrDivSamples.magnitude);
// --
StandardDeviationDeltaAfterLatestSample = oldStandardDeviation - newStandardDev;
CurrentStandardDeviation = newStandardDev;
// update the next list position
currentSampleIndex = (currentSampleIndex + 1) % maxSamples;
}
}
}