From 0c0b626f1cbb489edd07e532c1cffc91ffdb5b9f Mon Sep 17 00:00:00 2001 From: Luis Fernandes Date: Thu, 21 May 2026 11:24:16 +0200 Subject: [PATCH] fix: clamp glTF animation curves before first key --- .../Scripts/AnimationModuleProcessorTests.cs | 136 ++++++++++++ Packages/com.unity.cloud.gltfast/CHANGELOG.md | 1 + .../Animations/AnimationModuleProcessor.cs | 206 ++++++++++++++---- 3 files changed, 302 insertions(+), 41 deletions(-) diff --git a/Packages/com.unity.cloud.gltfast.tests/Tests/Runtime/Scripts/AnimationModuleProcessorTests.cs b/Packages/com.unity.cloud.gltfast.tests/Tests/Runtime/Scripts/AnimationModuleProcessorTests.cs index 66736041..1f9e74a7 100644 --- a/Packages/com.unity.cloud.gltfast.tests/Tests/Runtime/Scripts/AnimationModuleProcessorTests.cs +++ b/Packages/com.unity.cloud.gltfast.tests/Tests/Runtime/Scripts/AnimationModuleProcessorTests.cs @@ -67,6 +67,95 @@ public void AddMorphTargetWeightCurvesWithDefaultValuesStep() AddMorphTargetWeightCurvesWithDefaultValues(InterpolationType.Step); } + [TestCase(InterpolationType.Linear)] + [TestCase(InterpolationType.CubicSpline)] + [TestCase(InterpolationType.Step)] + public void AddVec3CurvesClampsBeforeFirstKey(InterpolationType interpolationType) + { +#if UNITY_ANIMATION + using var times = CreateNonZeroStartTimes(); + using var values = CreateVec3Values(interpolationType, new float3(1f, 2f, 3f), new float3(4f, 5f, 6f)); + var hierarchy = new NodeHierarchyInfo(new[] { "Target" }, new[] { -1 }); + + using var anim = new AnimationModuleProcessor(1, true); + anim.AddClip(0, "TestClip"); + anim.AddTranslationCurves(0, 0, hierarchy, times.AsReadOnly(), values.AsReadOnly(), interpolationType); + anim.AddScaleCurves(0, 0, hierarchy, times.AsReadOnly(), values.AsReadOnly(), interpolationType); + + var parent = new GameObject("Parent"); + var go = new GameObject("Target"); + go.transform.SetParent(parent.transform); + SampleAtZero(anim.AnimationClips[0], parent); + + AssertVector3AreEqual(new Vector3(1f, 2f, 3f), go.transform.localPosition, "Expected local position to clamp to the first key before the first key time."); + AssertVector3AreEqual(new Vector3(1f, 2f, 3f), go.transform.localScale, "Expected local scale to clamp to the first key before the first key time."); + Object.Destroy(parent); +#else + Assert.Ignore("UNITY_ANIMATION is not defined; AnimationModuleUtils is not compiled."); +#endif + } + + [TestCase(InterpolationType.Linear)] + [TestCase(InterpolationType.CubicSpline)] + [TestCase(InterpolationType.Step)] + public void AddRotationCurvesClampsBeforeFirstKey(InterpolationType interpolationType) + { +#if UNITY_ANIMATION + using var times = CreateNonZeroStartTimes(); + var expectedRotation = quaternion.EulerXYZ(math.radians(new float3(10f, 20f, 30f))); + using var values = CreateQuaternionValues( + interpolationType, + expectedRotation, + quaternion.EulerXYZ(math.radians(new float3(70f, 80f, 90f)))); + var hierarchy = new NodeHierarchyInfo(new[] { "Target" }, new[] { -1 }); + + using var anim = new AnimationModuleProcessor(1, true); + anim.AddClip(0, "TestClip"); + anim.AddRotationCurves(0, 0, hierarchy, times.AsReadOnly(), values.AsReadOnly(), interpolationType); + + var parent = new GameObject("Parent"); + var go = new GameObject("Target"); + go.transform.SetParent(parent.transform); + SampleAtZero(anim.AnimationClips[0], parent); + + Assert.AreEqual( + 0f, + Quaternion.Angle(new Quaternion(expectedRotation.value.x, expectedRotation.value.y, expectedRotation.value.z, expectedRotation.value.w), go.transform.localRotation), + 1e-3f, + "Expected local rotation to clamp to the first key before the first key time."); + Object.Destroy(parent); +#else + Assert.Ignore("UNITY_ANIMATION is not defined; AnimationModuleUtils is not compiled."); +#endif + } + + [TestCase(InterpolationType.Linear)] + [TestCase(InterpolationType.CubicSpline)] + [TestCase(InterpolationType.Step)] + public void AddMorphTargetWeightCurvesClampsBeforeFirstKey(InterpolationType interpolationType) + { +#if UNITY_ANIMATION + var morphTargetNames = new[] { "Shape0" }; + using var times = CreateNonZeroStartTimes(); + using var values = CreateScalarValues(interpolationType, 25f, 75f); + var hierarchy = new NodeHierarchyInfo(new[] { "Target" }, new[] { -1 }); + + using var anim = new AnimationModuleProcessor(1, true); + anim.AddClip(0, "TestClip"); + anim.AddMorphTargetWeightCurves( + 0, 0, 0, null, hierarchy, times.AsReadOnly(), values.AsReadOnly(), interpolationType, morphTargetNames); + + var parent = new GameObject("Parent"); + CreateSkinnedTargetWithBlendShape(parent.transform, "Shape0", out var mainRenderer, out _); + SampleAtZero(anim.AnimationClips[0], parent); + + Assert.AreEqual(25f, mainRenderer.GetBlendShapeWeight(0), 1e-3f, "Expected blend shape weight to clamp to the first key before the first key time."); + Object.Destroy(parent); +#else + Assert.Ignore("UNITY_ANIMATION is not defined; AnimationModuleUtils is not compiled."); +#endif + } + static void AddRotationCurvesWithDefaultValues(InterpolationType interpolationType) { #if UNITY_ANIMATION @@ -160,6 +249,53 @@ static void AddMorphTargetWeightCurvesWithDefaultValues(InterpolationType interp } #if UNITY_ANIMATION + static NativeArray CreateNonZeroStartTimes() + { + return new NativeArray(new[] { 0.033333335f, 1f }, Allocator.Temp); + } + + static NativeArray CreateVec3Values(InterpolationType interpolationType, float3 firstValue, float3 secondValue) + { + return interpolationType == InterpolationType.CubicSpline + ? new NativeArray(new[] { float3.zero, firstValue, float3.zero, float3.zero, secondValue, float3.zero }, Allocator.Temp) + : new NativeArray(new[] { firstValue, secondValue }, Allocator.Temp); + } + + static NativeArray CreateQuaternionValues( + InterpolationType interpolationType, + quaternion firstValue, + quaternion secondValue + ) + { + var zeroTangent = new quaternion(new float4(0f)); + return interpolationType == InterpolationType.CubicSpline + ? new NativeArray(new[] + { + zeroTangent, firstValue, zeroTangent, + zeroTangent, secondValue, zeroTangent + }, Allocator.Temp) + : new NativeArray(new[] { firstValue, secondValue }, Allocator.Temp); + } + + static NativeArray CreateScalarValues(InterpolationType interpolationType, float firstValue, float secondValue) + { + return interpolationType == InterpolationType.CubicSpline + ? new NativeArray(new[] { 0f, firstValue, 0f, 0f, secondValue, 0f }, Allocator.Temp) + : new NativeArray(new[] { firstValue, secondValue }, Allocator.Temp); + } + + static void SampleAtZero(AnimationClip clip, GameObject parent) + { + clip.SampleAnimation(parent, 0f); + } + + static void AssertVector3AreEqual(Vector3 expected, Vector3 actual, string message) + { + Assert.AreEqual(expected.x, actual.x, 1e-3f, message); + Assert.AreEqual(expected.y, actual.y, 1e-3f, message); + Assert.AreEqual(expected.z, actual.z, 1e-3f, message); + } + static void CreateSkinnedTargetWithBlendShape( Transform parent, string blendShapeName, diff --git a/Packages/com.unity.cloud.gltfast/CHANGELOG.md b/Packages/com.unity.cloud.gltfast/CHANGELOG.md index 6752c2a6..3d60274f 100644 --- a/Packages/com.unity.cloud.gltfast/CHANGELOG.md +++ b/Packages/com.unity.cloud.gltfast/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved comma placement in export summary (thanks anonymous for [#46](https://github.com/Unity-Technologies/com.unity.cloud.gltfast/pull/46)). - [Materials variants](xref:GLTFast.MaterialsVariantsComponent)' inspector shows correct variant when regaining focus (thanks [anonymous2585](https://github.com/anonymous2585) for [#48](https://github.com/Unity-Technologies/com.unity.cloud.gltfast/pull/48)). - (Import) Fixed merging of mesh primitives with shared vertex buffer, but different indices type. +- (Import) Animation clips now clamp to the first key when glTF animation sampler input times start after zero, avoiding incorrect samples at time 0. ### Removed diff --git a/Packages/com.unity.cloud.gltfast/Runtime/Scripts/Animations/AnimationModuleProcessor.cs b/Packages/com.unity.cloud.gltfast/Runtime/Scripts/Animations/AnimationModuleProcessor.cs index 9dedcf88..17063398 100644 --- a/Packages/com.unity.cloud.gltfast/Runtime/Scripts/Animations/AnimationModuleProcessor.cs +++ b/Packages/com.unity.cloud.gltfast/Runtime/Scripts/Animations/AnimationModuleProcessor.cs @@ -144,6 +144,11 @@ public void Dispose() #endif } + static bool NeedsInitialKey(NativeArray.ReadOnly times) + { + return times.Length > 0 && times[0] > 0f; + } + #if !UNITY_6000_2_OR_NEWER static #endif @@ -308,6 +313,7 @@ InterpolationType interpolationType var rotY = new AnimationCurve(); var rotZ = new AnimationCurve(); var rotW = new AnimationCurve(); + var addInitialKey = NeedsInitialKey(times); #if DEBUG uint duplicates = 0; @@ -318,6 +324,14 @@ InterpolationType interpolationType case InterpolationType.Step: case InterpolationType.CubicSpline: { + if (addInitialKey) + { + rotX.AddKey(new Keyframe(0, 0)); + rotY.AddKey(new Keyframe(0, 0)); + rotZ.AddKey(new Keyframe(0, 0)); + rotW.AddKey(new Keyframe(0, 0)); + } + foreach (var time in times) { rotX.AddKey(new Keyframe(time, 0)); @@ -331,9 +345,9 @@ InterpolationType interpolationType case InterpolationType.Linear: default: { - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : times[0]; - for (var i = 1; i < times.Length; i++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; @@ -389,7 +403,8 @@ NativeArrayPool keyframeArrayPool ) { Profiler.BeginSample("AnimationModuleLoader.AddQuaternionCurves"); - keyframeArrayPool.ReserveBuffers(times.Length, 4); + var addInitialKey = NeedsInitialKey(times); + keyframeArrayPool.ReserveBuffers(times.Length + (addInitialKey ? 1 : 0), 4); var keyframesX = keyframeArrayPool.GetBuffer(0); var keyframesY = keyframeArrayPool.GetBuffer(1); var keyframesZ = keyframeArrayPool.GetBuffer(2); @@ -404,49 +419,69 @@ NativeArrayPool keyframeArrayPool { case InterpolationType.Step: { + if (addInitialKey) + { + var value = values[0]; + keyframesX[count] = new Keyframe(0, value.value.x, float.PositiveInfinity, 0); + keyframesY[count] = new Keyframe(0, value.value.y, float.PositiveInfinity, 0); + keyframesZ[count] = new Keyframe(0, value.value.z, float.PositiveInfinity, 0); + keyframesW[count] = new Keyframe(0, value.value.w, float.PositiveInfinity, 0); + count++; + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; var value = values[i]; - keyframesX[i] = new Keyframe(time, value.value.x, float.PositiveInfinity, 0); - keyframesY[i] = new Keyframe(time, value.value.y, float.PositiveInfinity, 0); - keyframesZ[i] = new Keyframe(time, value.value.z, float.PositiveInfinity, 0); - keyframesW[i] = new Keyframe(time, value.value.w, float.PositiveInfinity, 0); + keyframesX[count] = new Keyframe(time, value.value.x, float.PositiveInfinity, 0); + keyframesY[count] = new Keyframe(time, value.value.y, float.PositiveInfinity, 0); + keyframesZ[count] = new Keyframe(time, value.value.z, float.PositiveInfinity, 0); + keyframesW[count] = new Keyframe(time, value.value.w, float.PositiveInfinity, 0); + count++; } - count = times.Length; break; } case InterpolationType.CubicSpline: { + if (addInitialKey) + { + var value = values[1]; + keyframesX[count] = new Keyframe(0, value.value.x, 0, 0, .5f, .5f); + keyframesY[count] = new Keyframe(0, value.value.y, 0, 0, .5f, .5f); + keyframesZ[count] = new Keyframe(0, value.value.z, 0, 0, .5f, .5f); + keyframesW[count] = new Keyframe(0, value.value.w, 0, 0, .5f, .5f); + count++; + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; - var inTangent = values[i * 3]; + var inTangent = addInitialKey && i == 0 ? new quaternion(new float4(0f)) : values[i * 3]; var value = values[i * 3 + 1]; var outTangent = values[i * 3 + 2]; - keyframesX[i] = new Keyframe(time, value.value.x, inTangent.value.x, outTangent.value.x, .5f, + keyframesX[count] = new Keyframe(time, value.value.x, inTangent.value.x, outTangent.value.x, .5f, .5f); - keyframesY[i] = new Keyframe(time, value.value.y, inTangent.value.y, outTangent.value.y, .5f, + keyframesY[count] = new Keyframe(time, value.value.y, inTangent.value.y, outTangent.value.y, .5f, .5f); - keyframesZ[i] = new Keyframe(time, value.value.z, inTangent.value.z, outTangent.value.z, .5f, + keyframesZ[count] = new Keyframe(time, value.value.z, inTangent.value.z, outTangent.value.z, .5f, .5f); - keyframesW[i] = new Keyframe(time, value.value.w, inTangent.value.w, outTangent.value.w, .5f, + keyframesW[count] = new Keyframe(time, value.value.w, inTangent.value.w, outTangent.value.w, .5f, .5f); + count++; } - count = times.Length; break; } default: { // LINEAR - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : 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++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; var value = values[i]; @@ -548,6 +583,7 @@ InterpolationType interpolationType var rotY = new AnimationCurve(); var rotZ = new AnimationCurve(); var rotW = new AnimationCurve(); + var addInitialKey = NeedsInitialKey(times); #if DEBUG uint duplicates = 0; @@ -557,6 +593,15 @@ InterpolationType interpolationType { case InterpolationType.Step: { + if (addInitialKey) + { + var value = values[0]; + rotX.AddKey(new Keyframe(0, value.value.x, float.PositiveInfinity, 0)); + rotY.AddKey(new Keyframe(0, value.value.y, float.PositiveInfinity, 0)); + rotZ.AddKey(new Keyframe(0, value.value.z, float.PositiveInfinity, 0)); + rotW.AddKey(new Keyframe(0, value.value.w, float.PositiveInfinity, 0)); + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; @@ -570,10 +615,19 @@ InterpolationType interpolationType } case InterpolationType.CubicSpline: { + if (addInitialKey) + { + var value = values[1]; + rotX.AddKey(new Keyframe(0, value.value.x, 0, 0, .5f, .5f)); + rotY.AddKey(new Keyframe(0, value.value.y, 0, 0, .5f, .5f)); + rotZ.AddKey(new Keyframe(0, value.value.z, 0, 0, .5f, .5f)); + rotW.AddKey(new Keyframe(0, value.value.w, 0, 0, .5f, .5f)); + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; - var inTangent = values[i * 3]; + var inTangent = addInitialKey && i == 0 ? new quaternion(new float4(0f)) : values[i * 3]; var value = values[i * 3 + 1]; var outTangent = values[i * 3 + 2]; rotX.AddKey(new Keyframe(time, value.value.x, inTangent.value.x, outTangent.value.x, .5f, .5f)); @@ -586,12 +640,12 @@ InterpolationType interpolationType default: { // LINEAR - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : 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++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; var value = values[i]; @@ -673,6 +727,7 @@ InterpolationType interpolationType var curveX = new AnimationCurve(); var curveY = new AnimationCurve(); var curveZ = new AnimationCurve(); + var addInitialKey = NeedsInitialKey(times); #if DEBUG var duplicates = 0u; @@ -682,6 +737,13 @@ InterpolationType interpolationType { case InterpolationType.Step: { + if (addInitialKey) + { + curveX.AddKey(new Keyframe(0, 0, float.PositiveInfinity, 0)); + curveY.AddKey(new Keyframe(0, 0, float.PositiveInfinity, 0)); + curveZ.AddKey(new Keyframe(0, 0, float.PositiveInfinity, 0)); + } + foreach (var time in times) { curveX.AddKey(new Keyframe(time, 0, float.PositiveInfinity, 0)); @@ -693,6 +755,13 @@ InterpolationType interpolationType } case InterpolationType.CubicSpline: { + if (addInitialKey) + { + curveX.AddKey(new Keyframe(0, 0, 0, 0, .5f, .5f)); + curveY.AddKey(new Keyframe(0, 0, 0, 0, .5f, .5f)); + curveZ.AddKey(new Keyframe(0, 0, 0, 0, .5f, .5f)); + } + foreach (var time in times) { curveX.AddKey(new Keyframe(time, 0, 0, 0, .5f, .5f)); @@ -705,9 +774,9 @@ InterpolationType interpolationType case InterpolationType.Linear: default: { - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : times[0]; - for (var i = 1; i < times.Length; i++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; @@ -761,7 +830,8 @@ NativeArrayPool keyframeArrayPool ) { Profiler.BeginSample("AnimationModuleLoader.AddVec3Curves"); - keyframeArrayPool.ReserveBuffers(times.Length, 3); + var addInitialKey = NeedsInitialKey(times); + keyframeArrayPool.ReserveBuffers(times.Length + (addInitialKey ? 1 : 0), 3); var keyframesX = keyframeArrayPool.GetBuffer(0); var keyframesY = keyframeArrayPool.GetBuffer(1); var keyframesZ = keyframeArrayPool.GetBuffer(2); @@ -776,42 +846,60 @@ NativeArrayPool keyframeArrayPool { case InterpolationType.Step: { + if (addInitialKey) + { + var value = values[0]; + keyframesX[count] = new Keyframe(0, value.x, float.PositiveInfinity, 0); + keyframesY[count] = new Keyframe(0, value.y, float.PositiveInfinity, 0); + keyframesZ[count] = new Keyframe(0, value.z, float.PositiveInfinity, 0); + count++; + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; var value = values[i]; - keyframesX[i] = new Keyframe(time, value.x, float.PositiveInfinity, 0); - keyframesY[i] = new Keyframe(time, value.y, float.PositiveInfinity, 0); - keyframesZ[i] = new Keyframe(time, value.z, float.PositiveInfinity, 0); + keyframesX[count] = new Keyframe(time, value.x, float.PositiveInfinity, 0); + keyframesY[count] = new Keyframe(time, value.y, float.PositiveInfinity, 0); + keyframesZ[count] = new Keyframe(time, value.z, float.PositiveInfinity, 0); + count++; } - count = times.Length; break; } case InterpolationType.CubicSpline: { + if (addInitialKey) + { + var value = values[1]; + keyframesX[count] = new Keyframe(0, value.x, 0, 0, .5f, .5f); + keyframesY[count] = new Keyframe(0, value.y, 0, 0, .5f, .5f); + keyframesZ[count] = new Keyframe(0, value.z, 0, 0, .5f, .5f); + count++; + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; - var inTangent = values[i * 3]; + var inTangent = addInitialKey && i == 0 ? new float3(0f) : values[i * 3]; var value = values[i * 3 + 1]; var outTangent = values[i * 3 + 2]; - keyframesX[i] = new Keyframe(time, value.x, inTangent.x, outTangent.x, .5f, .5f); - keyframesY[i] = new Keyframe(time, value.y, inTangent.y, outTangent.y, .5f, .5f); - keyframesZ[i] = new Keyframe(time, value.z, inTangent.z, outTangent.z, .5f, .5f); + keyframesX[count] = new Keyframe(time, value.x, inTangent.x, outTangent.x, .5f, .5f); + keyframesY[count] = new Keyframe(time, value.y, inTangent.y, outTangent.y, .5f, .5f); + keyframesZ[count] = new Keyframe(time, value.z, inTangent.z, outTangent.z, .5f, .5f); + count++; } - count = times.Length; break; } default: { // LINEAR - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : times[0]; var prevValue = values[0]; var inTangent = new float3(0f); - for (var i = 1; i < times.Length; i++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; var value = values[i]; @@ -896,6 +984,7 @@ InterpolationType interpolationType var curveX = new AnimationCurve(); var curveY = new AnimationCurve(); var curveZ = new AnimationCurve(); + var addInitialKey = NeedsInitialKey(times); #if DEBUG uint duplicates = 0; @@ -905,6 +994,14 @@ InterpolationType interpolationType { case InterpolationType.Step: { + if (addInitialKey) + { + var value = values[0]; + curveX.AddKey(new Keyframe(0, value.x, float.PositiveInfinity, 0)); + curveY.AddKey(new Keyframe(0, value.y, float.PositiveInfinity, 0)); + curveZ.AddKey(new Keyframe(0, value.z, float.PositiveInfinity, 0)); + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; @@ -917,10 +1014,18 @@ InterpolationType interpolationType } case InterpolationType.CubicSpline: { + if (addInitialKey) + { + var value = values[1]; + curveX.AddKey(new Keyframe(0, value.x, 0, 0, .5f, .5f)); + curveY.AddKey(new Keyframe(0, value.y, 0, 0, .5f, .5f)); + curveZ.AddKey(new Keyframe(0, value.z, 0, 0, .5f, .5f)); + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; - var inTangent = values[i * 3]; + var inTangent = addInitialKey && i == 0 ? new float3(0f) : values[i * 3]; var value = values[i * 3 + 1]; var outTangent = values[i * 3 + 2]; curveX.AddKey(new Keyframe(time, value.x, inTangent.x, outTangent.x, .5f, .5f)); @@ -931,11 +1036,11 @@ InterpolationType interpolationType } default: { // LINEAR - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : times[0]; var prevValue = values[0]; var inTangent = new float3(0f); - for (var i = 1; i < times.Length; i++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; var value = values[i]; @@ -1005,6 +1110,7 @@ InterpolationType interpolationType { Profiler.BeginSample("AnimationModuleLoader.AddScalarCurve"); var curve = new AnimationCurve(); + var addInitialKey = NeedsInitialKey(times); #if DEBUG uint duplicates = 0; @@ -1015,6 +1121,11 @@ InterpolationType interpolationType case InterpolationType.Step: case InterpolationType.CubicSpline: { + if (addInitialKey) + { + curve.AddKey(new Keyframe(0, 0)); + } + foreach (var time in times) { curve.AddKey(new Keyframe(time, 0)); @@ -1025,9 +1136,9 @@ InterpolationType interpolationType case InterpolationType.Linear: default: { - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : times[0]; - for (var i = 1; i < times.Length; i++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; @@ -1073,6 +1184,7 @@ InterpolationType interpolationType { Profiler.BeginSample("AnimationModuleLoader.AddScalarCurve"); var curve = new AnimationCurve(); + var addInitialKey = NeedsInitialKey(times); #if DEBUG uint duplicates = 0; @@ -1082,6 +1194,12 @@ InterpolationType interpolationType { case InterpolationType.Step: { + if (addInitialKey) + { + var value = values[curveIndex]; + curve.AddKey(new Keyframe(0, value, float.PositiveInfinity, 0)); + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; @@ -1093,11 +1211,17 @@ InterpolationType interpolationType } case InterpolationType.CubicSpline: { + if (addInitialKey) + { + var value = values[curveIndex * 3 + 1]; + curve.AddKey(new Keyframe(0, value, 0, 0, .5f, .5f)); + } + for (var i = 0; i < times.Length; i++) { var time = times[i]; var valueIndex = i * valueStride + curveIndex; - var inTangent = values[valueIndex * 3]; + var inTangent = addInitialKey && i == 0 ? 0f : values[valueIndex * 3]; var value = values[valueIndex * 3 + 1]; var outTangent = values[valueIndex * 3 + 2]; curve.AddKey(new Keyframe(time, value, inTangent, outTangent, .5f, .5f)); @@ -1106,11 +1230,11 @@ InterpolationType interpolationType } default: { // LINEAR - var prevTime = times[0]; + var prevTime = addInitialKey ? 0f : times[0]; var prevValue = values[curveIndex]; var inTangent = 0f; - for (var i = 1; i < times.Length; i++) + for (var i = addInitialKey ? 0 : 1; i < times.Length; i++) { var time = times[i]; var valueIndex = i * valueStride + curveIndex;