using System.Collections.Generic;
namespace InteractiveVideo
{
///
/// A whole tracking file, ready for lookup: object identities, and tracked frames sorted by frame number.
/// Built by ; the raw JSON is parsed exactly once.
///
public sealed class TrackingData
{
public string Video;
public int Width;
public int Height;
public float Fps;
/// Object identities in file order.
public readonly List Objects = new List();
public readonly Dictionary ObjectsById = new Dictionary();
/// Tracked frames sorted ascending by , no duplicates.
public readonly List Frames = new List();
/// Parallel array of frame numbers for binary search (kept in sync with ).
public long[] FrameNumbers = System.Array.Empty();
public long FirstFrame => Frames.Count > 0 ? Frames[0].Frame : -1;
public long LastFrame => Frames.Count > 0 ? Frames[Frames.Count - 1].Frame : -1;
///
/// Index of the last tracked frame whose number is <= videoFrame, or -1 when videoFrame precedes all data.
/// O(log n).
///
public int FindFrameIndexAtOrBefore(long videoFrame)
{
int lo = 0, hi = FrameNumbers.Length - 1, result = -1;
while (lo <= hi)
{
int mid = lo + ((hi - lo) >> 1);
if (FrameNumbers[mid] <= videoFrame)
{
result = mid;
lo = mid + 1;
}
else
{
hi = mid - 1;
}
}
return result;
}
/// Exact match lookup; -1 when the video frame has no tracked frame of its own.
public int FindExactFrameIndex(long videoFrame)
{
int i = FindFrameIndexAtOrBefore(videoFrame);
return i >= 0 && FrameNumbers[i] == videoFrame ? i : -1;
}
/// Called by the loader after frames are appended; sorts and builds the lookup array.
public void RebuildIndex()
{
Frames.Sort((a, b) => a.Frame.CompareTo(b.Frame));
// Drop duplicate frame numbers (keep the first); duplicates would break binary search semantics.
for (int i = Frames.Count - 1; i > 0; i--)
if (Frames[i].Frame == Frames[i - 1].Frame) Frames.RemoveAt(i);
FrameNumbers = new long[Frames.Count];
for (int i = 0; i < Frames.Count; i++) FrameNumbers[i] = Frames[i].Frame;
}
}
}