acdream/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs
Erik 1d2f2f738f
All checks were successful
CI / linux-portable (push) Successful in 3m32s
CI / windows-gate (push) Successful in 6m55s
CI / release (push) Successful in 2m12s
fix #451: stabilize portal seam rendering
2026-08-27 14:30:21 +02:00

825 lines
30 KiB
C#

using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Scene.Arch;
using AcDream.App.Rendering.Wb;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
public sealed class RenderScenePViewFrameProductTests
{
private const uint Landblock = 0xA9B4FFFF;
private const uint Cell = 0xA9B40170;
private static readonly RenderSceneGeneration Generation =
RenderSceneGeneration.FromRaw(1);
[Fact]
public void Builder_PreservesPViewRoutesAndExcludesShellsAndWithdrawnOwners()
{
using var scene = new ArchRenderScene(Generation);
RenderProjectionRecord outdoor = Record(
0x0100_0000_0000_0001,
RenderProjectionClass.OutdoorStatic);
RenderProjectionRecord buildingShell = Record(
0x0100_0000_0000_0008,
RenderProjectionClass.OutdoorStatic,
isBuildingShell: true);
RenderProjectionRecord hidden = Record(
0x0100_0000_0000_0002,
RenderProjectionClass.OutdoorStatic,
flags: RenderProjectionFlags.Hidden
| RenderProjectionFlags.SpatiallyResident);
RenderProjectionRecord withdrawn = Record(
0x0100_0000_0000_0003,
RenderProjectionClass.OutdoorStatic,
flags: RenderProjectionFlags.Draw);
RenderProjectionRecord shell = Record(
0x0200_0000_0000_0004,
RenderProjectionClass.IndoorCellStatic,
parentCell: Cell);
RenderProjectionRecord cellStatic = Record(
0x0100_0000_0000_0005,
RenderProjectionClass.IndoorCellStatic,
parentCell: Cell);
RenderProjectionRecord outdoorDynamic = Record(
0x0300_0000_0000_0006,
RenderProjectionClass.LiveDynamicRoot);
RenderProjectionRecord cellDynamic = Record(
0x0300_0000_0000_0007,
RenderProjectionClass.LiveDynamicRoot,
parentCell: Cell);
RenderProjectionRecord[] records =
[
outdoor,
buildingShell,
hidden,
withdrawn,
shell,
cellStatic,
outdoorDynamic,
cellDynamic,
];
var deltas = new RenderProjectionDelta[records.Length];
for (int index = 0; index < records.Length; index++)
{
deltas[index] = RenderProjectionDelta.Register(
Generation,
(ulong)index + 1,
records[index]);
}
scene.Apply(deltas);
PortalVisibilityFrame portal = Portal(Cell);
ClipFrameAssembly clip = FullScreenClip(Cell);
ViewconeCuller viewcone =
ViewconeCuller.Build(clip, Matrix4x4.Identity);
var exchange = new RenderFrameExchange();
var builder = new RenderScenePViewFrameBuilder();
RenderSceneDigest digest =
scene.BuildDigest(new RenderSceneDigestBuffer());
var input = new RenderScenePViewBuildInput(
scene.OpenQuery(),
digest,
portal,
clip,
viewcone,
[],
[Cell],
EmptyCellSource.Instance,
[],
RootIsOutdoor: true);
builder.Build(exchange, frameSequence: 1, in input);
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
try
{
Assert.Equal(
[outdoor.Id, hidden.Id, buildingShell.Id],
view.OutdoorStaticCandidates.ToArray()
.Select(static item => item.Id));
Assert.Equal(
[cellStatic.Id],
view.CellStaticCandidates.ToArray()
.Select(static item => item.Id));
Assert.Equal(
[outdoorDynamic.Id, cellDynamic.Id],
view.DynamicCandidates.ToArray()
.Select(static item => item.Id));
Assert.Equal(6, view.Transforms.Length);
Assert.Equal(6, view.RouteCandidates.Length);
Assert.Equal(3, view.RouteRanges.Length);
RenderFrameCandidateRange outdoorRange = Assert.Single(
view.RouteRanges.ToArray(),
range => range.Route
== RenderFrameCandidateRoute.LandscapeOutdoorStatic);
Assert.Equal(3, outdoorRange.Count);
Assert.Contains(
view.RouteCandidates.Slice(
outdoorRange.Offset,
outdoorRange.Count).ToArray(),
item => item.Id == buildingShell.Id);
Assert.DoesNotContain(
view.RouteRanges.ToArray(),
range => range.Route
== RenderFrameCandidateRoute.LandscapeBuildingShell);
Assert.DoesNotContain(
view.RouteCandidates.ToArray(),
item => item.Id == withdrawn.Id || item.Id == shell.Id);
Assert.Equal(
new RenderFrameDiagnosticCounts(
OutdoorStaticCandidates: 3,
CellStaticCandidates: 1,
DynamicCandidates: 2,
TransformCount: 6,
OpaqueClassificationCount: 0,
AlphaClassificationCount: 0,
LightSetCount: 0,
SelectionPartCount: 0,
RouteCandidateCount: 6,
EntityCandidateCount: 6,
MeshPartCount: 6),
view.DiagnosticCounts);
Assert.Equal(6, view.EntityCandidates.Length);
Assert.Equal(6, view.MeshParts.Length);
Assert.Same(portal, view.PortalFrame);
Assert.Same(clip, view.ClipAssembly);
Assert.Equal(digest, view.SourceDigest);
}
finally
{
exchange.Release(in view);
}
}
[Fact]
public void Builder_OrdersMultiSliceBuildingShellsAfterLookIns()
{
using var scene = new ArchRenderScene(Generation);
RenderProjectionRecord outdoor = Record(
0x0100_0000_0000_0101,
RenderProjectionClass.OutdoorStatic);
RenderProjectionRecord buildingShell = Record(
0x0100_0000_0000_0102,
RenderProjectionClass.OutdoorStatic,
isBuildingShell: true,
buildingShellAnchorCellId: Cell);
RenderProjectionRecord lookInObject = Record(
0x0100_0000_0000_0103,
RenderProjectionClass.IndoorCellStatic,
parentCell: Cell);
scene.Apply(
[
RenderProjectionDelta.Register(Generation, 1, outdoor),
RenderProjectionDelta.Register(Generation, 2, buildingShell),
RenderProjectionDelta.Register(Generation, 3, lookInObject),
]);
PortalVisibilityFrame portal = Portal(Cell);
PortalVisibilityFrame lookIn = Portal(Cell);
lookIn.SourceBuildingKey = 2u;
lookIn.SourceBuildingLandblockId = Cell & 0xFFFF0000u;
var anchorCell = new LoadedCell
{
CellId = Cell,
BuildingId = 2u,
};
ClipFrameAssembly clip = TwoSliceClip(Cell);
clip.LookInCellToViewSlices[new LookInClipCell(0, Cell)] =
[clip.CellIdToViewSlices[Cell][0]];
ViewconeCuller viewcone =
ViewconeCuller.Build(clip, Matrix4x4.Identity);
var exchange = new RenderFrameExchange();
var builder = new RenderScenePViewFrameBuilder();
RenderSceneDigest digest =
scene.BuildDigest(new RenderSceneDigestBuffer());
var input = new RenderScenePViewBuildInput(
scene.OpenQuery(),
digest,
portal,
clip,
viewcone,
[lookIn],
[Cell],
new DictionaryCellSource(anchorCell),
[],
RootIsOutdoor: true);
builder.Build(exchange, frameSequence: 1, in input);
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
try
{
Assert.Equal(
[
(RenderFrameCandidateRoute.LandscapeOutdoorStatic, 0, 0u),
(RenderFrameCandidateRoute.LandscapeOutdoorStatic, 1, 0u),
(RenderFrameCandidateRoute.LookInObject, 0, Cell),
(RenderFrameCandidateRoute.LandscapeBuildingShell, 0, 0u),
(RenderFrameCandidateRoute.LandscapeBuildingShell, 1, 0u),
(RenderFrameCandidateRoute.CellStatic, 0, 0u),
],
view.RouteRanges.ToArray().Select(static range =>
(range.Route, range.RouteIndex, range.CellId)));
}
finally
{
exchange.Release(in view);
}
}
[Fact]
public void Builder_InterleavesEachLookInWithItsOwnPackedBuildingShellRoutes()
{
const uint secondCell = 0xA9B40180u;
using var scene = new ArchRenderScene(Generation);
RenderProjectionRecord firstShell = Record(
0x0100_0000_0000_0201,
RenderProjectionClass.OutdoorStatic,
isBuildingShell: true,
buildingShellAnchorCellId: Cell);
RenderProjectionRecord secondShell = Record(
0x0100_0000_0000_0202,
RenderProjectionClass.OutdoorStatic,
isBuildingShell: true,
buildingShellAnchorCellId: secondCell);
RenderProjectionRecord firstObject = Record(
0x0100_0000_0000_0203,
RenderProjectionClass.IndoorCellStatic,
parentCell: Cell);
RenderProjectionRecord secondObject = Record(
0x0100_0000_0000_0204,
RenderProjectionClass.IndoorCellStatic,
parentCell: secondCell);
scene.Apply(
[
RenderProjectionDelta.Register(Generation, 1, firstShell),
RenderProjectionDelta.Register(Generation, 2, secondShell),
RenderProjectionDelta.Register(Generation, 3, firstObject),
RenderProjectionDelta.Register(Generation, 4, secondObject),
]);
PortalVisibilityFrame portal = Portal(Cell);
PortalVisibilityFrame firstLookIn = Portal(Cell);
firstLookIn.SourceBuildingKey = 2u;
firstLookIn.SourceBuildingLandblockId = Cell & 0xFFFF0000u;
PortalVisibilityFrame secondLookIn = Portal(secondCell);
secondLookIn.SourceBuildingKey = 3u;
secondLookIn.SourceBuildingLandblockId = secondCell & 0xFFFF0000u;
var firstAnchor = new LoadedCell
{
CellId = Cell,
BuildingId = 2u,
};
var secondAnchor = new LoadedCell
{
CellId = secondCell,
BuildingId = 3u,
};
ClipFrameAssembly clip = TwoSliceClip(Cell);
clip.LookInCellToViewSlices[new LookInClipCell(0, Cell)] =
[clip.CellIdToViewSlices[Cell][0]];
clip.LookInCellToViewSlices[new LookInClipCell(1, secondCell)] =
[clip.CellIdToViewSlices[Cell][0]];
ViewconeCuller viewcone =
ViewconeCuller.Build(clip, Matrix4x4.Identity);
var exchange = new RenderFrameExchange();
var builder = new RenderScenePViewFrameBuilder();
RenderSceneDigest digest =
scene.BuildDigest(new RenderSceneDigestBuffer());
var input = new RenderScenePViewBuildInput(
scene.OpenQuery(),
digest,
portal,
clip,
viewcone,
[firstLookIn, secondLookIn],
[Cell],
new DictionaryCellSource(firstAnchor, secondAnchor),
[],
RootIsOutdoor: true);
builder.Build(exchange, frameSequence: 1, in input);
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
try
{
Assert.Equal(
[
(RenderFrameCandidateRoute.LookInObject, 0, Cell),
(RenderFrameCandidateRoute.LandscapeBuildingShell, 0, 0u),
(RenderFrameCandidateRoute.LandscapeBuildingShell, 1, 0u),
(RenderFrameCandidateRoute.LookInObject, 1, secondCell),
(RenderFrameCandidateRoute.LandscapeBuildingShell, 2, 0u),
(RenderFrameCandidateRoute.LandscapeBuildingShell, 3, 0u),
],
view.RouteRanges.ToArray()
.Where(static range =>
range.Route is RenderFrameCandidateRoute.LookInObject
or RenderFrameCandidateRoute.LandscapeBuildingShell)
.Select(static range =>
(range.Route, range.RouteIndex, range.CellId)));
}
finally
{
exchange.Release(in view);
}
}
[Fact]
public void Builder_ReusesOrderedIndicesWhileRefreshingDirtyRecords()
{
using var scene = new ArchRenderScene(Generation);
RenderProjectionRecord later = Record(
0x0100_0000_0000_0012,
RenderProjectionClass.OutdoorStatic);
RenderProjectionRecord earlier = Record(
0x0100_0000_0000_0011,
RenderProjectionClass.OutdoorStatic);
scene.Apply(
[
RenderProjectionDelta.Register(Generation, 1, later),
RenderProjectionDelta.Register(Generation, 2, earlier),
]);
PortalVisibilityFrame portal = Portal(Cell);
ClipFrameAssembly clip = FullScreenClip(Cell);
ViewconeCuller viewcone =
ViewconeCuller.Build(clip, Matrix4x4.Identity);
var exchange = new RenderFrameExchange();
var builder = new RenderScenePViewFrameBuilder();
Build(frameSequence: 1);
RenderFrameView first = exchange.BorrowLatest(Generation, 1);
try
{
Assert.Equal(
[earlier.Id, later.Id],
first.OutdoorStaticCandidates.ToArray()
.Select(static record => record.Id));
}
finally
{
exchange.Release(in first);
}
scene.ClearDirty();
ulong retainedRevision = scene.OpenQuery().IndexRevision;
Matrix4x4 movedTransform =
Matrix4x4.CreateTranslation(0.25f, 0, 0);
RenderProjectionRecord moved = later with
{
Transform = new RenderTransform(movedTransform),
PreviousTransform =
new PreviousRenderTransform(
later.Transform.LocalToWorld),
Bounds = new RenderWorldBounds(
new Vector3(-0.75f, -1, -1),
new Vector3(1.25f, 1, 1)),
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
Generation,
3,
moved),
]);
Assert.Equal(
retainedRevision,
scene.OpenQuery().IndexRevision);
Build(frameSequence: 2);
RenderFrameView second = exchange.BorrowLatest(Generation, 2);
try
{
RenderProjectionRecord projected =
second.EntityCandidates.ToArray()
.Single(candidate =>
candidate.Projection.Id == later.Id)
.Projection;
Assert.Equal(
movedTransform,
projected.Transform.LocalToWorld);
Assert.Equal(
[earlier.Id, later.Id],
second.OutdoorStaticCandidates.ToArray()
.Select(static record => record.Id));
}
finally
{
exchange.Release(in second);
}
scene.ClearDirty();
RenderProjectionRecord reordered = moved with
{
SortKey = new RenderSortKey(0),
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
Generation,
4,
reordered),
]);
Assert.True(
scene.OpenQuery().IndexRevision > retainedRevision);
Build(frameSequence: 3);
RenderFrameView third = exchange.BorrowLatest(Generation, 3);
try
{
Assert.Equal(
[later.Id, earlier.Id],
third.OutdoorStaticCandidates.ToArray()
.Select(static record => record.Id));
}
finally
{
exchange.Release(in third);
}
void Build(ulong frameSequence)
{
RenderSceneQuery query = scene.OpenQuery();
var input = new RenderScenePViewBuildInput(
query,
new RenderSceneDigest(
query.Generation,
query.Counts,
default),
portal,
clip,
viewcone,
[],
[Cell],
EmptyCellSource.Instance,
[],
RootIsOutdoor: true);
builder.Build(exchange, frameSequence, in input);
}
}
[Fact]
public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate()
{
WorldEntity outdoor = Entity(10, serverGuid: 0, parentCell: null);
WorldEntity cellStatic = Entity(11, serverGuid: 0, parentCell: Cell);
WorldEntity outdoorDynamic =
Entity(1_000_012, 0x8000_0012, parentCell: null);
WorldEntity cellDynamic =
Entity(1_000_013, 0x8000_0013, parentCell: Cell);
WorldEntity[] entities =
[outdoor, cellStatic, outdoorDynamic, cellDynamic];
var oracle = new CurrentRenderSceneOracle();
var partition = new InteriorEntityPartition.Result();
InteriorEntityPartition.Partition(
partition,
[Cell],
[Entry(entities)],
oracle);
oracle.BeginPViewFrame();
oracle.ObservePViewBucket(
CurrentRenderPViewRoute.LandscapeOutdoorStatic,
0,
0,
[outdoor]);
oracle.ObservePViewBucket(
CurrentRenderPViewRoute.CellStatic,
0,
0,
[cellStatic]);
oracle.ObservePViewBucket(
CurrentRenderPViewRoute.DynamicLast,
0,
0,
[outdoorDynamic, cellDynamic]);
oracle.CompletePViewFrame();
oracle.BeginDispatcherFrame();
oracle.ObserveDispatcherDraw(
WbDrawDispatcher.EntitySet.All,
entitiesWalked: 1,
[(outdoor, 0, Landblock)]);
oracle.ObserveDispatcherDraw(
WbDrawDispatcher.EntitySet.All,
entitiesWalked: 1,
[(cellStatic, 0, Landblock)]);
oracle.ObserveDispatcherDraw(
WbDrawDispatcher.EntitySet.All,
entitiesWalked: 2,
[
(outdoorDynamic, 0, Landblock),
(cellDynamic, 0, Landblock),
]);
using var shadow = new RenderSceneShadowRuntime(Generation);
foreach (WorldEntity entity in entities)
{
RenderProjectionRecord record = Project(entity);
shadow.Journal.Register(in record);
}
shadow.DrainUpdateBoundary();
var logs = new List<string>();
var controller = new RenderScenePViewFrameProductController(
shadow,
oracle,
logs.Add);
PortalVisibilityFrame portal = Portal(Cell);
ClipFrameAssembly clip = FullScreenClip(Cell);
ViewconeCuller viewcone =
ViewconeCuller.Build(clip, Matrix4x4.Identity);
controller.BuildAndCompare(
portal,
clip,
viewcone,
[],
[Cell],
EmptyCellSource.Instance,
[],
Landblock,
rootIsOutdoor: true);
RenderFrameProductComparisonSnapshot matched = controller.Snapshot;
Assert.True(matched.Enabled);
Assert.Equal(1uL, matched.ComparisonCount);
Assert.Equal(1uL, matched.SuccessfulComparisonCount);
Assert.Equal(4, matched.ExpectedCandidateCount);
Assert.Equal(4, matched.ActualCandidateCount);
Assert.Equal(0, matched.MismatchCount);
Assert.Null(matched.FirstMismatch);
Assert.Equal(matched.ExpectedDigest, matched.ActualDigest);
Assert.Equal(1uL, matched.PackedInputComparisonCount);
Assert.Equal(1uL, matched.PackedInputSuccessfulComparisonCount);
Assert.Equal(4, matched.PackedInputExpectedCount);
Assert.Equal(4, matched.PackedInputActualCount);
Assert.Equal(0, matched.PackedInputMismatchCount);
Assert.Null(matched.PackedInputFirstMismatch);
Assert.Equal(
matched.PackedInputExpectedDigest,
matched.PackedInputActualDigest);
Assert.Empty(logs);
RenderProjectionId withdrawnId =
LiveRenderProjectionJournal.ProjectionId(cellDynamic.Id);
shadow.Journal.Unregister(
withdrawnId,
RenderOwnerIncarnation.FromRaw(cellDynamic.Id));
shadow.DrainUpdateBoundary();
controller.BuildAndCompare(
portal,
clip,
viewcone,
[],
[Cell],
EmptyCellSource.Instance,
[],
Landblock,
rootIsOutdoor: true);
RenderFrameProductComparisonSnapshot mismatch = controller.Snapshot;
Assert.Equal(2uL, mismatch.ComparisonCount);
Assert.Equal(1uL, mismatch.SuccessfulComparisonCount);
Assert.Equal(1, mismatch.MismatchCount);
Assert.Contains("field=count expected=4 actual=3", mismatch.FirstMismatch);
Assert.Equal(1, mismatch.PackedInputMismatchCount);
Assert.Contains(
"field=count expected=4 actual=3",
mismatch.PackedInputFirstMismatch);
Assert.Equal(2, logs.Count);
}
[Fact]
public void Controller_RejectsEqualMembershipInDifferentTraversalOrder()
{
WorldEntity first =
Entity(1_000_001, 0x8000_0001, parentCell: null);
WorldEntity second =
Entity(1_000_002, 0x8000_0002, parentCell: null);
var oracle = new CurrentRenderSceneOracle();
var partition = new InteriorEntityPartition.Result();
InteriorEntityPartition.Partition(
partition,
[],
[Entry([first, second])],
oracle);
oracle.BeginPViewFrame();
oracle.ObservePViewBucket(
CurrentRenderPViewRoute.DynamicLast,
0,
0,
[first, second]);
oracle.CompletePViewFrame();
oracle.BeginDispatcherFrame();
oracle.ObserveDispatcherDraw(
WbDrawDispatcher.EntitySet.All,
entitiesWalked: 2,
[
(first, 0, Landblock),
(second, 0, Landblock),
]);
using var shadow = new RenderSceneShadowRuntime(Generation);
RenderProjectionRecord firstRecord = Project(first) with
{
SortKey = new RenderSortKey(2),
};
RenderProjectionRecord secondRecord = Project(second) with
{
SortKey = new RenderSortKey(1),
};
shadow.Journal.Register(in firstRecord);
shadow.Journal.Register(in secondRecord);
shadow.DrainUpdateBoundary();
var controller = new RenderScenePViewFrameProductController(
shadow,
oracle);
PortalVisibilityFrame portal = Portal(Cell);
ClipFrameAssembly clip = FullScreenClip(Cell);
ViewconeCuller viewcone =
ViewconeCuller.Build(clip, Matrix4x4.Identity);
controller.BuildAndCompare(
portal,
clip,
viewcone,
[],
[],
EmptyCellSource.Instance,
[],
Landblock,
rootIsOutdoor: true);
RenderFrameProductComparisonSnapshot snapshot =
controller.Snapshot;
Assert.Equal(1, snapshot.MismatchCount);
Assert.Contains(
$"ProjectionId = {LiveRenderProjectionJournal.ProjectionId(first.Id)}",
snapshot.FirstMismatch);
Assert.Contains(
$"ProjectionId = {LiveRenderProjectionJournal.ProjectionId(second.Id)}",
snapshot.FirstMismatch);
Assert.Equal(1, snapshot.PackedInputMismatchCount);
}
private static RenderProjectionRecord Project(WorldEntity entity)
{
bool dynamic = entity.ServerGuid != 0;
RenderProjectionId id = dynamic
? LiveRenderProjectionJournal.ProjectionId(entity.Id)
: StaticRenderProjectionJournal.StaticEntityId(
Landblock,
entity.Id);
RenderProjectionClass projectionClass = dynamic
? RenderProjectionClass.LiveDynamicRoot
: entity.ParentCellId is uint parentCell
&& InteriorEntityPartition.IsIndoorCellId(parentCell)
? RenderProjectionClass.IndoorCellStatic
: RenderProjectionClass.OutdoorStatic;
return RenderProjectionRecordFactory.ProjectEntity(
id,
projectionClass,
RenderOwnerIncarnation.FromRaw(entity.Id),
Landblock,
entity.ParentCellId ?? Landblock,
entity,
spatiallyVisible: true);
}
private static RenderProjectionRecord Record(
ulong id,
RenderProjectionClass projectionClass,
uint? parentCell = null,
RenderProjectionFlags flags =
RenderProjectionFlags.Draw
| RenderProjectionFlags.SpatiallyResident,
bool isBuildingShell = false,
uint buildingShellAnchorCellId = 0)
{
uint fullCellId = parentCell ?? Landblock;
Matrix4x4 transform = Matrix4x4.Identity;
return new RenderProjectionRecord(
RenderProjectionId.FromRaw(id),
projectionClass,
RenderOwnerIncarnation.FromRaw(id),
new RenderTransform(transform),
new PreviousRenderTransform(transform),
new RenderMeshSet(RenderAssetHandle.FromRaw(id), 1, 1),
new RenderMaterialVariant(0, 0, 1),
new RenderSpatialResidency(
RenderSpatialBucket.FromRaw(fullCellId),
Landblock,
fullCellId),
new RenderWorldBounds(
new Vector3(-1),
new Vector3(1)),
flags,
default,
new RenderSortKey(id),
RenderDirtyMask.All,
new RenderSourceMetadata(
LocalEntityId: (uint)id,
ServerGuid: projectionClass
is RenderProjectionClass.LiveDynamicRoot ? (uint)id : 0,
SourceId: 1,
ParentCellId: parentCell ?? 0,
EffectCellId: 0,
BuildingShellAnchorCellId: buildingShellAnchorCellId,
TransformFingerprint: default,
GeometryFingerprint: default,
AppearanceFingerprint: default),
new RenderEntityPayload(
[new MeshRef((uint)id, Matrix4x4.Identity)],
PaletteOverride: null,
IsBuildingShell: isBuildingShell));
}
private static WorldEntity Entity(
uint id,
uint serverGuid,
uint? parentCell) =>
new()
{
Id = id,
ServerGuid = serverGuid,
SourceGfxObjOrSetupId = 0x0200_0001,
Position = Vector3.Zero,
Rotation = Quaternion.Identity,
MeshRefs =
[
new MeshRef(0x0100_0001, Matrix4x4.Identity),
],
ParentCellId = parentCell,
};
private static PortalVisibilityFrame Portal(uint cellId)
{
var frame = new PortalVisibilityFrame();
frame.OrderedVisibleCells.Add(cellId);
return frame;
}
private static ClipFrameAssembly FullScreenClip(uint cellId)
{
var slice = new ClipViewSlice(
Slot: 0,
NdcAabb: new Vector4(-1, -1, 1, 1),
Planes: []);
var assembly = new ClipFrameAssembly();
assembly.SetOutsideViewSlices([slice]);
assembly.CellIdToViewSlices[cellId] = [slice];
return assembly;
}
private static ClipFrameAssembly TwoSliceClip(uint cellId)
{
var first = new ClipViewSlice(
Slot: 0,
NdcAabb: new Vector4(-1, -1, 0, 1),
Planes: []);
var second = new ClipViewSlice(
Slot: 1,
NdcAabb: new Vector4(0, -1, 1, 1),
Planes: []);
var assembly = new ClipFrameAssembly();
assembly.SetOutsideViewSlices([first, second]);
assembly.CellIdToViewSlices[cellId] = [first];
return assembly;
}
private static (
uint LandblockId,
Vector3 AabbMin,
Vector3 AabbMax,
IReadOnlyList<WorldEntity> Entities,
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)
Entry(IReadOnlyList<WorldEntity> entities) =>
(
Landblock,
Vector3.Zero,
Vector3.One,
entities,
null);
private sealed class EmptyCellSource : IRetailPViewCellSource
{
public static EmptyCellSource Instance { get; } = new();
private EmptyCellSource()
{
}
public LoadedCell? Find(uint cellId) => null;
}
private sealed class DictionaryCellSource : IRetailPViewCellSource
{
private readonly Dictionary<uint, LoadedCell> _cells;
public DictionaryCellSource(params LoadedCell[] cells) =>
_cells = cells.ToDictionary(static cell => cell.CellId);
public LoadedCell? Find(uint cellId) =>
_cells.TryGetValue(cellId, out LoadedCell? cell) ? cell : null;
}
}