504 lines
15 KiB
C#
504 lines
15 KiB
C#
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<short> GetFaceVertexIds(SourceBSP bsp, dface_t face)
|
||
|
|
{
|
||
|
|
List<short> faceVertexIds = new List<short>();
|
||
|
|
|
||
|
|
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<MeshFilter>();
|
||
|
|
mf.sharedMesh = new Mesh();
|
||
|
|
g.AddComponent<MeshRenderer>();
|
||
|
|
|
||
|
|
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<MeshFilter>();
|
||
|
|
mf.sharedMesh = new Mesh();
|
||
|
|
result.AddComponent<MeshRenderer>();
|
||
|
|
|
||
|
|
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<Vector3> disp_verts = new List<Vector3>();
|
||
|
|
List<Vector2> UVs = new List<Vector2>();
|
||
|
|
List<Color32> cols = new List<Color32>();
|
||
|
|
List<int> indices = new List<int>();
|
||
|
|
|
||
|
|
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<Material>(assetPath);
|
||
|
|
|
||
|
|
var result = CreateGameObject("BspModel", parent);
|
||
|
|
var meshFilter = result.GetComponent<MeshFilter>();
|
||
|
|
var meshRenderer = result.GetComponent<MeshRenderer>();
|
||
|
|
|
||
|
|
var mesh = meshFilter.sharedMesh;
|
||
|
|
|
||
|
|
List<short> tex = new List<short>();
|
||
|
|
|
||
|
|
List<Vector3> vertices = new List<Vector3>();
|
||
|
|
List<int> triangles = new List<int>();
|
||
|
|
|
||
|
|
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<VPKFile.ExtensionRecord> 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);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|