Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 263 additions & 6 deletions Packages/com.unity.cloud.gltfast/Runtime/Scripts/AnimationUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#if UNITY_ANIMATION

using System;
using System.Buffers;
using System.Text;
using GLTFast.Schema;
using Unity.Collections;
Expand All @@ -19,15 +20,153 @@ static class AnimationUtils {

const float k_TimeEpsilon = 0.00001f;

const int k_TranslationPropertyIndex = 0;
const int k_ScalePropertyIndex = 1;

static readonly string[][] k_Vec3PropertyNames = {
new[] { "localPosition.x", "localPosition.y", "localPosition.z" },
new[] { "localScale.x", "localScale.y", "localScale.z" },
};

public static void AddTranslationCurves(AnimationClip clip, string animationPath, NativeArray<float> times, NativeArray<float3> values, InterpolationType interpolationType) {
AddVec3Curves(clip, animationPath, "localPosition.", times, values, interpolationType);
AddVec3Curves(clip, animationPath, k_TranslationPropertyIndex, times, values, interpolationType);
}

public static void AddScaleCurves(AnimationClip clip, string animationPath, NativeArray<float> times, NativeArray<float3> values, InterpolationType interpolationType) {
AddVec3Curves(clip, animationPath, "localScale.", times, values, interpolationType);
AddVec3Curves(clip, animationPath, k_ScalePropertyIndex, times, values, interpolationType);
}

public static void AddRotationCurves(AnimationClip clip, string animationPath, NativeArray<float> times, NativeArray<quaternion> values, InterpolationType interpolationType) {
#if UNITY_6000_2_OR_NEWER
Profiler.BeginSample("AnimationUtils.AddRotationCurves");
var rentedX = ArrayPool<Keyframe>.Shared.Rent(times.Length);
var rentedY = ArrayPool<Keyframe>.Shared.Rent(times.Length);
var rentedZ = ArrayPool<Keyframe>.Shared.Rent(times.Length);
var rentedW = ArrayPool<Keyframe>.Shared.Rent(times.Length);
var count = 0;

#if DEBUG
uint duplicates = 0;
#endif

try {
switch (interpolationType) {
case InterpolationType.Step: {
for (var i = 0; i < times.Length; i++) {
var time = times[i];
var value = values[i];
rentedX[i] = new Keyframe(time, value.value.x, float.PositiveInfinity, 0);
rentedY[i] = new Keyframe(time, value.value.y, float.PositiveInfinity, 0);
rentedZ[i] = new Keyframe(time, value.value.z, float.PositiveInfinity, 0);
rentedW[i] = new Keyframe(time, value.value.w, float.PositiveInfinity, 0);
}
count = times.Length;
break;
}
case InterpolationType.CubicSpline: {
for (var i = 0; i < times.Length; i++) {
var time = times[i];
var inTangent = values[i * 3];
var value = values[i * 3 + 1];
var outTangent = values[i * 3 + 2];
rentedX[i] = new Keyframe(time, value.value.x, inTangent.value.x, outTangent.value.x, .5f, .5f);
rentedY[i] = new Keyframe(time, value.value.y, inTangent.value.y, outTangent.value.y, .5f, .5f);
rentedZ[i] = new Keyframe(time, value.value.z, inTangent.value.z, outTangent.value.z, .5f, .5f);
rentedW[i] = new Keyframe(time, value.value.w, inTangent.value.w, outTangent.value.w, .5f, .5f);
}
count = times.Length;
break;
}
default: { // LINEAR
var prevTime = times[0];
var prevValue = values[0];
var inTangent = new quaternion(new float4(0f));

Assert.AreEqual(times.Length, values.Length);
for (var i = 1; i < times.Length; i++) {
var time = times[i];
var value = values[i];

if (prevTime >= time) {
// Time value is not increasing, so we ignore this keyframe
// This happened on some Sketchfab files (see #298)
#if DEBUG
duplicates++;
#endif
continue;
}

// Ensure shortest path rotation ( see https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#interpolation-slerp )
if (math.dot(prevValue, value) < 0) {
value.value = -value.value;
}

var dT = time - prevTime;
var dV = value.value - prevValue.value;
quaternion outTangent;
if (dT < k_TimeEpsilon) {
outTangent.value.x = (dV.x < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity;
outTangent.value.y = (dV.y < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity;
outTangent.value.z = (dV.z < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity;
outTangent.value.w = (dV.w < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity;
} else {
outTangent = dV / dT;
}

rentedX[count] = new Keyframe(prevTime, prevValue.value.x, inTangent.value.x, outTangent.value.x);
rentedY[count] = new Keyframe(prevTime, prevValue.value.y, inTangent.value.y, outTangent.value.y);
rentedZ[count] = new Keyframe(prevTime, prevValue.value.z, inTangent.value.z, outTangent.value.z);
rentedW[count] = new Keyframe(prevTime, prevValue.value.w, inTangent.value.w, outTangent.value.w);
count++;

inTangent = outTangent;
prevTime = time;
prevValue = value;
}

rentedX[count] = new Keyframe(prevTime, prevValue.value.x, inTangent.value.x, 0);
rentedY[count] = new Keyframe(prevTime, prevValue.value.y, inTangent.value.y, 0);
rentedZ[count] = new Keyframe(prevTime, prevValue.value.z, inTangent.value.z, 0);
rentedW[count] = new Keyframe(prevTime, prevValue.value.w, inTangent.value.w, 0);
count++;
break;
}
}

var rotX = new AnimationCurve();
var rotY = new AnimationCurve();
var rotZ = new AnimationCurve();
var rotW = new AnimationCurve();
rotX.SetKeys(new ReadOnlySpan<Keyframe>(rentedX, 0, count));
rotY.SetKeys(new ReadOnlySpan<Keyframe>(rentedY, 0, count));
rotZ.SetKeys(new ReadOnlySpan<Keyframe>(rentedZ, 0, count));
rotW.SetKeys(new ReadOnlySpan<Keyframe>(rentedW, 0, count));

clip.SetCurve(animationPath, typeof(Transform), "localRotation.x", rotX);
clip.SetCurve(animationPath, typeof(Transform), "localRotation.y", rotY);
clip.SetCurve(animationPath, typeof(Transform), "localRotation.z", rotZ);
clip.SetCurve(animationPath, typeof(Transform), "localRotation.w", rotW);
}
finally {
ArrayPool<Keyframe>.Shared.Return(rentedX);
ArrayPool<Keyframe>.Shared.Return(rentedY);
ArrayPool<Keyframe>.Shared.Return(rentedZ);
ArrayPool<Keyframe>.Shared.Return(rentedW);
}

Profiler.EndSample();
#if DEBUG
if (duplicates > 0) {
ReportDuplicateKeyframes();
}
#endif
#else
AddRotationCurvesLegacy(clip, animationPath, times, values, interpolationType);
#endif
}

#if !UNITY_6000_2_OR_NEWER
static void AddRotationCurvesLegacy(AnimationClip clip, string animationPath, NativeArray<float> times, NativeArray<quaternion> values, InterpolationType interpolationType) {
Profiler.BeginSample("AnimationUtils.AddRotationCurves");
var rotX = new AnimationCurve();
var rotY = new AnimationCurve();
Expand Down Expand Up @@ -130,6 +269,7 @@ public static void AddRotationCurves(AnimationClip clip, string animationPath, N
}
#endif
}
#endif

public static string CreateAnimationPath(int nodeIndex, string[] nodeNames, int[] parentIndex) {
Profiler.BeginSample("AnimationUtils.CreateAnimationPath");
Expand Down Expand Up @@ -183,7 +323,122 @@ public static void AddMorphTargetWeightCurves(
Profiler.EndSample();
}

static void AddVec3Curves(AnimationClip clip, string animationPath, string propertyPrefix, NativeArray<float> times, NativeArray<float3> values, InterpolationType interpolationType) {
static void AddVec3Curves(AnimationClip clip, string animationPath, int propertyIndex, NativeArray<float> times, NativeArray<float3> values, InterpolationType interpolationType) {
#if UNITY_6000_2_OR_NEWER
Profiler.BeginSample("AnimationUtils.AddVec3Curves");
var rentedX = ArrayPool<Keyframe>.Shared.Rent(times.Length);
var rentedY = ArrayPool<Keyframe>.Shared.Rent(times.Length);
var rentedZ = ArrayPool<Keyframe>.Shared.Rent(times.Length);
var count = 0;

#if DEBUG
uint duplicates = 0;
#endif

try {
switch (interpolationType) {
case InterpolationType.Step: {
for (var i = 0; i < times.Length; i++) {
var time = times[i];
var value = values[i];
rentedX[i] = new Keyframe(time, value.x, float.PositiveInfinity, 0);
rentedY[i] = new Keyframe(time, value.y, float.PositiveInfinity, 0);
rentedZ[i] = new Keyframe(time, value.z, float.PositiveInfinity, 0);
}
count = times.Length;
break;
}
case InterpolationType.CubicSpline: {
for (var i = 0; i < times.Length; i++) {
var time = times[i];
var inTangent = values[i * 3];
var value = values[i * 3 + 1];
var outTangent = values[i * 3 + 2];
rentedX[i] = new Keyframe(time, value.x, inTangent.x, outTangent.x, .5f, .5f);
rentedY[i] = new Keyframe(time, value.y, inTangent.y, outTangent.y, .5f, .5f);
rentedZ[i] = new Keyframe(time, value.z, inTangent.z, outTangent.z, .5f, .5f);
}
count = times.Length;
break;
}
default: { // LINEAR
var prevTime = times[0];
var prevValue = values[0];
var inTangent = new float3(0f);

for (var i = 1; i < times.Length; i++) {
var time = times[i];
var value = values[i];

if (prevTime >= time) {
// Time value is not increasing, so we ignore this keyframe
// This happened on some Sketchfab files (see #298)
#if DEBUG
duplicates++;
#endif
continue;
}

var dT = time - prevTime;
var dV = value - prevValue;
float3 outTangent;
if (dT < k_TimeEpsilon) {
outTangent.x = (dV.x < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity;
outTangent.y = (dV.y < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity;
outTangent.z = (dV.z < 0f) ^ (dT < 0f) ? float.NegativeInfinity : float.PositiveInfinity;
} else {
outTangent = dV / dT;
}

rentedX[count] = new Keyframe(prevTime, prevValue.x, inTangent.x, outTangent.x);
rentedY[count] = new Keyframe(prevTime, prevValue.y, inTangent.y, outTangent.y);
rentedZ[count] = new Keyframe(prevTime, prevValue.z, inTangent.z, outTangent.z);
count++;

inTangent = outTangent;
prevTime = time;
prevValue = value;
}

rentedX[count] = new Keyframe(prevTime, prevValue.x, inTangent.x, 0);
rentedY[count] = new Keyframe(prevTime, prevValue.y, inTangent.y, 0);
rentedZ[count] = new Keyframe(prevTime, prevValue.z, inTangent.z, 0);
count++;
break;
}
}

var curveX = new AnimationCurve();
var curveY = new AnimationCurve();
var curveZ = new AnimationCurve();
curveX.SetKeys(new ReadOnlySpan<Keyframe>(rentedX, 0, count));
curveY.SetKeys(new ReadOnlySpan<Keyframe>(rentedY, 0, count));
curveZ.SetKeys(new ReadOnlySpan<Keyframe>(rentedZ, 0, count));

var propNames = k_Vec3PropertyNames[propertyIndex];
clip.SetCurve(animationPath, typeof(Transform), propNames[0], curveX);
clip.SetCurve(animationPath, typeof(Transform), propNames[1], curveY);
clip.SetCurve(animationPath, typeof(Transform), propNames[2], curveZ);
}
finally {
ArrayPool<Keyframe>.Shared.Return(rentedX);
ArrayPool<Keyframe>.Shared.Return(rentedY);
ArrayPool<Keyframe>.Shared.Return(rentedZ);
}

Profiler.EndSample();
#if DEBUG
if (duplicates > 0) {
ReportDuplicateKeyframes();
}
#endif
#else
AddVec3CurvesLegacy(clip, animationPath, propertyIndex, times, values, interpolationType);
#endif
}

#if !UNITY_6000_2_OR_NEWER
static void AddVec3CurvesLegacy(AnimationClip clip, string animationPath, int propertyIndex, NativeArray<float> times, NativeArray<float3> values, InterpolationType interpolationType) {
Profiler.BeginSample("AnimationUtils.AddVec3Curves");
var curveX = new AnimationCurve();
var curveY = new AnimationCurve();
Expand Down Expand Up @@ -262,16 +517,18 @@ static void AddVec3Curves(AnimationClip clip, string animationPath, string prope
}
}

clip.SetCurve(animationPath, typeof(Transform), $"{propertyPrefix}x", curveX);
clip.SetCurve(animationPath, typeof(Transform), $"{propertyPrefix}y", curveY);
clip.SetCurve(animationPath, typeof(Transform), $"{propertyPrefix}z", curveZ);
var propNames = k_Vec3PropertyNames[propertyIndex];
clip.SetCurve(animationPath, typeof(Transform), propNames[0], curveX);
clip.SetCurve(animationPath, typeof(Transform), propNames[1], curveY);
clip.SetCurve(animationPath, typeof(Transform), propNames[2], curveZ);
Profiler.EndSample();
#if DEBUG
if (duplicates > 0) {
ReportDuplicateKeyframes();
}
#endif
}
#endif

static void AddScalarCurve(AnimationClip clip, string animationPath, string propertyPrefix, int curveIndex, int valueStride, NativeArray<float> times, NativeArray<float> values, InterpolationType interpolationType) {
Profiler.BeginSample("AnimationUtils.AddScalarCurve");
Expand Down