91 lines
4.1 KiB
C#
91 lines
4.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Numerics;
|
|
using AcDream.Core.Physics; // BSPQuery
|
|
using DatReaderWriter.Enums; // EnvCellFlags
|
|
using DatReaderWriter.Types; // CellBSPTree, CellStruct
|
|
|
|
namespace AcDream.Core.World.Cells;
|
|
|
|
/// <summary>Indoor room cell. Retail anchor: CEnvCell (acclient.h:32072).</summary>
|
|
public sealed class EnvCell : ObjCell
|
|
{
|
|
/// <summary>Cell-containment BSP (retail CellStruct.CellBSP). Null => AABB fallback.</summary>
|
|
public CellBSPTree? ContainmentBsp { get; }
|
|
|
|
public EnvCell(uint id, Matrix4x4 worldTransform, Matrix4x4 inverseWorldTransform,
|
|
Vector3 localBoundsMin, Vector3 localBoundsMax,
|
|
IReadOnlyList<CellPortal> portals, IReadOnlyList<uint> stabList,
|
|
bool seenOutside, CellBSPTree? containmentBsp)
|
|
: base(id, worldTransform, inverseWorldTransform, localBoundsMin, localBoundsMax,
|
|
portals, stabList, seenOutside)
|
|
{
|
|
ContainmentBsp = containmentBsp;
|
|
}
|
|
|
|
public override bool PointInCell(Vector3 worldPoint)
|
|
{
|
|
var local = Vector3.Transform(worldPoint, InverseWorldTransform);
|
|
if (ContainmentBsp?.Root is not null)
|
|
return BSPQuery.PointInsideCellBsp(ContainmentBsp.Root, local); // BSPQuery.cs:1034
|
|
return local.X >= LocalBoundsMin.X && local.X <= LocalBoundsMax.X
|
|
&& local.Y >= LocalBoundsMin.Y && local.Y <= LocalBoundsMax.Y
|
|
&& local.Z >= LocalBoundsMin.Z && local.Z <= LocalBoundsMax.Z;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Build an EnvCell from dat data. Mirrors the render derivation in
|
|
/// BuildLoadedCell (GameWindow.cs:5588-5704) so Core and render stay equivalent.
|
|
/// <paramref name="worldTransform"/> MUST be the physics-verbatim transform
|
|
/// (no +2 cm render lift).
|
|
/// </summary>
|
|
public static EnvCell FromDat(uint id, DatReaderWriter.DBObjs.EnvCell datCell,
|
|
CellStruct cellStruct, Matrix4x4 worldTransform)
|
|
{
|
|
Matrix4x4.Invert(worldTransform, out var inverse);
|
|
|
|
var min = new Vector3(float.MaxValue);
|
|
var max = new Vector3(float.MinValue);
|
|
foreach (var kvp in cellStruct.VertexArray.Vertices)
|
|
{
|
|
var p = new Vector3(kvp.Value.Origin.X, kvp.Value.Origin.Y, kvp.Value.Origin.Z);
|
|
min = Vector3.Min(min, p);
|
|
max = Vector3.Max(max, p);
|
|
}
|
|
if (min.X == float.MaxValue) { min = Vector3.Zero; max = Vector3.Zero; }
|
|
|
|
var portals = new List<CellPortal>(datCell.CellPortals.Count);
|
|
foreach (var p in datCell.CellPortals)
|
|
{
|
|
portals.Add(new CellPortal(
|
|
otherCellId: p.OtherCellId,
|
|
otherPortalId: p.OtherPortalId,
|
|
polygonId: p.PolygonId,
|
|
flags: (ushort)p.Flags,
|
|
polygonLocal: ResolvePortalPolygon(cellStruct, p.PolygonId)));
|
|
}
|
|
|
|
uint lbPrefix = id & 0xFFFF0000u;
|
|
var stab = new List<uint>();
|
|
if (datCell.VisibleCells is not null) // match BuildLoadedCell:5701 null guard
|
|
foreach (var low in datCell.VisibleCells) stab.Add(lbPrefix | low);
|
|
bool seenOutside = datCell.Flags.HasFlag(EnvCellFlags.SeenOutside);
|
|
|
|
return new EnvCell(id, worldTransform, inverse, min, max, portals, stab,
|
|
seenOutside, cellStruct.CellBSP);
|
|
}
|
|
|
|
private static IReadOnlyList<Vector3> ResolvePortalPolygon(CellStruct cellStruct, ushort polygonId)
|
|
{
|
|
if (!cellStruct.Polygons.TryGetValue(polygonId, out var poly) || poly.VertexIds.Count < 3)
|
|
return Array.Empty<Vector3>();
|
|
var verts = new Vector3[poly.VertexIds.Count];
|
|
for (int i = 0; i < poly.VertexIds.Count; i++)
|
|
{
|
|
if (!cellStruct.VertexArray.Vertices.TryGetValue((ushort)poly.VertexIds[i], out var v))
|
|
return Array.Empty<Vector3>(); // any miss -> empty (matches BuildLoadedCell:5686-5691)
|
|
verts[i] = new Vector3(v.Origin.X, v.Origin.Y, v.Origin.Z);
|
|
}
|
|
return verts;
|
|
}
|
|
}
|