feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -163,11 +163,13 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
private int _mutationDepth;
|
||||
private long _visibilityCommitCount;
|
||||
private ulong _flatViewGeneration;
|
||||
private ulong _residentWindowRevision;
|
||||
private bool _flatMembershipDirty;
|
||||
|
||||
public IReadOnlyList<WorldEntity> Entities => _flatEntities;
|
||||
public ulong FlatViewGeneration => _flatViewGeneration;
|
||||
public IReadOnlyCollection<uint> LoadedLandblockIds => _loaded.Keys;
|
||||
internal ulong ResidentWindowRevision => _residentWindowRevision;
|
||||
public event Action<RuntimeEntityKey, bool>? LiveProjectionVisibilityChanged;
|
||||
|
||||
public bool IsLoaded(uint landblockId) => _loaded.ContainsKey(landblockId);
|
||||
|
|
@ -428,6 +430,7 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
throw new InvalidOperationException(
|
||||
$"Landblock 0x{landblockId:X8} already owns a render traversal slot.");
|
||||
}
|
||||
_residentWindowRevision = checked(_residentWindowRevision + 1);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: true);
|
||||
}
|
||||
|
||||
|
|
@ -449,10 +452,81 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
|
||||
_renderTraversalLandblockSlots[slot] = 0;
|
||||
_freeRenderTraversalLandblockSlots.Push(slot);
|
||||
_residentWindowRevision = checked(_residentWindowRevision + 1);
|
||||
InvalidateLandblockRenderViews(entries: true, bounds: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures the largest complete, actually-published square around the
|
||||
/// current render centre. This is a presentation-only read: it neither
|
||||
/// consults the desired <see cref="StreamingRegion"/> nor requests missing
|
||||
/// landblocks. Hysteresis-retained cells outside a gap cannot inflate the
|
||||
/// result, which is what makes it safe during recenter and portal turnover.
|
||||
/// </summary>
|
||||
internal ResidentStreamingWindowFact CaptureResidentStreamingWindow(
|
||||
int centerX,
|
||||
int centerY)
|
||||
{
|
||||
if ((uint)centerX > byte.MaxValue || (uint)centerY > byte.MaxValue)
|
||||
return ResidentStreamingWindowFact.Unavailable(_residentWindowRevision);
|
||||
|
||||
uint center = StreamingRegion.EncodeLandblockId(centerX, centerY);
|
||||
if (!_loaded.ContainsKey(center))
|
||||
return ResidentStreamingWindowFact.Unavailable(_residentWindowRevision);
|
||||
|
||||
int maximumCandidate = 0;
|
||||
foreach (uint landblockId in _loaded.Keys)
|
||||
{
|
||||
int x = (int)((landblockId >> 24) & 0xFFu);
|
||||
int y = (int)((landblockId >> 16) & 0xFFu);
|
||||
maximumCandidate = Math.Max(
|
||||
maximumCandidate,
|
||||
Math.Max(Math.Abs(x - centerX), Math.Abs(y - centerY)));
|
||||
}
|
||||
|
||||
int completeRadius = 0;
|
||||
for (int radius = 1; radius <= maximumCandidate; radius++)
|
||||
{
|
||||
if (!ContainsCompleteRing(radius))
|
||||
break;
|
||||
completeRadius = radius;
|
||||
}
|
||||
|
||||
return new ResidentStreamingWindowFact(
|
||||
_residentWindowRevision,
|
||||
centerX,
|
||||
centerY,
|
||||
completeRadius,
|
||||
_loaded.Count,
|
||||
HasPublishedCenter: true);
|
||||
|
||||
bool ContainsCompleteRing(int radius)
|
||||
{
|
||||
int minX = Math.Max(0, centerX - radius);
|
||||
int maxX = Math.Min(byte.MaxValue, centerX + radius);
|
||||
int minY = Math.Max(0, centerY - radius);
|
||||
int maxY = Math.Min(byte.MaxValue, centerY + radius);
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
{
|
||||
if (!_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(x, minY))
|
||||
|| !_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(x, maxY)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int y = minY + 1; y < maxY; y++)
|
||||
{
|
||||
if (!_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(minX, y))
|
||||
|| !_loaded.ContainsKey(StreamingRegion.EncodeLandblockId(maxX, y)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Total live entities currently parked in the pending bucket waiting
|
||||
/// for their landblock to arrive. Useful diagnostic for verifying the
|
||||
|
|
@ -1482,6 +1556,8 @@ public sealed class GpuWorldState : ILiveEntitySpatialQuery
|
|||
_loadedLiveByLandblock.Clear();
|
||||
_liveProjectionByKey.Clear();
|
||||
|
||||
if (_loaded.Count != 0)
|
||||
_residentWindowRevision = checked(_residentWindowRevision + 1);
|
||||
_loaded.Clear();
|
||||
_renderTraversalLandblockSlots.Clear();
|
||||
_freeRenderTraversalLandblockSlots.Clear();
|
||||
|
|
|
|||
31
src/AcDream.App/Streaming/ResidentStreamingWindowFact.cs
Normal file
31
src/AcDream.App/Streaming/ResidentStreamingWindowFact.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
namespace AcDream.App.Streaming;
|
||||
|
||||
/// <summary>
|
||||
/// Read-only presentation fact describing the complete portion of the live
|
||||
/// two-tier landblock window. A landblock is 192 metres wide. Radius zero means
|
||||
/// only the centre is published and therefore offers no safe camera-relative
|
||||
/// cascade reach beyond that landblock.
|
||||
/// </summary>
|
||||
internal readonly record struct ResidentStreamingWindowFact(
|
||||
ulong Revision,
|
||||
int CenterX,
|
||||
int CenterY,
|
||||
int CompleteRadiusLandblocks,
|
||||
int PublishedLandblockCount,
|
||||
bool HasPublishedCenter)
|
||||
{
|
||||
internal const float LandblockSizeMeters = 192f;
|
||||
|
||||
internal float MaximumReachMeters => HasPublishedCenter
|
||||
? CompleteRadiusLandblocks * LandblockSizeMeters
|
||||
: 0f;
|
||||
|
||||
internal static ResidentStreamingWindowFact Unavailable(ulong revision) =>
|
||||
new(
|
||||
revision,
|
||||
CenterX: 0,
|
||||
CenterY: 0,
|
||||
CompleteRadiusLandblocks: 0,
|
||||
PublishedLandblockCount: 0,
|
||||
HasPublishedCenter: false);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue