using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; public class BrushModel { private readonly string name; private readonly List meshes = 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(); private readonly GameObject rootGameObject; private readonly Material debugMaterial; public BrushModel(string name) { this.name = name; rootGameObject = new GameObject(name); rootGameObject.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); debugMaterial = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); } public void ImportMeshData(QModel model) { var subModels = model.SubModels; var surfaces = model.Surfaces; for (int modelIdx = 0; modelIdx < subModels.Length; ++modelIdx) { var modelGO = new GameObject($"SubModel_{modelIdx}"); modelGO.transform.SetParent(rootGameObject.transform); var headNode = subModels[modelIdx].GetHeadNode(model); var surfaceGroups = new Dictionary<(IntPtr, int), List>(); GroupSurfaces(headNode, surfaces, surfaceGroups); foreach (var group in surfaceGroups) { var key = group.Key; var groupGO = new GameObject($"T{key.Item1}_L{key.Item2}"); groupGO.transform.SetParent(modelGO.transform); CreateMeshFromSurfaces(group.Value, groupGO.name, groupGO); } } } public void Dispose() { foreach (var mesh in meshes) { UnityEngine.Object.Destroy(mesh); } meshes.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 void CreateMeshFromSurfaces(List surfaces, string key, GameObject parentGO) { 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.ToVector3().ToUnity()); 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); meshes.Add(mesh); CreateMeshObject(mesh, parentGO); } private void CreateMeshObject(Mesh mesh, GameObject parentGO) { var mf = parentGO.AddComponent(); mf.sharedMesh = mesh; var mr = parentGO.AddComponent(); mr.sharedMaterial = debugMaterial; mr.shadowCastingMode = ShadowCastingMode.Off; mr.receiveShadows = false; mr.lightProbeUsage = LightProbeUsage.Off; mr.reflectionProbeUsage = ReflectionProbeUsage.Off; } }