diff --git a/src/AtomAnimations/Operations/ReduceOperations.cs b/src/AtomAnimations/Operations/ReduceOperations.cs index b32e47ab..991218d0 100644 --- a/src/AtomAnimations/Operations/ReduceOperations.cs +++ b/src/AtomAnimations/Operations/ReduceOperations.cs @@ -132,9 +132,9 @@ private IEnumerator Process(ITargetReduceProcessor processor) yield return 0; // STEP 3: Average keyframes based on the desired FPS - if (_settings.avgToSnap && fps <= 50) + if (fps < 50) { - AverageToFPS(processor, fps, animationLength); + AverageToFPS(processor, fps, animationLength, _settings.round); } yield return 0; @@ -228,30 +228,44 @@ private static bool ProcessSimplifyIteration(ITargetReduceProcessor processor, I buckets.RemoveAt(bucketToSplitIndex); if (bucketToSplit.to - keyWithLargestDelta + 1 > 2) buckets.Insert(bucketToSplitIndex, processor.CreateBucket(keyWithLargestDelta + 1, bucketToSplit.to)); - if (keyWithLargestDelta - 1 - bucketToSplit.@from > 2) - buckets.Insert(bucketToSplitIndex, processor.CreateBucket(bucketToSplit.@from, keyWithLargestDelta - 1)); + if (keyWithLargestDelta - 1 - bucketToSplit.from > 2) + buckets.Insert(bucketToSplitIndex, processor.CreateBucket(bucketToSplit.from, keyWithLargestDelta - 1)); } return true; } - private static void AverageToFPS(ITargetReduceProcessor processor, float fps, float animationLength) + private static void AverageToFPS(ITargetReduceProcessor processor, float fps, float animationLength, bool round) { - var minFrameDistance = Mathf.Max(1f / fps, 0.001f); - var groupByTimeRange = minFrameDistance / 2f; + var frameDistance = Mathf.Max(1f / fps, 0.001f); + var halfFrameDistance = frameDistance / 2f; var lead = processor.target.GetLeadCurve(); var toKey = 0; processor.Branch(); - for (var keyTime = 0f; keyTime <= animationLength; keyTime += minFrameDistance) + for (var keyTime = -halfFrameDistance; keyTime <= animationLength; keyTime += frameDistance) { var fromKey = toKey; - while (toKey < lead.length - 1 && lead.keys[toKey].time < keyTime + groupByTimeRange) + var fromNormalized = processor.GetComparableNormalizedValue(fromKey); + var mostMeaningfulKey = fromKey; + var maxDelta = 0f; + while (toKey < lead.length - 1) { + var time = lead.keys[toKey].time; + if (time >= keyTime + frameDistance) break; + var delta = Mathf.Abs(fromNormalized - processor.GetComparableNormalizedValue(toKey)); + if (delta > maxDelta) + { + mostMeaningfulKey = toKey; + maxDelta = delta; + } toKey++; } if (toKey - fromKey > 0) - processor.AverageToBranch(keyTime + ((lead.keys[toKey].time - keyTime) / 2f).Snap(), fromKey, toKey); + { + var time = round ? keyTime + halfFrameDistance : lead.keys[mostMeaningfulKey].time; + processor.AverageToBranch(time.Snap(), fromKey, toKey); + } } processor.Commit(); diff --git a/src/AtomAnimations/Operations/Reduction/ControllerTargetReduceProcessor.cs b/src/AtomAnimations/Operations/Reduction/ControllerTargetReduceProcessor.cs index c9a93a5e..c935c694 100644 --- a/src/AtomAnimations/Operations/Reduction/ControllerTargetReduceProcessor.cs +++ b/src/AtomAnimations/Operations/Reduction/ControllerTargetReduceProcessor.cs @@ -25,7 +25,7 @@ public void CopyToBranch(int key, int curveType = CurveTypeValues.Undefined) public void AverageToBranch(float keyTime, int fromKey, int toKey) { var position = Vector3.zero; - var rotationCum = Vector4.zero; + var rotationSum = Vector4.zero; var firstRotation = source.GetKeyframeRotation(fromKey); var duration = source.x.GetKeyframeByKey(toKey).time - source.x.GetKeyframeByKey(fromKey).time; for (var key = fromKey; key < toKey; key++) @@ -33,7 +33,7 @@ public void AverageToBranch(float keyTime, int fromKey, int toKey) var frameDuration = source.x.GetKeyframeByKey(key + 1).time - source.x.GetKeyframeByKey(key).time; var weight = frameDuration / duration; position += source.GetKeyframePosition(key) * weight; - QuaternionUtil.AverageQuaternion(ref rotationCum, source.GetKeyframeRotation(key), firstRotation, weight); + QuaternionUtil.AverageQuaternion(ref rotationSum, source.GetKeyframeRotation(key), firstRotation, weight); } branch.SetKeyframe(keyTime, position, source.GetKeyframeRotation(fromKey), CurveTypeValues.SmoothLocal); @@ -54,33 +54,24 @@ public bool IsStable(int key1, int key2) return true; } - public override ReducerBucket CreateBucket(int from, int to) + public override float GetComparableNormalizedValue(int key) { - var bucket = base.CreateBucket(from, to); - for (var i = from; i <= to; i++) - { - var time = source.x.keys[i].time; + var time = source.x.keys[key].time; - var positionDiff = Vector3.Distance( - branch.EvaluatePosition(time), - source.EvaluatePosition(time) - ); - var rotationDot = 1f - Mathf.Abs(Quaternion.Dot( - branch.EvaluateRotation(time), - source.EvaluateRotation(time) - )); - // This is an attempt to compare translations and rotations - // TODO: Normalize the values, investigate how to do this with settings - var normalizedPositionDistance = settings.minMeaningfulDistance > 0 ? positionDiff / settings.minMeaningfulDistance : 1f; - var normalizedRotationAngle = settings.minMeaningfulRotation > 0 ? rotationDot / settings.minMeaningfulRotation : 1f; - var delta = normalizedPositionDistance + normalizedRotationAngle; - if (delta > bucket.largestDelta) - { - bucket.largestDelta = delta; - bucket.keyWithLargestDelta = i; - } - } - return bucket; + var positionDiff = Vector3.Distance( + branch.EvaluatePosition(time), + source.EvaluatePosition(time) + ); + var rotationDot = 1f - Mathf.Abs(Quaternion.Dot( + branch.EvaluateRotation(time), + source.EvaluateRotation(time) + )); + // This is an attempt to compare translations and rotations + // TODO: Normalize the values, investigate how to do this with settings + var normalizedPositionDistance = settings.minMeaningfulDistance > 0 ? positionDiff / settings.minMeaningfulDistance : 1f; + var normalizedRotationAngle = settings.minMeaningfulRotation > 0 ? rotationDot / settings.minMeaningfulRotation : 1f; + var delta = normalizedPositionDistance + normalizedRotationAngle; + return delta; } } } diff --git a/src/AtomAnimations/Operations/Reduction/FloatParamTargetReduceProcessor.cs b/src/AtomAnimations/Operations/Reduction/FloatParamTargetReduceProcessor.cs index 006d078f..460907d9 100644 --- a/src/AtomAnimations/Operations/Reduction/FloatParamTargetReduceProcessor.cs +++ b/src/AtomAnimations/Operations/Reduction/FloatParamTargetReduceProcessor.cs @@ -43,29 +43,19 @@ public bool IsStable(int key1, int key2) return Mathf.Abs(value2 - value1) / (source.animatableRef.floatParam.max - source.animatableRef.floatParam.min) < (settings.minMeaningfulFloatParamRangeRatio / 10f); } - public override ReducerBucket CreateBucket(int from, int to) + public override float GetComparableNormalizedValue(int key) { - var bucket = base.CreateBucket(from, to); - for (var i = from; i <= to; i++) - { - var time = source.value.keys[i].time; - // TODO: Normalize the delta values based on range - float delta; - if (settings.minMeaningfulFloatParamRangeRatio > 0) - delta = Mathf.Abs( - branch.value.Evaluate(time) - - source.value.Evaluate(time) - ) / (source.animatableRef.floatParam.max - source.animatableRef.floatParam.min) / settings.minMeaningfulFloatParamRangeRatio; - else - delta = 1f; - if (delta > bucket.largestDelta) - { - bucket.largestDelta = delta; - bucket.keyWithLargestDelta = i; - } - } - - return bucket; + var time = source.value.keys[key].time; + // TODO: Normalize the delta values based on range + float delta; + if (settings.minMeaningfulFloatParamRangeRatio > 0) + delta = Mathf.Abs( + branch.value.Evaluate(time) - + source.value.Evaluate(time) + ) / (source.animatableRef.floatParam.max - source.animatableRef.floatParam.min) / settings.minMeaningfulFloatParamRangeRatio; + else + delta = 1f; + return delta; } } } diff --git a/src/AtomAnimations/Operations/Reduction/ITargetReduceProcessor.cs b/src/AtomAnimations/Operations/Reduction/ITargetReduceProcessor.cs index 713ca165..f7673976 100644 --- a/src/AtomAnimations/Operations/Reduction/ITargetReduceProcessor.cs +++ b/src/AtomAnimations/Operations/Reduction/ITargetReduceProcessor.cs @@ -9,5 +9,6 @@ public interface ITargetReduceProcessor void CopyToBranch(int sourceKey, int curveType = CurveTypeValues.Undefined); void AverageToBranch(float keyTime, int fromKey, int toKey); bool IsStable(int key1, int key2); + float GetComparableNormalizedValue(int key); } } diff --git a/src/AtomAnimations/Operations/Reduction/ReduceSettings.cs b/src/AtomAnimations/Operations/Reduction/ReduceSettings.cs index 132fb2b3..92e4b9f6 100644 --- a/src/AtomAnimations/Operations/Reduction/ReduceSettings.cs +++ b/src/AtomAnimations/Operations/Reduction/ReduceSettings.cs @@ -2,12 +2,13 @@ { public class ReduceSettings { - public int fps; - public bool avgToSnap; public bool removeFlats; public bool simplify; public float minMeaningfulDistance; public float minMeaningfulRotation; public float minMeaningfulFloatParamRangeRatio; + public bool round; + public bool snap; + public int fps; } } diff --git a/src/AtomAnimations/Operations/Reduction/TargetReduceProcessorBase.cs b/src/AtomAnimations/Operations/Reduction/TargetReduceProcessorBase.cs index cbd13429..81bae862 100644 --- a/src/AtomAnimations/Operations/Reduction/TargetReduceProcessorBase.cs +++ b/src/AtomAnimations/Operations/Reduction/TargetReduceProcessorBase.cs @@ -23,14 +23,26 @@ public void Commit() branch = null; } - public virtual ReducerBucket CreateBucket(int from, int to) + public ReducerBucket CreateBucket(int from, int to) { - return new ReducerBucket + var bucket = new ReducerBucket { @from = from, to = to, keyWithLargestDelta = -1 }; + for (var i = from; i <= to; i++) + { + var delta = GetComparableNormalizedValue(i); + if (delta > bucket.largestDelta) + { + bucket.largestDelta = delta; + bucket.keyWithLargestDelta = i; + } + } + return bucket; } + + public abstract float GetComparableNormalizedValue(int key); } } diff --git a/src/UI/Screens/ReduceScreen.cs b/src/UI/Screens/ReduceScreen.cs index 1884c85e..14505f50 100644 --- a/src/UI/Screens/ReduceScreen.cs +++ b/src/UI/Screens/ReduceScreen.cs @@ -7,13 +7,13 @@ public class ReduceScreen : ScreenBase { public const string ScreenName = "Reduce"; - private JSONStorableFloat _reduceMaxFramesPerSecondJSON; - private JSONStorableBool _averageToSnapJSON; + private JSONStorableFloat _maxFramesPerSecondJSON; + private JSONStorableBool _roundJSON; private JSONStorableBool _removeFlatSectionsKeyframes; private JSONStorableBool _simplifyKeyframes; - private JSONStorableFloat _reduceMinDistanceJSON; - private JSONStorableFloat _reduceMinRotationJSON; - private JSONStorableFloat _reduceMinFloatParamRangeRatioJSON; + private JSONStorableFloat _minDistanceJSON; + private JSONStorableFloat _minRotationJSON; + private JSONStorableFloat _minFloatParamRangeRatioJSON; public override string screenId => ScreenName; @@ -53,23 +53,23 @@ public override void Init(IAtomPlugin plugin, object arg) private void CreateReduceSettingsUI() { - _averageToSnapJSON = new JSONStorableBool("Round to frames per second", true); - _reduceMaxFramesPerSecondJSON = new JSONStorableFloat("Frames per second", 25f, 1f, 100f); - _reduceMaxFramesPerSecondJSON.setCallbackFunction = val => _reduceMaxFramesPerSecondJSON.valNoCallback = Mathf.Round(val); + _roundJSON = new JSONStorableBool("Round key time to fps", false); + _maxFramesPerSecondJSON = new JSONStorableFloat("Max frames per second", 10f, 1f, 50f); + _maxFramesPerSecondJSON.setCallbackFunction = val => _maxFramesPerSecondJSON.valNoCallback = Mathf.Round(val); _removeFlatSectionsKeyframes = new JSONStorableBool("Remove flat sections", true); _simplifyKeyframes = new JSONStorableBool("Simplify keyframes", true); - _reduceMinDistanceJSON = new JSONStorableFloat("Minimum meaningful distance", 0.1f, 0f, 1f, false); - _reduceMinRotationJSON = new JSONStorableFloat("Minimum meaningful rotation (dot)", 0.001f, 0f, 1f); - _reduceMinFloatParamRangeRatioJSON = new JSONStorableFloat("Minimum meaningful float range ratio", 0.01f, 0f, 1f); + _minDistanceJSON = new JSONStorableFloat("Minimum meaningful distance", 0.1f, 0f, 1f, false); + _minRotationJSON = new JSONStorableFloat("Minimum meaningful rotation (dot)", 0.001f, 0f, 1f); + _minFloatParamRangeRatioJSON = new JSONStorableFloat("Minimum meaningful float range ratio", 0.01f, 0f, 1f); prefabFactory.CreateToggle(_removeFlatSectionsKeyframes); prefabFactory.CreateSpacer(); prefabFactory.CreateToggle(_simplifyKeyframes); - prefabFactory.CreateSlider(_reduceMinDistanceJSON).valueFormat = "F3"; - prefabFactory.CreateSlider(_reduceMinRotationJSON).valueFormat = "F4"; - prefabFactory.CreateSlider(_reduceMinFloatParamRangeRatioJSON).valueFormat = "F3"; + prefabFactory.CreateSlider(_minDistanceJSON).valueFormat = "F3"; + prefabFactory.CreateSlider(_minRotationJSON).valueFormat = "F4"; + prefabFactory.CreateSlider(_minFloatParamRangeRatioJSON).valueFormat = "F3"; prefabFactory.CreateSpacer(); - prefabFactory.CreateToggle(_averageToSnapJSON); - prefabFactory.CreateSlider(_reduceMaxFramesPerSecondJSON).valueFormat = "F1"; + prefabFactory.CreateSlider(_maxFramesPerSecondJSON).valueFormat = "F1"; + prefabFactory.CreateToggle(_roundJSON); prefabFactory.CreateSpacer(); } @@ -90,13 +90,13 @@ private void Reduce() _reduceUI.label = "Please be patient..."; var settings = new ReduceSettings { - fps = (int)_reduceMaxFramesPerSecondJSON.val, - avgToSnap = _averageToSnapJSON.val, + fps = (int)_maxFramesPerSecondJSON.val, + round = _roundJSON.val, removeFlats = _removeFlatSectionsKeyframes.val, simplify = _simplifyKeyframes.val, - minMeaningfulDistance = _reduceMinDistanceJSON.val, - minMeaningfulRotation = _reduceMinRotationJSON.val, - minMeaningfulFloatParamRangeRatio = _reduceMinFloatParamRangeRatioJSON.val, + minMeaningfulDistance = _minDistanceJSON.val, + minMeaningfulRotation = _minRotationJSON.val, + minMeaningfulFloatParamRangeRatio = _minFloatParamRangeRatioJSON.val, }; StartCoroutine(operations.Reduce(settings).ReduceKeyframes( animationEditContext.current.GetAllCurveTargets().Where(t => t.selected).ToList(),