Files
FirstStrike.Unity/Assets/Editor/ModelImporter.cs
T
2025-05-13 03:08:33 +03:00

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);
}
}
}
}
}