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

@ -38,6 +38,14 @@ public sealed class SkyObjectData
public uint PesObjectId;
public uint Properties;
/// <summary>
/// Source GfxObj sort centre. Celestial billboards are authored at their
/// apparent direction from the camera, so transforming and normalizing
/// this point yields the exact direction rendered by the sky pass. Zero
/// means the optional DAT lookup was unavailable.
/// </summary>
public Vector3 AuthoredSortCenter;
/// <summary>
/// True when this SkyObject is gated on the weather system (Properties
/// bit <c>0x04</c>). Per the named retail decomp,
@ -140,6 +148,9 @@ public sealed class SkyObjectReplaceData
public float Transparent;
public float Luminosity;
public float MaxBright;
/// <summary>Sort centre for a replacement <see cref="GfxObjId"/>.</summary>
public Vector3 AuthoredSortCenter;
}
/// <summary>
@ -334,7 +345,7 @@ public static class SkyDescLoader
ArgumentNullException.ThrowIfNull(dats);
var region = dats.Get<Region>(RegionDatId);
if (region is null) return null;
return LoadFromRegion(region);
return LoadFromRegion(region, dats);
}
/// <summary>
@ -352,7 +363,9 @@ public static class SkyDescLoader
/// GfxObjReplace swap pattern.
/// </para>
/// </summary>
public static LoadedSkyDesc? LoadFromRegion(Region region)
public static LoadedSkyDesc? LoadFromRegion(
Region region,
IDatObjectSource? dats = null)
{
ArgumentNullException.ThrowIfNull(region);
if (!region.PartsMask.HasFlag(PartsMask.HasSkyInfo) || region.SkyInfo is null)
@ -367,8 +380,12 @@ public static class SkyDescLoader
foreach (var dg in sky.DayGroups)
{
var objs = dg.SkyObjects.Select(ConvertSkyObject).ToList();
var times = dg.SkyTime.Select(ConvertTimeOfDay).ToList();
var objs = dg.SkyObjects
.Select(value => ConvertSkyObject(value, dats))
.ToList();
var times = dg.SkyTime
.Select(value => ConvertTimeOfDay(value, dats))
.ToList();
dayGroups.Add(new DayGroupData
{
@ -495,7 +512,9 @@ public static class SkyDescLoader
Console.WriteLine("[sky-dump] ======== END SkyDesc dump ========");
}
private static SkyObjectData ConvertSkyObject(SkyObject s) => new()
private static SkyObjectData ConvertSkyObject(
SkyObject s,
IDatObjectSource? dats) => new()
{
BeginTime = s.BeginTime,
EndTime = s.EndTime,
@ -506,9 +525,14 @@ public static class SkyDescLoader
GfxObjId = s.DefaultGfxObjectId?.DataId ?? 0u,
PesObjectId = s.DefaultPesObjectId?.DataId ?? 0u,
Properties = s.Properties,
AuthoredSortCenter = ResolveSortCenter(
s.DefaultGfxObjectId?.DataId ?? 0u,
dats),
};
private static DatSkyKeyframeData ConvertTimeOfDay(SkyTimeOfDay s)
private static DatSkyKeyframeData ConvertTimeOfDay(
SkyTimeOfDay s,
IDatObjectSource? dats)
{
// Transparent / Luminosity / MaxBright are stored in the retail
// Region dat as PERCENTAGES (0..100), not fractions (0..1). Our
@ -537,6 +561,9 @@ public static class SkyDescLoader
Transparent = r.Transparent / 100f,
Luminosity = r.Luminosity / 100f,
MaxBright = r.MaxBright / 100f,
AuthoredSortCenter = ResolveSortCenter(
r.GfxObjId?.DataId ?? 0u,
dats),
}).ToList();
var fogMode = s.WorldFog switch
@ -577,6 +604,26 @@ public static class SkyDescLoader
};
}
private static Vector3 ResolveSortCenter(
uint gfxObjId,
IDatObjectSource? dats)
{
if (dats is null || (gfxObjId & 0xFF000000u) != 0x01000000u)
return Vector3.Zero;
try
{
return dats.TryGet<GfxObj>(gfxObjId, out var gfx) && gfx is not null
? gfx.SortCenter
: Vector3.Zero;
}
catch
{
// Enhancement metadata cannot make authoritative sky loading fail.
return Vector3.Zero;
}
}
/// <summary>
/// <see cref="ColorARGB"/> stores bytes as B,G,R,A — but the logical
/// channel mapping is just "R/G/B in 0..255". Convert to linear