Browse Source

Reconstruct brush model data based on glpoly_t data generated by GLQuake. We can now successfully display BSP maps in Unity!

console
Nico de Poel 5 years ago
parent
commit
89f3600608
  1. 5
      Assets/Scripts/Data/QExtensions.cs
  2. 7
      Assets/Scripts/Data/QMath.cs
  3. 75
      Assets/Scripts/Data/QModel.cs
  4. 81
      Assets/Scripts/Modules/BrushModel.cs
  5. 3
      Assets/Scripts/Modules/BrushModel.cs.meta
  6. 5
      Assets/Scripts/Modules/RenderModule.Interop.cs
  7. 4
      Assets/Scripts/Modules/RenderModule.cs

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

7
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
{

75
Assets/Scripts/Data/QModel.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
/// <summary>
@ -221,42 +222,6 @@ public struct QSTVert
public int s, t;
}
/// <summary>
/// Managed equivalent of dmodel_t
/// </summary>
[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;
}
/// <summary>
/// Managed equivalent of mplane_t
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct QPlane
{
public QVec3 normal;
public float dist;
public byte type;
public byte signBits;
public byte pad0, pad1;
}
/// <summary>
/// Managed equivalent of medge_t
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct QEdge
{
public uint v0, v1;
public uint cachedEdgeOffset;
}
/// <summary>
/// Managed equivalent of msurface_t
/// </summary>
@ -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<QGLPolyVert[]> GetPolygons()
{
// This is so nasty. We have to deconstruct a linked list of variable-sized structs. Yuck.
int offset = Marshal.SizeOf<IntPtr>() * 2 + Marshal.SizeOf<int>();
IntPtr polyPtr = polys;
while (polyPtr != IntPtr.Zero)
{
QGLPoly polygon = Marshal.PtrToStructure<QGLPoly>(polyPtr);
QGLPolyVert[] vertices = IntPtr.Add(polyPtr, offset).ToStructArray<QGLPolyVert>(polygon.numVerts);
yield return vertices;
polyPtr = polygon.next;
}
}
}
/// <summary>
/// Managed equivalent of glpoly_t
/// </summary>
[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;
}

81
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<Mesh> meshes = new List<Mesh>();
public BrushModel(string name)
{
this.name = name;
}
public void ImportMeshData(QModel model, QSurface[] surfaces)
{
List<Vector3> vertices = new List<Vector3>();
List<Vector2> textureUVs = new List<Vector2>();
List<Vector2> lightmapUVs = new List<Vector2>();
List<ushort> indices = new List<ushort>();
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<MeshFilter>();
mf.sharedMesh = mesh;
var mr = meshGO.AddComponent<MeshRenderer>();
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));
}
}

3
Assets/Scripts/Modules/BrushModel.cs.meta

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 20d063dc6801478691510afa943bc87e
timeCreated: 1619358727

5
Assets/Scripts/Modules/RenderModule.Interop.cs

@ -92,12 +92,9 @@ public partial class RenderModule: CallbackHandler<RenderModule>
if (model == null || model.type != QModelType.Brush)
return -1;
var vertices = model.vertices.ToStructArray<QVec3>(model.numVertices);
var edges = model.edges.ToStructArray<QEdge>(model.numEdges);
var surfaces = model.surfaces.ToStructArray<QSurface>(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)]

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

Loading…
Cancel
Save