|
|
|
@ -81,11 +81,20 @@ public class AliasModel |
|
|
|
ConvertUVs(stVertices, header.skinWidth, header.skinHeight, out var uvs); |
|
|
|
|
|
|
|
// Identify animation sequences and turn each one into a separate Mesh with a single blend shape animation
|
|
|
|
if (frameType == QAliasFrameType.Single) |
|
|
|
ImportSingleFrameAnimations(header, poseVertices, indices, uvs); |
|
|
|
else |
|
|
|
ImportGroupFrameAnimations(header, poseVertices, indices, uvs); |
|
|
|
} |
|
|
|
|
|
|
|
private void ImportSingleFrameAnimations(QAliasHeader header, QTriVertex[][] poseVertices, ushort[] indices, Vector2[] uvs) |
|
|
|
{ |
|
|
|
Mesh mesh; |
|
|
|
string animName = null; |
|
|
|
int startFrame = 0; |
|
|
|
for (int frameIdx = 0; frameIdx < header.numFrames; ++frameIdx) |
|
|
|
{ |
|
|
|
// Individual sequences are identified by their prefix
|
|
|
|
string frameName = AnimationRegex.Match(header.frames[frameIdx].name).Value; |
|
|
|
if (animName == null) |
|
|
|
{ |
|
|
|
@ -109,6 +118,18 @@ public class AliasModel |
|
|
|
animationMeshes.Add((startFrame, mesh)); |
|
|
|
} |
|
|
|
|
|
|
|
private void ImportGroupFrameAnimations(QAliasHeader header, QTriVertex[][] poseVertices, ushort[] indices, Vector2[] uvs) |
|
|
|
{ |
|
|
|
for (int frameIdx = 0; frameIdx < header.numFrames; ++frameIdx) |
|
|
|
{ |
|
|
|
var frame = header.frames[frameIdx]; |
|
|
|
Mesh mesh = CreateAnimatedMesh(header, poseVertices, indices, uvs, $"{name}_{frameIdx}", |
|
|
|
frame.firstPose, frame.firstPose + frame.numPoses); |
|
|
|
|
|
|
|
animationMeshes.Add((frame.firstPose, mesh)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// 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
|
|
|
|
@ -233,17 +254,17 @@ public class AliasModel |
|
|
|
|
|
|
|
private static Mesh CreateAnimatedMesh( |
|
|
|
QAliasHeader header, QTriVertex[][] poseVertices, ushort[] indices, Vector2[] uvs, |
|
|
|
string animationName, int startFrame, int endFrame) |
|
|
|
string animationName, int startPose, int endPose) |
|
|
|
{ |
|
|
|
ConvertVertices(header, poseVertices[startFrame], out var baseVertices, out var baseNormals); |
|
|
|
ConvertVertices(header, poseVertices[startPose], out var baseVertices, out var baseNormals); |
|
|
|
|
|
|
|
var mesh = new Mesh { name = animationName }; |
|
|
|
mesh.SetVertices(baseVertices); |
|
|
|
mesh.SetNormals(baseNormals); |
|
|
|
|
|
|
|
if (endFrame - startFrame > 1) |
|
|
|
if (endPose - startPose > 1) |
|
|
|
{ |
|
|
|
CreateBlendShapes(mesh, animationName, baseVertices, baseNormals, header, poseVertices, startFrame, endFrame); |
|
|
|
CreateBlendShapes(mesh, animationName, baseVertices, baseNormals, header, poseVertices, startPose, endPose); |
|
|
|
} |
|
|
|
|
|
|
|
mesh.SetIndices(indices, MeshTopology.Triangles, 0, false); |
|
|
|
@ -256,7 +277,7 @@ public class AliasModel |
|
|
|
|
|
|
|
private static void CreateBlendShapes( |
|
|
|
Mesh mesh, string animName, Vector3[] baseVertices, Vector3[] baseNormals, |
|
|
|
QAliasHeader header, QTriVertex[][] poseVertices, int startFrame, int endFrame) |
|
|
|
QAliasHeader header, QTriVertex[][] poseVertices, int startPose, int endPose) |
|
|
|
{ |
|
|
|
var deltaVertices = new Vector3[header.numVerts]; |
|
|
|
var deltaNormals = new Vector3[header.numVerts]; |
|
|
|
@ -264,13 +285,13 @@ public class AliasModel |
|
|
|
Vector3 scale = header.scale.ToVector3(); |
|
|
|
Vector3 origin = header.scaleOrigin.ToVector3(); |
|
|
|
|
|
|
|
int numFrames = endFrame - startFrame; |
|
|
|
int numPoses = endPose - startPose; |
|
|
|
|
|
|
|
// Repeat the first frame at the end, so we can smoothly animate the entire cycle and loop the animation without any breaks.
|
|
|
|
for (int index = 1; index <= numFrames; ++index) |
|
|
|
// Repeat the first pose at the end, so we can smoothly animate the entire cycle and loop the animation without any breaks.
|
|
|
|
for (int index = 1; index <= numPoses; ++index) |
|
|
|
{ |
|
|
|
int frameIdx = startFrame + index % numFrames; |
|
|
|
var poseVerts = poseVertices[frameIdx]; |
|
|
|
int poseIdx = startPose + index % numPoses; |
|
|
|
var poseVerts = poseVertices[poseIdx]; |
|
|
|
|
|
|
|
for (int vertIdx = 0; vertIdx < header.numVerts; ++vertIdx) |
|
|
|
{ |
|
|
|
@ -279,7 +300,7 @@ public class AliasModel |
|
|
|
deltaNormals[vertIdx] = QLightNormals.Get(poseVerts[vertIdx].lightNormalIndex) - baseNormals[vertIdx]; |
|
|
|
} |
|
|
|
|
|
|
|
mesh.AddBlendShapeFrame(animName, (float)index / numFrames, deltaVertices, deltaNormals, null); |
|
|
|
mesh.AddBlendShapeFrame(animName, (float)index / numPoses, deltaVertices, deltaNormals, null); |
|
|
|
} |
|
|
|
} |
|
|
|
} |