avatar/Assets/VRCFury/Scripts/Editor/VF/Builder/AnimatorConditionLogic.cs

178 lines
8.1 KiB
C#

using System;
using System.Collections.Immutable;
using System.Linq;
using BestHTTP.SecureProtocol.Org.BouncyCastle.Utilities.Collections;
using UnityEditor.Animations;
using VF.Inspector;
using AnimatorConditions = System.Collections.Generic.IEnumerable<UnityEditor.Animations.AnimatorCondition>;
using AnimatorConditionsUnion = System.Collections.Generic.IEnumerable<
System.Collections.Generic.IEnumerable<UnityEditor.Animations.AnimatorCondition>>;
namespace VF.Builder {
/**
* This class can take any arbitrary set of animator transform conditions,
* and AND, OR, or NOT them cleanly with other conditions.
*/
public class AnimatorConditionLogic {
private static AnimatorCondition Not(AnimatorCondition input) {
var copy = input;
switch (copy.mode) {
case AnimatorConditionMode.Equals:
copy.mode = AnimatorConditionMode.NotEqual;
break;
case AnimatorConditionMode.Greater:
copy.mode = AnimatorConditionMode.Less;
copy.threshold = VRCFuryEditorUtils.NextFloatUp(copy.threshold);
break;
case AnimatorConditionMode.If:
copy.mode = AnimatorConditionMode.IfNot;
break;
case AnimatorConditionMode.Less:
copy.mode = AnimatorConditionMode.Greater;
copy.threshold = VRCFuryEditorUtils.NextFloatDown(copy.threshold);
break;
case AnimatorConditionMode.IfNot:
copy.mode = AnimatorConditionMode.If;
break;
case AnimatorConditionMode.NotEqual:
copy.mode = AnimatorConditionMode.Equals;
break;
default:
throw new Exception("Unknown condition mode: " + copy.mode);
}
return copy;
}
public static AnimatorConditionsUnion Not(AnimatorConditionsUnion input) {
AnimatorConditionsUnion emptyProduct = new[] { Enumerable.Empty<AnimatorCondition>() };
var output = input.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] {Not(item)}));
return Simplify(output);
}
public static AnimatorConditionsUnion And(AnimatorConditionsUnion in1, AnimatorConditionsUnion in2) {
var combined = in1.SelectMany(a => in2.Select(b => a.Concat(b)));
return Simplify(combined);
}
public static AnimatorConditionsUnion Or(AnimatorConditionsUnion in1, AnimatorConditionsUnion in2) {
var combined = in1.Concat(in2);
return Simplify(combined);
}
private static AnimatorConditionsUnion Simplify(AnimatorConditionsUnion conds) {
// Simplify each transform
conds = conds.Select(Simplify);
// Remove duplicates and supersets
var all = conds.ToList();
conds = all.Where(c => {
var set = c.ToImmutableHashSet();
var hasSubset = all.Any(other => c != other && set.IsSupersetOf(other));
return !hasSubset;
});
// Remove impossible
conds = conds.Where(c => !IsImpossible(c));
return conds;
}
private static string Stringify(AnimatorConditions conds) {
return string.Join("|", conds.Select(c => c.mode + "." + c.parameter + "." + c.threshold));
}
private static AnimatorConditions Simplify(AnimatorConditions conds) {
var all = conds.ToList();
// Remove redundant conditions
conds = all.Where(c => !IsRedundant(c, all));
// Remove duplicate conditions
conds = conds.GroupBy(c => c).Select(grp => grp.First());
// Sort
conds = conds.OrderBy(c => c.parameter);
return conds;
}
private static bool IsImpossible(AnimatorConditions conds) {
var all = conds.ToList();
return all.Any(c => IsImpossible(c, all));
}
private static bool IsImpossible(AnimatorCondition rule, AnimatorConditions others) {
foreach (var other in others) {
if (rule.parameter != other.parameter) continue;
switch (rule.mode) {
case AnimatorConditionMode.Equals:
if (other.mode == AnimatorConditionMode.Greater && other.threshold >= rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Equals && other.threshold != rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Less && other.threshold <= rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.NotEqual && other.threshold == rule.threshold)
return true;
break;
case AnimatorConditionMode.Greater:
if (other.mode == AnimatorConditionMode.Equals && other.threshold <= rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Less && other.threshold <= rule.threshold)
return true;
break;
case AnimatorConditionMode.Less:
if (other.mode == AnimatorConditionMode.Equals && other.threshold >= rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Greater && other.threshold >= rule.threshold)
return true;
break;
case AnimatorConditionMode.NotEqual:
if (other.mode == AnimatorConditionMode.Equals && other.threshold == rule.threshold)
return true;
break;
case AnimatorConditionMode.If:
if (other.mode == AnimatorConditionMode.IfNot) return true;
break;
case AnimatorConditionMode.IfNot:
if (other.mode == AnimatorConditionMode.If) return true;
break;
}
}
return false;
}
private static bool IsRedundant(AnimatorCondition rule, AnimatorConditions others) {
foreach (var other in others) {
if (rule.parameter != other.parameter) continue;
switch (rule.mode) {
case AnimatorConditionMode.Greater:
if (other.mode == AnimatorConditionMode.Greater && other.threshold < rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Equals && other.threshold > rule.threshold)
return true;
break;
case AnimatorConditionMode.Less:
if (other.mode == AnimatorConditionMode.Less && other.threshold > rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Equals && other.threshold < rule.threshold)
return true;
break;
case AnimatorConditionMode.NotEqual:
if (other.mode == AnimatorConditionMode.Greater && other.threshold >= rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Equals && other.threshold != rule.threshold)
return true;
if (other.mode == AnimatorConditionMode.Less && other.threshold <= rule.threshold)
return true;
break;
}
}
return false;
}
}
}