From 834ff9a7949316f5b920c01d1b4410f71a9d0e29 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Fri, 16 Apr 2021 17:25:37 +0200 Subject: [PATCH] First test converting models from Quake into Unity meshes, including normals and UVs. It's still far from perfect (back side UVs are not handled at all yet) but it's a good demonstration of what will be possible. --- Assets/Scripts/Data/QExtensions.cs | 6 + Assets/Scripts/Data/QLightNormals.cs | 179 ++++++++++++++++++ Assets/Scripts/Data/QLightNormals.cs.meta | 3 + .../Scripts/Modules/RenderModule.Interop.cs | 10 +- Assets/Scripts/Modules/RenderModule.cs | 73 ++++++- engine/Quake/gl_model.h | 2 +- engine/UniQuake/gl_uniquake.c | 6 +- 7 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 Assets/Scripts/Data/QLightNormals.cs create mode 100644 Assets/Scripts/Data/QLightNormals.cs.meta diff --git a/Assets/Scripts/Data/QExtensions.cs b/Assets/Scripts/Data/QExtensions.cs index 053044a..ec73005 100644 --- a/Assets/Scripts/Data/QExtensions.cs +++ b/Assets/Scripts/Data/QExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using UnityEngine; public static class QExtensions { @@ -18,4 +19,9 @@ public static class QExtensions } return result; } + + public static Vector3 ToVector3(this QVec3 vec) + { + return new Vector3(vec.x, vec.y, vec.z); + } } diff --git a/Assets/Scripts/Data/QLightNormals.cs b/Assets/Scripts/Data/QLightNormals.cs new file mode 100644 index 0000000..d2b7542 --- /dev/null +++ b/Assets/Scripts/Data/QLightNormals.cs @@ -0,0 +1,179 @@ +using UnityEngine; + +public static class QLightNormals +{ + public static Vector3 Get(byte index) + { + if (index >= Normals.Length) + return Vector3.zero; + + return Normals[index]; + } + + // Converted from anorms.h + private static readonly Vector3[] Normals = + { + new Vector3(-0.525731f, 0.000000f, 0.850651f), + new Vector3(-0.442863f, 0.238856f, 0.864188f), + new Vector3(-0.295242f, 0.000000f, 0.955423f), + new Vector3(-0.309017f, 0.500000f, 0.809017f), + new Vector3(-0.162460f, 0.262866f, 0.951056f), + new Vector3(0.000000f, 0.000000f, 1.000000f), + new Vector3(0.000000f, 0.850651f, 0.525731f), + new Vector3(-0.147621f, 0.716567f, 0.681718f), + new Vector3(0.147621f, 0.716567f, 0.681718f), + new Vector3(0.000000f, 0.525731f, 0.850651f), + new Vector3(0.309017f, 0.500000f, 0.809017f), + new Vector3(0.525731f, 0.000000f, 0.850651f), + new Vector3(0.295242f, 0.000000f, 0.955423f), + new Vector3(0.442863f, 0.238856f, 0.864188f), + new Vector3(0.162460f, 0.262866f, 0.951056f), + new Vector3(-0.681718f, 0.147621f, 0.716567f), + new Vector3(-0.809017f, 0.309017f, 0.500000f), + new Vector3(-0.587785f, 0.425325f, 0.688191f), + new Vector3(-0.850651f, 0.525731f, 0.000000f), + new Vector3(-0.864188f, 0.442863f, 0.238856f), + new Vector3(-0.716567f, 0.681718f, 0.147621f), + new Vector3(-0.688191f, 0.587785f, 0.425325f), + new Vector3(-0.500000f, 0.809017f, 0.309017f), + new Vector3(-0.238856f, 0.864188f, 0.442863f), + new Vector3(-0.425325f, 0.688191f, 0.587785f), + new Vector3(-0.716567f, 0.681718f, -0.147621f), + new Vector3(-0.500000f, 0.809017f, -0.309017f), + new Vector3(-0.525731f, 0.850651f, 0.000000f), + new Vector3(0.000000f, 0.850651f, -0.525731f), + new Vector3(-0.238856f, 0.864188f, -0.442863f), + new Vector3(0.000000f, 0.955423f, -0.295242f), + new Vector3(-0.262866f, 0.951056f, -0.162460f), + new Vector3(0.000000f, 1.000000f, 0.000000f), + new Vector3(0.000000f, 0.955423f, 0.295242f), + new Vector3(-0.262866f, 0.951056f, 0.162460f), + new Vector3(0.238856f, 0.864188f, 0.442863f), + new Vector3(0.262866f, 0.951056f, 0.162460f), + new Vector3(0.500000f, 0.809017f, 0.309017f), + new Vector3(0.238856f, 0.864188f, -0.442863f), + new Vector3(0.262866f, 0.951056f, -0.162460f), + new Vector3(0.500000f, 0.809017f, -0.309017f), + new Vector3(0.850651f, 0.525731f, 0.000000f), + new Vector3(0.716567f, 0.681718f, 0.147621f), + new Vector3(0.716567f, 0.681718f, -0.147621f), + new Vector3(0.525731f, 0.850651f, 0.000000f), + new Vector3(0.425325f, 0.688191f, 0.587785f), + new Vector3(0.864188f, 0.442863f, 0.238856f), + new Vector3(0.688191f, 0.587785f, 0.425325f), + new Vector3(0.809017f, 0.309017f, 0.500000f), + new Vector3(0.681718f, 0.147621f, 0.716567f), + new Vector3(0.587785f, 0.425325f, 0.688191f), + new Vector3(0.955423f, 0.295242f, 0.000000f), + new Vector3(1.000000f, 0.000000f, 0.000000f), + new Vector3(0.951056f, 0.162460f, 0.262866f), + new Vector3(0.850651f, -0.525731f, 0.000000f), + new Vector3(0.955423f, -0.295242f, 0.000000f), + new Vector3(0.864188f, -0.442863f, 0.238856f), + new Vector3(0.951056f, -0.162460f, 0.262866f), + new Vector3(0.809017f, -0.309017f, 0.500000f), + new Vector3(0.681718f, -0.147621f, 0.716567f), + new Vector3(0.850651f, 0.000000f, 0.525731f), + new Vector3(0.864188f, 0.442863f, -0.238856f), + new Vector3(0.809017f, 0.309017f, -0.500000f), + new Vector3(0.951056f, 0.162460f, -0.262866f), + new Vector3(0.525731f, 0.000000f, -0.850651f), + new Vector3(0.681718f, 0.147621f, -0.716567f), + new Vector3(0.681718f, -0.147621f, -0.716567f), + new Vector3(0.850651f, 0.000000f, -0.525731f), + new Vector3(0.809017f, -0.309017f, -0.500000f), + new Vector3(0.864188f, -0.442863f, -0.238856f), + new Vector3(0.951056f, -0.162460f, -0.262866f), + new Vector3(0.147621f, 0.716567f, -0.681718f), + new Vector3(0.309017f, 0.500000f, -0.809017f), + new Vector3(0.425325f, 0.688191f, -0.587785f), + new Vector3(0.442863f, 0.238856f, -0.864188f), + new Vector3(0.587785f, 0.425325f, -0.688191f), + new Vector3(0.688191f, 0.587785f, -0.425325f), + new Vector3(-0.147621f, 0.716567f, -0.681718f), + new Vector3(-0.309017f, 0.500000f, -0.809017f), + new Vector3(0.000000f, 0.525731f, -0.850651f), + new Vector3(-0.525731f, 0.000000f, -0.850651f), + new Vector3(-0.442863f, 0.238856f, -0.864188f), + new Vector3(-0.295242f, 0.000000f, -0.955423f), + new Vector3(-0.162460f, 0.262866f, -0.951056f), + new Vector3(0.000000f, 0.000000f, -1.000000f), + new Vector3(0.295242f, 0.000000f, -0.955423f), + new Vector3(0.162460f, 0.262866f, -0.951056f), + new Vector3(-0.442863f, -0.238856f, -0.864188f), + new Vector3(-0.309017f, -0.500000f, -0.809017f), + new Vector3(-0.162460f, -0.262866f, -0.951056f), + new Vector3(0.000000f, -0.850651f, -0.525731f), + new Vector3(-0.147621f, -0.716567f, -0.681718f), + new Vector3(0.147621f, -0.716567f, -0.681718f), + new Vector3(0.000000f, -0.525731f, -0.850651f), + new Vector3(0.309017f, -0.500000f, -0.809017f), + new Vector3(0.442863f, -0.238856f, -0.864188f), + new Vector3(0.162460f, -0.262866f, -0.951056f), + new Vector3(0.238856f, -0.864188f, -0.442863f), + new Vector3(0.500000f, -0.809017f, -0.309017f), + new Vector3(0.425325f, -0.688191f, -0.587785f), + new Vector3(0.716567f, -0.681718f, -0.147621f), + new Vector3(0.688191f, -0.587785f, -0.425325f), + new Vector3(0.587785f, -0.425325f, -0.688191f), + new Vector3(0.000000f, -0.955423f, -0.295242f), + new Vector3(0.000000f, -1.000000f, 0.000000f), + new Vector3(0.262866f, -0.951056f, -0.162460f), + new Vector3(0.000000f, -0.850651f, 0.525731f), + new Vector3(0.000000f, -0.955423f, 0.295242f), + new Vector3(0.238856f, -0.864188f, 0.442863f), + new Vector3(0.262866f, -0.951056f, 0.162460f), + new Vector3(0.500000f, -0.809017f, 0.309017f), + new Vector3(0.716567f, -0.681718f, 0.147621f), + new Vector3(0.525731f, -0.850651f, 0.000000f), + new Vector3(-0.238856f, -0.864188f, -0.442863f), + new Vector3(-0.500000f, -0.809017f, -0.309017f), + new Vector3(-0.262866f, -0.951056f, -0.162460f), + new Vector3(-0.850651f, -0.525731f, 0.000000f), + new Vector3(-0.716567f, -0.681718f, -0.147621f), + new Vector3(-0.716567f, -0.681718f, 0.147621f), + new Vector3(-0.525731f, -0.850651f, 0.000000f), + new Vector3(-0.500000f, -0.809017f, 0.309017f), + new Vector3(-0.238856f, -0.864188f, 0.442863f), + new Vector3(-0.262866f, -0.951056f, 0.162460f), + new Vector3(-0.864188f, -0.442863f, 0.238856f), + new Vector3(-0.809017f, -0.309017f, 0.500000f), + new Vector3(-0.688191f, -0.587785f, 0.425325f), + new Vector3(-0.681718f, -0.147621f, 0.716567f), + new Vector3(-0.442863f, -0.238856f, 0.864188f), + new Vector3(-0.587785f, -0.425325f, 0.688191f), + new Vector3(-0.309017f, -0.500000f, 0.809017f), + new Vector3(-0.147621f, -0.716567f, 0.681718f), + new Vector3(-0.425325f, -0.688191f, 0.587785f), + new Vector3(-0.162460f, -0.262866f, 0.951056f), + new Vector3(0.442863f, -0.238856f, 0.864188f), + new Vector3(0.162460f, -0.262866f, 0.951056f), + new Vector3(0.309017f, -0.500000f, 0.809017f), + new Vector3(0.147621f, -0.716567f, 0.681718f), + new Vector3(0.000000f, -0.525731f, 0.850651f), + new Vector3(0.425325f, -0.688191f, 0.587785f), + new Vector3(0.587785f, -0.425325f, 0.688191f), + new Vector3(0.688191f, -0.587785f, 0.425325f), + new Vector3(-0.955423f, 0.295242f, 0.000000f), + new Vector3(-0.951056f, 0.162460f, 0.262866f), + new Vector3(-1.000000f, 0.000000f, 0.000000f), + new Vector3(-0.850651f, 0.000000f, 0.525731f), + new Vector3(-0.955423f, -0.295242f, 0.000000f), + new Vector3(-0.951056f, -0.162460f, 0.262866f), + new Vector3(-0.864188f, 0.442863f, -0.238856f), + new Vector3(-0.951056f, 0.162460f, -0.262866f), + new Vector3(-0.809017f, 0.309017f, -0.500000f), + new Vector3(-0.864188f, -0.442863f, -0.238856f), + new Vector3(-0.951056f, -0.162460f, -0.262866f), + new Vector3(-0.809017f, -0.309017f, -0.500000f), + new Vector3(-0.681718f, 0.147621f, -0.716567f), + new Vector3(-0.681718f, -0.147621f, -0.716567f), + new Vector3(-0.850651f, 0.000000f, -0.525731f), + new Vector3(-0.688191f, 0.587785f, -0.425325f), + new Vector3(-0.587785f, 0.425325f, -0.688191f), + new Vector3(-0.425325f, 0.688191f, -0.587785f), + new Vector3(-0.425325f, -0.688191f, -0.587785f), + new Vector3(-0.587785f, -0.425325f, -0.688191f), + new Vector3(-0.688191f, -0.587785f, -0.425325f), + }; +} diff --git a/Assets/Scripts/Data/QLightNormals.cs.meta b/Assets/Scripts/Data/QLightNormals.cs.meta new file mode 100644 index 0000000..7bf119e --- /dev/null +++ b/Assets/Scripts/Data/QLightNormals.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: db4ebdce7b464932b60da167ed311249 +timeCreated: 1618581844 \ No newline at end of file diff --git a/Assets/Scripts/Modules/RenderModule.Interop.cs b/Assets/Scripts/Modules/RenderModule.Interop.cs index 3391deb..872ba1e 100644 --- a/Assets/Scripts/Modules/RenderModule.Interop.cs +++ b/Assets/Scripts/Modules/RenderModule.Interop.cs @@ -31,12 +31,15 @@ public partial class RenderModule: CallbackHandler } [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private delegate int UploadAliasModelCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string name, + private delegate int UploadAliasModelCallback(IntPtr target, + [MarshalAs(UnmanagedType.LPStr)] string name, QVec3 boundsMin, QVec3 boundsMax, QAliasHeader header, IntPtr frames, [MarshalAs(UnmanagedType.LPArray, SizeConst = MaxAliasFrames)] IntPtr[] poseVerts, IntPtr triangles, IntPtr stVerts); [MonoPInvokeCallback(typeof(UploadAliasModelCallback))] - private static int Callback_UploadAliasModel(IntPtr target, string name, QAliasHeader header, IntPtr frames, + private static int Callback_UploadAliasModel(IntPtr target, + string name, QVec3 boundsMin, QVec3 boundsMax, + QAliasHeader header, IntPtr frames, IntPtr[] poseVerts, IntPtr triangles, IntPtr stVerts) { if (header == null) @@ -54,7 +57,8 @@ public partial class RenderModule: CallbackHandler poseVertices[i] = poseVerts[i].ToStructArray(header.numVerts); } - return GetSelf(target).UploadAliasModel(name, header, poseVertices, + return GetSelf(target).UploadAliasModel( + name, boundsMin, boundsMax, header, poseVertices, triangles.ToStructArray(header.numTriangles), stVerts.ToStructArray(header.numVerts)); } diff --git a/Assets/Scripts/Modules/RenderModule.cs b/Assets/Scripts/Modules/RenderModule.cs index 683bb9e..970e23e 100644 --- a/Assets/Scripts/Modules/RenderModule.cs +++ b/Assets/Scripts/Modules/RenderModule.cs @@ -10,9 +10,80 @@ public partial class RenderModule BuildCallbacks(); } - private int UploadAliasModel(string name, QAliasHeader header, QTriVertex[][] poseVertices, QTriangle[] triangles, QSTVert[] stVertices) + private float xPos = -8f; + + private int UploadAliasModel(string name, QVec3 boundsMin, QVec3 boundsMax, QAliasHeader header, + QTriVertex[][] poseVertices, QTriangle[] triangles, QSTVert[] stVertices) { Debug.Log($"Alias model '{name}' with {header.numVerts} vertices, {header.numTriangles} triangles, {header.numFrames} frame(s)"); + + // TODO: convert Quake poses into Mesh blend shapes with AddBlendShapeFrame + + ConvertVertices(poseVertices[0], boundsMin.ToVector3(), boundsMax.ToVector3(), out var vertices, out var normals); + ConvertTriangles(triangles, out var indices); + ConvertUVs(stVertices, header.skinWidth, header.skinHeight, out var uvs); + + var mesh = new Mesh { name = name }; + mesh.SetVertices(vertices); + mesh.SetNormals(normals); + mesh.SetIndices(indices, MeshTopology.Triangles, 0); + mesh.SetUVs(0, uvs); + mesh.Optimize(); // This ensures that triangles will be properly fused and organized in the best possible way + mesh.UploadMeshData(true); + + var go = new GameObject(System.IO.Path.GetFileNameWithoutExtension(name)); + go.transform.SetPositionAndRotation(new Vector3(xPos, 0, 0), Quaternion.Euler(-90, 90, 0)); + go.transform.localScale = Vector3.one * 0.01f; + var mf = go.AddComponent(); + mf.sharedMesh = mesh; + var mr = go.AddComponent(); + mr.material = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); + + xPos += 1f; return 1; } + + private static void ConvertVertices(QTriVertex[] triVerts, Vector3 boundsMin, Vector3 boundsMax, out Vector3[] vertices, out Vector3[] normals) + { + int numVerts = triVerts.Length; + vertices = new Vector3[numVerts]; + normals = new Vector3[numVerts]; + + for (int i = 0; i < numVerts; ++i) + { + byte[] v = triVerts[i].v; + Vector3 vec = new Vector3(v[0] / 255f, v[1] / 255f, v[2] / 255f); + vertices[i] = boundsMin + Vector3.Scale(boundsMax - boundsMin, vec); + normals[i] = QLightNormals.Get(triVerts[i].lightNormalIndex); + } + } + + private static void ConvertTriangles(QTriangle[] triangles, out ushort[] indices) + { + int numTris = triangles.Length; + indices = new ushort[numTris * 3]; + + for (int i = 0; i < numTris; ++i) + { + indices[i * 3 + 0] = (ushort)triangles[i].vertIndex[2]; + indices[i * 3 + 1] = (ushort)triangles[i].vertIndex[1]; + indices[i * 3 + 2] = (ushort)triangles[i].vertIndex[0]; + } + } + + private static void ConvertUVs(QSTVert[] stVerts, int skinWidth, int skinHeight, out Vector2[] uvs) + { + int numVerts = stVerts.Length; + uvs = new Vector2[numVerts]; + + // TODO FIXME: this only works correctly for the front side of a model. + // To also correctly UV the back side, we need to duplicate vertices on the back/front seam, + // and add half the skin width to UVs that are on back side vertices. + Vector2 scale = new Vector2(1.0f / skinWidth, 1.0f / skinHeight); + + for (int i = 0; i < numVerts; ++i) + { + uvs[i] = Vector2.Scale(new Vector2(stVerts[i].s, skinHeight - stVerts[i].t), scale); + } + } } diff --git a/engine/Quake/gl_model.h b/engine/Quake/gl_model.h index 81f8b9b..31e2e48 100644 --- a/engine/Quake/gl_model.h +++ b/engine/Quake/gl_model.h @@ -516,6 +516,6 @@ byte *Mod_NoVisPVS (qmodel_t *model); void Mod_SetExtraFlags (qmodel_t *mod); -int UQ_GL_UploadAliasModel(const char *name, aliashdr_t *aliashdr, trivertx_t **poseVerts, mtriangle_t *triangles, stvert_t *stVerts); +int UQ_GL_UploadAliasModel(qmodel_t *model, aliashdr_t *aliashdr, trivertx_t **poseVerts, mtriangle_t *triangles, stvert_t *stVerts); #endif // __MODEL__ diff --git a/engine/UniQuake/gl_uniquake.c b/engine/UniQuake/gl_uniquake.c index 7763b02..05daafb 100644 --- a/engine/UniQuake/gl_uniquake.c +++ b/engine/UniQuake/gl_uniquake.c @@ -10,14 +10,14 @@ typedef struct unity_glcalls_s { void *target; - int(*UploadAliasModel)(void *target, const char *name, aliashdr_t *aliashdr, maliasframedesc_t *frames, trivertx_t **poseVerts, mtriangle_t *triangles, stvert_t *stVerts); + int(*UploadAliasModel)(void *target, const char *name, vec3_t mins, vec3_t maxs, aliashdr_t *aliashdr, maliasframedesc_t *frames, trivertx_t **poseVerts, mtriangle_t *triangles, stvert_t *stVerts); } unity_glcalls_t; const unity_glcalls_t *unity_glcalls; -int UQ_GL_UploadAliasModel(const char *name, aliashdr_t *aliashdr, trivertx_t **poseVerts, mtriangle_t *triangles, stvert_t *stVerts) +int UQ_GL_UploadAliasModel(qmodel_t *model, aliashdr_t *aliashdr, trivertx_t **poseVerts, mtriangle_t *triangles, stvert_t *stVerts) { // C# doesn't really understand this idea of a variable-length embedded array of structs, // so we pass along the pointer to the first frame struct which allows us to manually marshal the entire array. - return unity_glcalls->UploadAliasModel(unity_glcalls->target, name, aliashdr, &aliashdr->frames[0], poseVerts, triangles, stVerts); + return unity_glcalls->UploadAliasModel(unity_glcalls->target, model->name, model->mins, model->maxs, aliashdr, &aliashdr->frames[0], poseVerts, triangles, stVerts); }