Browse Source

Replaced SQUAD quat interpolation with cubic hermite to align with gltf 2.0 spec (#3920)

Benji 1 year ago
parent
commit
4491ff0426
2 changed files with 42 additions and 16 deletions
  1. 22 8
      src/raymath.h
  2. 20 8
      src/rmodels.c

+ 22 - 8
src/raymath.h

@@ -956,7 +956,7 @@ RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount)
 }
 
 // Calculate cubic hermite interpolation between two vectors and their tangents
-// taken directly from: https://en.wikipedia.org/wiki/Cubic_Hermite_spline
+// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic
 RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount)
 {
     Vector3 result = { 0 };
@@ -2213,15 +2213,29 @@ RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount)
     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)
+// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm
+// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic
+RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t)
 {
-    Quaternion slerp1 = QuaternionSlerp(q1, q2, amount);
-    Quaternion slerp2 = QuaternionSlerp(tangent1, tangent2, amount);
-    float t = 2 * amount * (1 - amount);
+    float t2 = t * t;
+    float t3 = t2 * t;
+    float h00 = 2 * t3 - 3 * t2 + 1;
+    float h10 = t3 - 2 * t2 + t;    
+    float h01 = -2 * t3 + 3 * t2;   
+    float h11 = t3 - t2;            
+
+    Quaternion p0 = QuaternionScale(q1, h00);
+    Quaternion m0 = QuaternionScale(outTangent1, h10);
+    Quaternion p1 = QuaternionScale(q2, h01);
+    Quaternion m1 = QuaternionScale(inTangent2, h11);
+
+    Quaternion result = { 0 };
+
+    result = QuaternionAdd(p0, m0);
+    result = QuaternionAdd(result, p1);
+    result = QuaternionAdd(result, m1);
+    result = QuaternionNormalize(result);
 
-    Quaternion result = QuaternionSlerp(slerp1, slerp2, t);
     return result;
 }
 

+ 20 - 8
src/rmodels.c

@@ -5380,7 +5380,8 @@ static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_
         }
     }
     
-    float t = (time - tstart)/fmax((tend - tstart), EPSILON);
+    float duration = fmax((tend - tstart), EPSILON);
+    float t = (time - tstart)/duration;
     t = (t < 0.0f)? 0.0f : t;
     t = (t > 1.0f)? 1.0f : t;
 
@@ -5419,9 +5420,9 @@ static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_
                 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 v2 = {tmp[0], tmp[1], tmp[2]};
+                cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 3);
                 Vector3 tangent2 = {tmp[0], tmp[1], tmp[2]};
                 Vector3 *r = data;
 
@@ -5462,14 +5463,25 @@ static bool GetPoseAtTimeGLTF(cgltf_interpolation_type interpolationType, cgltf_
                 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]};
+                Vector4 outTangent1 = {tmp[0], tmp[1], tmp[2]};
                 cgltf_accessor_read_float(output, 3*(keyframe+1)+1, tmp, 4);
-                Vector4 tangent2 = {tmp[0], tmp[1], tmp[2]};
+                Vector4 v2 = {tmp[0], tmp[1], tmp[2], tmp[3]};
+                cgltf_accessor_read_float(output, 3*(keyframe+1), tmp, 4);
+                Vector4 inTangent2 = {tmp[0], tmp[1], tmp[2]};
                 Vector4 *r = data;
 
-                *r = QuaternionCubicSpline(v1, tangent1, v2, tangent2, t);
+                v1 = QuaternionNormalize(v1);
+                v2 = QuaternionNormalize(v2);
+
+                if (Vector4DotProduct(v1, v2) < 0.0f)
+                {
+                    v2 = Vector4Negate(v2);
+                }
+                
+                outTangent1 = Vector4Scale(outTangent1, duration);
+                inTangent2 = Vector4Scale(inTangent2, duration);
+
+                *r = QuaternionCubicHermiteSpline(v1, outTangent1, v2, inTangent2, t);
             } break;
         }
     }