using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEditor.PackageManager.UI; using SourceFormatParser.BSP; using static SourceFormatParser.BSP.SourceBSPStructs; using System.Linq; using Codice.Client.BaseCommands; using System; using System.IO; using uSrcTools; using Unity.VisualScripting.Antlr3.Runtime.Tree; public class ModelImporter : EditorWindow { [MenuItem("CustomTools/ModelImporter")] public static void ShowWindow() { EditorWindow.GetWindow(typeof(ModelImporter)); } private float _worldScale = 0.0254f; static Vector3 ToUnityVector(SourceFormatParser.Common.SourceVector v) { return new Vector3(v.x, v.z, v.y); } static List GetFaceVertexIds(SourceBSP bsp, dface_t face) { List faceVertexIds = new List(); for (int e = 0; e < face.numedges; ++e) { var edgeId = bsp.SurfEdge[e + face.firstedge]; bool flipped = false; if (edgeId < 0) { edgeId = -edgeId; flipped = true; } var edge = bsp.Edges[edgeId]; short a; short b; if (flipped) { a = edge.v.y; b = edge.v.x; } else { a = edge.v.x; b = edge.v.y; } if (e == 0) { faceVertexIds.Add(a); faceVertexIds.Add(b); } else { if (a != faceVertexIds[faceVertexIds.Count - 1]) { Debug.LogError("Unexpected vertex ID"); } else { if (e < face.numedges - 1) { faceVertexIds.Add(b); } } } } return faceVertexIds; } GameObject CreateGameObject(string name, Transform parent = null) { GameObject g = new GameObject(name); var mf = g.AddComponent(); mf.sharedMesh = new Mesh(); g.AddComponent(); if(parent != null) { g.transform.parent = parent; } g.transform.localPosition = Vector3.zero; g.transform.localRotation = Quaternion.identity; g.transform.localScale = Vector3.one; return g; } GameObject CreateMeshGameObject(string name, Transform parent = null) { var result = CreateGameObject(name, parent); var mf = result.AddComponent(); mf.sharedMesh = new Mesh(); result.AddComponent(); return result; } struct surface { public int index; public int texFlags; public int texID; public short dispinfo; public Vector3[] points; public Vector2[] uv; public Vector2[] uv2; public Color32[] cols; public int[] triangles; public int lightMapW; public int lightMapH; public Vector2 lightmapScale; public Vector2 lightmapOffset; } static surface BuildDispFace(SourceBSP bsp, int faceIndex) { var curFace = bsp.Faces[faceIndex]; var dispinfoId = curFace.dispinfo; Vector3[] vertices = new Vector3[4]; List disp_verts = new List(); List UVs = new List(); List cols = new List(); List indices = new List(); var curTexInfo = bsp.Texinfo[curFace.texinfo]; var curTexData = bsp.Texdata[curTexInfo.texdata]; int fEdge = curFace.firstedge; for (int i = 0; i < curFace.numedges; i++) { var sourceVector = (bsp.SurfEdge[fEdge + i] > 0 ? bsp.Vertexes[bsp.Edges[Mathf.Abs(bsp.SurfEdge[fEdge + i])].v.x] : bsp.Vertexes[bsp.Edges[Mathf.Abs(bsp.SurfEdge[fEdge + i])].v.y]); vertices[i] = ToUnityVector(sourceVector); } var curDisp = bsp.DispInfo[dispinfoId]; Vector3 startPos = ToUnityVector(curDisp.startPosition); float dist; float minDist = 0.1f; int minIndex = 0; for (int i = 0; i < 4; i++) { dist = Vector3.Distance(startPos, vertices[i]); if (dist < minDist) { minDist = dist; minIndex = i; } } Vector3 temp; for (int i = 0; i < minIndex; i++) { temp = vertices[0]; vertices[0] = vertices[1]; vertices[1] = vertices[2]; vertices[2] = vertices[3]; vertices[3] = temp; } Vector3 leftEdge = vertices[1] - vertices[0]; Vector3 rightEdge = vertices[2] - vertices[3]; int numEdgeVertices = (1 << curDisp.power) + 1; float subdivideScale = 1.0f / (float)(numEdgeVertices - 1); Vector3 leftEdgeStep = leftEdge * subdivideScale; Vector3 rightEdgeStep = rightEdge * subdivideScale; int firstVertex = 0; Vector3 leftEnd; Vector3 rightEnd; Vector3 leftRightSeg; Vector3 leftRightStep; int dispVertIndex; CDispVert dispVert; Vector3 flatVertex; Vector3 dispVertex; float scaleU = (float)1f / curTexData.width; float scaleV = (float)1f / curTexData.height; for (int i = 0; i < numEdgeVertices; i++) { leftEnd = leftEdgeStep * (float)i; leftEnd += vertices[0]; rightEnd = rightEdgeStep * (float)i; rightEnd += vertices[3]; leftRightSeg = rightEnd - leftEnd; leftRightStep = leftRightSeg * subdivideScale; for (int j = 0; j < numEdgeVertices; j++) { dispVertIndex = curDisp.m_iDispVertStart; dispVertIndex += i * numEdgeVertices + j; dispVert = bsp.DispVerts[dispVertIndex]; flatVertex = leftEnd + (leftRightStep * (float)j); dispVertex = ToUnityVector(dispVert.m_vVector) * (dispVert.m_flDist /* *scale*/ ); dispVertex += flatVertex; disp_verts.Add(dispVertex); var tvi = curTexInfo.textureVecsTexelsPerWorldUnits; float tU = Vector3.Dot(flatVertex, new Vector3(tvi[0].x, tvi[0].z, tvi[0].y)) + (curTexInfo.textureVecsTexelsPerWorldUnits[0].w); float tV = Vector3.Dot(flatVertex, new Vector3(tvi[1].x, tvi[1].z, tvi[1].y)) + (curTexInfo.textureVecsTexelsPerWorldUnits[1].w); UVs.Add(new Vector2(tU * scaleU, tV * scaleV)); cols.Add(new Color32((byte)(dispVert.m_flAlpha), 0, 0, 0)); } } int curIndex; for (int i = 0; i < numEdgeVertices - 1; i++) { for (int j = 0; j < numEdgeVertices - 1; j++) { curIndex = i * numEdgeVertices + j; if ((curIndex % 2) == 1) { curIndex += firstVertex; indices.Add(curIndex + 1); indices.Add(curIndex); indices.Add(curIndex + numEdgeVertices); indices.Add(curIndex + numEdgeVertices + 1); indices.Add(curIndex + 1); indices.Add(curIndex + numEdgeVertices); } else { curIndex += firstVertex; indices.Add(curIndex); indices.Add(curIndex + numEdgeVertices); indices.Add(curIndex + numEdgeVertices + 1); indices.Add(curIndex + 1); indices.Add(curIndex); indices.Add(curIndex + numEdgeVertices + 1); } } } /*for (int i = 0; i < disp_verts.Count; i++) { disp_verts[i] *= uSrcSettings.Inst.worldScale; }*/ surface f = new surface(); f.index = faceIndex; //f.flags = flags; f.dispinfo = dispinfoId; f.points = disp_verts.ToArray(); f.uv = UVs.ToArray(); //f.uv2 = UV2s; f.cols = cols.ToArray(); f.triangles = indices.ToArray(); //f.lightMapW = lightmapW; //f.lightMapH = lightmapH; return f; } GameObject LoadModel(SourceBSP bsp, dmodel_t model, bool skipSky, Transform parent = null) { var matPath = AssetDatabase.FindAssets("DefaultBspMaterial"); var assetPath = AssetDatabase.GUIDToAssetPath(matPath[0]); var mat = AssetDatabase.LoadAssetAtPath(assetPath); var result = CreateGameObject("BspModel", parent); var meshFilter = result.GetComponent(); var meshRenderer = result.GetComponent(); var mesh = meshFilter.sharedMesh; List tex = new List(); List vertices = new List(); List triangles = new List(); int dispCount = 0; for (int i = 0; i < model.numfaces; ++i) { int faceId = i + model.firstface; var face = bsp.Faces[faceId]; var faceTexInfo = bsp.Texinfo[face.texinfo]; if(skipSky && ((faceTexInfo.flags & SourceBSPStructs.SurfaceFlags.SURF_SKY) != 0)) { continue; } var vertexIndexOffset = vertices.Count; if (face.dispinfo == -1) { if (face.numedges != 0) { var faceVertexIds = GetFaceVertexIds(bsp, face); var verticesCount = faceVertexIds.Count; for (int v = 0; v < faceVertexIds.Count; ++v) { var srcVertex = bsp.Vertexes[faceVertexIds[v]]; vertices.Add(new Vector3(srcVertex.x, srcVertex.z, srcVertex.y)); } int trianglesCount = verticesCount - 2; int indicesCount = trianglesCount * 3; //int[] indices = new int[indicesCount]; for (int t = 0; t < trianglesCount; ++t) { triangles.Add(vertexIndexOffset + 0); triangles.Add(vertexIndexOffset + (t + 1)); triangles.Add(vertexIndexOffset + (t + 2)); } //Debug.Log($"Vertex IDs count: {faceVertexIds.Count} Edges count: {face.numedges}"); // break; //TEST!!! Only first face } else { Debug.Log("Face without edges"); } } else { var dispSurface = BuildDispFace(bsp, faceId); for(int t = 0; t< dispSurface.triangles.Length; ++t) { dispSurface.triangles[t] += vertexIndexOffset; } triangles.AddRange(dispSurface.triangles); vertices.AddRange(dispSurface.points); dispCount++; /* if (dispCount >= 800) { break; }*/ } } Debug.Log("Index format: " + mesh.indexFormat); mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; vertices = vertices.Select(v => v * _worldScale).ToList(); mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.RecalculateBounds(); mesh.RecalculateNormals(); meshRenderer.sharedMaterials = new Material[] { mat }; //Debug.Log($"Texinfos count: {tex.Count} Distinct: {tex.Distinct().Count()}") ; return result; } private bool SkipSky = true; private bool LoadProps = true; void LoadProp() { } private string _gameDir = @"C:\Program Files (x86)\Steam\steamapps\common\Counter-Strike Global Offensive\csgo"; private string GameMapsDir => System.IO.Path.Combine(_gameDir, "maps"); private string MapFilePath(string mapName) { return System.IO.Path.Combine(GameMapsDir, mapName + ".bsp"); } GameObject CreateCubeGameObject(string name, Transform parent = null) { var result = GameObject.CreatePrimitive(PrimitiveType.Cube); result.name = name; if (parent != null) { result.transform.parent = parent; } result.transform.localPosition = Vector3.zero; result.transform.localRotation = Quaternion.identity; result.transform.localScale = Vector3.one; return result; } private void LoadStaticProp(StaticPropLumpV11_t prop, List tree, Transform propsRoot, dgamelumpheader_t game) { var propType = prop.m_PropType; var propName = game.staticPropNames[propType]; GameObject propObject = CreateGameObject(propName.m_Name, propsRoot.transform); propObject.transform.localPosition = ToUnityVector(prop.m_Origin) * _worldScale; propObject.transform.localRotation = Quaternion.Euler(-prop.m_Angles.z, -prop.m_Angles.y, -prop.m_Angles.x); if (prop.m_UniformScale != 0.0f) { propObject.transform.localScale = new Vector3(prop.m_UniformScale, prop.m_UniformScale, prop.m_UniformScale); } var fileRecord = VPKFile.FindFile(tree, propName.m_Name); if (fileRecord != null) { SourceStudioModel model = new SourceStudioModel(); model.Load(propName.m_Name, (string path) => { var fileRecord = VPKFile.FindFile(tree, path); var propData = VPKFile.ReadFile(_gameDir, "pak01", fileRecord); Debug.Log("Loading file: " + path + " Size: " + propData.Length); MemoryStream propDataStream = new MemoryStream(propData); return propDataStream; }); Debug.Log("Model loaded"); model.GetInstance(propObject, false, 0); // SourceMDL mdl = new SourceMDL(propDataStream, false); // mdl.Header. } } void OnGUI() { SkipSky = GUILayout.Toggle(SkipSky, "Skip sky"); LoadProps = GUILayout.Toggle(LoadProps, "Load props"); if (GUILayout.Button("Import")) { string mapName = "de_inferno"; // "de_dust2"; //de_inferno GameObject mapRoot = CreateGameObject(mapName); SourceBSP bsp = new SourceBSP(MapFilePath(mapName)); LoadModel(bsp, bsp.Models[0], SkipSky, mapRoot.transform); /*foreach(var model in bsp.Models) { if(model.numfaces != 0) { LoadModel(bsp, model, SkipSky); } else { Debug.Log("WTF"); } }*/ var game = bsp.GameLump; var tree = VPKFile.ReadDirTree(System.IO.Path.Combine(_gameDir, "pak01_dir.vpk")); if (LoadProps) { var staticProps = (SourceFormatParser.BSP.SourceBSPStructs.StaticPropLumpV11_t[])game.staticProps; GameObject propsRoot = CreateGameObject("Props", mapRoot.transform); foreach (var prop in staticProps) { LoadStaticProp(prop, tree, propsRoot.transform, game); } } } } }