feat(headless): share immutable gameplay content
This commit is contained in:
parent
12b500d383
commit
9569dadb57
25 changed files with 1031 additions and 429 deletions
266
src/AcDream.Content/LandblockPhysicsContentBuilder.cs
Normal file
266
src/AcDream.Content/LandblockPhysicsContentBuilder.cs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatEnvironment = DatReaderWriter.DBObjs.Environment;
|
||||
|
||||
namespace AcDream.Content;
|
||||
|
||||
/// <summary>
|
||||
/// Presentation-independent content half of one near-landblock collision
|
||||
/// build. Graphical and no-window hosts consume the same parsed DAT closure,
|
||||
/// immutable package collision records, and static Setup/GfxObj identities.
|
||||
/// No Runtime world or backend resource is mutated here.
|
||||
/// </summary>
|
||||
public static class LandblockPhysicsContentBuilder
|
||||
{
|
||||
public static IReadOnlyList<WorldEntity> HydrateStaticEntities(
|
||||
IDatReaderWriter dats,
|
||||
LoadedLandblock source,
|
||||
Vector3 worldOffset)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dats);
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
|
||||
var hydrated = new List<WorldEntity>(source.Entities.Count);
|
||||
foreach (WorldEntity candidate in source.Entities)
|
||||
{
|
||||
var meshRefs = new List<MeshRef>();
|
||||
var bounds = new LocalBoundsAccumulator();
|
||||
uint sourceId = candidate.SourceGfxObjOrSetupId;
|
||||
if ((sourceId & 0xFF000000u) == 0x01000000u)
|
||||
{
|
||||
GfxObj? gfx = dats.Get<GfxObj>(sourceId);
|
||||
if (gfx is not null)
|
||||
{
|
||||
(Vector3 Min, Vector3 Max)? partBounds =
|
||||
GfxObjBounds.Get(gfx);
|
||||
if (partBounds is not null)
|
||||
{
|
||||
bounds.Add(
|
||||
Matrix4x4.Identity,
|
||||
partBounds.Value);
|
||||
}
|
||||
meshRefs.Add(new MeshRef(
|
||||
sourceId,
|
||||
Matrix4x4.Identity));
|
||||
}
|
||||
}
|
||||
else if ((sourceId & 0xFF000000u) == 0x02000000u)
|
||||
{
|
||||
Setup? setup = dats.Get<Setup>(sourceId);
|
||||
if (setup is not null)
|
||||
{
|
||||
foreach (MeshRef meshRef in SetupMesh.Flatten(setup))
|
||||
{
|
||||
GfxObj? gfx =
|
||||
dats.Get<GfxObj>(meshRef.GfxObjId);
|
||||
if (gfx is null)
|
||||
continue;
|
||||
(Vector3 Min, Vector3 Max)? partBounds =
|
||||
GfxObjBounds.Get(gfx);
|
||||
if (partBounds is not null)
|
||||
{
|
||||
bounds.Add(
|
||||
meshRef.PartTransform,
|
||||
partBounds.Value);
|
||||
}
|
||||
meshRefs.Add(meshRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (meshRefs.Count == 0)
|
||||
continue;
|
||||
|
||||
var entity = new WorldEntity
|
||||
{
|
||||
Id = candidate.Id,
|
||||
SourceGfxObjOrSetupId = sourceId,
|
||||
Position = candidate.Position + worldOffset,
|
||||
Rotation = candidate.Rotation,
|
||||
MeshRefs = meshRefs,
|
||||
EffectCellId = candidate.EffectCellId,
|
||||
IsBuildingShell = candidate.IsBuildingShell,
|
||||
BuildingShellAnchorCellId =
|
||||
candidate.BuildingShellAnchorCellId,
|
||||
};
|
||||
if (bounds.TryGet(out Vector3 min, out Vector3 max))
|
||||
entity.SetLocalBounds(min, max);
|
||||
hydrated.Add(entity);
|
||||
}
|
||||
return hydrated;
|
||||
}
|
||||
|
||||
public static PhysicsDatBundle BuildDatBundle(
|
||||
IDatReaderWriter dats,
|
||||
uint landblockId,
|
||||
IReadOnlyList<WorldEntity> entities)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(dats);
|
||||
ArgumentNullException.ThrowIfNull(entities);
|
||||
|
||||
var envCells = new Dictionary<uint, EnvCell>();
|
||||
var environments = new Dictionary<uint, DatEnvironment>();
|
||||
var setups = new Dictionary<uint, Setup>();
|
||||
var gfxObjs = new Dictionary<uint, GfxObj>();
|
||||
LandBlockInfo? info = dats.Get<LandBlockInfo>(
|
||||
(landblockId & 0xFFFF0000u) | 0xFFFEu);
|
||||
|
||||
if (info is not null)
|
||||
{
|
||||
uint firstCellId =
|
||||
(landblockId & 0xFFFF0000u) | 0x0100u;
|
||||
for (uint offset = 0; offset < info.NumCells; offset++)
|
||||
{
|
||||
uint envCellId = firstCellId + offset;
|
||||
EnvCell? envCell = dats.Get<EnvCell>(envCellId);
|
||||
if (envCell is null)
|
||||
continue;
|
||||
envCells[envCellId] = envCell;
|
||||
if (envCell.EnvironmentId == 0)
|
||||
continue;
|
||||
uint environmentId =
|
||||
0x0D000000u | envCell.EnvironmentId;
|
||||
if (!environments.ContainsKey(environmentId)
|
||||
&& dats.Get<DatEnvironment>(
|
||||
environmentId) is { } environment)
|
||||
{
|
||||
environments[environmentId] = environment;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var building in info.Buildings)
|
||||
{
|
||||
uint setupId = building.ModelId;
|
||||
if ((setupId & 0xFF000000u) == 0x02000000u
|
||||
&& !setups.ContainsKey(setupId)
|
||||
&& dats.Get<Setup>(setupId) is { } setup)
|
||||
{
|
||||
setups[setupId] = setup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (WorldEntity entity in entities)
|
||||
{
|
||||
uint sourceId = entity.SourceGfxObjOrSetupId;
|
||||
if ((sourceId & 0xFF000000u) == 0x02000000u
|
||||
&& !setups.ContainsKey(sourceId)
|
||||
&& dats.Get<Setup>(sourceId) is { } setup)
|
||||
{
|
||||
setups[sourceId] = setup;
|
||||
}
|
||||
|
||||
foreach (MeshRef meshRef in entity.MeshRefs)
|
||||
{
|
||||
uint gfxObjId = meshRef.GfxObjId;
|
||||
if ((gfxObjId & 0xFF000000u) != 0x01000000u
|
||||
|| gfxObjs.ContainsKey(gfxObjId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (dats.Get<GfxObj>(gfxObjId) is { } gfxObj)
|
||||
gfxObjs[gfxObjId] = gfxObj;
|
||||
}
|
||||
}
|
||||
|
||||
return new PhysicsDatBundle(
|
||||
info,
|
||||
envCells,
|
||||
environments,
|
||||
setups,
|
||||
gfxObjs);
|
||||
}
|
||||
|
||||
public static LandblockCollisionBuild BuildPreparedCollisionClosure(
|
||||
IPreparedCollisionSource source,
|
||||
LoadedLandblock landblock)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(landblock);
|
||||
|
||||
PhysicsDatBundle dats =
|
||||
landblock.PhysicsDats ?? PhysicsDatBundle.Empty;
|
||||
ImmutableArray<uint> gfxObjIds =
|
||||
[.. dats.GfxObjs.Keys.Order()];
|
||||
ImmutableArray<uint> setupIds =
|
||||
[.. dats.Setups.Keys.Order()];
|
||||
ImmutableArray<uint> envCellIds =
|
||||
[.. dats.EnvCells.Keys.Order()];
|
||||
var gfxObjs = ImmutableDictionary.CreateBuilder<
|
||||
uint,
|
||||
FlatGfxObjCollisionAsset>();
|
||||
var setups = ImmutableDictionary.CreateBuilder<
|
||||
uint,
|
||||
FlatSetupCollision>();
|
||||
var cellStructures = ImmutableDictionary.CreateBuilder<
|
||||
uint,
|
||||
FlatCellStructureCollisionAsset>();
|
||||
var envCells = ImmutableDictionary.CreateBuilder<
|
||||
uint,
|
||||
FlatEnvCellTopology>();
|
||||
|
||||
foreach (uint id in gfxObjIds)
|
||||
{
|
||||
gfxObjs.Add(
|
||||
id,
|
||||
Require(
|
||||
source.ReadGfxObjCollision(id),
|
||||
"GfxObj collision",
|
||||
id));
|
||||
}
|
||||
foreach (uint id in setupIds)
|
||||
{
|
||||
setups.Add(
|
||||
id,
|
||||
Require(
|
||||
source.ReadSetupCollision(id),
|
||||
"Setup collision",
|
||||
id));
|
||||
}
|
||||
foreach (uint id in envCellIds)
|
||||
{
|
||||
cellStructures.Add(
|
||||
id,
|
||||
Require(
|
||||
source.ReadCellStructureCollision(id),
|
||||
"CellStruct collision",
|
||||
id));
|
||||
envCells.Add(
|
||||
id,
|
||||
Require(
|
||||
source.ReadEnvCellTopology(id),
|
||||
"EnvCell topology",
|
||||
id));
|
||||
}
|
||||
|
||||
return new LandblockCollisionBuild(
|
||||
gfxObjs.ToImmutable(),
|
||||
setups.ToImmutable(),
|
||||
cellStructures.ToImmutable(),
|
||||
envCells.ToImmutable(),
|
||||
gfxObjIds,
|
||||
setupIds,
|
||||
envCellIds);
|
||||
}
|
||||
|
||||
private static T Require<T>(
|
||||
PreparedCollisionReadResult<T> result,
|
||||
string kind,
|
||||
uint id)
|
||||
where T : class
|
||||
{
|
||||
if (result.Status == PreparedAssetReadStatus.Loaded
|
||||
&& result.Data is not null)
|
||||
{
|
||||
return result.Data;
|
||||
}
|
||||
|
||||
throw new InvalidDataException(
|
||||
$"{kind} 0x{id:X8} is {result.Status}. "
|
||||
+ "The complete near-tier generation cannot be published.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue