public bool GetInterpolatedAvatars(ref AvatarState[] avatar, out int count, out ushort resetId)
    {
        count   = 0;
        resetId = 0;
        var frame = (long)Math.Floor(packetFrame + time * PhysicsFrameRate);

        if (frame < 0.0)
        {
            return(false);         //if interpolation frame is negative, it's too early to display anything(reset)
        }
        const int n = 16;

        if (isInterpolating && frame - startFrame > n)
        {
            isInterpolating = false; //if we are interpolating but the interpolation start frame is too old, go back to the not interpolating state, so we can find a new start point.
        }
        //if not interpolating, attempt to find an interpolation start point.
        //if start point exists, go into interpolating mode and set end point to start point
        //so we can reuse code below to find a suitable end point on first time through.
        //if no interpolation start point is found, return.
        if (!isInterpolating)
        {
            for (var i = frame + 1; i > frame - n && i >= 0; i--)
            {
                var entry = GetEntry((uint)i);
                if (entry == null)
                {
                    continue;
                }

                var sampleTime = (i - packetFrame) / PhysicsFrameRate + entry.header.timeOffset;
                if (time < sampleTime || time > sampleTime + (1.0f / PhysicsFrameRate))
                {
                    continue;
                }

                startFrame      = i;
                endFrame        = i;
                startTime       = sampleTime;
                endTime         = sampleTime;
                isInterpolating = true;
            }
        }
        if (!isInterpolating)
        {
            return(false);
        }

        Assert.IsTrue(time >= startTime);
        //if current time is >= end time, we need to start a new interpolation
        //from the previous end time to the next sample that exists up to n samples ahead.
        if (time >= endTime)
        {
            startFrame = endFrame;
            startTime  = endTime;

            for (int i = 0; i < n; ++i)
            {
                var entry = GetEntry((uint)(startFrame + 1 + i));
                if (entry == null)
                {
                    continue;
                }

                var sampleTime = (startFrame + 1 + i - packetFrame) / PhysicsFrameRate + entry.header.timeOffset;
                if (sampleTime < time)
                {
                    continue;
                }

                endFrame = startFrame + 1 + i;
                endTime  = sampleTime + (1.0 / PhysicsFrameRate);
                break;
            }
        }
        if (time > endTime)
        {
            return(false);            //if current time is still > end time, we couldn't start a new interpolation so return.
        }
        //we are in a valid interpolation, calculate t by looking at current time
        //relative to interpolation start/end times and perform the interpolation.
        var t = (float)Clamp((time - startTime) / (endTime - startTime), 0.0, 1.0);
        var a = GetEntry((uint)(startFrame));
        var b = GetEntry((uint)(endFrame));

        for (int i = 0; i < a.avatarCount; ++i)
        {
            for (int j = 0; j < b.avatarCount; ++j)
            {
                if (a.avatars[i].clientId != b.avatars[j].clientId)
                {
                    continue;
                }

                AvatarState.Interpolate(ref a.avatars[i], ref b.avatars[j], out avatar[count], t);
                count++;
            }
        }
        resetId = a.header.resetId;

        return(true);
    }
    public bool GetInterpolatedAvatarState(ref AvatarState[] output, out int numOutputAvatarStates, out ushort resetSequence)
    {
        numOutputAvatarStates = 0;
        resetSequence         = 0;

        // if interpolation frame is negative, it's too early to display anything

        double interpolation_frame = initial_frame + time * Constants.PhysicsFrameRate;

        if (interpolation_frame < 0.0)
        {
            return(false);
        }

        // if we are interpolating but the interpolation start frame is too old,
        // go back to the not interpolating state, so we can find a new start point.

        const int n = 16;

        if (interpolating)
        {
            long frame = (long)Math.Floor(interpolation_frame);

            if (frame - interpolation_start_frame > n)
            {
                interpolating = false;
            }
        }

        // if not interpolating, attempt to find an interpolation start point.
        // if start point exists, go into interpolating mode and set end point to start point
        // so we can reuse code below to find a suitable end point on first time through.
        // if no interpolation start point is found, return.

        if (!interpolating)
        {
            long current_frame = (uint)Math.Floor(interpolation_frame);

            for (long frame = current_frame + 1; (frame > current_frame - n) && (frame >= 0); frame--)
            {
                JitterBufferEntry entry = GetEntry((uint)frame);

                if (entry != null)
                {
                    double avatar_sample_time = (frame - initial_frame) * (1.0 / Constants.PhysicsFrameRate) + entry.packetHeader.avatarSampleTimeOffset;

                    if (time >= avatar_sample_time && time <= avatar_sample_time + (1.0f / Constants.PhysicsFrameRate))
                    {
                        interpolation_start_frame = frame;
                        interpolation_end_frame   = frame;

                        interpolation_start_time = avatar_sample_time;
                        interpolation_end_time   = avatar_sample_time;

                        interpolating = true;
                    }
                }
            }
        }

        if (!interpolating)
        {
            return(false);
        }

        Assert.IsTrue(time >= interpolation_start_time);

        // if current time is >= end time, we need to start a new interpolation
        // from the previous end time to the next sample that exists up to n samples ahead.

        if (time >= interpolation_end_time)
        {
            interpolation_start_frame = interpolation_end_frame;
            interpolation_start_time  = interpolation_end_time;

            for (int i = 0; i < n; ++i)
            {
                JitterBufferEntry entry = GetEntry((uint)(interpolation_start_frame + 1 + i));

                if (entry != null)
                {
                    double avatar_sample_time = (interpolation_start_frame + 1 + i - initial_frame) * (1.0 / Constants.PhysicsFrameRate) + entry.packetHeader.avatarSampleTimeOffset;

                    if (avatar_sample_time >= time)
                    {
                        interpolation_end_frame = interpolation_start_frame + 1 + i;
                        interpolation_end_time  = avatar_sample_time + (1.0 / Constants.PhysicsFrameRate);
                        break;
                    }
                }
            }
        }

        // if current time is still > end time, we couldn't start a new interpolation so return.

        if (time > interpolation_end_time)
        {
            return(false);
        }

        // we are in a valid interpolation, calculate t by looking at current time
        // relative to interpolation start/end times and perform the interpolation.

        float t = (float)Clamp((time - interpolation_start_time) / (interpolation_end_time - interpolation_start_time), 0.0, 1.0);

        JitterBufferEntry a = GetEntry((uint)(interpolation_start_frame));
        JitterBufferEntry b = GetEntry((uint)(interpolation_end_frame));

        for (int i = 0; i < a.numAvatarStates; ++i)
        {
            for (int j = 0; j < b.numAvatarStates; ++j)
            {
                if (a.avatarState[i].client_index == b.avatarState[j].client_index)
                {
                    AvatarState.Interpolate(ref a.avatarState[i], ref b.avatarState[j], out output[numOutputAvatarStates], t);
                    numOutputAvatarStates++;
                }
            }
        }

        resetSequence = a.packetHeader.resetSequence;

        return(true);
    }