feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -1,6 +1,7 @@
using System.Numerics;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Scene.Arch;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
@ -661,6 +662,429 @@ public sealed class ArchRenderSceneTests
scene.OpenQuery().IndexRevision > registeredRevision);
}
[Fact]
public void DirectionalShadowTopologyRevision_IgnoresDynamicPoseButTracksEligibilityGeometryAppearanceAndCasterIdentity()
{
RenderSceneGeneration generation = Generation(19);
using var scene = new ArchRenderScene(generation);
Matrix4x4 firstPart = Matrix4x4.CreateTranslation(1f, 2f, 3f);
RenderProjectionRecord original = Record(
19,
1,
RenderProjectionClass.LiveDynamicRoot) with
{
Source = new RenderSourceMetadata(
LocalEntityId: 19,
ServerGuid: 19,
SourceId: 19,
ParentCellId: 0,
EffectCellId: 0,
BuildingShellAnchorCellId: 0,
TransformFingerprint: new RenderSceneHash128(1, 1),
GeometryFingerprint: new RenderSceneHash128(2, 2),
AppearanceFingerprint: new RenderSceneHash128(3, 3),
DirectionalShadowTopologyFingerprint:
new RenderSceneHash128(4, 4)),
EntityPayload = new RenderEntityPayload(
[new MeshRef(0x01000001, firstPart)],
PaletteOverride: null,
IsBuildingShell: false),
};
scene.Apply([RenderProjectionDelta.Register(generation, 1, original)]);
ulong registered =
scene.OpenQuery().DirectionalShadowTopologyRevision;
Matrix4x4 movedRoot = Matrix4x4.CreateTranslation(20f, 21f, 22f);
Matrix4x4 movedPart = Matrix4x4.CreateTranslation(23f, 24f, 25f);
RenderProjectionRecord poseOnly = original with
{
Transform = new RenderTransform(movedRoot),
MeshSet = original.MeshSet with
{
Handle = Asset(999),
},
Source = original.Source with
{
TransformFingerprint = new RenderSceneHash128(10, 10),
GeometryFingerprint = new RenderSceneHash128(20, 20),
},
EntityPayload = original.EntityPayload with
{
MeshRefs = [new MeshRef(0x01000001, movedPart)],
},
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
2,
poseOnly),
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
3,
poseOnly),
]);
Assert.Equal(
registered,
scene.OpenQuery().DirectionalShadowTopologyRevision);
RenderProjectionRecord hidden = poseOnly with
{
Flags = poseOnly.Flags & ~RenderProjectionFlags.Draw,
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateFlags,
generation,
4,
hidden),
]);
ulong eligibilityChanged =
scene.OpenQuery().DirectionalShadowTopologyRevision;
Assert.True(eligibilityChanged > registered);
RenderProjectionRecord geometryChanged = hidden with
{
Source = hidden.Source with
{
DirectionalShadowTopologyFingerprint =
new RenderSceneHash128(5, 5),
},
EntityPayload = hidden.EntityPayload with
{
MeshRefs =
[
new MeshRef(0x01000002, movedPart)
{
SurfaceOverrides = new Dictionary<uint, uint>
{
[0x08000001] = 0x05000001,
},
},
],
},
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
5,
geometryChanged),
]);
ulong geometryRevision =
scene.OpenQuery().DirectionalShadowTopologyRevision;
Assert.True(geometryRevision > eligibilityChanged);
var palette = new PaletteOverride(
0x04000001,
[new PaletteOverride.SubPaletteRange(0x04000002, 1, 2)]);
RenderProjectionRecord appearanceChanged = geometryChanged with
{
Material = geometryChanged.Material with { PaletteKey = 1234 },
Source = geometryChanged.Source with
{
AppearanceFingerprint = new RenderSceneHash128(6, 6),
},
EntityPayload = geometryChanged.EntityPayload with
{
PaletteOverride = palette,
},
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
6,
appearanceChanged),
]);
ulong appearanceRevision =
scene.OpenQuery().DirectionalShadowTopologyRevision;
Assert.True(appearanceRevision > geometryRevision);
RenderProjectionRecord identityChanged = appearanceChanged with
{
EntityPayload = appearanceChanged.EntityPayload with
{
CasterIdentity = RenderCasterIdentityKind.RemotePlayer,
},
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
7,
identityChanged),
]);
Assert.True(
scene.OpenQuery().DirectionalShadowTopologyRevision
> appearanceRevision);
}
[Fact]
public void DirectionalShadowTopologyRevision_TracksRareStaticTransformChanges()
{
RenderSceneGeneration generation = Generation(20);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord original = Record(
20,
1,
RenderProjectionClass.OutdoorStatic);
scene.Apply([RenderProjectionDelta.Register(generation, 1, original)]);
ulong registered =
scene.OpenQuery().DirectionalShadowTopologyRevision;
RenderProjectionRecord moved = original with
{
Transform = new RenderTransform(
Matrix4x4.CreateTranslation(100f, 101f, 102f)),
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
2,
moved),
]);
Assert.True(
scene.OpenQuery().DirectionalShadowTopologyRevision > registered);
}
[Fact]
public void DirectionalShadowTransformJournal_TracksRootPartAndDynamicSync_IndependentlyOfDirtyDrain()
{
RenderSceneGeneration generation = Generation(21);
using var scene = new ArchRenderScene(generation);
Matrix4x4 firstPart = Matrix4x4.CreateTranslation(1f, 2f, 3f);
RenderProjectionRecord original = Record(
21,
1,
RenderProjectionClass.LiveDynamicRoot) with
{
EntityPayload = new RenderEntityPayload(
[new MeshRef(0x01000021, firstPart)],
PaletteOverride: null,
IsBuildingShell: false),
};
scene.Apply([RenderProjectionDelta.Register(generation, 1, original)]);
RenderSceneQuery query = scene.OpenQuery();
ulong initialRevision = query.DirectionalShadowTransformRevision;
Matrix4x4 movedRoot = Matrix4x4.CreateTranslation(
BitConverter.Int32BitsToSingle(0x41234567),
4f,
5f);
RenderProjectionRecord moved = original with
{
Transform = new RenderTransform(movedRoot),
PreviousTransform = new PreviousRenderTransform(
original.Transform.LocalToWorld),
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
2,
moved),
]);
Matrix4x4 movedPart = Matrix4x4.CreateTranslation(
6f,
BitConverter.Int32BitsToSingle(0x40ABCDEF),
8f);
RenderProjectionRecord posed = moved with
{
EntityPayload = moved.EntityPayload with
{
MeshRefs = [new MeshRef(0x01000021, movedPart)],
},
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
3,
posed),
]);
scene.ClearDirty();
var changes = new DirectionalShadowTransformSnapshot[3];
DirectionalShadowTransformChanges firstChanges =
query.CopyDirectionalShadowTransformChanges(
initialRevision,
changes);
Assert.False(firstChanges.RequiresFullRefresh);
Assert.Equal(2, firstChanges.Count);
Assert.Equal(1, firstChanges.UpdateTransformCount);
Assert.Equal(1, firstChanges.UpdateAppearanceCount);
Assert.Equal(0, firstChanges.DynamicSynchronizationCount);
Assert.Equal(2, firstChanges.LiveDynamicRootCount);
Assert.Equal(original.Id, changes[0].Id);
Assert.Equal(original.Id, changes[1].Id);
var synchronized = new DynamicProjectionUpdate(
original.Id,
original.OwnerIncarnation,
new RenderTransform(Matrix4x4.CreateTranslation(9f, 10f, 11f)),
posed.Bounds);
scene.SynchronizeDynamicSources(
new DynamicProjectionSyncInput(generation, [synchronized]));
DirectionalShadowTransformChanges syncChanges =
query.CopyDirectionalShadowTransformChanges(
firstChanges.LatestRevision,
changes);
Assert.False(syncChanges.RequiresFullRefresh);
Assert.Equal(1, syncChanges.Count);
Assert.Equal(1, syncChanges.DynamicSynchronizationCount);
Assert.Equal(1, syncChanges.LiveDynamicRootCount);
Assert.Equal(original.Id, changes[0].Id);
RenderProjectionRecord flagsOnly = posed with
{
Flags = posed.Flags ^ RenderProjectionFlags.Selectable,
};
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateFlags,
generation,
4,
flagsOnly),
]);
Assert.Equal(
syncChanges.LatestRevision,
query.DirectionalShadowTransformRevision);
}
[Fact]
public void DirectionalShadowTransformJournal_OverflowAndGenerationResetFailSafe()
{
RenderSceneGeneration generation = Generation(22);
using var scene = new ArchRenderScene(generation);
RenderProjectionRecord original = Record(
22,
1,
RenderProjectionClass.LiveDynamicRoot);
scene.Apply([RenderProjectionDelta.Register(generation, 1, original)]);
RenderSceneQuery oldQuery = scene.OpenQuery();
ulong initialRevision = oldQuery.DirectionalShadowTransformRevision;
for (int index = 0;
index < DirectionalShadowTransformChangeJournal.Capacity + 1;
index++)
{
var update = new DynamicProjectionUpdate(
original.Id,
original.OwnerIncarnation,
new RenderTransform(Matrix4x4.CreateTranslation(index + 1, 0f, 0f)),
original.Bounds);
scene.SynchronizeDynamicSources(
new DynamicProjectionSyncInput(generation, [update]));
}
var changes = new DirectionalShadowTransformSnapshot[
DirectionalShadowTransformChangeJournal.Capacity];
DirectionalShadowTransformChanges overflow =
oldQuery.CopyDirectionalShadowTransformChanges(
initialRevision,
changes);
Assert.True(overflow.RequiresFullRefresh);
Assert.Equal(0, overflow.Count);
RenderSceneGeneration replacement = Generation(23);
scene.Clear(replacement);
Assert.Throws<InvalidOperationException>(
() => _ = oldQuery.DirectionalShadowTransformRevision);
Assert.Equal(1ul, scene.OpenQuery().DirectionalShadowTransformRevision);
}
[Fact]
public void DirectionalShadowTransformJournal_IgnoresIdenticalPoseAndBoundsOnlySynchronization()
{
RenderSceneGeneration generation = Generation(24);
using var scene = new ArchRenderScene(generation);
var meshes = new List<MeshRef>
{
new(0x01000024, Matrix4x4.CreateTranslation(1f, 2f, 3f)),
};
RenderProjectionRecord original = Record(
24,
1,
RenderProjectionClass.LiveDynamicRoot) with
{
EntityPayload = new RenderEntityPayload(
meshes,
PaletteOverride: null,
IsBuildingShell: false),
};
scene.Apply([RenderProjectionDelta.Register(generation, 1, original)]);
RenderSceneQuery query = scene.OpenQuery();
ulong initial = query.DirectionalShadowTransformRevision;
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateTransform,
generation,
2,
original),
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
3,
original),
]);
var boundsOnly = new DynamicProjectionUpdate(
original.Id,
original.OwnerIncarnation,
original.Transform,
new RenderWorldBounds(Vector3.One, new Vector3(2f)));
scene.SynchronizeDynamicSources(
new DynamicProjectionSyncInput(generation, [boundsOnly]));
Assert.Equal(initial, query.DirectionalShadowTransformRevision);
Matrix4x4 changedPart = Matrix4x4.CreateTranslation(4f, 5f, 6f);
meshes[0] = new MeshRef(0x01000024, changedPart);
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
4,
original),
]);
var changes = new DirectionalShadowTransformSnapshot[1];
DirectionalShadowTransformChanges changed =
query.CopyDirectionalShadowTransformChanges(initial, changes);
Assert.Equal(1, changed.Count);
Assert.Equal(0, changed.UpdateTransformCount);
Assert.Equal(1, changed.UpdateAppearanceCount);
Assert.Equal(0, changed.DynamicSynchronizationCount);
Assert.Equal(1, changed.LiveDynamicRootCount);
Assert.Equal(original.Id, changes[0].Id);
Assert.Equal(
BitConverter.SingleToInt32Bits(changedPart.M41),
BitConverter.SingleToInt32Bits(
changes[0].EntityPayload.MeshRefs[0].PartTransform.M41));
scene.Apply(
[
RenderProjectionDelta.Update(
RenderProjectionDeltaKind.UpdateAppearance,
generation,
5,
original),
]);
Assert.Equal(changed.LatestRevision, query.DirectionalShadowTransformRevision);
}
private static RenderProjectionRecord Record(
ulong id,
ulong incarnation,