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

76
Assets/Scripts/Modules/RenderModule.cs

@ -18,25 +18,40 @@ public partial class RenderModule
{
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);
ConvertUVs(stVertices, header.skinWidth, header.skinHeight, out var uvs);
AliasModel aliasModel = new AliasModel();
var mesh = new Mesh { name = name };
mesh.SetVertices(vertices);
mesh.SetNormals(normals);
mesh.SetIndices(indices, MeshTopology.Triangles, 0, false);
mesh.SetUVs(0, uvs);
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;
}
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.Optimize(); // This ensures that triangles will be properly fused and organized in the best possible way
mesh.RecalculateBounds();
mesh.UploadMeshData(true);
mesh = CreateAnimatedMesh(header, poseVertices, indices, uvs, animName, startFrame, header.numFrames);
aliasModel.AddAnimation(startFrame, mesh);
var go = new GameObject(System.IO.Path.GetFileNameWithoutExtension(name));
go.transform.SetPositionAndRotation(new Vector3(xPos, 0, 0), Quaternion.Euler(-90, 90, 0));
if (header.numFrames > 1)
{
var mr = go.AddComponent<SkinnedMeshRenderer>();
@ -46,7 +61,9 @@ public partial class RenderModule
mr.receiveShadows = false;
mr.lightProbeUsage = LightProbeUsage.Off;
mr.reflectionProbeUsage = ReflectionProbeUsage.Off;
go.AddComponent<AliasModelAnimator>();
var animator = go.AddComponent<AliasModelAnimator>();
animator.aliasModel = aliasModel;
}
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(
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 deltaNormals = new Vector3[header.numVerts];
@ -119,9 +156,12 @@ public partial class RenderModule
Vector3 scale = header.scale.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];
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];
}
mesh.AddBlendShapeFrame(mesh.name, (float)frameIdx / (header.numFrames-1), deltaVertices, deltaNormals, null);
mesh.AddBlendShapeFrame(animName, (float)index / numFrames, deltaVertices, deltaNormals, null);
}
}
}
Loading…
Cancel
Save