using System.Numerics; using KeepersCompound.LGS; using KeepersCompound.LGS.Database; using KeepersCompound.LGS.Database.Chunks; namespace KeepersCompound.Lightmapper; // TODO: Rename to CastSurfaceType? public enum SurfaceType { Solid, Sky, Water, Air, } public class Mesh(int triangleCount, List vertices, List indices, List triangleSurfaceMap) { public int TriangleCount { get; } = triangleCount; public Vector3[] Vertices { get; } = [..vertices]; public int[] Indices { get; } = [..indices]; public SurfaceType[] TriangleSurfaceMap { get; } = [..triangleSurfaceMap]; } public class MeshBuilder { private int _triangleCount = 0; private readonly List _vertices = []; private readonly List _indices = []; private readonly List _primSurfaceMap = []; public void AddWorldRepPolys(WorldRep worldRep) { var polyVertices = new List(); foreach (var cell in worldRep.Cells) { var numPolys = cell.PolyCount; var numRenderPolys = cell.RenderPolyCount; var numPortalPolys = cell.PortalPolyCount; var solidPolys = numPolys - numPortalPolys; var cellIdxOffset = 0; for (var polyIdx = 0; polyIdx < numPolys; polyIdx++) { // There's 3 types of poly that we need to include in the mesh: // - Terrain // - Water surfaces // - Door vision blockers // // Door vision blockers are the interesting one. They're not RenderPolys at all, and we only include // them in the mesh if the cell only has two of them (otherwise the door is in the middle of the air) SurfaceType primType; if (polyIdx < solidPolys) { primType = cell.RenderPolys[polyIdx].TextureId == 249 ? SurfaceType.Sky : SurfaceType.Solid; } else if (polyIdx < numRenderPolys) { primType = SurfaceType.Water; } else if (cell is { Flags: 24, PortalPolyCount: 2 }) // TODO: Work out what these flags are!! { primType = SurfaceType.Solid; } else { continue; } var poly = cell.Polys[polyIdx]; polyVertices.Clear(); polyVertices.EnsureCapacity(poly.VertexCount); for (var i = 0; i < poly.VertexCount; i++) { polyVertices.Add(cell.Vertices[cell.Indices[cellIdxOffset + i]]); } AddPolygon(polyVertices, primType); cellIdxOffset += poly.VertexCount; } } } public void AddObjectPolys( BrList brushList, ObjectHierarchy hierarchy, ResourcePathManager.CampaignResources campaignResources) { var polyVertices = new List(); foreach (var brush in brushList.Brushes) { if (brush.media != BrList.Brush.Media.Object) { continue; } var id = (int)brush.brushInfo; var modelNameProp = hierarchy.GetProperty(id, "P$ModelName"); var scaleProp = hierarchy.GetProperty(id, "P$Scale"); var renderTypeProp = hierarchy.GetProperty(id, "P$RenderTyp"); var jointPosProp = hierarchy.GetProperty(id, "P$JointPos"); var immobileProp = hierarchy.GetProperty(id, "P$Immobile"); var staticShadowProp = hierarchy.GetProperty(id, "P$StatShad"); var joints = jointPosProp?.Positions ?? [0, 0, 0, 0, 0, 0]; var castsShadows = (immobileProp?.value ?? false) || (staticShadowProp?.value ?? false); var renderMode = renderTypeProp?.mode ?? PropRenderType.Mode.Normal; // TODO: Check which rendermodes cast shadows :) if (modelNameProp == null || !castsShadows || renderMode == PropRenderType.Mode.CoronaOnly) { continue; } // Let's try and place an object :) var modelName = modelNameProp.value.ToLower() + ".bin"; var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName); if (modelPath == null) { continue; } // TODO: Handle failing to find model more gracefully var pos = brush.position; var rot = brush.angle; var scale = scaleProp?.value ?? Vector3.One; var model = new ModelFile(modelPath); pos -= model.Header.Center; // Calculate base model transform var scalePart = Matrix4x4.CreateScale(scale); var rotPart = Matrix4x4.Identity; rotPart *= Matrix4x4.CreateRotationX(float.DegreesToRadians(rot.X)); rotPart *= Matrix4x4.CreateRotationY(float.DegreesToRadians(rot.Y)); rotPart *= Matrix4x4.CreateRotationZ(float.DegreesToRadians(rot.Z)); var transPart = Matrix4x4.CreateTranslation(pos); var modelTrans = scalePart * rotPart * transPart; // Calculate base transforms for every subobj (including joint) var objCount = model.Objects.Length; var subObjTransforms = new Matrix4x4[objCount]; for (var i = 0; i < objCount; i++) { var subObj = model.Objects[i]; var objTrans = Matrix4x4.Identity; if (subObj.Joint != -1) { var ang = float.DegreesToRadians(joints[subObj.Joint]); // TODO: Is this correct? Should I use a manual rotation matrix? var jointRot = Matrix4x4.CreateFromYawPitchRoll(0, ang, 0); objTrans = jointRot * subObj.Transform; } subObjTransforms[i] = objTrans; } // Build map of objects to their parent id var parentIds = new int[objCount]; for (var i = 0; i < objCount; i++) { parentIds[i] = -1; } for (var i = 0; i < objCount; i++) { var subObj = model.Objects[i]; var childIdx = subObj.Child; while (childIdx != -1) { parentIds[childIdx] = i; childIdx = model.Objects[childIdx].Next; } } // Apply sub object transforms + the base object transform to each vertex for (var i = 0; i < objCount; i++) { var subObj = model.Objects[i]; var transform = subObjTransforms[i]; var parentId = parentIds[i]; while (parentId != -1) { transform *= subObjTransforms[parentId]; parentId = parentIds[parentId]; } transform *= modelTrans; var start = subObj.PointIdx; var end = start + subObj.PointCount; for (var j = start; j < end; j++) { var v = model.Vertices[j]; model.Vertices[j] = Vector3.Transform(v, transform); } } // for each polygon slam its vertices and indices :) foreach (var poly in model.Polygons) { polyVertices.Clear(); polyVertices.EnsureCapacity(poly.VertexCount); foreach (var idx in poly.VertexIndices) { polyVertices.Add(model.Vertices[idx]); } AddPolygon(polyVertices, SurfaceType.Solid); } } } private void AddPolygon(List vertices, SurfaceType surfaceType) { var vertexCount = vertices.Count; var indexOffset = _vertices.Count; // Polygons are n-sided, but fortunately they're convex so we can just do a fan triangulation _vertices.AddRange(vertices); for (var i = 1; i < vertexCount - 1; i++) { _indices.Add(indexOffset); _indices.Add(indexOffset + i); _indices.Add(indexOffset + i + 1); _primSurfaceMap.Add(surfaceType); _triangleCount++; } } public Mesh Build() { return new Mesh(_triangleCount, _vertices, _indices, _primSurfaceMap); } }