Browse Source

Added missing interpolation types for GLTF animation channels (#3919)

Benji 1 year ago
parent
commit
4e37c8e370
2 changed files with 120 additions and 22 deletions
  1. 28 0
      src/raymath.h
  2. 92 22
      src/rmodels.c

+ 28 - 0
src/raymath.h

@@ -955,6 +955,22 @@ RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount)
     return result;
     return result;
 }
 }
 
 
+// Calculate cubic hermite interpolation between two vectors and their tangents
+// taken directly from: https://en.wikipedia.org/wiki/Cubic_Hermite_spline
+RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount)
+{
+    Vector3 result = { 0 };
+
+    float amountPow2 = amount * amount;
+    float amountPow3 = amount * amount * amount;
+
+    result.x = (2 * amountPow3 - 3 * amountPow2 + 1) * v1.x + (amountPow3 - 2 * amountPow2 + amount) * tangent1.x + (-2 * amountPow3 + 3 * amountPow2) * v2.x + (amountPow3 - amountPow2) * tangent2.x;
+    result.y = (2 * amountPow3 - 3 * amountPow2 + 1) * v1.y + (amountPow3 - 2 * amountPow2 + amount) * tangent1.y + (-2 * amountPow3 + 3 * amountPow2) * v2.y + (amountPow3 - amountPow2) * tangent2.y;
+    result.z = (2 * amountPow3 - 3 * amountPow2 + 1) * v1.z + (amountPow3 - 2 * amountPow2 + amount) * tangent1.z + (-2 * amountPow3 + 3 * amountPow2) * v2.z + (amountPow3 - amountPow2) * tangent2.z;
+
+    return result;
+}
+
 // Calculate reflected vector to normal
 // Calculate reflected vector to normal
 RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal)
 RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal)
 {
 {
@@ -2197,6 +2213,18 @@ RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount)
     return result;
     return result;
 }
 }
 
 
+// Calculate quaternion cubic spline interpolation using the SQUAD algorithm
+// roughly adapted from the SQUAD algorithm presented here: https://roboop.sourceforge.io/htmldoc/robotse9.html
+RMAPI Quaternion QuaternionCubicSpline(Quaternion q1, Quaternion tangent1, Quaternion q2, Quaternion tangent2, float amount)
+{
+    Quaternion slerp1 = QuaternionSlerp(q1, q2, amount);
+    Quaternion slerp2 = QuaternionSlerp(tangent1, tangent2, amount);
+    float t = 2 * amount * (1 - amount);
+
+    Quaternion result = QuaternionSlerp(slerp1, slerp2, t);
+    return result;
+}
+
 // Calculate quaternion based on the rotation from one vector to another
 // Calculate quaternion based on the rotation from one vector to another
 RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to)
 RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to)
 {
 {

+ 92 - 22
src/rmodels.c

@@ -5356,8 +5356,10 @@ static Model LoadGLTF(const char *fileName)
 }
 }
 
 
 // Get interpolated pose for bone sampler at a specific time. Returns true on success.
 // Get interpolated pose for bone sampler at a specific time. Returns true on success.
-static bool GetPoseAtTimeGLTF(cgltf_accessor *input, cgltf_accessor *output, float time, void *data)
+static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_accessor *input, cgltf_accessor *output, float time, void *data)
 {
 {
+    if (interpolationType >= cgltf_interpolation_type_max_enum) return false;
+
     // Input and output should have the same count
     // Input and output should have the same count
     float tstart = 0.0f;
     float tstart = 0.0f;
     float tend = 0.0f;
     float tend = 0.0f;
@@ -5377,7 +5379,7 @@ static bool GetPoseAtTimeGLTF(cgltf_accessor *input, cgltf_accessor *output, flo
             break;
             break;
         }
         }
     }
     }
-
+    
     float t = (time - tstart)/fmax((tend - tstart), EPSILON);
     float t = (time - tstart)/fmax((tend - tstart), EPSILON);
     t = (t < 0.0f)? 0.0f : t;
     t = (t < 0.0f)? 0.0f : t;
     t = (t > 1.0f)? 1.0f : t;
     t = (t > 1.0f)? 1.0f : t;
@@ -5386,25 +5388,90 @@ static bool GetPoseAtTimeGLTF(cgltf_accessor *input, cgltf_accessor *output, flo
 
 
     if (output->type == cgltf_type_vec3)
     if (output->type == cgltf_type_vec3)
     {
     {
-        float tmp[3] = { 0.0f };
-        cgltf_accessor_read_float(output, keyframe, tmp, 3);
-        Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
-        cgltf_accessor_read_float(output, keyframe+1, tmp, 3);
-        Vector3 v2 = {tmp[0], tmp[1], tmp[2]};
-        Vector3 *r = data;
-        *r = Vector3Lerp(v1, v2, t);
+        switch (interpolationType)
+        {
+            case cgltf_interpolation_type_step:
+            {
+                float tmp[3] = { 0.0f };
+                cgltf_accessor_read_float(output, keyframe, tmp, 3);
+                Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
+                Vector3 *r = data;
+
+                *r = v1;
+            } break;
+
+            case cgltf_interpolation_type_linear:
+            {
+                float tmp[3] = { 0.0f };
+                cgltf_accessor_read_float(output, keyframe, tmp, 3);
+                Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
+                cgltf_accessor_read_float(output, keyframe+1, tmp, 3);
+                Vector3 v2 = {tmp[0], tmp[1], tmp[2]};
+                Vector3 *r = data;
+                
+                *r = Vector3Lerp(v1, v2, t);
+            } break;
+
+            case cgltf_interpolation_type_cubic_spline:
+            {
+                float tmp[3] = { 0.0f };
+                cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 3);
+                Vector3 v1 = {tmp[0], tmp[1], tmp[2]};
+                cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 3);
+                Vector3 tangent1 = {tmp[0], tmp[1], tmp[2]};
+                cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 3);
+                Vector3 v2 = {tmp[0], tmp[1], tmp[2]};
+                cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 3);
+                Vector3 tangent2 = {tmp[0], tmp[1], tmp[2]};
+                Vector3 *r = data;
+
+                *r = Vector3CubicHermite(v1, tangent1, v2, tangent2, t);
+            } break;
+        }
     }
     }
     else if (output->type == cgltf_type_vec4)
     else if (output->type == cgltf_type_vec4)
     {
     {
-        float tmp[4] = { 0.0f };
-        cgltf_accessor_read_float(output, keyframe, tmp, 4);
-        Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
-        cgltf_accessor_read_float(output, keyframe+1, tmp, 4);
-        Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]};
-        Vector4 *r = data;
-
         // Only v4 is for rotations, so we know it's a quaternion
         // Only v4 is for rotations, so we know it's a quaternion
-        *r = QuaternionSlerp(v1, v2, t);
+        switch (interpolationType)
+        {
+            case cgltf_interpolation_type_step:
+            {
+                float tmp[4] = { 0.0f };
+                cgltf_accessor_read_float(output, keyframe, tmp, 4);
+                Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+                Vector4 *r = data;
+
+                *r = v1;
+            } break;
+
+            case cgltf_interpolation_type_linear:
+            {
+                float tmp[4] = { 0.0f };
+                cgltf_accessor_read_float(output, keyframe, tmp, 4);
+                Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+                cgltf_accessor_read_float(output, keyframe+1, tmp, 4);
+                Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+                Vector4 *r = data;
+                
+                *r = QuaternionSlerp(v1, v2, t);
+            } break;
+
+            case cgltf_interpolation_type_cubic_spline:
+            {
+                float tmp[4] = { 0.0f };
+                cgltf_accessor_read_float(output, 3*keyframe+1, tmp, 4);
+                Vector4 v1 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+                cgltf_accessor_read_float(output, 3*keyframe+2, tmp, 4);
+                Vector4 tangent1 = {tmp[0], tmp[1], tmp[2]};
+                cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 4);
+                Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+                cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 4);
+                Vector4 tangent2 = {tmp[0], tmp[1], tmp[2]};
+                Vector4 *r = data;
+
+                *r = QuaternionCubicSpline(v1, tangent1, v2, tangent2, t);
+            } break;
+        }
     }
     }
 
 
     return true;
     return true;
@@ -5455,6 +5522,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
                     cgltf_animation_channel *translate;
                     cgltf_animation_channel *translate;
                     cgltf_animation_channel *rotate;
                     cgltf_animation_channel *rotate;
                     cgltf_animation_channel *scale;
                     cgltf_animation_channel *scale;
+                    cgltf_interpolation_type interpolationType;
                 };
                 };
 
 
                 struct Channels *boneChannels = RL_CALLOC(animations[i].boneCount, sizeof(struct Channels));
                 struct Channels *boneChannels = RL_CALLOC(animations[i].boneCount, sizeof(struct Channels));
@@ -5480,7 +5548,9 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
                         continue;
                         continue;
                     }
                     }
 
 
-                    if (animData.channels[j].sampler->interpolation == cgltf_interpolation_type_linear)
+                    boneChannels[boneIndex].interpolationType = animData.channels[j].sampler->interpolation;
+
+                    if (animData.channels[j].sampler->interpolation != cgltf_interpolation_type_max_enum)
                     {
                     {
                         if (channel.target_path == cgltf_animation_path_type_translation)
                         if (channel.target_path == cgltf_animation_path_type_translation)
                         {
                         {
@@ -5499,7 +5569,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
                             TRACELOG(LOG_WARNING, "MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping.", fileName, j, i);
                             TRACELOG(LOG_WARNING, "MODEL: [%s] Unsupported target_path on channel %d's sampler for animation %d. Skipping.", fileName, j, i);
                         }
                         }
                     }
                     }
-                    else TRACELOG(LOG_WARNING, "MODEL: [%s] Only linear interpolation curves are supported for GLTF animation.", fileName);
+                    else TRACELOG(LOG_WARNING, "MODEL: [%s] Invalid interpolation curve encountered for GLTF animation.", fileName);
 
 
                     float t = 0.0f;
                     float t = 0.0f;
                     cgltf_bool r = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &t, 1);
                     cgltf_bool r = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &t, 1);
@@ -5532,7 +5602,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
 
 
                         if (boneChannels[k].translate)
                         if (boneChannels[k].translate)
                         {
                         {
-                            if (!GetPoseAtTimeGLTF(boneChannels[k].translate->sampler->input, boneChannels[k].translate->sampler->output, time, &translation))
+                            if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].translate->sampler->input, boneChannels[k].translate->sampler->output, time, &translation))
                             {
                             {
                                 TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load translate pose data for bone %s", fileName, animations[i].bones[k].name);
                                 TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load translate pose data for bone %s", fileName, animations[i].bones[k].name);
                             }
                             }
@@ -5540,7 +5610,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
 
 
                         if (boneChannels[k].rotate)
                         if (boneChannels[k].rotate)
                         {
                         {
-                            if (!GetPoseAtTimeGLTF(boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, &rotation))
+                            if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].rotate->sampler->input, boneChannels[k].rotate->sampler->output, time, &rotation))
                             {
                             {
                                 TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, animations[i].bones[k].name);
                                 TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load rotate pose data for bone %s", fileName, animations[i].bones[k].name);
                             }
                             }
@@ -5548,7 +5618,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
 
 
                         if (boneChannels[k].scale)
                         if (boneChannels[k].scale)
                         {
                         {
-                            if (!GetPoseAtTimeGLTF(boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, &scale))
+                            if (!GetPoseAtTimeGLTF(boneChannels[k].interpolationType, boneChannels[k].scale->sampler->input, boneChannels[k].scale->sampler->output, time, &scale))
                             {
                             {
                                 TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, animations[i].bones[k].name);
                                 TRACELOG(LOG_INFO, "MODEL: [%s] Failed to load scale pose data for bone %s", fileName, animations[i].bones[k].name);
                             }
                             }