Browse Source

Reworked the entity animation system, so that blending between poses is pulled fully to the Unity side. With this we can detect the situation where we are trying to blend from one animation sequence to another. Since this is not possible in UniQuake due to each animation sequence being converted to a separate mesh, we instead hold the poses for a frame as if we're not using animation lerping at all. This fixes the occasional animation glitch where the previous sequence would be animated a bit too far, which can be very noticeable on non-looping animation sequences (e.g. the Chthon's rise animation).

readme
Nico de Poel 5 years ago
parent
commit
f668c97b52
  1. 6
      Assets/Scripts/Game/Entity.cs
  2. 6
      Assets/Scripts/Modules/GameModule.Interop.cs
  3. 4
      Assets/Scripts/Modules/GameModule.cs
  4. 62
      Assets/Scripts/Support/AliasModel.cs
  5. 2
      engine/Quake/client.h
  6. 5
      engine/Quake/r_alias.c
  7. 6
      engine/UniQuake/game_uniquake.c

6
Assets/Scripts/Game/Entity.cs

@ -79,7 +79,7 @@ public class Entity
AssignMeshRenderer(); AssignMeshRenderer();
// Set a default pose based on the first animation frame // Set a default pose based on the first animation frame
UpdateAnimation(0);
UpdateAnimation(0, 0, 0f);
if (model.AutoAnimate) if (model.AutoAnimate)
{ {
@ -103,11 +103,11 @@ public class Entity
worldModel.transform.SetParent(gameObject.transform); worldModel.transform.SetParent(gameObject.transform);
} }
public void UpdateAnimation(float frameNum)
public void UpdateAnimation(int pose1, int pose2, float blend)
{ {
if (aliasModel != null) 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) if (skinnedMeshRenderer.enabled)
{ {

6
Assets/Scripts/Modules/GameModule.Interop.cs

@ -66,12 +66,12 @@ public partial class GameModule : CallbackHandler<GameModule>
} }
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [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))] [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)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]

4
Assets/Scripts/Modules/GameModule.cs

@ -58,9 +58,9 @@ public partial class GameModule
uq.GameState.RemoveEntity(entityNum); 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) private void SetEntitySkin(int entityNum, int skinNum)

62
Assets/Scripts/Support/AliasModel.cs

@ -26,7 +26,9 @@ public class AliasModel
public int GetAnimationFrameCount(float frameNum) 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; return 0;
if (mesh.blendShapeCount == 0) if (mesh.blendShapeCount == 0)
@ -37,34 +39,74 @@ public class AliasModel
public void Animate(float frameNum, out Mesh mesh, out float blendWeight) 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; 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; 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) if (mesh == null || mesh.blendShapeCount == 0)
return; 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; mesh = null;
startFrame = 0; startFrame = 0;
for (int i = 0; i < animationMeshes.Count; ++i) 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; startFrame = animationMeshes[i].Item1;
mesh = animationMeshes[i].Item2; mesh = animationMeshes[i].Item2;
} }
return true;
} }
public void Dispose() public void Dispose()

2
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_SetEntityModel(int entityNum, const char *modelName);
void UQ_Game_SetEntityTransform(int entityNum, vec3_t origin, vec3_t angles); void UQ_Game_SetEntityTransform(int entityNum, vec3_t origin, vec3_t angles);
void UQ_Game_RemoveEntity(int entityNum); 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); void UQ_Game_SetEntitySkin(int entityNum, int skinNum);
#endif /* _CLIENT_H_ */ #endif /* _CLIENT_H_ */

5
engine/Quake/r_alias.c

@ -642,10 +642,7 @@ void R_DrawAliasModel (entity_t *e)
R_SetupEntityTransform (e, &lerpdata); R_SetupEntityTransform (e, &lerpdata);
UQ_Game_SetEntityTransform(e->num, lerpdata.origin, lerpdata.angles); 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 // cull it

6
engine/UniQuake/game_uniquake.c

@ -9,7 +9,7 @@ typedef struct unity_gamecalls_s
void(*SetEntityModel)(void *target, int entityNum, const char *modelName); void(*SetEntityModel)(void *target, int entityNum, const char *modelName);
void(*SetEntityTransform)(void *target, int entityNum, vec3_t origin, vec3_t angles); void(*SetEntityTransform)(void *target, int entityNum, vec3_t origin, vec3_t angles);
void(*RemoveEntity)(void *target, int entityNum); 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); void(*SetEntitySkin)(void *target, int entityNum, int skinNum);
} unity_gamecalls_t; } unity_gamecalls_t;
@ -30,9 +30,9 @@ void UQ_Game_RemoveEntity(int entityNum)
unity_gamecalls->RemoveEntity(unity_gamecalls->target, 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) void UQ_Game_SetEntitySkin(int entityNum, int skinNum)

Loading…
Cancel
Save