Skip to content

Commit

Permalink
Added missing interpolation types for GLTF animation channels (raysan…
Browse files Browse the repository at this point in the history
  • Loading branch information
benjitrosch authored Apr 15, 2024
1 parent 289e7d3 commit 4e37c8e
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 22 deletions.
28 changes: 28 additions & 0 deletions src/raymath.h
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,22 @@ RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount)
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
RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal)
{
Expand Down Expand Up @@ -2197,6 +2213,18 @@ 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)
{
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
RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to)
{
Expand Down
114 changes: 92 additions & 22 deletions src/rmodels.c
Original file line number Diff line number Diff line change
Expand Up @@ -5356,8 +5356,10 @@ static Model LoadGLTF(const char *fileName)
}

// 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
float tstart = 0.0f;
float tend = 0.0f;
Expand All @@ -5377,7 +5379,7 @@ static bool GetPoseAtTimeGLTF(cgltf_accessor *input, cgltf_accessor *output, flo
break;
}
}

float t = (time - tstart)/fmax((tend - tstart), EPSILON);
t = (t < 0.0f)? 0.0f : t;
t = (t > 1.0f)? 1.0f : t;
Expand All @@ -5386,25 +5388,90 @@ static bool GetPoseAtTimeGLTF(cgltf_accessor *input, cgltf_accessor *output, flo

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)
{
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
*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;
Expand Down Expand Up @@ -5455,6 +5522,7 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
cgltf_animation_channel *translate;
cgltf_animation_channel *rotate;
cgltf_animation_channel *scale;
cgltf_interpolation_type interpolationType;
};

struct Channels *boneChannels = RL_CALLOC(animations[i].boneCount, sizeof(struct Channels));
Expand All @@ -5480,7 +5548,9 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo
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)
{
Expand All @@ -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);
}
}
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;
cgltf_bool r = cgltf_accessor_read_float(channel.sampler->input, channel.sampler->input->count - 1, &t, 1);
Expand Down Expand Up @@ -5532,23 +5602,23 @@ static ModelAnimation *LoadModelAnimationsGLTF(const char *fileName, int *animCo

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);
}
}

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);
}
}

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);
}
Expand Down

0 comments on commit 4e37c8e

Please sign in to comment.