perf(vfx): port retail particle visibility degradation
Resolve DAT-authored particle ranges from the hardware GfxObj, apply retail distance and completed-cell visibility gates, and preserve the exact finite/infinite off-view update semantics. This removes dense-world simulation work without shortening terrain, entity, fog, or streaming distance. Publish doorway-clipped outdoor cells through a focused frame controller, retain effect cell identity for outdoor statics, reject hidden emitters before particle-slot scans, and offer an explicit opt-in Extended particle range. Release build succeeds and all 5,857 tests pass with five intentional skips. Retail-conformance, architecture, and adversarial review cycles are clean; connected Aerlinthe visual/performance gate pending. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
82789eea88
commit
f1ba147ac5
29 changed files with 1810 additions and 125 deletions
|
|
@ -2,9 +2,13 @@ using System;
|
|||
using System.Collections.Concurrent;
|
||||
using System.Numerics;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.Lib.IO;
|
||||
using DatParticleEmitter = DatReaderWriter.DBObjs.ParticleEmitter;
|
||||
using DatGfxObj = DatReaderWriter.DBObjs.GfxObj;
|
||||
using DatGfxObjDegradeInfo = DatReaderWriter.DBObjs.GfxObjDegradeInfo;
|
||||
using DatEmitterType = DatReaderWriter.Enums.EmitterType;
|
||||
using DatParticleType = DatReaderWriter.Enums.ParticleType;
|
||||
using DatGfxObjFlags = DatReaderWriter.Enums.GfxObjFlags;
|
||||
|
||||
namespace AcDream.Core.Vfx;
|
||||
|
||||
|
|
@ -15,6 +19,7 @@ namespace AcDream.Core.Vfx;
|
|||
public sealed class EmitterDescRegistry
|
||||
{
|
||||
private readonly Func<uint, DatParticleEmitter?>? _resolver;
|
||||
private readonly Func<DatParticleEmitter, float?>? _degradeDistanceResolver;
|
||||
private readonly ConcurrentDictionary<uint, EmitterDesc> _byId = new();
|
||||
|
||||
public EmitterDescRegistry()
|
||||
|
|
@ -23,8 +28,10 @@ public sealed class EmitterDescRegistry
|
|||
}
|
||||
|
||||
public EmitterDescRegistry(DatCollection dats)
|
||||
: this(id => SafeGet(dats, id))
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dats);
|
||||
_resolver = id => SafeGet<DatParticleEmitter>(dats, id);
|
||||
_degradeDistanceResolver = emitter => ResolveMaxDegradeDistance(dats, emitter);
|
||||
}
|
||||
|
||||
public EmitterDescRegistry(Func<uint, DatParticleEmitter?>? resolver)
|
||||
|
|
@ -55,7 +62,18 @@ public sealed class EmitterDescRegistry
|
|||
var dat = _resolver(emitterId);
|
||||
if (dat is not null)
|
||||
{
|
||||
desc = FromDat(emitterId, dat);
|
||||
float? resolvedDistance = _degradeDistanceResolver?.Invoke(dat);
|
||||
if (_degradeDistanceResolver is not null && !resolvedDistance.HasValue)
|
||||
{
|
||||
// ParticleEmitter::SetInfo (0x0051CE90) fails when the
|
||||
// authored hardware GfxObj cannot be loaded. It never
|
||||
// substitutes the software GfxObj.
|
||||
desc = null!;
|
||||
return false;
|
||||
}
|
||||
float maxDegradeDistance = resolvedDistance
|
||||
?? RetailParticleDegradeDistance.DefaultDistance;
|
||||
desc = FromDat(emitterId, dat, maxDegradeDistance);
|
||||
_byId[emitterId] = desc;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -66,7 +84,10 @@ public sealed class EmitterDescRegistry
|
|||
|
||||
public int Count => _byId.Count;
|
||||
|
||||
public static EmitterDesc FromDat(uint emitterId, DatParticleEmitter dat)
|
||||
public static EmitterDesc FromDat(
|
||||
uint emitterId,
|
||||
DatParticleEmitter dat,
|
||||
float maxDegradeDistance = RetailParticleDegradeDistance.DefaultDistance)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dat);
|
||||
|
||||
|
|
@ -90,6 +111,7 @@ public sealed class EmitterDescRegistry
|
|||
|
||||
return new EmitterDesc
|
||||
{
|
||||
MaxDegradeDistance = maxDegradeDistance,
|
||||
DatId = emitterId,
|
||||
Type = MapParticleType(dat.ParticleType),
|
||||
EmitterKind = MapEmitterKind(dat.EmitterType),
|
||||
|
|
@ -132,13 +154,37 @@ public sealed class EmitterDescRegistry
|
|||
};
|
||||
}
|
||||
|
||||
private static DatParticleEmitter? SafeGet(DatCollection dats, uint id)
|
||||
private static float? ResolveMaxDegradeDistance(
|
||||
DatCollection dats,
|
||||
DatParticleEmitter emitter)
|
||||
{
|
||||
uint gfxObjId = GetRetailHardwareGfxObjId(emitter);
|
||||
if (gfxObjId == 0)
|
||||
return null;
|
||||
|
||||
DatGfxObj? gfx = SafeGet<DatGfxObj>(dats, gfxObjId);
|
||||
if (gfx is null)
|
||||
return null;
|
||||
if (!gfx.Flags.HasFlag(DatGfxObjFlags.HasDIDDegrade)
|
||||
|| gfx.DIDDegrade == 0)
|
||||
{
|
||||
return RetailParticleDegradeDistance.DefaultDistance;
|
||||
}
|
||||
|
||||
DatGfxObjDegradeInfo? degrade = SafeGet<DatGfxObjDegradeInfo>(dats, gfx.DIDDegrade);
|
||||
return RetailParticleDegradeDistance.FromEntries(degrade?.Degrades);
|
||||
}
|
||||
|
||||
internal static uint GetRetailHardwareGfxObjId(DatParticleEmitter emitter)
|
||||
=> emitter.HwGfxObjId.DataId;
|
||||
|
||||
private static T? SafeGet<T>(DatCollection dats, uint id) where T : class, IDBObj
|
||||
{
|
||||
if (dats is null)
|
||||
return null;
|
||||
try
|
||||
{
|
||||
return dats.Get<DatParticleEmitter>(id);
|
||||
return dats.Get<T>(id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -170,3 +216,24 @@ public sealed class EmitterDescRegistry
|
|||
_ => ParticleType.Unknown,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact retail maximum-distance selection for a particle GfxObj.
|
||||
/// <c>CPhysicsPart::GetMaxDegradeDistance</c> (0x0050D510) supplies the
|
||||
/// 100-unit default; <c>GfxObjDegradeInfo::get_max_degrade_distance</c>
|
||||
/// (0x0051E2D0) selects entry zero for one/two-entry tables and the
|
||||
/// second-to-last entry for larger tables.
|
||||
/// </summary>
|
||||
public static class RetailParticleDegradeDistance
|
||||
{
|
||||
public const float DefaultDistance = 100f;
|
||||
|
||||
public static float FromEntries(IReadOnlyList<DatReaderWriter.Types.GfxObjInfo>? entries)
|
||||
{
|
||||
if (entries is null || entries.Count == 0)
|
||||
return DefaultDistance;
|
||||
|
||||
int index = entries.Count <= 2 ? 0 : entries.Count - 2;
|
||||
return entries[index].MaxDist;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue