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; } } }