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