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<(string, 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); for (int i = 0; i < group.Value.Count; ++i) { CreateSurfaceMeshes(group.Value[i], $"{i}", groupGO); } } } } private void GroupSurfaces(QNode node, QSurface[] surfaces, Dictionary<(string, int), List> surfaceGroups) { if (node.contents < 0) // Leaf node return; for (int surfIdx = 0; surfIdx < node.numSurfaces; ++surfIdx) { var surface = surfaces[node.firstSurface + surfIdx]; string texName = surface.TextureInfo.Texture.name; int lightNum = surface.lightmapTextureNum; var key = (texName, 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 CreateNodeMeshes(QNode node, QSurface[] surfaces, GameObject parentGO) { if (node.contents < 0) // Leaf node return; var nodeGO = new GameObject("Node"); nodeGO.transform.SetParent(parentGO.transform); for (int surfIdx = 0; surfIdx < node.numSurfaces; ++surfIdx) { var surface = surfaces[node.firstSurface + surfIdx]; CreateSurfaceMeshes(surface, $"{node.firstSurface + surfIdx}", nodeGO); } foreach (var childNode in node.Children) { CreateNodeMeshes(childNode, surfaces, nodeGO); } } private void CreateSurfaceMeshes(QSurface surface, string key, GameObject nodeGO) { foreach (var polyVerts in surface.GetPolygons()) { tempVertices.Clear(); tempTextureUVs.Clear(); tempLightmapUVs.Clear(); tempIndices.Clear(); 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(0); tempIndices.Add((ushort) (index - 1)); tempIndices.Add(index); } Mesh mesh = new Mesh(); mesh.name = $"Surface_{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, nodeGO); } } private void CreateMeshObject(Mesh mesh, GameObject parentGO) { var meshGO = new GameObject(mesh.name); meshGO.transform.SetParent(parentGO.transform); var mf = meshGO.AddComponent(); mf.sharedMesh = mesh; var mr = meshGO.AddComponent(); mr.sharedMaterial = debugMaterial; mr.shadowCastingMode = ShadowCastingMode.Off; mr.receiveShadows = false; mr.lightProbeUsage = LightProbeUsage.Off; mr.reflectionProbeUsage = ReflectionProbeUsage.Off; } }