Code/HumanoidMocap/Motion/MotionEvidence.cs
using System;
namespace HumanoidMocap.Motion;
/// <summary>Samples evidence on the same timestamp grid used for animation export.
/// These are provenance labels, not confidence or accuracy estimates.</summary>
public static class MotionEvidence
{
public static JointEvidence At(MotionDocument motion,int bone,double time)
{
if(!double.IsFinite(time))throw new ArgumentOutOfRangeException(nameof(time));
if(bone<0||bone>=motion.Bones.Count)throw new ArgumentOutOfRangeException(nameof(bone));
var frames=motion.Frames;
if(frames.Count==0)throw new ArgumentException("Motion has no samples.",nameof(motion));
if(time<=frames[0].Time)return frames[0].Evidence[bone];
if(time>=frames[^1].Time)return frames[^1].Evidence[bone];
var lo=0;var hi=frames.Count-1;
while(lo<hi){var mid=(lo+hi)/2;if(frames[mid].Time<time)lo=mid+1;else hi=mid;}
var a=frames[lo-1];var b=frames[lo];
return Blend(a.Evidence[bone],b.Evidence[bone],Fraction(time,a.Time,b.Time));
}
/// <summary>Media Foundation timestamps have 100 ns resolution. Treat only
/// numerical neighbours of a sample as that sample; retain conservative evidence
/// blending across genuine intervals. The tolerance is at most one microsecond
/// and at most 0.01% of this interval.</summary>
internal static float Fraction(double time,double start,double end)
{
if(end<=start)return 0;
var tolerance=Math.Min(1e-6,(end-start)*1e-4);
if(time-start<=tolerance)return 0;
if(end-time<=tolerance)return 1;
return (float)((time-start)/(end-start));
}
internal static JointEvidence Blend(JointEvidence a,JointEvidence b,float amount)
=>amount<=0?a:amount>=1?b:
a==JointEvidence.Unobserved||b==JointEvidence.Unobserved?JointEvidence.Unobserved:
a==b?a:JointEvidence.InferredGap;
}