Skip to content

Commit 9bf4b39

Browse files
committed
fix(ghosts): correct replay rendering and sampling
1 parent 02dc5ca commit 9bf4b39

28 files changed

Lines changed: 1542 additions & 737 deletions

Ghosting/Ghosts/GhostBase.cs

Lines changed: 233 additions & 94 deletions
Large diffs are not rendered by default.

Ghosting/Ghosts/GhostFrameSearch.cs

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,139 @@
33

44
namespace TNRD.Zeepkist.GTR.Ghosting.Ghosts;
55

6+
internal readonly struct GhostFrameSample
7+
{
8+
internal GhostFrameSample(int currentIndex, int nextIndex, float interpolation)
9+
{
10+
CurrentIndex = currentIndex;
11+
NextIndex = nextIndex;
12+
Interpolation = interpolation;
13+
}
14+
15+
internal int CurrentIndex { get; }
16+
internal int NextIndex { get; }
17+
internal float Interpolation { get; }
18+
}
19+
620
internal static class GhostFrameSearch
721
{
22+
internal static bool TryGetFrameSample(
23+
int frameCount,
24+
float time,
25+
Func<int, float> getTimeAtIndex,
26+
out GhostFrameSample sample)
27+
{
28+
sample = default;
29+
if (frameCount <= 0)
30+
return false;
31+
32+
if (frameCount == 1 || time < getTimeAtIndex(0))
33+
{
34+
sample = new GhostFrameSample(0, 0, 0f);
35+
return true;
36+
}
37+
38+
int currentIndex = FindLastFrameIndexAtOrBeforeTime(frameCount, time, getTimeAtIndex);
39+
if (currentIndex < 0)
40+
{
41+
sample = new GhostFrameSample(0, 0, 0f);
42+
return true;
43+
}
44+
45+
if (currentIndex >= frameCount - 1)
46+
{
47+
int lastIndex = frameCount - 1;
48+
sample = new GhostFrameSample(lastIndex, lastIndex, 0f);
49+
return true;
50+
}
51+
52+
int nextIndex = currentIndex + 1;
53+
float currentTime = getTimeAtIndex(currentIndex);
54+
float nextTime = getTimeAtIndex(nextIndex);
55+
float interpolation = nextTime > currentTime
56+
? (time - currentTime) / (nextTime - currentTime)
57+
: 0f;
58+
if (interpolation < 0f)
59+
interpolation = 0f;
60+
else if (interpolation > 1f)
61+
interpolation = 1f;
62+
63+
sample = new GhostFrameSample(currentIndex, nextIndex, interpolation);
64+
return true;
65+
}
66+
67+
internal static bool TryGetFrameSample(
68+
int frameCount,
69+
float time,
70+
int currentIndexHint,
71+
Func<int, float> getTimeAtIndex,
72+
out GhostFrameSample sample)
73+
{
74+
sample = default;
75+
if (frameCount <= 0)
76+
return false;
77+
78+
if (currentIndexHint < 0 || currentIndexHint >= frameCount)
79+
return TryGetFrameSample(frameCount, time, getTimeAtIndex, out sample);
80+
81+
float currentTime = getTimeAtIndex(currentIndexHint);
82+
if (time < currentTime)
83+
{
84+
if (currentIndexHint == 0)
85+
{
86+
sample = new GhostFrameSample(0, 0, 0f);
87+
return true;
88+
}
89+
90+
return TryGetFrameSample(frameCount, time, getTimeAtIndex, out sample);
91+
}
92+
93+
int currentIndex = currentIndexHint;
94+
while (currentIndex < frameCount - 1)
95+
{
96+
int nextIndex = currentIndex + 1;
97+
float nextTime = getTimeAtIndex(nextIndex);
98+
if (nextTime > time)
99+
{
100+
float interpolation = nextTime > currentTime
101+
? (time - currentTime) / (nextTime - currentTime)
102+
: 0f;
103+
if (interpolation < 0f)
104+
interpolation = 0f;
105+
else if (interpolation > 1f)
106+
interpolation = 1f;
107+
108+
sample = new GhostFrameSample(currentIndex, nextIndex, interpolation);
109+
return true;
110+
}
111+
112+
currentIndex++;
113+
currentTime = nextTime;
114+
}
115+
116+
sample = new GhostFrameSample(currentIndex, currentIndex, 0f);
117+
return true;
118+
}
119+
120+
private static int FindLastFrameIndexAtOrBeforeTime(
121+
int frameCount,
122+
float time,
123+
Func<int, float> getTimeAtIndex)
124+
{
125+
int low = 0;
126+
int high = frameCount;
127+
while (low < high)
128+
{
129+
int mid = (low + high) >> 1;
130+
if (getTimeAtIndex(mid) <= time)
131+
low = mid + 1;
132+
else
133+
high = mid;
134+
}
135+
136+
return low - 1;
137+
}
138+
8139
internal static int FindFirstFrameIndexAtOrAfterTime(
9140
int frameCount,
10141
float time,
@@ -41,7 +172,7 @@ internal static bool TryGetAdjacentFrameTime(
41172
out float adjacentTime)
42173
{
43174
adjacentTime = 0f;
44-
if (frameCount <= 0 || direction is not (1 or -1))
175+
if (frameCount <= 1 || direction is not (1 or -1))
45176
return false;
46177

47178
if (direction > 0)

Ghosting/Ghosts/IGhost.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ public interface IGhost
77
{
88
Color Color { get; }
99
float Duration { get; }
10-
void Initialize(GhostData ghost);
10+
void Initialize(
11+
GhostData ghost,
12+
BulkGhostModeState bulkModeState,
13+
GhostTimingService timingService);
1114
void ApplyCosmetics(string steamName);
12-
void Start();
13-
void Stop();
15+
void Start(float time);
16+
void Stop(float time);
1417
void Seek(float time);
15-
void Update();
16-
void FixedUpdate();
18+
void Pause(float time);
19+
void Resume(float time);
20+
void Sample(float time);
1721
}

Ghosting/Ghosts/V1Ghost.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ public partial class V1Ghost : GhostBase
1111
{
1212
private readonly List<Frame> _frames;
1313

14-
public V1Ghost(GhostTimingService timingService, BulkGhostModeState bulkModeState, List<Frame> frames)
15-
: base(timingService, bulkModeState)
14+
public V1Ghost(List<Frame> frames)
1615
{
1716
_frames = frames;
1817
}

Ghosting/Ghosts/V2Ghost.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ public partial class V2Ghost : GhostBase
1515
private readonly List<Frame> _frames;
1616

1717
public V2Ghost(
18-
GhostTimingService timingService,
19-
BulkGhostModeState bulkModeState,
2018
ulong steamId,
2119
int soapboxId,
2220
int hatId,
2321
int colorId,
24-
List<Frame> frames) : base(timingService, bulkModeState)
22+
List<Frame> frames)
2523
{
2624
_steamId = steamId;
2725
_soapboxId = soapboxId;

Ghosting/Ghosts/V3Ghost.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ public partial class V3Ghost : GhostBase, IGhostInputProvider
1515
private readonly List<Frame> _frames;
1616

1717
public V3Ghost(
18-
GhostTimingService timingService,
19-
BulkGhostModeState bulkModeState,
2018
ulong steamId,
2119
int soapboxId,
2220
int hatId,
2321
int colorId,
24-
List<Frame> frames) : base(timingService, bulkModeState)
22+
List<Frame> frames)
2523
{
2624
_steamId = steamId;
2725
_soapboxId = soapboxId;

Ghosting/Ghosts/V4Ghost.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ public partial class V4Ghost : GhostBase, IGhostInputProvider
1515
private readonly List<Frame> _frames;
1616

1717
public V4Ghost(
18-
GhostTimingService timingService,
19-
BulkGhostModeState bulkModeState,
2018
ulong steamId,
2119
int soapboxId,
2220
int hatId,
2321
int colorId,
24-
List<Frame> frames) : base(timingService, bulkModeState)
22+
List<Frame> frames)
2523
{
2624
_steamId = steamId;
2725
_soapboxId = soapboxId;

0 commit comments

Comments
 (0)