Skip to content

Commit

Permalink
Reduce: Rewrite fps logic, optionally round key, otherwise selects mo…
Browse files Browse the repository at this point in the history
…st meaningful time
  • Loading branch information
acidbubbles committed Sep 15, 2021
1 parent 1bd6793 commit d9c91c4
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 84 deletions.
34 changes: 24 additions & 10 deletions src/AtomAnimations/Operations/ReduceOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ 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++)
{
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);

Expand All @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
5 changes: 3 additions & 2 deletions src/AtomAnimations/Operations/Reduction/ReduceSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
42 changes: 21 additions & 21 deletions src/UI/Screens/ReduceScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}

Expand All @@ -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(),
Expand Down

0 comments on commit d9c91c4

Please sign in to comment.