internal void Interpolate()

in MREUnityRuntimeLib/Core/Components/AnimationComponent.cs [206:424]


		internal void Interpolate(
			ActorPatch finalFrame,
			string animationName,
			float duration,
			float[] curve,
			bool enabled)
		{
			// Ensure duration is in range [0...n].
			duration = Math.Max(0, duration);

			const int FPS = 10;
			float timeStep = duration / FPS;

			// If the curve is malformed, fall back to linear.
			if (curve.Length != 4)
			{
				curve = new float[] { 0, 0, 1, 1 };
			}

			// Are we patching the transform?
			bool animateTransform = finalFrame.Transform != null && finalFrame.Transform.Local != null && finalFrame.Transform.Local.IsPatched();
			var finalTransform = finalFrame.Transform.Local;

			// What parts of the transform are we animating?
			bool animatePosition = animateTransform && finalTransform.Position != null && finalTransform.Position.IsPatched();
			bool animateRotation = animateTransform && finalTransform.Rotation != null && finalTransform.Rotation.IsPatched();
			bool animateScale = animateTransform && finalTransform.Scale != null && finalTransform.Scale.IsPatched();

			// Ensure we have a well-formed rotation quaternion.
			for (; animateRotation;)
			{
				var rotation = finalTransform.Rotation;
				bool hasAllComponents =
					rotation.X.HasValue &&
					rotation.Y.HasValue &&
					rotation.Z.HasValue &&
					rotation.W.HasValue;

				// If quaternion is incomplete, fall back to the identity.
				if (!hasAllComponents)
				{
					finalTransform.Rotation = new QuaternionPatch(Quaternion.identity);
					break;
				}

				// Ensure the quaternion is normalized.
				var lengthSquared =
					(rotation.X.Value * rotation.X.Value) +
					(rotation.Y.Value * rotation.Y.Value) +
					(rotation.Z.Value * rotation.Z.Value) +
					(rotation.W.Value * rotation.W.Value);
				if (lengthSquared == 0)
				{
					// If the quaternion is length zero, fall back to the identity.
					finalTransform.Rotation = new QuaternionPatch(Quaternion.identity);
					break;
				}
				else if (lengthSquared != 1.0f)
				{
					// If the quaternion length is not 1, normalize it.
					var inverseLength = 1.0f / Mathf.Sqrt(lengthSquared);
					rotation.X *= inverseLength;
					rotation.Y *= inverseLength;
					rotation.Z *= inverseLength;
					rotation.W *= inverseLength;
				}
				break;
			}

			// Create the sampler to calculate ease curve values.
			var sampler = new CubicBezier(curve[0], curve[1], curve[2], curve[3]);

			var keyframes = new List<MWAnimationKeyframe>();

			// Generate keyframes
			float currTime = 0;

			do
			{
				var keyframe = NewKeyframe(currTime);
				var unitTime = duration > 0 ? currTime / duration : 1;
				BuildKeyframe(keyframe, unitTime);
				keyframes.Add(keyframe);
				currTime += timeStep;
			}
			while (currTime <= duration && timeStep > 0);

			// Final frame (if needed)
			if (currTime - duration > 0)
			{
				var keyframe = NewKeyframe(duration);
				BuildKeyframe(keyframe, 1);
				keyframes.Add(keyframe);
			}

			// Create and optionally start the animation.
			CreateAnimation(
				animationName,
				keyframes,
				events: null,
				wrapMode: MWAnimationWrapMode.Once,
				initialState: new MWSetAnimationStateOptions { Enabled = enabled },
				isInternal: true,
				managed: false,
				onCreatedCallback: null);

			bool LerpFloat(out float dest, float start, float? end, float t)
			{
				if (end.HasValue)
				{
					dest = Mathf.LerpUnclamped(start, end.Value, t);
					return true;
				}
				dest = 0;
				return false;
			}

			bool SlerpQuaternion(out Quaternion dest, Quaternion start, QuaternionPatch end, float t)
			{
				if (end != null)
				{
					dest = Quaternion.SlerpUnclamped(start, new Quaternion(end.X.Value, end.Y.Value, end.Z.Value, end.W.Value), t);
					return true;
				}
				dest = Quaternion.identity;
				return false;
			}

			void BuildKeyframePosition(MWAnimationKeyframe keyframe, float t)
			{
				float value;
				if (LerpFloat(out value, transform.localPosition.x, finalTransform.Position.X, t))
				{
					keyframe.Value.Transform.Local.Position.X = value;
				}
				if (LerpFloat(out value, transform.localPosition.y, finalTransform.Position.Y, t))
				{
					keyframe.Value.Transform.Local.Position.Y = value;
				}
				if (LerpFloat(out value, transform.localPosition.z, finalTransform.Position.Z, t))
				{
					keyframe.Value.Transform.Local.Position.Z = value;
				}
			}

			void BuildKeyframeScale(MWAnimationKeyframe keyframe, float t)
			{
				float value;
				if (LerpFloat(out value, transform.localScale.x, finalTransform.Scale.X, t))
				{
					keyframe.Value.Transform.Local.Scale.X = value;
				}
				if (LerpFloat(out value, transform.localScale.y, finalTransform.Scale.Y, t))
				{
					keyframe.Value.Transform.Local.Scale.Y = value;
				}
				if (LerpFloat(out value, transform.localScale.z, finalTransform.Scale.Z, t))
				{
					keyframe.Value.Transform.Local.Scale.Z = value;
				}
			}

			void BuildKeyframeRotation(MWAnimationKeyframe keyframe, float t)
			{
				Quaternion value;
				if (SlerpQuaternion(out value, transform.localRotation, finalTransform.Rotation, t))
				{
					keyframe.Value.Transform.Local.Rotation = new QuaternionPatch(value);
				}
			}

			void BuildKeyframe(MWAnimationKeyframe keyframe, float unitTime)
			{
				float curveTime = sampler.Sample(unitTime);

				if (animatePosition)
				{
					BuildKeyframePosition(keyframe, curveTime);
				}
				if (animateRotation)
				{
					BuildKeyframeRotation(keyframe, curveTime);
				}
				if (animateScale)
				{
					BuildKeyframeScale(keyframe, curveTime);
				}
			}

			MWAnimationKeyframe NewKeyframe(float time)
			{
				var keyframe = new MWAnimationKeyframe
				{
					Time = time,
					Value = new ActorPatch()
				};

				if (animateTransform)
				{
					keyframe.Value.Transform = new ActorTransformPatch()
					{
						Local = new ScaledTransformPatch()
					};
				}
				if (animatePosition)
				{
					keyframe.Value.Transform.Local.Position = new Vector3Patch();
				}
				if (animateRotation)
				{
					keyframe.Value.Transform.Local.Rotation = new QuaternionPatch();
				}
				if (animateScale)
				{
					keyframe.Value.Transform.Local.Scale = new Vector3Patch();
				}
				return keyframe;
			}
		}