acdream/src/AcDream.App/Rendering/Scene/RenderProjectionRecordFactory.cs
Erik 0eb6648589 feat(rendering): compare the incremental shadow scene
Construct Slice F's non-drawing scene only for lifecycle automation, drain accepted static and live deltas at the final update boundary, and compare exact current-path fingerprints at cadence and checkpoints. Publish bounded mismatch, journal, index, digest, and memory evidence without changing normal launches or draw submission.

Release: 8,211 passed, 5 skipped.
2026-07-24 22:24:30 +02:00

132 lines
4.6 KiB
C#

using System.Numerics;
using AcDream.Core.World;
namespace AcDream.App.Rendering.Scene;
/// <summary>
/// Pure conversion from an accepted <see cref="WorldEntity"/> presentation
/// snapshot into the packed render-scene record. This method never mutates
/// source AABB state or acquires gameplay identity.
/// </summary>
internal static class RenderProjectionRecordFactory
{
private const float DefaultAabbRadius = 5.0f;
public static RenderProjectionRecord ProjectEntity(
RenderProjectionId id,
RenderProjectionClass projectionClass,
RenderOwnerIncarnation incarnation,
uint ownerLandblockId,
uint fullCellId,
WorldEntity entity,
bool spatiallyVisible)
{
ArgumentNullException.ThrowIfNull(entity);
CurrentRenderProjectionFingerprint fingerprint =
CurrentRenderSceneOracle.CreateProjectionFingerprint(
ownerLandblockId,
entity);
RenderProjectionFlags flags = RenderProjectionFlags.Selectable;
if (entity.MeshRefs.Count > 0
&& spatiallyVisible
&& entity.IsDrawVisible
&& entity.IsAncestorDrawVisible)
{
flags |= RenderProjectionFlags.Draw;
}
if (!entity.IsDrawVisible)
flags |= RenderProjectionFlags.Hidden;
if (!entity.IsAncestorDrawVisible)
flags |= RenderProjectionFlags.AncestorHidden;
RenderTransform transform = RenderTransform.FromRoot(
entity.Position,
entity.Rotation,
entity.Scale);
(Vector3 minimum, Vector3 maximum) = CalculateBounds(entity);
return new RenderProjectionRecord(
id,
projectionClass,
incarnation,
transform,
new PreviousRenderTransform(transform.LocalToWorld),
new RenderMeshSet(
RenderAssetHandle.FromRaw(
fingerprint.Geometry.Low ^ fingerprint.Geometry.High),
entity.MeshRefs.Count,
0),
new RenderMaterialVariant(
fingerprint.Appearance.Low,
fingerprint.Appearance.High,
1.0f),
new RenderSpatialResidency(
RenderSpatialBucket.FromRaw(fullCellId),
ownerLandblockId,
fullCellId),
new RenderWorldBounds(minimum, maximum),
flags,
default,
id.ToSortKey(),
RenderDirtyMask.All,
new RenderSourceMetadata(
entity.Id,
entity.ServerGuid,
entity.SourceGfxObjOrSetupId,
entity.ParentCellId ?? 0,
entity.EffectCellId ?? 0,
entity.BuildingShellAnchorCellId ?? 0,
fingerprint.Transform,
fingerprint.Geometry,
fingerprint.Appearance,
fingerprint.Flags));
}
private static (Vector3 Minimum, Vector3 Maximum) CalculateBounds(
WorldEntity entity)
{
Vector3 position = entity.Position;
if (entity.HasLocalBounds)
{
Vector3 localMin = entity.LocalBoundMin;
Vector3 localMax = entity.LocalBoundMax;
Vector3 minimum = default;
Vector3 maximum = default;
for (int cornerIndex = 0; cornerIndex < 8; cornerIndex++)
{
Vector3 corner = new(
(cornerIndex & 1) == 0 ? localMin.X : localMax.X,
(cornerIndex & 2) == 0 ? localMin.Y : localMax.Y,
(cornerIndex & 4) == 0 ? localMin.Z : localMax.Z);
Vector3 transformed =
Vector3.Transform(corner, entity.Rotation);
if (cornerIndex == 0)
{
minimum = transformed;
maximum = transformed;
}
else
{
minimum = Vector3.Min(minimum, transformed);
maximum = Vector3.Max(maximum, transformed);
}
}
Vector3 margin = new(DefaultAabbRadius);
return (
position + minimum - margin,
position + maximum + margin);
}
float radius = DefaultAabbRadius;
for (int i = 0; i < entity.MeshRefs.Count; i++)
{
radius = MathF.Max(
radius,
DefaultAabbRadius
+ entity.MeshRefs[i].PartTransform.Translation.Length());
}
Vector3 extent = new(radius);
return (position - extent, position + extent);
}
}