using System; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Rendering; public class BrushModel { public string Name { get; } private readonly List subModels = new List(); // Reusable temporary data containers private readonly List tempVertices = new List(); private readonly List tempTextureUVs = new List(); private readonly List tempLightmapUVs = new List(); private readonly List tempIndices = new List(); public BrushModel(string name) { Name = name; } public int SubModelCount => subModels.Count; public SubModel GetSubModel(int index) { return subModels[index]; } public void ImportMeshData(QModel model) { var inSubModels = model.SubModels; var inSurfaces = model.Surfaces; for (int modelIdx = 0; modelIdx < inSubModels.Length; ++modelIdx) { var subModel = new SubModel($"SubModel_{modelIdx}"); subModels.Add(subModel); // Traverse the BSP tree and group the surfaces based on their material properties var headNode = inSubModels[modelIdx].GetHeadNode(model); var surfaceGroups = new Dictionary<(IntPtr, int), List>(); GroupSurfaces(headNode, inSurfaces, surfaceGroups); // Create a single mesh for each group of surfaces foreach (var group in surfaceGroups) { var key = group.Key; var mesh = CreateMeshFromSurfaces(group.Value, $"T{key.Item1}_L{key.Item2}"); subModel.AddSurfaceMesh(new SurfaceMesh(mesh, key.Item1, key.Item2)); } } } public void Dispose() { foreach (var subModel in subModels) { subModel.Dispose(); } subModels.Clear(); } private void GroupSurfaces(QNode node, QSurface[] surfaces, Dictionary<(IntPtr, int), List> surfaceGroups) { if (node.contents < 0) // Leaf node return; for (int surfIdx = 0; surfIdx < node.numSurfaces; ++surfIdx) { var surface = surfaces[node.firstSurface + surfIdx]; IntPtr texPtr = surface.TextureInfo.texture; int lightNum = surface.lightmapTextureNum; var key = (texPtr, lightNum); if (!surfaceGroups.ContainsKey(key)) surfaceGroups[key] = new List(); surfaceGroups[key].Add(surface); } foreach (var childNode in node.Children) { GroupSurfaces(childNode, surfaces, surfaceGroups); } } private Mesh CreateMeshFromSurfaces(List surfaces, string key) { tempVertices.Clear(); tempTextureUVs.Clear(); tempLightmapUVs.Clear(); tempIndices.Clear(); int vertOffset = 0; for (int surfIdx = 0; surfIdx < surfaces.Count; ++surfIdx) { foreach (var polyVerts in surfaces[surfIdx].GetPolygons()) { for (int vertIdx = 0; vertIdx < polyVerts.Length; ++vertIdx) { tempVertices.Add(polyVerts[vertIdx].position.ToUnityPosition()); tempTextureUVs.Add(polyVerts[vertIdx].textureUV.ToVector2()); tempLightmapUVs.Add(polyVerts[vertIdx].lightmapUV.ToVector2()); } // Reconstruct triangle fan for (ushort index = 2; index < polyVerts.Length; ++index) { tempIndices.Add((ushort)vertOffset); tempIndices.Add((ushort)(vertOffset + index - 1)); tempIndices.Add((ushort)(vertOffset + index)); } vertOffset += polyVerts.Length; } } Mesh mesh = new Mesh(); mesh.name = $"Surfaces_{key}"; mesh.SetVertices(tempVertices); mesh.SetUVs(0, tempTextureUVs); mesh.SetUVs(1, tempLightmapUVs); mesh.SetIndices(tempIndices, MeshTopology.Triangles, 0); mesh.RecalculateNormals(); mesh.UploadMeshData(true); return mesh; } public class SubModel { public string Name { get; private set; } public List SurfaceMeshes { get; } = new List(); public SubModel(string name) { Name = name; } public void AddSurfaceMesh(SurfaceMesh surfaceMesh) { SurfaceMeshes.Add(surfaceMesh); } public void Dispose() { foreach (var surfaceMesh in SurfaceMeshes) { surfaceMesh.Dispose(); } } } public class SurfaceMesh { public Mesh Mesh { get; } public uint TextureNum { get; } public uint FullBrightNum { get; } public uint WarpImageNum { get; } public int Lightmap { get; } public SurfaceMesh(Mesh mesh, IntPtr texturePtr, int lightmap) { Mesh = mesh; var texture = Marshal.PtrToStructure(texturePtr); TextureNum = texture.TextureNum; FullBrightNum = texture.FullBrightNum; WarpImageNum = texture.WarpImageNum; Lightmap = lightmap; } public void Dispose() { UnityEngine.Object.Destroy(Mesh); } } }