private void ReduceKeyframes(AnimationCurve source) { var sw = Stopwatch.StartNew(); var minFrameDistance = 1f / _reduceMaxFramesPerSecondJSON.val; var maxIterations = (int)(source[source.length - 1].time * 10); var batchStopwatch = Stopwatch.StartNew(); var containingAtom = plugin.containingAtom; var steps = source.keys .GroupBy(s => s.time.Snap(minFrameDistance).ToMilliseconds()) .Select(g => { var keyframe = g.OrderBy(s => Math.Abs(g.Key - s.time)).First(); return(new Keyframe((g.Key / 1000f).Snap(), keyframe.value, 0, 0)); }) .ToList(); var target = new AnimationCurve(); target.FlatFrame(target.AddKey(0, source[0].value)); target.FlatFrame(target.AddKey(source[source.length - 1].time, source[source.length - 1].value)); for (var iteration = 0; iteration < maxIterations; iteration++) { // Scan for largest difference with curve // TODO: Use the buckets strategy var keyWithLargestDiff = -1; var largestDiff = 0f; for (var i = 1; i < steps.Count - 1; i++) { var diff = Mathf.Abs(target.Evaluate(steps[i].time) - steps[i].value); if (diff > largestDiff) { largestDiff = diff; keyWithLargestDiff = i; } } // Cannot find large enough diffs, exit if (keyWithLargestDiff == -1) { break; } var inRange = largestDiff >= _reduceMinPosDistanceJSON.val; if (!inRange) { break; } // This is an attempt to compare translations and rotations var keyToApply = keyWithLargestDiff; var step = steps[keyToApply]; steps.RemoveAt(keyToApply); var key = target.SetKeyframe(step.time, step.value); target.FlatFrame(key); } source.keys = target.keys; }