From 89f360060833c31fd4cca29cd08071082d2e1f07 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Sun, 25 Apr 2021 17:11:29 +0200 Subject: [PATCH] Reconstruct brush model data based on glpoly_t data generated by GLQuake. We can now successfully display BSP maps in Unity! --- Assets/Scripts/Data/QExtensions.cs | 5 ++ Assets/Scripts/Data/QMath.cs | 7 ++ Assets/Scripts/Data/QModel.cs | 75 ++++++++--------- Assets/Scripts/Modules/BrushModel.cs | 81 +++++++++++++++++++ Assets/Scripts/Modules/BrushModel.cs.meta | 3 + .../Scripts/Modules/RenderModule.Interop.cs | 7 +- Assets/Scripts/Modules/RenderModule.cs | 4 +- 7 files changed, 139 insertions(+), 43 deletions(-) create mode 100644 Assets/Scripts/Modules/BrushModel.cs create mode 100644 Assets/Scripts/Modules/BrushModel.cs.meta diff --git a/Assets/Scripts/Data/QExtensions.cs b/Assets/Scripts/Data/QExtensions.cs index 9853558..a48546a 100644 --- a/Assets/Scripts/Data/QExtensions.cs +++ b/Assets/Scripts/Data/QExtensions.cs @@ -30,6 +30,11 @@ public static class QExtensions return result; } + public static Vector3 ToVector2(this QVec2 vec) + { + return new Vector2(vec.x, vec.y); + } + public static Vector3 ToVector3(this QVec3 vec) { return new Vector3(vec.x, vec.y, vec.z); diff --git a/Assets/Scripts/Data/QMath.cs b/Assets/Scripts/Data/QMath.cs index c0eacf1..73b5fa8 100644 --- a/Assets/Scripts/Data/QMath.cs +++ b/Assets/Scripts/Data/QMath.cs @@ -12,6 +12,13 @@ public struct QVec3 public float z; } +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public struct QVec2 +{ + public float x; + public float y; +} + [StructLayout(LayoutKind.Sequential, Pack = 0)] public struct QVec4i { diff --git a/Assets/Scripts/Data/QModel.cs b/Assets/Scripts/Data/QModel.cs index 919d0fe..37ec567 100644 --- a/Assets/Scripts/Data/QModel.cs +++ b/Assets/Scripts/Data/QModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; /// @@ -221,42 +222,6 @@ public struct QSTVert public int s, t; } -/// -/// Managed equivalent of dmodel_t -/// -[StructLayout(LayoutKind.Sequential, Pack = 0)] -public struct QDModel -{ - public QVec3 mins, maxs; - public QVec3 origin; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxMapHulls)] public int[] headNode; - public int visLeafs; - public int firstFace, numFaces; -} - -/// -/// Managed equivalent of mplane_t -/// -[StructLayout(LayoutKind.Sequential, Pack = 0)] -public struct QPlane -{ - public QVec3 normal; - public float dist; - public byte type; - public byte signBits; - public byte pad0, pad1; -} - -/// -/// Managed equivalent of medge_t -/// -[StructLayout(LayoutKind.Sequential, Pack = 0)] -public struct QEdge -{ - public uint v0, v1; - public uint cachedEdgeOffset; -} - /// /// Managed equivalent of msurface_t /// @@ -293,4 +258,42 @@ public struct QSurface [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxLightmaps)] public int[] cachedLight; public bool cachedDLight; public IntPtr samples; // Pointer to byte + + public IEnumerable GetPolygons() + { + // This is so nasty. We have to deconstruct a linked list of variable-sized structs. Yuck. + int offset = Marshal.SizeOf() * 2 + Marshal.SizeOf(); + + IntPtr polyPtr = polys; + while (polyPtr != IntPtr.Zero) + { + QGLPoly polygon = Marshal.PtrToStructure(polyPtr); + QGLPolyVert[] vertices = IntPtr.Add(polyPtr, offset).ToStructArray(polygon.numVerts); + yield return vertices; + + polyPtr = polygon.next; + } + } +} + +/// +/// Managed equivalent of glpoly_t +/// +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public struct QGLPoly +{ + public IntPtr next; // Pointer to glpoly_t (next in linked list) + public IntPtr chain; // Pointer to glpoly_t (start of linked list) + public int numVerts; + + // No need to include this field in the struct, as we calculate its offset and marshal the data manually + //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public QGLPolyVert[] verts; // Variable sized +} + +[StructLayout(LayoutKind.Sequential, Pack = 0)] +public struct QGLPolyVert +{ + public QVec3 position; + public QVec2 textureUV; + public QVec2 lightmapUV; } diff --git a/Assets/Scripts/Modules/BrushModel.cs b/Assets/Scripts/Modules/BrushModel.cs new file mode 100644 index 0000000..8f5dda7 --- /dev/null +++ b/Assets/Scripts/Modules/BrushModel.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; + +public class BrushModel +{ + private readonly string name; + private readonly List meshes = new List(); + + public BrushModel(string name) + { + this.name = name; + } + + public void ImportMeshData(QModel model, QSurface[] surfaces) + { + List vertices = new List(); + List textureUVs = new List(); + List lightmapUVs = new List(); + List indices = new List(); + + for (int surfIdx = 0; surfIdx < surfaces.Length; ++surfIdx) + { + foreach (var polyVerts in surfaces[surfIdx].GetPolygons()) + { + vertices.Clear(); + textureUVs.Clear(); + lightmapUVs.Clear(); + indices.Clear(); + + for (int vertIdx = 0; vertIdx < polyVerts.Length; ++vertIdx) + { + vertices.Add(polyVerts[vertIdx].position.ToVector3()); + textureUVs.Add(polyVerts[vertIdx].textureUV.ToVector2()); + lightmapUVs.Add(polyVerts[vertIdx].lightmapUV.ToVector2()); + } + + // Reconstruct triangle fan (in reverse order) + for (ushort index = 2; index < polyVerts.Length; ++index) + { + indices.Add(index); + indices.Add((ushort)(index - 1)); + indices.Add(0); + } + + Mesh mesh = new Mesh(); + mesh.SetVertices(vertices); + mesh.SetUVs(0, textureUVs); + mesh.SetUVs(1, lightmapUVs); + mesh.SetIndices(indices, MeshTopology.Triangles, 0); + mesh.RecalculateNormals(); + mesh.UploadMeshData(true); + meshes.Add(mesh); + } + } + + // DEBUG + var go = new GameObject(name); + var mat = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); + + for (int i = 0; i < meshes.Count; ++i) + { + var mesh = meshes[i]; + + var meshGO = new GameObject($"Surface_{i}"); + meshGO.transform.SetParent(go.transform); + + var mf = meshGO.AddComponent(); + mf.sharedMesh = mesh; + + var mr = meshGO.AddComponent(); + mr.sharedMaterial = mat; + mr.shadowCastingMode = ShadowCastingMode.Off; + mr.receiveShadows = false; + mr.lightProbeUsage = LightProbeUsage.Off; + mr.reflectionProbeUsage = ReflectionProbeUsage.Off; + } + + go.transform.SetPositionAndRotation(Vector3.zero, Quaternion.Euler(-90, 90, 0)); + } +} diff --git a/Assets/Scripts/Modules/BrushModel.cs.meta b/Assets/Scripts/Modules/BrushModel.cs.meta new file mode 100644 index 0000000..a499186 --- /dev/null +++ b/Assets/Scripts/Modules/BrushModel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 20d063dc6801478691510afa943bc87e +timeCreated: 1619358727 \ No newline at end of file diff --git a/Assets/Scripts/Modules/RenderModule.Interop.cs b/Assets/Scripts/Modules/RenderModule.Interop.cs index 054f022..0489cfc 100644 --- a/Assets/Scripts/Modules/RenderModule.Interop.cs +++ b/Assets/Scripts/Modules/RenderModule.Interop.cs @@ -92,12 +92,9 @@ public partial class RenderModule: CallbackHandler if (model == null || model.type != QModelType.Brush) return -1; - var vertices = model.vertices.ToStructArray(model.numVertices); - var edges = model.edges.ToStructArray(model.numEdges); var surfaces = model.surfaces.ToStructArray(model.numSurfaces); - var surfaceEdges = model.surfEdges.ToIntArray(model.numSurfEdges); - - return GetSelf(target).UploadBrushModel(model, vertices, edges, surfaces, surfaceEdges); + + return GetSelf(target).UploadBrushModel(model, surfaces); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] diff --git a/Assets/Scripts/Modules/RenderModule.cs b/Assets/Scripts/Modules/RenderModule.cs index 0e8d4da..d11e031 100644 --- a/Assets/Scripts/Modules/RenderModule.cs +++ b/Assets/Scripts/Modules/RenderModule.cs @@ -119,12 +119,12 @@ public partial class RenderModule return 1; } - private int UploadBrushModel(QModel model, QVec3[] vertices, QEdge[] edges, QSurface[] surfaces, int[] surfaceEdges) + private int UploadBrushModel(QModel model, QSurface[] surfaces) { Debug.Log($"Brush model '{model.name}' with {model.numVertices} vertices, {model.numEdges} edges, {model.numSurfaces} surfaces"); var brushModel = new BrushModel(model.name); - brushModel.ImportMeshData(model, vertices, edges, surfaces, surfaceEdges); + brushModel.ImportMeshData(model, surfaces); return 1; }