diff --git a/Assets/Scripts/Game/Entity.cs b/Assets/Scripts/Game/Entity.cs index 71599b6..3b3d06e 100644 --- a/Assets/Scripts/Game/Entity.cs +++ b/Assets/Scripts/Game/Entity.cs @@ -79,7 +79,7 @@ public class Entity AssignMeshRenderer(); // Set a default pose based on the first animation frame - UpdateAnimation(0); + UpdateAnimation(0, 0, 0f); if (model.AutoAnimate) { @@ -103,11 +103,11 @@ public class Entity worldModel.transform.SetParent(gameObject.transform); } - public void UpdateAnimation(float frameNum) + public void UpdateAnimation(int pose1, int pose2, float blend) { if (aliasModel != null) { - aliasModel.Animate(frameNum, out Mesh mesh, out float blendWeight); + aliasModel.Animate(pose1, pose2, blend, out Mesh mesh, out float blendWeight); if (skinnedMeshRenderer.enabled) { diff --git a/Assets/Scripts/Modules/GameModule.Interop.cs b/Assets/Scripts/Modules/GameModule.Interop.cs index 3e72b1b..022aafc 100644 --- a/Assets/Scripts/Modules/GameModule.Interop.cs +++ b/Assets/Scripts/Modules/GameModule.Interop.cs @@ -66,12 +66,12 @@ public partial class GameModule : CallbackHandler } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate void GameUpdateEntityAnimationCallback(IntPtr target, int entityNum, float frameNum); + private delegate void GameUpdateEntityAnimationCallback(IntPtr target, int entityNum, int pose1, int pose2, float blend); [MonoPInvokeCallback(typeof(GameUpdateEntityAnimationCallback))] - private static void Callback_GameUpdateEntityAnimation(IntPtr target, int entityNum, float frameNum) + private static void Callback_GameUpdateEntityAnimation(IntPtr target, int entityNum, int pose1, int pose2, float blend) { - GetSelf(target).UpdateEntityAnimation(entityNum, frameNum); + GetSelf(target).UpdateEntityAnimation(entityNum, pose1, pose2, blend); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] diff --git a/Assets/Scripts/Modules/GameModule.cs b/Assets/Scripts/Modules/GameModule.cs index e4b5e7a..5f7e3a8 100644 --- a/Assets/Scripts/Modules/GameModule.cs +++ b/Assets/Scripts/Modules/GameModule.cs @@ -58,9 +58,9 @@ public partial class GameModule uq.GameState.RemoveEntity(entityNum); } - private void UpdateEntityAnimation(int entityNum, float frameNum) + private void UpdateEntityAnimation(int entityNum, int pose1, int pose2, float blend) { - uq.GameState.GetEntity(entityNum)?.UpdateAnimation(frameNum); + uq.GameState.GetEntity(entityNum)?.UpdateAnimation(pose1, pose2, blend); } private void SetEntitySkin(int entityNum, int skinNum) diff --git a/Assets/Scripts/Support/AliasModel.cs b/Assets/Scripts/Support/AliasModel.cs index 781ae9c..c35edac 100644 --- a/Assets/Scripts/Support/AliasModel.cs +++ b/Assets/Scripts/Support/AliasModel.cs @@ -26,7 +26,9 @@ public class AliasModel public int GetAnimationFrameCount(float frameNum) { - if (!FindAnimation((int)frameNum, out Mesh mesh, out int startFrame)) + GetAnimationMesh((int)frameNum, out Mesh mesh, out int startFrame); + + if (mesh == null) return 0; if (mesh.blendShapeCount == 0) @@ -37,34 +39,74 @@ public class AliasModel public void Animate(float frameNum, out Mesh mesh, out float blendWeight) { + GetAnimationMesh((int)frameNum, out mesh, out int startFrame); + + if (mesh == null || mesh.blendShapeCount == 0) + { + blendWeight = 0; + return; + } + + int numFrames = mesh.GetBlendShapeFrameCount(0); + blendWeight = ((frameNum - startFrame) / numFrames) % 1; + } + + public void Animate(int pose1, int pose2, float blend, out Mesh mesh, out float blendWeight) + { + int startFrame, numFrames; blendWeight = 0; - int frameIndex = (int)frameNum; - if (!FindAnimation(frameIndex, out mesh, out int startFrame)) + // Don't interpolate if we're on a fixed pose + if (pose1 == pose2) + { + GetAnimationMesh(pose1, out mesh, out startFrame); + if (mesh == null || mesh.blendShapeCount == 0) + return; + + numFrames = mesh.GetBlendShapeFrameCount(0); + blendWeight = ((float)pose1 - startFrame) / numFrames % 1; return; + } + + GetAnimationMesh(pose1, out var mesh1, out int startFrame1); + GetAnimationMesh(pose2, out var mesh2, out int startFrame2); + float pose; + if (mesh1 != mesh2) + { + // We cannot blend from one animation sequence to another, since each sequence is a separate Unity mesh. + // Therefore when transitioning between sequences, we hold the previous and next pose for half a frame each. + mesh = blend > 0.5f ? mesh2 : mesh1; + startFrame = blend > 0.5f ? startFrame2 : startFrame1; + pose = blend > 0.5f ? pose2 : pose1; + } + else + { + mesh = mesh1; + startFrame = startFrame1; + pose = pose1 + blend; + } + if (mesh == null || mesh.blendShapeCount == 0) return; - int numFrames = mesh.GetBlendShapeFrameCount(0); - blendWeight = ((frameNum - startFrame) / numFrames) % 1; + numFrames = mesh.GetBlendShapeFrameCount(0); + blendWeight = (pose - startFrame) / numFrames % 1; } - private bool FindAnimation(int frameIndex, out Mesh mesh, out int startFrame) + private void GetAnimationMesh(int pose, out Mesh mesh, out int startFrame) { mesh = null; startFrame = 0; for (int i = 0; i < animationMeshes.Count; ++i) { - if (animationMeshes[i].Item1 > frameIndex) - return true; + if (animationMeshes[i].Item1 > pose) + return; startFrame = animationMeshes[i].Item1; mesh = animationMeshes[i].Item2; } - - return true; } public void Dispose() diff --git a/engine/Quake/client.h b/engine/Quake/client.h index 843ee6e..529d1aa 100644 --- a/engine/Quake/client.h +++ b/engine/Quake/client.h @@ -375,7 +375,7 @@ void Chase_UpdateForDrawing (void); //johnfitz void UQ_Game_SetEntityModel(int entityNum, const char *modelName); void UQ_Game_SetEntityTransform(int entityNum, vec3_t origin, vec3_t angles); void UQ_Game_RemoveEntity(int entityNum); -void UQ_Game_UpdateEntityAnimation(int entityNum, float frameNum); +void UQ_Game_UpdateEntityAnimation(int entityNum, int pose1, int pose2, float blend); void UQ_Game_SetEntitySkin(int entityNum, int skinNum); #endif /* _CLIENT_H_ */ diff --git a/engine/Quake/r_alias.c b/engine/Quake/r_alias.c index f787047..e4bfe6e 100644 --- a/engine/Quake/r_alias.c +++ b/engine/Quake/r_alias.c @@ -642,10 +642,7 @@ void R_DrawAliasModel (entity_t *e) R_SetupEntityTransform (e, &lerpdata); UQ_Game_SetEntityTransform(e->num, lerpdata.origin, lerpdata.angles); - if (lerpdata.pose1 == lerpdata.pose2) - UQ_Game_UpdateEntityAnimation(e->num, (float)lerpdata.pose1); // Fixed pose, don't lerp - else - UQ_Game_UpdateEntityAnimation(e->num, (float)lerpdata.pose1 + lerpdata.blend); + UQ_Game_UpdateEntityAnimation(e->num, lerpdata.pose1, lerpdata.pose2, lerpdata.blend); // // cull it diff --git a/engine/UniQuake/game_uniquake.c b/engine/UniQuake/game_uniquake.c index 2bbdf86..0f057f9 100644 --- a/engine/UniQuake/game_uniquake.c +++ b/engine/UniQuake/game_uniquake.c @@ -9,7 +9,7 @@ typedef struct unity_gamecalls_s void(*SetEntityModel)(void *target, int entityNum, const char *modelName); void(*SetEntityTransform)(void *target, int entityNum, vec3_t origin, vec3_t angles); void(*RemoveEntity)(void *target, int entityNum); - void(*UpdateEntityAnimation)(void *target, int entityNum, float frameNum); + void(*UpdateEntityAnimation)(void *target, int entityNum, int pose1, int pose2, float blend); void(*SetEntitySkin)(void *target, int entityNum, int skinNum); } unity_gamecalls_t; @@ -30,9 +30,9 @@ void UQ_Game_RemoveEntity(int entityNum) unity_gamecalls->RemoveEntity(unity_gamecalls->target, entityNum); } -void UQ_Game_UpdateEntityAnimation(int entityNum, float frameNum) +void UQ_Game_UpdateEntityAnimation(int entityNum, int pose1, int pose2, float blend) { - unity_gamecalls->UpdateEntityAnimation(unity_gamecalls->target, entityNum, frameNum); + unity_gamecalls->UpdateEntityAnimation(unity_gamecalls->target, entityNum, pose1, pose2, blend); } void UQ_Game_SetEntitySkin(int entityNum, int skinNum)