public void InvalidateTime() { reachedEnd = false; currentKeyFrame = keyFrames.GetEnumerator(); var animationInitialValues = new AnimationInitialValues <float>(); // Skip two elements (right before third) currentKeyFrame.MoveNext(); animationInitialValues.Value1 = currentKeyFrame.Current; currentKeyFrame.MoveNext(); animationInitialValues.Value2 = currentKeyFrame.Current; currentTime = animationInitialValues.Value1.Time; InitializeAnimation(ref data, ref animationInitialValues); }
protected void ProcessChannel(ref Channel channel, CompressedTimeSpan currentTime, UpdateObjectData[] objects) { if (channel.Offset == -1) { return; } var startTime = channel.ValueStart.Time; // TODO: Should we really do that? // Sampling before start (should not really happen because we add a keyframe at TimeSpan.Zero, but let's keep it in case it changes later. if (currentTime <= startTime) { objects[channel.Offset].Value = channel.ValueStart.Value; return; } // (This including sampling after end) objects[channel.Offset].Value = channel.ValueStart.Value; }
protected unsafe override void ProcessChannel(ref Channel channel, CompressedTimeSpan newTime, IntPtr location) { SetTime(ref channel, newTime); var currentTime = channel.CurrentTime; var currentIndex = channel.CurrentIndex; var keyFrames = channel.Curve.KeyFrames; var keyFramesItems = keyFrames.Items; var keyFramesCount = keyFrames.Count; // Extract data int timeStart = keyFrames[currentIndex + 0].Time.Ticks; int timeEnd = keyFrames[currentIndex + 1].Time.Ticks; // Compute interpolation factor float t = ((float)currentTime.Ticks - (float)timeStart) / ((float)timeEnd - (float)timeStart); if (channel.InterpolationType == AnimationCurveInterpolationType.Cubic) { *(float *)(location + channel.Offset) = Interpolator.Cubic( keyFramesItems[currentIndex > 0 ? currentIndex - 1 : 0].Value, keyFramesItems[currentIndex].Value, keyFramesItems[currentIndex + 1].Value, keyFramesItems[currentIndex + 2 >= keyFramesCount ? currentIndex + 1 : currentIndex + 2].Value, t); } else if (channel.InterpolationType == AnimationCurveInterpolationType.Linear) { *(float *)(location + channel.Offset) = MathUtil.Lerp(keyFramesItems[currentIndex].Value, keyFramesItems[currentIndex + 1].Value, t); } else if (channel.InterpolationType == AnimationCurveInterpolationType.Constant) { *(float *)(location + channel.Offset) = keyFrames[currentIndex].Value; } else { throw new NotImplementedException(); } }
/// <summary> /// Evaluates the error within specified segment. /// </summary> /// <param name="originalCurve">The original curve.</param> /// <param name="evaluator">The evaluator.</param> /// <param name="stepSize">Size of the step.</param> /// <param name="keyFrame">The key frame.</param> /// <param name="nextKeyFrame">The next key frame.</param> /// <returns></returns> private KeyValuePair <CompressedTimeSpan, float> EvaluateError(Func <CompressedTimeSpan, float> originalCurve, Evaluator evaluator, CompressedTimeSpan stepSize, KeyFrameData <float> keyFrame, KeyFrameData <float> nextKeyFrame) { var startTime = keyFrame.Time; var endTime = nextKeyFrame.Time; var biggestDifference = 0.0f; var biggestDifferenceTime = startTime; // Rounds up start time (i.e. startTime is multiple of stepSize) startTime = new CompressedTimeSpan((startTime.Ticks / stepSize.Ticks + 1) * stepSize.Ticks); for (var time = startTime; time < endTime; time += stepSize) { var difference = Math.Abs(originalCurve(time) - evaluator.Evaluate(time)); if (difference > biggestDifference) { biggestDifference = difference; biggestDifferenceTime = time; } } return(new KeyValuePair <CompressedTimeSpan, float>(biggestDifferenceTime, biggestDifference)); }
public float EvaluateCubic(CompressedTimeSpan time) { int keyIndex; for (keyIndex = 0; keyIndex < KeyFrames.Count - 1; ++keyIndex) { if (time < KeyFrames[keyIndex + 1].Time) { break; } } // TODO: Check if before or after curve (and on those limits) // Exact value, just returns it. if (time == KeyFrames[keyIndex].Time) { return(KeyFrames[keyIndex].Value); } // http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Interpolation_on_the_unit_interval_without_exact_derivatives long timeStart = KeyFrames[keyIndex + 0].Time.Ticks; long timeEnd = KeyFrames[keyIndex + 1].Time.Ticks; // Compute interpolation factor and avoid NaN operations when timeStart >= timeEnd float t = (timeEnd <= timeStart) ? 0 : ((float)time.Ticks - (float)timeStart) / ((float)timeEnd - (float)timeStart); var evaluator = new EvaluatorData(); evaluator.ValuePrev = KeyFrames[keyIndex > 0 ? keyIndex - 1 : 0]; evaluator.ValueStart = KeyFrames[keyIndex + 0]; evaluator.ValueEnd = KeyFrames[keyIndex + 1]; evaluator.ValueNext = KeyFrames[keyIndex + 2 < KeyFrames.Count ? keyIndex + 2 : KeyFrames.Count - 1]; return(evaluator.Evaluate(t)); }
protected abstract void ProcessChannel(ref Channel channel, CompressedTimeSpan newTime, IntPtr location);
public void Fitting(Func <CompressedTimeSpan, float> originalCurve, CompressedTimeSpan stepSize, float maxErrorThreshold) { // Some info: http://wscg.zcu.cz/wscg2008/Papers_2008/full/A23-full.pdf // Compression of Temporal Video Data by Catmull-Rom Spline and Quadratic Bézier Curve Fitting // And: http://bitsquid.blogspot.jp/2009/11/bitsquid-low-level-animation-system.html // Only one or zero keys: no need to do anything. if (KeyFrames.Count <= 1) { return; } var keyFrames = new LinkedList <KeyFrameData <float> >(this.KeyFrames); var evaluator = new Evaluator(keyFrames); // Compute initial errors (using Least Square Equation) var errors = new LinkedList <ErrorNode>(); //var errors = new List<float>(); var errorQueue = new PriorityQueue <LinkedListNode <ErrorNode> >(new ErrorComparer()); foreach (var keyFrame in keyFrames.EnumerateNodes()) { if (keyFrame.Next == null) { break; } var error = EvaluateError(originalCurve, evaluator, stepSize, keyFrame.Value, keyFrame.Next.Value); var errorNode = errors.AddLast(new ErrorNode(keyFrame, error.Key, error.Value)); errorQueue.Enqueue(errorNode); } //for (int keyFrame = 0; keyFrame < KeyFrames.Count - 1; ++keyFrame) //{ // //errors.Add(EvaluateError(originalCurve, evaluator, stepSize, keyFrame)); // var errorNode = errors.AddLast(new ErrorNode(EvaluateError(originalCurve, evaluator, stepSize, keyFrame))); // errorQueue.Enqueue(errorNode); //} while (true) { // Already reached enough subdivisions var highestError = errorQueue.Dequeue(); if (highestError.Value.Error <= maxErrorThreshold) { break; } //// Find segment to optimize //var biggestErrorIndex = 0; //for (int keyFrame = 1; keyFrame < KeyFrames.Count - 1; ++keyFrame) //{ // if (errors[keyFrame] > errors[biggestErrorIndex]) // biggestErrorIndex = keyFrame; //} //// Already reached enough subdivisions //if (errors[biggestErrorIndex] <= maxErrorThreshold) // break; // Create new key (use middle point, but better heuristic might improve situation -- like point with biggest error) //var middleTime = (start.Value.Time + end.Value.Time) / 2; var middleTime = highestError.Value.BiggestDeltaTime; //KeyFrames.Insert(biggestErrorIndex + 1, new KeyFrameData<float> { Time = middleTime, Value = originalCurve(middleTime) }); var newKeyFrame = keyFrames.AddAfter(highestError.Value.KeyFrame, new KeyFrameData <float> { Time = middleTime, Value = originalCurve(middleTime) }); //errors.Insert(biggestErrorIndex + 1, 0.0f); var newError = errors.AddAfter(highestError, new ErrorNode(newKeyFrame, CompressedTimeSpan.Zero, 0.0f)); var highestErrorLastUpdate = newError; if (highestErrorLastUpdate.Next != null) { highestErrorLastUpdate = highestErrorLastUpdate.Next; } // Invalidate evaluator (data changed) evaluator.InvalidateTime(); // Update errors for (var error = highestError.Previous ?? highestError; error != null; error = error.Next) { if (error != highestError && error != newError) { errorQueue.Remove(error); } var errorInfo = EvaluateError(originalCurve, evaluator, stepSize, error.Value.KeyFrame.Value, error.Value.KeyFrame.Next.Value); error.Value.BiggestDeltaTime = errorInfo.Key; error.Value.Error = errorInfo.Value; errorQueue.Enqueue(error); if (error == highestErrorLastUpdate) { break; } } } KeyFrames = new List <KeyFrameData <float> >(keyFrames); }
public ErrorNode(LinkedListNode <KeyFrameData <float> > keyFrame, CompressedTimeSpan biggestDeltaTime, float error) { KeyFrame = keyFrame; BiggestDeltaTime = biggestDeltaTime; Error = error; }
/// <summary> /// Writes a new value at the end of the curve (used for building curves). /// It should be done in increasing order as it will simply add a new key at the end of <see cref="AnimationCurve{T}.KeyFrames"/>. /// </summary> /// <param name="newTime">The new time.</param> /// <param name="location">The location.</param> public abstract void AddValue(CompressedTimeSpan newTime, IntPtr location);
protected abstract void ProcessChannel(ref Channel channel, CompressedTimeSpan currentTime, IntPtr data, float factor);
protected unsafe override void ProcessChannel(ref Channel channel, CompressedTimeSpan currentTime, IntPtr location, float factor) { *(int *)(location + channel.Offset) = channel.ValueStart.Value; }
public KeyFrameData(CompressedTimeSpan time, T value) { Time = time; Value = value; }
protected override unsafe void ProcessChannel(ref Channel channel, CompressedTimeSpan currentTime, IntPtr location, float factor) { Interop.CopyInline((void *)(location + channel.Offset), ref channel.ValueStart.Value); }
/// <summary> /// Shifts all animation keys by the specified time, adding it to all <see cref="KeyFrameData.Time"/> /// </summary> /// <param name="shiftTimeSpan">The time span by which the keys should be shifted</param> public virtual void ShiftKeys(CompressedTimeSpan shiftTimeSpan) { }
public abstract void Evaluate(CompressedTimeSpan newTime, IntPtr data, UpdateObjectData[] objects);