diff --git a/Assets/Scripts/Modules/RenderModule.Interop.cs b/Assets/Scripts/Modules/RenderModule.Interop.cs index 099a956..05c97d3 100644 --- a/Assets/Scripts/Modules/RenderModule.Interop.cs +++ b/Assets/Scripts/Modules/RenderModule.Interop.cs @@ -46,11 +46,14 @@ public partial class RenderModule: CallbackHandler return 0; } + if (header.numFrames > MaxAliasFrames) + header.numFrames = MaxAliasFrames; + if (frames != IntPtr.Zero) header.frames = frames.ToStructArray(header.numFrames); var poseVertices = new QTriVertex[header.numFrames][]; - for (int i = 0; i < header.numFrames && i < MaxAliasFrames; ++i) + for (int i = 0; i < header.numFrames; ++i) { poseVertices[i] = poseVerts[i].ToStructArray(header.numVerts); } diff --git a/Assets/Scripts/Modules/RenderModule.cs b/Assets/Scripts/Modules/RenderModule.cs index 6139887..55a883a 100644 --- a/Assets/Scripts/Modules/RenderModule.cs +++ b/Assets/Scripts/Modules/RenderModule.cs @@ -1,4 +1,5 @@ -using UnityEngine; +using System; +using UnityEngine; using UnityEngine.Rendering; public partial class RenderModule @@ -18,12 +19,17 @@ public partial class RenderModule { Debug.Log($"Alias model '{name}' with {header.numVerts} vertices, {header.numTriangles} triangles, {header.numFrames} frame(s)"); + // Massage the input data for easier conversion + PreprocessMeshData(header, triangles, ref poseVertices, ref stVertices); + + // Triangle indices and UVs are the same for all animation frames ConvertTriangles(triangles, out var indices); ConvertUVs(stVertices, header.skinWidth, header.skinHeight, out var uvs); string modelName = System.IO.Path.GetFileNameWithoutExtension(name); AliasModel aliasModel = new AliasModel(modelName); + // Identify animation sequences and turn each one into a separate Mesh with a single blend shape animation Mesh mesh; string animName = null; int startFrame = 0; @@ -82,6 +88,76 @@ public partial class RenderModule return 1; } + /// + /// Quake has a bit of a weird outdated mesh setup where skin textures are split into a front side and a back side. + /// Vertices on the seam between the front and back are used by triangles on both sides, but require a correction + /// to their UVs when rendering the backside. To handle this properly, we duplicate these vertices and correct the + /// UVs ahead of time. We do this as a separate pre-process step to keep things simple. + /// + private static void PreprocessMeshData(QAliasHeader header, QTriangle[] triangles, + ref QTriVertex[][] poseVertices, ref QSTVert[] stVerts) + { + int newVertCount = 0; + + // First count how many new vertices we need to make, so we can preallocate the required arrays + for (int triIdx = 0; triIdx < header.numTriangles; ++triIdx) + { + if (triangles[triIdx].facesFront != 0) + continue; + + for (int indIdx = 0; indIdx < 3; ++indIdx) + { + int index = triangles[triIdx].vertIndex[indIdx]; + if (stVerts[index].onSeam == 0) + continue; + + // Back-side vertex on seam, needs to be duplicated and corrected + ++newVertCount; + } + } + + if (newVertCount == 0) + return; + + Array.Resize(ref stVerts, header.numVerts + newVertCount); + for (int frameIdx = 0; frameIdx < header.numFrames; ++frameIdx) + { + Array.Resize(ref poseVertices[frameIdx], header.numVerts + newVertCount); + } + + int newVertIndex = header.numVerts; + + // Now we go over all the triangles again and duplicate the vertices that are on a seam + for (int triIdx = 0; triIdx < header.numTriangles; ++triIdx) + { + if (triangles[triIdx].facesFront != 0) + continue; + + for (int indIdx = 0; indIdx < 3; ++indIdx) + { + int vertIndex = triangles[triIdx].vertIndex[indIdx]; + if (stVerts[vertIndex].onSeam == 0) + continue; + + // Clone the ST value and correct it to map onto the backside of the skin + QSTVert stVertCopy = stVerts[vertIndex]; + stVertCopy.s += header.skinWidth / 2; + stVerts[newVertIndex] = stVertCopy; + + // Clone the vertex position data for all frames + for (int frameIdx = 0; frameIdx < header.numFrames; ++frameIdx) + { + QTriVertex triVertCopy = poseVertices[frameIdx][vertIndex]; + poseVertices[frameIdx][newVertIndex] = triVertCopy; + } + + triangles[triIdx].vertIndex[indIdx] = newVertIndex++; + } + } + + header.numVerts += newVertCount; + } + private static void ConvertVertices(QAliasHeader header, QTriVertex[] triVerts, out Vector3[] vertices, out Vector3[] normals) { int numVerts = triVerts.Length; @@ -105,6 +181,7 @@ public partial class RenderModule for (int i = 0; i < numTris; ++i) { + // Quake triangles are wound clockwise, so we reverse them here 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]; @@ -116,9 +193,6 @@ public partial class RenderModule 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)