Browse Source

Further mesh research: create an individual mesh for each animation sequence, with a single blend shape animation per mesh. Added an AliasModel class to manage meshes and find the correct animations. With this we can smoothly animate looped animation cycles.

console
Nico de Poel 5 years ago
parent
commit
4138290723
  1. 53
      Assets/Scripts/Modules/AliasModel.cs
  2. 3
      Assets/Scripts/Modules/AliasModel.cs.meta
  3. 10
      Assets/Scripts/Modules/AliasModelAnimator.cs
  4. 72
      Assets/Scripts/Modules/RenderModule.cs

53
Assets/Scripts/Modules/AliasModel.cs

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
public class AliasModel
{
public static readonly Regex AnimationRegex = new Regex(@"^[a-zA-Z]+");
private readonly List<(int, Mesh)> animationMeshes = new List<(int, Mesh)>(); // TODO: make this a fixed array after initialization
public void AddAnimation(int startFrame, Mesh mesh)
{
animationMeshes.Add((startFrame, mesh));
}
public int GetAnimationFrameCount(float frameNum)
{
if (!FindAnimation((int)frameNum, out Mesh mesh, out int startFrame))
return 0;
return mesh.GetBlendShapeFrameCount(0);
}
public void Animate(float frameNum, out Mesh mesh, out float blendWeight)
{
blendWeight = 0;
int frameIndex = (int)frameNum;
if (!FindAnimation(frameIndex, out mesh, out int startFrame))
return;
int numFrames = mesh.GetBlendShapeFrameCount(0);
blendWeight = ((frameNum - startFrame) / numFrames) % 1;
}
private bool FindAnimation(int frameIndex, out Mesh mesh, out int startFrame)
{
mesh = null;
startFrame = 0;
for (int i = 0; i < animationMeshes.Count; ++i)
{
startFrame = animationMeshes[i].Item1;
if (frameIndex >= startFrame)
{
mesh = animationMeshes[i].Item2;
return true;
}
}
return false; // Shouldn't happen
}
}

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

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 74fb95086b6c4400bc3696481e4679e6
timeCreated: 1618650253

10
Assets/Scripts/Modules/AliasModelAnimator.cs

@ -3,6 +3,8 @@ using UnityEngine;
public class AliasModelAnimator : MonoBehaviour public class AliasModelAnimator : MonoBehaviour
{ {
public AliasModel aliasModel;
//private int frameNumber = 0; //private int frameNumber = 0;
private float frameNumber = 0; private float frameNumber = 0;
@ -15,19 +17,19 @@ public class AliasModelAnimator : MonoBehaviour
yield break; yield break;
} }
var mesh = meshRenderer.sharedMesh;
int numFrames = mesh.GetBlendShapeFrameCount(0);
int numFrames = aliasModel.GetAnimationFrameCount(0);
while (true) while (true)
{ {
float blendWeight = (float)frameNumber / numFrames;
aliasModel.Animate(frameNumber, out Mesh mesh, out float blendWeight);
meshRenderer.sharedMesh = mesh;
meshRenderer.SetBlendShapeWeight(0, blendWeight); meshRenderer.SetBlendShapeWeight(0, blendWeight);
//yield return new WaitForSeconds(0.1f); // Animate at 10 fps //yield return new WaitForSeconds(0.1f); // Animate at 10 fps
yield return null; yield return null;
//frameNumber = (frameNumber + 1) % numFrames; //frameNumber = (frameNumber + 1) % numFrames;
frameNumber = (frameNumber + Time.deltaTime * 10) % numFrames;
frameNumber = (frameNumber + Time.deltaTime * 5) % numFrames;
} }
} }
} }

72
Assets/Scripts/Modules/RenderModule.cs

@ -18,21 +18,36 @@ public partial class RenderModule
{ {
Debug.Log($"Alias model '{name}' with {header.numVerts} vertices, {header.numTriangles} triangles, {header.numFrames} frame(s)"); Debug.Log($"Alias model '{name}' with {header.numVerts} vertices, {header.numTriangles} triangles, {header.numFrames} frame(s)");
ConvertVertices(header, poseVertices[0], out var vertices, out var normals);
ConvertTriangles(triangles, out var indices); ConvertTriangles(triangles, out var indices);
ConvertUVs(stVertices, header.skinWidth, header.skinHeight, out var uvs); 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, false);
mesh.SetUVs(0, uvs);
AliasModel aliasModel = new AliasModel();
CreateBlendShapes(mesh, vertices, normals, header, poseVertices);
Mesh mesh;
string animName = null;
int startFrame = 0;
for (int frameIdx = 0; frameIdx < header.numFrames; ++frameIdx)
{
string frameName = AliasModel.AnimationRegex.Match(header.frames[frameIdx].name).Value;
if (animName == null)
{
animName = frameName;
continue;
}
mesh.Optimize(); // This ensures that triangles will be properly fused and organized in the best possible way
mesh.RecalculateBounds();
mesh.UploadMeshData(true);
if (frameName != animName)
{
// New animation sequence; convert the previous sequence into a blend shape animation
mesh = CreateAnimatedMesh(header, poseVertices, indices, uvs, animName, startFrame, frameIdx);
aliasModel.AddAnimation(startFrame, mesh);
animName = frameName;
startFrame = frameIdx;
}
}
mesh = CreateAnimatedMesh(header, poseVertices, indices, uvs, animName, startFrame, header.numFrames);
aliasModel.AddAnimation(startFrame, mesh);
var go = new GameObject(System.IO.Path.GetFileNameWithoutExtension(name)); var go = new GameObject(System.IO.Path.GetFileNameWithoutExtension(name));
go.transform.SetPositionAndRotation(new Vector3(xPos, 0, 0), Quaternion.Euler(-90, 90, 0)); go.transform.SetPositionAndRotation(new Vector3(xPos, 0, 0), Quaternion.Euler(-90, 90, 0));
@ -46,7 +61,9 @@ public partial class RenderModule
mr.receiveShadows = false; mr.receiveShadows = false;
mr.lightProbeUsage = LightProbeUsage.Off; mr.lightProbeUsage = LightProbeUsage.Off;
mr.reflectionProbeUsage = ReflectionProbeUsage.Off; mr.reflectionProbeUsage = ReflectionProbeUsage.Off;
go.AddComponent<AliasModelAnimator>();
var animator = go.AddComponent<AliasModelAnimator>();
animator.aliasModel = aliasModel;
} }
else else
{ {
@ -109,9 +126,29 @@ public partial class RenderModule
} }
} }
private static Mesh CreateAnimatedMesh(
QAliasHeader header, QTriVertex[][] poseVertices, ushort[] indices, Vector2[] uvs,
string animationName, int startFrame, int endFrame)
{
ConvertVertices(header, poseVertices[startFrame], out var baseVertices, out var baseNormals);
var mesh = new Mesh { name = animationName };
mesh.SetVertices(baseVertices);
mesh.SetNormals(baseNormals);
CreateBlendShapes(mesh, animationName, baseVertices, baseNormals, header, poseVertices, startFrame, endFrame);
mesh.SetIndices(indices, MeshTopology.Triangles, 0, false);
mesh.SetUVs(0, uvs);
mesh.Optimize();
mesh.RecalculateBounds();
mesh.UploadMeshData(true);
return mesh;
}
private static void CreateBlendShapes( private static void CreateBlendShapes(
Mesh mesh, Vector3[] baseVertices, Vector3[] baseNormals,
QAliasHeader header, QTriVertex[][] poseVertices)
Mesh mesh, string animName, Vector3[] baseVertices, Vector3[] baseNormals,
QAliasHeader header, QTriVertex[][] poseVertices, int startFrame, int endFrame)
{ {
var deltaVertices = new Vector3[header.numVerts]; var deltaVertices = new Vector3[header.numVerts];
var deltaNormals = new Vector3[header.numVerts]; var deltaNormals = new Vector3[header.numVerts];
@ -119,9 +156,12 @@ public partial class RenderModule
Vector3 scale = header.scale.ToVector3(); Vector3 scale = header.scale.ToVector3();
Vector3 origin = header.scaleOrigin.ToVector3(); Vector3 origin = header.scaleOrigin.ToVector3();
// Frame 0 is the base pose, so we start with frame 1
for (int frameIdx = 1; frameIdx < header.numFrames; ++frameIdx)
int numFrames = endFrame - startFrame;
// 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)
{ {
int frameIdx = startFrame + index % numFrames;
var poseVerts = poseVertices[frameIdx]; var poseVerts = poseVertices[frameIdx];
for (int vertIdx = 0; vertIdx < header.numVerts; ++vertIdx) for (int vertIdx = 0; vertIdx < header.numVerts; ++vertIdx)
@ -131,7 +171,7 @@ public partial class RenderModule
deltaNormals[vertIdx] = QLightNormals.Get(poseVerts[vertIdx].lightNormalIndex) - baseNormals[vertIdx]; deltaNormals[vertIdx] = QLightNormals.Get(poseVerts[vertIdx].lightNormalIndex) - baseNormals[vertIdx];
} }
mesh.AddBlendShapeFrame(mesh.name, (float)frameIdx / (header.numFrames-1), deltaVertices, deltaNormals, null);
mesh.AddBlendShapeFrame(animName, (float)index / numFrames, deltaVertices, deltaNormals, null);
} }
} }
} }
Loading…
Cancel
Save