private void InitializeImplicitKeyframes(bool recycleLastFrames = false) { for (var ti = 0; ti < Data.Tracks.Length; ti++) { var track = Data.Tracks[ti]; // if looping, set implicit start exactly at the last frame of the previous loop if (recycleLastFrames && track.Relative == true) { var temp = ImplicitStartKeyframes[ti].Value; ImplicitStartKeyframes[ti].Value = ResolvedRelativeKeyframes[ti][track.Keyframes.Length - 1].Value; ResolvedRelativeKeyframes[ti][track.Keyframes.Length - 1].Value = temp; } // only snapshot start state if we need it else if (track.Keyframes[0].Time > 0 || track.Relative == true) { // generate keyframe if (ImplicitStartKeyframes[ti] == null) { ImplicitStartKeyframes[ti] = new Keyframe() { Time = 0f, Value = TokenPool.Lease(TargetPath.TypeOfPath[track.TargetPath.Path]) }; } // get a patch of the target type, traverse patch for the targeted field JToken json = ImplicitStartKeyframes[ti].Value; if (!GetPatchAtPath(track.TargetPath, out IPatchable patch) || !patch.ReadFromPath(track.TargetPath, ref json, 0)) { continue; } } // resolve all relative keyframes now if (track.Relative == true) { for (var ki = 0; ki < track.Keyframes.Length; ki++) { var keyframe = track.Keyframes[ki]; if (ResolvedRelativeKeyframes[ti][ki] == null) { ResolvedRelativeKeyframes[ti][ki] = new Keyframe() { Time = keyframe.Time, Value = TokenPool.Lease(keyframe.Value), Easing = keyframe.Easing }; } Interpolations.ResolveRelativeValue( ImplicitStartKeyframes[ti].Value, keyframe.Value, ref ResolvedRelativeKeyframes[ti][ki].Value); } } } }
internal override void Update(long serverTime) { if (Data == null) { // only way for Data to be unset is if it's unloaded if (DataSet) { manager.DeregisterAnimation(this); } return; } // normalize time to animation length based on wrap settings float currentTime; if (Weight > 0 && Speed != 0) { // normal operation currentTime = (serverTime - BasisTime) * Speed / 1000; LastWeight = Weight; StopUpdating = false; } else if (!StopUpdating) { // supposed to stop, but run one last update currentTime = Time; StopUpdating = true; } else { // don't update return; } currentTime = ApplyWrapMode(currentTime); // process tracks for (var ti = 0; ti < Data.Tracks.Length; ti++) { Track track = Data.Tracks[ti]; bool usesPrevFrameValue = false, usesNextFrameValue = false; (Keyframe prevFrame, Keyframe nextFrame) = GetActiveKeyframes(ti, currentTime); // either no keyframes, or time out of range if (prevFrame == null) { continue; } float linearT = (currentTime - prevFrame.Time) / (nextFrame.Time - prevFrame.Time); // get realtime value for trailing frame JToken prevFrameValue = prevFrame.Value; if (prevFrame.ValuePath != null) { if (GetPatchAtPath(prevFrame.ValuePath, out IPatchable patch)) { prevFrameValue = TokenPool.Lease(TargetPath.TypeOfPath[prevFrame.ValuePath.Path]); if (patch.ReadFromPath(prevFrame.ValuePath, ref prevFrameValue, 0)) { usesPrevFrameValue = true; } else { TokenPool.Return(prevFrameValue); continue; } } else { continue; } } // get realtime value for leading frame (same as above) JToken nextFrameValue = nextFrame.Value; if (nextFrame.ValuePath != null) { if (GetPatchAtPath(nextFrame.ValuePath, out IPatchable patch)) { nextFrameValue = TokenPool.Lease(TargetPath.TypeOfPath[nextFrame.ValuePath.Path]); if (patch.ReadFromPath(nextFrame.ValuePath, ref nextFrameValue, 0)) { usesNextFrameValue = true; } else { TokenPool.Return(nextFrameValue); continue; } } else { continue; } } // compute new value for targeted field JToken outputToken = TokenPool.Lease(prevFrameValue); Interpolations.Interpolate(prevFrameValue, nextFrameValue, linearT, ref outputToken, nextFrame.Bezier ?? track.Bezier ?? LinearEasing); if (usesPrevFrameValue) { TokenPool.Return(prevFrameValue); } if (usesNextFrameValue) { TokenPool.Return(nextFrameValue); } // mix computed value with the result of any other anims targeting the same property AnimationManager.AnimBlend blendData = manager.AnimBlends.GetOrCreate( ResolvedTargetPaths[ti], () => new AnimationManager.AnimBlend(ResolvedTargetPaths[ti])); blendData.FinalUpdate = blendData.FinalUpdate || StopUpdating; if (blendData.TotalWeight == 0) { blendData.TotalWeight = LastWeight; if (blendData.CurrentValue == null) { blendData.CurrentValue = outputToken.DeepClone(); } else { JToken temp = blendData.CurrentValue; blendData.CurrentValue = outputToken; TokenPool.Return(temp); } } else { blendData.TotalWeight += LastWeight; JToken temp = TokenPool.Lease(outputToken); Interpolations.Interpolate(outputToken, blendData.CurrentValue, LastWeight / blendData.TotalWeight, ref temp, LinearEasing); TokenPool.Return(blendData.CurrentValue); blendData.CurrentValue = temp; } } }