Exemple #1
0
        protected override IEnumerable <string> OnFindResourcePaths(ValveBspFile bsp)
        {
            var items = Enumerable.Range(0, bsp.StaticProps.ModelCount)
                        .Select(x => bsp.StaticProps.GetModelName(x))
                        .Select(x =>
            {
                var mdl = StudioModelFile.FromProvider(x, bsp.PakFile, Program.Resources);
                if (mdl == null)
                {
                    return(null);
                }
                return(new
                {
                    Path = x,
                    VertexCount = mdl.TotalVertices,
                    FirstMaterialIndex = MaterialDictionary.GetResourceIndex(bsp, mdl.GetMaterialName(0, bsp.PakFile, Program.Resources))
                });
            })
                        .Where(x => x != null)
                        .GroupBy(x => x.FirstMaterialIndex)
                        .OrderByDescending(x => x.Count())
                        .SelectMany(x => x)
                        .ToArray();

            foreach (var item in items)
            {
                yield return(item.Path);

                var index = GetResourceIndex(item.Path);
                if (index == _vertexCounts.Count)
                {
                    _vertexCounts.Add(item.VertexCount);
                }
            }
        }
Exemple #2
0
        protected override IEnumerable <string> OnFindResourcePaths(ValveBspFile bsp)
        {
            for (var i = 0; i < bsp.TextureStringTable.Length; ++i)
            {
                yield return(bsp.GetTextureString(i));
            }

            for (var i = 0; i < bsp.StaticProps.ModelCount; ++i)
            {
                var modelName = bsp.StaticProps.GetModelName(i);
                var mdl       = StudioModelFile.FromProvider(modelName, bsp.PakFile, Program.Resources);

                if (mdl == null)
                {
                    continue;
                }

                for (var j = 0; j < mdl.MaterialCount; ++j)
                {
                    yield return(mdl.GetMaterialName(j, bsp.PakFile, Program.Resources));
                }
            }

            foreach (var entity in bsp.Entities)
            {
                switch (entity.ClassName)
                {
                case "move_rope":
                case "keyframe_rope":
                    yield return(entity["RopeMaterial"]);

                    break;
                }
            }
        }
Exemple #3
0
        /*IEnumerable<StudioVertex[]> LoadMdl(IResourceProvider Res) {
         *      for (int BodyPartIdx = 0; BodyPartIdx < Mdl.BodyPartCount; BodyPartIdx++) {
         *              StudioModelFile.StudioModel[] Models = Mdl.GetModels(BodyPartIdx).ToArray();
         *
         *              for (int ModelIndex = 0; ModelIndex < Models.Length; ModelIndex++) {
         *                      StudioModelFile.StudioModel Model = Models[ModelIndex];
         *                      StudioModelFile.StudioMesh[] Meshes = Mdl.GetMeshes(ref Model).ToArray();
         *
         *                      for (int MeshIndex = 0; MeshIndex < Meshes.Length; MeshIndex++) {
         *                              StudioModelFile.StudioMesh Mesh = Meshes[MeshIndex];
         *
         *                              StudioVertex[] StudioVerts = new StudioVertex[Tris.GetVertexCount(BodyPartIdx, ModelIndex, 0, MeshIndex)];
         *                              Tris.GetVertices(BodyPartIdx, ModelIndex, 0, MeshIndex, StudioVerts);
         *
         *                              int[] Indices = new int[Tris.GetIndexCount(BodyPartIdx, ModelIndex, 0, MeshIndex)];
         *                              Tris.GetIndices(BodyPartIdx, ModelIndex, 0, MeshIndex, Indices);
         *
         *                              List<StudioVertex> Vts = new List<StudioVertex>();
         *                              for (int i = 0; i < Indices.Length; i++)
         *                                      Vts.Add(StudioVerts[Indices[i]]);
         *
         *                              yield return Vts.ToArray();
         *                      }
         *              }
         *      }
         * }*/

        public static SourceMdl FromFile(string FilePath, IResourceProvider Res)
        {
            FilePath = FilePath.Substring(0, FilePath.Length - Path.GetExtension(FilePath).Length);

            SourceMdl Model = new SourceMdl();

            Model.Mdl   = StudioModelFile.FromProvider(FilePath + ".mdl", Res);
            Model.Verts = ValveVertexFile.FromProvider(FilePath + ".vvd", Res);
            Model.Tris  = ValveTriangleFile.FromProvider(FilePath + ".dx90.vtx", Model.Mdl, Model.Verts, Res);

            return(Model);
        }
Exemple #4
0
        static int ModelExtract(MaterialExtractOptions args)
        {
            SetBaseOptions(args);

            var mdl = StudioModelFile.FromProvider(args.InputPath, Resources);

            if (mdl == null)
            {
                Console.Error.WriteLine($"Unable to find model at path '{args.InputPath}'!");
                return(1);
            }

            for (var i = 0; i < mdl.MaterialCount; ++i)
            {
                var srcPath = mdl.GetMaterialName(i, Resources);

                if (!Resources.ContainsFile(srcPath))
                {
                    continue;
                }

                Console.WriteLine(srcPath);

                if (args.OutputPath != null)
                {
                    var dstPath = Path.Combine(args.OutputPath, srcPath);
                    var dstDir  = Path.GetDirectoryName(dstPath);

                    if (dstDir != null && !Directory.Exists(dstDir))
                    {
                        Directory.CreateDirectory(dstDir);
                    }

                    using (var src = Resources.OpenFile(srcPath))
                        using (var dst = File.Create(dstPath))
                        {
                            src.CopyTo(dst);
                        }
                }
            }

            return(0);
        }
Exemple #5
0
        static int ModelPatch(ModelPatchOptions args)
        {
            SetBaseOptions(args);

            var mdl = StudioModelFile.FromProvider(args.InputPath, Resources);

            if (mdl == null)
            {
                Console.Error.WriteLine($"Unable to find model at path '{args.InputPath}'!");
                return(1);
            }

            using (var outStream = new MemoryStream())
            {
                using (var inStream = Resources.OpenFile(args.InputPath))
                {
                    inStream.CopyTo(outStream);
                }

                var empty = new List <Range> {
                    new Range(mdl.FileHeader.Length, int.MaxValue)
                };

                var commands                        = new List <ReplacementCommand>();
                var shouldOverrideFlags             = false;
                StudioModelFile.Flags overrideFlags = 0;

                if (args.Replace.Any() || args.Flags != null)
                {
                    WriteVerboseHeader("Commands");

                    foreach (var replaceStr in args.Replace)
                    {
                        ReplacementCommand cmd;
                        if (!ReplacementCommand.TryParse(replaceStr, out cmd))
                        {
                            Console.Error.WriteLine($"Unable to parse replacement command '{replaceStr}'.");
                            return(1);
                        }

                        WriteVerbose($"Replacing {cmd.Type}[{cmd.Index}] with \"{cmd.Value}\"");

                        commands.Add(cmd);
                    }

                    WriteSeparator();

                    if (args.Flags != null)
                    {
                        shouldOverrideFlags = true;

                        foreach (var flagStr in args.Flags.Split(',', ';', '|'))
                        {
                            var trimmedString = flagStr.TrimStart();

                            int flagInt;
                            StudioModelFile.Flags flagValue;
                            var numberStyle = NumberStyles.Integer;

                            if (trimmedString.StartsWith("0x"))
                            {
                                numberStyle   = NumberStyles.HexNumber;
                                trimmedString = trimmedString.Substring("0x".Length);
                            }

                            if (int.TryParse(trimmedString, numberStyle, CultureInfo.InvariantCulture, out flagInt))
                            {
                                overrideFlags |= (StudioModelFile.Flags)flagInt;
                            }
                            else if (Enum.TryParse(trimmedString, true, out flagValue))
                            {
                                overrideFlags |= flagValue;
                            }
                            else
                            {
                                Console.Error.WriteLine($"Unexpected model flag value \"{trimmedString}\".");
                                return(1);
                            }
                        }

                        WriteVerbose($"Override model flags: {overrideFlags:x}:");

                        foreach (StudioModelFile.Flags value in Enum.GetValues(typeof(StudioModelFile.Flags)))
                        {
                            if ((overrideFlags & value) != 0)
                            {
                                WriteVerbose($" - {value}");
                            }
                        }

                        WriteSeparator();
                    }
                }

                WriteVerboseHeader("Input Model");
                WriteVerbose($"Existing model flags: {mdl.FileHeader.Flags:x}:");

                foreach (StudioModelFile.Flags value in Enum.GetValues(typeof(StudioModelFile.Flags)))
                {
                    if ((mdl.FileHeader.Flags & value) != 0)
                    {
                        WriteVerbose($" - {value}");
                    }
                }

                WriteSeparator();
                WriteVerbose($"Model has {mdl.TextureDirectories.Count} material directories:");

                var i = 0;
                foreach (var texDir in mdl.TextureDirectories)
                {
                    var indexOffset = mdl.FileHeader.CdTextureIndex + sizeof(int) * i;
                    var index       = ReadInt32(outStream, indexOffset);

                    ClearRange(outStream, new Range(index, index + texDir.Length + 1), empty);

                    if (!commands.Any(x => x.Type == ReplacementType.Directory && (x.Wildcard || x.Index == i)))
                    {
                        commands.Add(new ReplacementCommand(ReplacementType.Directory, i, texDir));
                    }

                    WriteVerbose($" [{i}]: {texDir}");

                    ++i;
                }

                WriteSeparator();
                WriteVerbose($"Model has {mdl.TextureNames.Count} material names:");

                i = 0;
                foreach (var texName in mdl.TextureNames)
                {
                    var texOffset = mdl.FileHeader.TextureIndex + Marshal.SizeOf <StudioModelFile.StudioTexture>() * i;
                    var index     = texOffset + mdl.Textures[i].NameIndex;

                    ClearRange(outStream, new Range(index, index + texName.Length + 1), empty);

                    if (!commands.Any(x => x.Type == ReplacementType.Name && (x.Wildcard || x.Index == i)))
                    {
                        commands.Add(new ReplacementCommand(ReplacementType.Name, i, texName));
                    }

                    WriteVerbose($" [{i}]: {texName}");

                    ++i;
                }

                if (args.Replace.Any() || shouldOverrideFlags)
                {
                    WriteVerboseHeader("Applying Commands");

                    if (args.Replace.Any())
                    {
                        commands.Sort((a, b) => a.Type != b.Type
                            ? a.Type.CompareTo(b.Type)
                            : a.Index - b.Index);

                        foreach (var cmd in commands)
                        {
                            switch (cmd.Type)
                            {
                            case ReplacementType.Directory:
                            {
                                var startIndex = cmd.Wildcard ? 0 : cmd.Index;
                                var endIndex   = cmd.Wildcard ? mdl.TextureDirectories.Count : cmd.Index + 1;

                                for (var index = startIndex; index < endIndex; ++index)
                                {
                                    var indexOffset = mdl.FileHeader.CdTextureIndex + sizeof(int) * index;
                                    var original    = mdl.TextureDirectories[index];
                                    var newIndex    = AppendString(outStream, cmd.GetFormattedValue(index, original), empty);

                                    WriteVerbose($" - Writing directory index: 0x{newIndex:x8}, at: 0x{indexOffset:x8}.");
                                    WriteInt32(outStream, indexOffset, newIndex);
                                }
                                break;
                            }

                            case ReplacementType.Name:
                            {
                                var startIndex = cmd.Wildcard ? 0 : cmd.Index;
                                var endIndex   = cmd.Wildcard ? mdl.TextureNames.Count : cmd.Index + 1;

                                for (var index = startIndex; index < endIndex; ++index)
                                {
                                    var texOffset   = mdl.FileHeader.TextureIndex + Marshal.SizeOf <StudioModelFile.StudioTexture>() * index;
                                    var indexOffset = texOffset + StudioModelFile.StudioTexture.NameIndexOffset;
                                    var original    = mdl.TextureNames[index];
                                    var newIndex    = AppendString(outStream, cmd.GetFormattedValue(index, original), empty);

                                    WriteVerbose($" - Writing name index: 0x{newIndex:x8}, at: 0x{indexOffset:x8}.");
                                    WriteInt32(outStream, indexOffset, newIndex - texOffset);
                                }
                                break;
                            }
                            }
                        }

                        var length = empty.Last().Min;

                        outStream.SetLength(length);

                        WriteVerbose($" - Writing file length: 0x{length:x8}, at: 0x{StudioModelFile.Header.LengthOffset:x8}.");
                        WriteInt32(outStream, StudioModelFile.Header.LengthOffset, length);
                    }

                    if (shouldOverrideFlags)
                    {
                        WriteVerbose($" - Writing model flags: 0x{overrideFlags:x}, at: 0x{StudioModelFile.Header.FlagsOffset:x8}.");
                        WriteInt32(outStream, StudioModelFile.Header.FlagsOffset, (int)overrideFlags);
                    }
                }

                if (!string.IsNullOrEmpty(args.OutputPath))
                {
                    WriteVerboseHeader("Output");

                    var fullOutPath = new FileInfo(args.OutputPath).FullName;

                    WriteVerbose($"Writing output to \"{fullOutPath}\"...");

                    outStream.Seek(0, SeekOrigin.Begin);

                    var dir = Path.GetDirectoryName(fullOutPath);
                    if (dir != null && !Directory.Exists(dir))
                    {
                        Directory.CreateDirectory(dir);
                    }

                    using (var fileStream = File.Create(fullOutPath))
                    {
                        outStream.CopyTo(fileStream);
                    }
                }
            }

            return(0);
        }
Exemple #6
0
        public StudioModelPage GetStudioModelPage([Url] string map, [Url] int index)
        {
            var bsp = Program.GetMap(map);

            var info = IndexController.GetPageLayout(bsp, StudioModelDictionary.GetResourceCount(bsp),
                                                     StudioModelPage.VerticesPerPage,
                                                     null, i => StudioModelDictionary.GetVertexCount(bsp, i)).Skip(index).FirstOrDefault();

            var first = info?.First ?? StudioModelDictionary.GetResourceCount(bsp);
            var count = info?.Count ?? 0;

            var page = new StudioModelPage();

            StudioVertex[] vertices = null;
            int[]          indices  = null;

            for (var i = 0; i < count; ++i)
            {
                var mdlPath = StudioModelDictionary.GetResourcePath(bsp, first + i);
                var vvdPath = mdlPath.Replace(".mdl", ".vvd");
                var vtxPath = mdlPath.Replace(".mdl", ".dx90.vtx");

                var mdlFile = StudioModelFile.FromProvider(mdlPath, bsp.PakFile, Program.Resources);
                var vvdFile = ValveVertexFile.FromProvider(vvdPath, bsp.PakFile, Program.Resources);
                var vtxFile = ValveTriangleFile.FromProvider(vtxPath, mdlFile, vvdFile, bsp.PakFile, Program.Resources);

                StudioModel mdl;
                page.Models.Add(mdl = new StudioModel());

                for (var j = 0; j < mdlFile.BodyPartCount; ++j)
                {
                    SmdBodyPart smdBodyPart;
                    mdl.BodyParts.Add(smdBodyPart = new SmdBodyPart
                    {
                        Name = mdlFile.GetBodyPartName(j)
                    });

                    smdBodyPart.Models.AddRange(mdlFile.GetModels(j).Select((model, modelIndex) =>
                    {
                        var smdModel = new SmdModel();

                        smdModel.Meshes.AddRange(mdlFile.GetMeshes(ref model).Select((mesh, meshIndex) =>
                        {
                            var vertexCount = vtxFile.GetVertexCount(j, modelIndex, 0, meshIndex);
                            if (vertices == null || vertices.Length < vertexCount)
                            {
                                vertices = new StudioVertex[MathHelper.NextPowerOfTwo(vertexCount)];
                            }

                            var indexCount = vtxFile.GetIndexCount(j, modelIndex, 0, meshIndex);
                            if (indices == null || indices.Length < indexCount)
                            {
                                indices = new int[MathHelper.NextPowerOfTwo(indexCount)];
                            }

                            vtxFile.GetVertices(j, modelIndex, 0, meshIndex, vertices);
                            vtxFile.GetIndices(j, modelIndex, 0, meshIndex, indices);

                            var meshData = GetOrCreateMeshData(bsp, page,
                                                               mdlFile.GetMaterialName(mesh.Material, bsp.PakFile, Program.Resources), false);

                            var meshElem = new MeshElement
                            {
                                Mode         = PrimitiveType.Triangles,
                                IndexOffset  = meshData.Indices.Count,
                                VertexOffset = meshData.Vertices.Count
                            };

                            var smdMesh = new SmdMesh
                            {
                                MeshId   = mesh.MeshId,
                                Material = meshData.MaterialIndex,
                                Element  = meshData.Elements.Count
                            };

                            meshData.BeginPrimitive();

                            for (var k = 0; k < vertexCount; ++k)
                            {
                                var vertex = vertices[k];

                                meshData.VertexAttribute(VertexAttribute.Position, vertex.Position);
                                meshData.VertexAttribute(VertexAttribute.Normal, vertex.Normal);
                                meshData.VertexAttribute(VertexAttribute.Uv, new Vector2(vertex.TexCoordX, vertex.TexCoordY));
                                meshData.CommitVertex();
                            }

                            meshData.CommitPrimitive(PrimitiveType.Triangles, indices.Take(indexCount));

                            meshElem.IndexCount  = meshData.Indices.Count - meshElem.IndexOffset;
                            meshElem.VertexCount = meshData.Vertices.Count - meshElem.VertexOffset;

                            meshData.Elements.Add(meshElem);

                            return(smdMesh);
                        }));

                        return(smdModel);
                    }));
                }
            }

            return(page);
        }
Exemple #7
0
        static FoamModel LoadMdl(string FilePath)
        {
            FilePath = FilePath.Substring(0, FilePath.Length - Path.GetExtension(FilePath).Length);

            StudioModelFile Mdl = StudioModelFile.FromProvider(FilePath + ".mdl", VFS);

            if (Mdl == null)
            {
                throw new FileNotFoundException("File not found", FilePath + ".mdl");
            }

            ValveVertexFile   Verts = ValveVertexFile.FromProvider(FilePath + ".vvd", VFS);
            ValveTriangleFile Tris  = ValveTriangleFile.FromProvider(FilePath + ".dx90.vtx", Mdl, Verts, VFS);


            // Foam stuff

            List <FoamMaterial> FoamMaterials = new List <FoamMaterial>();

            string[] TexNames = Mdl.TextureNames.ToArray();

            for (int i = 0; i < Mdl.MaterialCount; i++)
            {
                string MatName      = Mdl.GetMaterialName(i, VFS);
                string ShortMatName = Path.GetFileNameWithoutExtension(MatName);

                ValveMaterialFile VMF = ValveMaterialFile.FromProvider(MatName, VFS);
                FoamMaterials.Add(new FoamMaterial(ShortMatName, new FoamTexture[] { new FoamTexture(TexNames[i], FoamTextureType.Diffuse) }));
            }

            List <FoamMesh> FoamMeshes = new List <FoamMesh>();
            List <FoamBone> FoamBones  = new List <FoamBone>();

            StudioModelFile.StudioBone[] Bones = Mdl.GetBones();
            for (int i = 0; i < Bones.Length; i++)
            {
                string BoneName = Mdl.GetBoneName(i);
                FoamBones.Add(new FoamBone(BoneName, Bones[i].Parent, Matrix4x4.Identity));
            }

            // BODIES
            for (int BodyPartIdx = 0; BodyPartIdx < Mdl.BodyPartCount; BodyPartIdx++)
            {
                StudioModelFile.StudioModel[] StudioModels = Mdl.GetModels(BodyPartIdx).ToArray();

                // MODELS
                for (int ModelIdx = 0; ModelIdx < StudioModels.Length; ModelIdx++)
                {
                    ref StudioModelFile.StudioModel StudioModel  = ref StudioModels[ModelIdx];
                    StudioModelFile.StudioMesh[]    StudioMeshes = Mdl.GetMeshes(ref StudioModel).ToArray();

                    // MESHES
                    for (int MeshIdx = 0; MeshIdx < StudioMeshes.Length; MeshIdx++)
                    {
                        ref StudioModelFile.StudioMesh StudioMesh = ref StudioMeshes[MeshIdx];

                        StudioVertex[] StudioVerts = new StudioVertex[Tris.GetVertexCount(BodyPartIdx, ModelIdx, 0, MeshIdx)];
                        Tris.GetVertices(BodyPartIdx, ModelIdx, 0, MeshIdx, StudioVerts);

                        int[] Indices = new int[Tris.GetIndexCount(BodyPartIdx, ModelIdx, 0, MeshIdx)];
                        Tris.GetIndices(BodyPartIdx, ModelIdx, 0, MeshIdx, Indices);


                        // Foam converted
                        List <FoamVertex3> FoamVerts = new List <FoamVertex3>(StudioVerts.Select(V => {
                            // TODO: CCW
                            nVector2 UV = new nVector2(V.TexCoordX, V.TexCoordY);
                            return(new FoamVertex3(Conv(V.Position), UV, nVector2.Zero, Conv(V.Normal), nVector3.Zero, FoamColor.White));
                        }));

                        List <FoamBoneInfo> FoamInfo = new List <FoamBoneInfo>(StudioVerts.Select(V => {
                            FoamBoneInfo Info = new FoamBoneInfo();
                            Info.Bone1        = V.BoneWeights.Bone0;
                            Info.Bone2        = V.BoneWeights.Bone1;
                            Info.Bone3        = V.BoneWeights.Bone1;

                            Info.Weight1 = V.BoneWeights.Weight0;
                            Info.Weight2 = V.BoneWeights.Weight1;
                            Info.Weight3 = V.BoneWeights.Weight2;
                            return(Info);
                        }));

                        List <ushort> FoamInds = new List <ushort>(Indices.Select(I => (ushort)I));

                        if (ConvertToCCW)
                        {
                            FoamInds.Reverse();
                        }

                        FoamMeshes.Add(new FoamMesh(FoamVerts.ToArray(), FoamInds.ToArray(), FoamInfo.ToArray(), StudioModel.Name + ";" + MeshIdx, StudioMesh.Material));

                        /*List<FoamVertex3> Vts = new List<FoamVertex3>();
                         * for (int i = 0; i < Indices.Length; i++) {
                         *      ref StudioVertex V = ref StudioVerts[Indices[i]];
                         *      Vts.Add(new Vertex3(new Vector3(V.Position.X, V.Position.Y, V.Position.Z), new Vector2(V.TexCoordX, 1.0f - V.TexCoordY), Color.White));
                         * }*/

                        /*string MatName = MaterialNames[StudioMesh.Material];
                         * Material Mat = Engine.GetMaterial(MatName);
                         *
                         * if (Mat == Engine.GetMaterial("error")) {
                         *      Mat = ValveMaterial.CreateMaterial(MatName);
                         *
                         *      if (Mat != Engine.GetMaterial("error"))
                         *              Engine.RegisterMaterial(Mat);
                         * }
                         *
                         * libTechMesh Msh = new libTechMesh(Vts.ToArray(), Mat);
                         * Msh.Name = StudioModel.Name;
                         * Model.AddMesh(Msh);*/
                    }
                }
Exemple #8
0
        public GameObject GenerateMesh(BspToUnity bspToUnity, string modelName, string vvdPath, string vtxPath, IResourceProvider resourceProvider)
        {
            Mdl = StudioModelFile.FromProvider(modelName, resourceProvider);
            Vvd = ValveVertexFile.FromProvider(vvdPath, resourceProvider);
            Vtx = ValveTriangleFile.FromProvider(vtxPath, Mdl, Vvd, resourceProvider);

            var parent = new GameObject();

            for (int b = 0; b < Mdl.BodyPartCount; b++)
            {
                var models = Mdl.GetModels(b);

                var e          = models.GetEnumerator();
                var modelIndex = 0;

                while (e.MoveNext())
                {
                    var model     = e.Current;
                    var meshes    = Mdl.GetMeshes(ref model);
                    var meshIndex = 0;

                    foreach (var m in meshes)
                    {
                        try
                        {
                            var vertexCount = Vtx.GetVertexCount(b, modelIndex, 0, meshIndex);
                            var sv          = new StudioVertex[vertexCount];

                            var indexCount = Vtx.GetIndexCount(b, modelIndex, 0, meshIndex);
                            var indices    = new int[indexCount];

                            Vtx.GetVertices(b, modelIndex, 0, meshIndex, sv);
                            Vtx.GetIndices(b, modelIndex, 0, meshIndex, indices);

                            var go = new GameObject();
                            go.transform.SetParent(parent.transform);
                            go.transform.localPosition = UnityEngine.Vector3.zero;
                            go.transform.localRotation = Quaternion.identity;
                            go.transform.localScale    = UnityEngine.Vector3.one;

                            var mr        = go.AddComponent <MeshRenderer>();
                            var mf        = go.AddComponent <MeshFilter>();
                            var modelMesh = new Mesh();

                            var vertices = new List <UnityEngine.Vector3>();
                            var normals  = new List <UnityEngine.Vector3>();
                            var uvs      = new List <UnityEngine.Vector2>();
                            var tris     = new List <int>();

                            mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
                            mr.receiveShadows    = false;

                            // verts, normals, uvs
                            for (int j = 0; j < sv.Length; j++)
                            {
                                vertices.Add(sv[j].Position.ToUVector() * bspToUnity.Options.WorldScale);
                                normals.Add(sv[j].Normal.ToUVector());
                                uvs.Add(new UnityEngine.Vector2(sv[j].TexCoordX, sv[j].TexCoordY));
                            }

                            // tris
                            tris.AddRange(indices);

                            // build mesh
                            modelMesh.SetVertices(vertices);
                            modelMesh.SetUVs(0, uvs);
                            modelMesh.SetNormals(normals);
                            modelMesh.SetTriangles(tris, 0);
                            modelMesh.RecalculateNormals();
                            mf.mesh = modelMesh;

                            var matName = Mdl.GetMaterialName(m.Material, resourceProvider);
                            var mat     = bspToUnity.ApplyMaterial(mr, matName);
                            go.name = matName;
                        }
                        catch
                        {
                            Debug.Log("NOPE: " + Vtx.NumLods);
                            continue;
                        }

                        meshIndex++;
                    }

                    modelIndex++;
                }
            }

            return(parent);
        }