Kursach_StealthAction/Assets/Tremble/IO/BSP/BspParser.cs
2026-10-11 19:16:11 +03:00

461 lines
13 KiB
C#

//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
// This class based on BSP Map Tools for Unity by John Evans (evans3d512@gmail.com)
//
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
namespace TinyGoose.Tremble
{
public static class BspParser
{
public static MapBsp Parse(string bspPath, float importScale, BspOptions options = null, MapGroupData[] foundGroups = null)
{
StreamReadIO reader = new(bspPath);
string signature = new (reader.ReadChars(4));
int version = reader.ReadInt32();
if (signature != "IBSP" || version != 46)
{
Debug.LogWarning($"BSP Importer: Unknown signature or version. Expected IBSP, 46. Got {signature}, {version}");
}
int entitiesOffset = reader.ReadInt32();
int entitiesLength = reader.ReadInt32(); // 16
int texturesOffset = reader.ReadInt32();
int texturesLength = reader.ReadInt32(); // 24
int planesOffset = reader.ReadInt32();
int planesLength = reader.ReadInt32();
int nodesOffset = reader.ReadInt32();
int nodesLength = reader.ReadInt32();
int leavesOffset = reader.ReadInt32();
int leavesLength = reader.ReadInt32();
int leafFacesOffset = reader.ReadInt32();
int leaffacesLength = reader.ReadInt32();
reader.Seek(64);
int modelsOffset = reader.ReadInt32();
int modelsLength = reader.ReadInt32();
reader.Seek(88); // Vertices
int verticesOffset = reader.ReadInt32();
int verticesLength = reader.ReadInt32();
int mVerticesOffset = reader.ReadInt32();
int mVerticesLength = reader.ReadInt32();
reader.Seek(112); // Faces
int facesOffset = reader.ReadInt32();
int facesLength = reader.ReadInt32();
int vertexCount = verticesLength / 44;
int mVertexCount = mVerticesLength / 4;
int faceCount = facesLength / 104;
int texCount = texturesLength / 72;
int modelCount = modelsLength / 40;
int planesCount = planesLength / 16;
int nodesCount = nodesLength / 36;
int leavesCount = leavesLength / 48;
int leafFacesCount = leaffacesLength / 4;
MapBsp newMapBsp = new() {
MapName = Path.GetFileNameWithoutExtension(bspPath),
ImportScale = importScale,
Options = options ?? new(),
TexInfos = new TexInfo[texCount],
Planes = new Plane[planesCount],
Nodes = new BspNode[nodesCount],
Leaves = new BspLeaf[leavesCount],
LeafFaces = new int[leafFacesCount],
Models = new BspModel[modelCount],
Vertices = new BspVertex[vertexCount],
Faces = new BspFace[faceCount],
MeshVertices = new int[mVertexCount],
Entities = new(),
UsedFaces = new bool[faceCount],
Groups = foundGroups,
};
reader.Seek(texturesOffset);
for (int i = 0; i < texCount; i++)
{
string tempName = reader.ReadString(64);
if (tempName.StartsWithInvariant("textures/"))
{
tempName = tempName.Substring("textures/".Length);
}
newMapBsp.TexInfos[i] = new()
{
Name = tempName,
Flags = reader.ReadInt32(),
Contents = reader.ReadInt32(),
};
}
reader.Seek(planesOffset);
for (int i = 0; i < planesCount; i++)
{
Vector3 planeNormal = reader.ReadVector3(convertToQ3: true);
newMapBsp.Planes[i] = new()
{
normal = planeNormal,
distance = reader.ReadSingle() * importScale,
};
}
reader.Seek(leavesOffset);
for (int i = 0; i < leavesCount; i++)
{
newMapBsp.Leaves[i] = new()
{
VisCluster = reader.ReadInt32(),
PortalArea = reader.ReadInt32(),
Min = reader.ReadVector3Int(),
Max = reader.ReadVector3Int(),
LeafFaceStartIdx = reader.ReadInt32(),
NumLeafFaces = reader.ReadInt32(),
LeafBrushStartIdx = reader.ReadInt32(),
NumLeafBrushes = reader.ReadInt32(),
};
}
reader.Seek(nodesOffset);
for (int i = 0; i < nodesCount; i++)
{
newMapBsp.Nodes[i] = new()
{
PlaneIndex = reader.ReadInt32(),
Child1 = reader.ReadInt32(),
Child2 = reader.ReadInt32(),
Min = reader.ReadVector3Int(),
Max = reader.ReadVector3Int(),
};
}
reader.Seek(leafFacesOffset);
for (int i = 0; i < leafFacesCount; i++)
{
newMapBsp.LeafFaces[i] = reader.ReadInt32();
}
reader.Seek(modelsOffset);
for (int i = 0; i < modelCount; i++)
{
newMapBsp.Models[i] = new()
{
Min = reader.ReadVector3(convertToQ3: true, scale: importScale),
Max = reader.ReadVector3(convertToQ3: true, scale: importScale),
StartFaceIdx = reader.ReadInt32(),
NumFaces = reader.ReadInt32(),
StartBrushIdx = reader.ReadInt32(),
NumBrushes = reader.ReadInt32(),
};
}
reader.Seek(verticesOffset);
for (int i = 0; i < vertexCount; i++)
{
newMapBsp.Vertices[i] = new()
{
Position = reader.ReadVector3(convertToQ3: true, scale: importScale),
TexCoord = reader.ReadUV(),
LightmapCoord = reader.ReadUV(),
Normal = Vector3.Normalize(reader.ReadVector3(convertToQ3: true)),
Color = reader.ReadColour(),
};
}
reader.Seek(facesOffset);
for (int i = 0; i < faceCount; i++)
{
newMapBsp.Faces[i] = new()
{
TexID = reader.ReadInt32(),
Effect = reader.ReadInt32(),
Type = reader.ReadInt32(),
VertexStartIdx = reader.ReadInt32(),
NumVertices = reader.ReadInt32(),
MeshVertexStartIdx = reader.ReadInt32(),
NumMeshVertices = reader.ReadInt32(),
LightmapIndex = reader.ReadInt32(),
LightmapStart = reader.ReadVector2Int(),
LightmapSize = reader.ReadVector2Int(),
LightmapOrigin = reader.ReadVector3Int(),
LightmapVector1 = reader.ReadVector3(),
LightmapVector2 = reader.ReadVector3(),
Normal = reader.ReadVector3(convertToQ3: true),
Size = reader.ReadVector2Int(),
};
}
reader.Seek(mVerticesOffset);
for (int i = 0; i < mVertexCount; i++)
{
newMapBsp.MeshVertices[i] = reader.ReadInt32();
}
// Read entity key/value pairs
reader.Seek(entitiesOffset);
List<string> kvPairs = new();
int entityNumber = 0;
int endOfEntities = entitiesOffset + entitiesLength - 1;
int charsToRead = endOfEntities - reader.Position;
string entities = new(reader.ReadChars(charsToRead));
TokenParser tokenParser = new(entities);
do
{
ReadOnlySpan<char> token = tokenParser.ReadToken();
if (token == null || token.Length == 0)
{
kvPairs.Add("");
continue;
}
switch (token[0])
{
case '{':
{
continue;
}
case '}':
{
Dictionary<string, string> entries = new();
for (int kvIdx = 0; kvIdx < kvPairs.Count; kvIdx += 2)
{
entries.Add(kvPairs[kvIdx], kvPairs[kvIdx+1]);
}
newMapBsp.Entities.Add(new() { SerialNumber = entityNumber++, Entries = entries, ImportScale = newMapBsp.ImportScale});
kvPairs.Clear();
continue;
}
default:
{
kvPairs.Add(token.ToString());
break;
}
}
} while (!tokenParser.IsAtEnd);
return newMapBsp;
}
private static float GetTriangleAreaSqr(in Vector3 v1, in Vector3 v2, in Vector3 v3)
{
// Sqr Magnitudes inlined!
float ax = v1.x - v2.x;
float ay = v1.y - v2.y;
float az = v1.z - v2.z;
float a = ax * ax + ay * ay + az * az;
float bx = v2.x - v3.x;
float by = v2.y - v3.y;
float bz = v2.z - v3.z;
float b = bx * bx + by * by + bz * bz;
float cx = v2.x - v3.x;
float cy = v2.y - v3.y;
float cz = v2.z - v3.z;
float c = cx * cx + cy * cy + cz * cz;
float s = (a + b + c) * 0.25f;
float surfAreaSqr = Mathf.Sqrt(s * (s - a) * (s - b) * (s - c));
return float.IsNaN(surfAreaSqr) ? 0f : surfAreaSqr;
}
public static void GetFaceList(MapBsp mapBsp, BspLeaf leaf, List<int> receiver)
{
int faceMin = mapBsp.Models[0].StartFaceIdx;
int faceMax = faceMin + mapBsp.Models[0].NumFaces;
for (int faceOffset = leaf.LeafFaceStartIdx; faceOffset < leaf.LeafFaceStartIdx + leaf.NumLeafFaces; faceOffset++)
{
int leafFaceIdx = mapBsp.LeafFaces[faceOffset];
if (leafFaceIdx < faceMin || leafFaceIdx > faceMax)
continue;
if (mapBsp.UsedFaces[leafFaceIdx])
continue;
receiver.Add(leafFaceIdx);
mapBsp.UsedFaces[leafFaceIdx] = true;
}
}
public static void GetFaceList(MapBsp mapBsp, BspNode node, List<int> receiver)
{
if (node.Child1 < 0)
{
GetFaceList(mapBsp, mapBsp.Leaves[-node.Child1 - 1], receiver);
}
else
{
GetFaceList(mapBsp, mapBsp.Nodes[node.Child1], receiver);
}
if (node.Child2 < 0)
{
GetFaceList(mapBsp, mapBsp.Leaves[-node.Child2 - 1], receiver);
}
else
{
GetFaceList(mapBsp, mapBsp.Nodes[node.Child2], receiver);
}
}
private static void GetSurfaceAreaSqr(MapBsp mapBsp, BspLeaf leaf, ref float receiver, HashSet<int> faceList)
{
int faceMin = mapBsp.Models[0].StartFaceIdx;
int faceMax = faceMin + mapBsp.Models[0].NumFaces;
for (int faceOffset = leaf.LeafFaceStartIdx; faceOffset < leaf.LeafFaceStartIdx + leaf.NumLeafFaces; faceOffset++)
{
if (mapBsp.LeafFaces[faceOffset] < faceMin || mapBsp.LeafFaces[faceOffset] > faceMax)
continue;
int leafFaceIdx = mapBsp.LeafFaces[faceOffset];
if (faceList.Contains(leafFaceIdx))
continue;
BspFace meshFace = mapBsp.Faces[leafFaceIdx];
for (int vertexOffset = meshFace.MeshVertexStartIdx; vertexOffset < meshFace.MeshVertexStartIdx + meshFace.NumMeshVertices; vertexOffset += 3)
{
receiver += GetTriangleAreaSqr
(
mapBsp.Vertices[mapBsp.MeshVertices[vertexOffset + 0] + meshFace.VertexStartIdx].Position,
mapBsp.Vertices[mapBsp.MeshVertices[vertexOffset + 1] + meshFace.VertexStartIdx].Position,
mapBsp.Vertices[mapBsp.MeshVertices[vertexOffset + 2] + meshFace.VertexStartIdx].Position
);
}
faceList.Add(leafFaceIdx);
}
}
public static void GetSurfaceAreaSqr(MapBsp mapBsp, BspNode node, ref float receiver, HashSet<int> faceList)
{
if (node.Child1 < 0)
{
GetSurfaceAreaSqr(mapBsp, mapBsp.Leaves[-node.Child1 - 1], ref receiver, faceList);
}
else
{
GetSurfaceAreaSqr(mapBsp, mapBsp.Nodes[node.Child1], ref receiver, faceList);
}
if (node.Child2 < 0)
{
GetSurfaceAreaSqr(mapBsp, mapBsp.Leaves[-node.Child2 - 1], ref receiver, faceList);
}
else
{
GetSurfaceAreaSqr(mapBsp, mapBsp.Nodes[node.Child2], ref receiver, faceList);
}
}
public static void DumpToFile(this MapBsp mapBsp, string file)
{
using FileStream fs = new(file, FileMode.Create, FileAccess.Write);
using StreamWriter sw = new(fs);
sw.WriteLine($"m_TexInfos[{mapBsp.TexInfos.Length}]:");
for (int i = 0; i < mapBsp.TexInfos.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.TexInfos[i].Contents} {mapBsp.TexInfos[i].Flags} {mapBsp.TexInfos[i].Name}");
}
sw.WriteLine();
sw.WriteLine($"bsp.Planes[{mapBsp.Planes.Length}]:");
for (int i = 0; i < mapBsp.Planes.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.Planes[i].normal} {mapBsp.Planes[i].distance}");
}
sw.WriteLine();
sw.WriteLine($"bsp.Nodes[{mapBsp.Nodes.Length}]:");
for (int i = 0; i < mapBsp.Nodes.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.Nodes[i].Child1} {mapBsp.Nodes[i].Child2} {mapBsp.Nodes[i].PlaneIndex} {mapBsp.Nodes[i].Min} {mapBsp.Nodes[i].Max}");
}
sw.WriteLine();
sw.WriteLine($"bsp.Leaves[{mapBsp.Leaves.Length}]:");
for (int i = 0; i < mapBsp.Leaves.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.Leaves[i].Min} {mapBsp.Leaves[i].Max} {mapBsp.Leaves[i].LeafBrushStartIdx} {mapBsp.Leaves[i].NumLeafBrushes} {mapBsp.Leaves[i].LeafFaceStartIdx} {mapBsp.Leaves[i].NumLeafFaces}");
}
sw.WriteLine();
sw.WriteLine($"bsp.LeafFaces[{mapBsp.LeafFaces.Length}]:");
for (int i = 0; i < mapBsp.LeafFaces.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.LeafFaces[i]}");
}
sw.WriteLine();
sw.WriteLine($"bsp.UsedFaces[{mapBsp.UsedFaces.Length}]:");
for (int i = 0; i < mapBsp.UsedFaces.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.UsedFaces[i]}");
}
sw.WriteLine();
sw.WriteLine($"bsp.Models[{mapBsp.Models.Length}]:");
for (int i = 0; i < mapBsp.Models.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.Models[i].Min} {mapBsp.Models[i].Max} {mapBsp.Models[i].StartBrushIdx} {mapBsp.Models[i].NumBrushes} {mapBsp.Models[i].StartFaceIdx} {mapBsp.Models[i].NumFaces}");
}
sw.WriteLine();
sw.WriteLine($"bsp.Vertices[{mapBsp.Vertices.Length}]:");
for (int i = 0; i < mapBsp.Vertices.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.Vertices[i].Position} {mapBsp.Vertices[i].Normal} {mapBsp.Vertices[i].Color} {mapBsp.Vertices[i].TexCoord} {mapBsp.Vertices[i].LightmapCoord}");
}
sw.WriteLine();
sw.WriteLine($"bsp.Faces[{mapBsp.Faces.Length}]:");
for (int i = 0; i < mapBsp.Faces.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.Faces[i].VertexStartIdx} {mapBsp.Faces[i].NumVertices} {mapBsp.Faces[i].MeshVertexStartIdx} {mapBsp.Faces[i].NumMeshVertices} {mapBsp.Faces[i].Type}");
}
sw.WriteLine();
sw.WriteLine($"bsp.MeshVertices[{mapBsp.MeshVertices.Length}]:");
for (int i = 0; i < mapBsp.MeshVertices.Length; i++)
{
sw.WriteLine($" [{i}]: {mapBsp.MeshVertices[i]}");
}
sw.WriteLine();
sw.WriteLine($"bsp.Entities[{mapBsp.Entities.Count}]:");
for (int i = 0; i < mapBsp.Entities.Count; i++)
{
sw.Write($" [{i}]: {mapBsp.Entities[i].GetClassname()} {{");
foreach (string key in mapBsp.Entities[i].Entries.Keys)
{
sw.Write($" {key}=\"{mapBsp.Entities[i].Entries[key]}\"");
}
sw.WriteLine(" }");
}
sw.WriteLine();
}
}
}