fix: multiple bugs discovered during dogfooding (#964)

* fix: initial state does not set proxy variables for toggled objects

* fix: incorrect constant analysis in property overlay pass

* fix: reactive objects respond to parent submenu active state inappropriately

* fix: property overlay pass does not register itself in animation database

* fix: object toggle default state is not applied properly
This commit is contained in:
bd_ 2024-08-07 20:59:13 -07:00 committed by GitHub
parent 6d89db6a8a
commit 053a0d464b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 112 additions and 7 deletions

View File

@ -88,6 +88,8 @@ namespace nadena.dev.modular_avatar.core.editor
internal static ControlCondition AssignMenuItemParameter(ndmf.BuildContext context, ModularAvatarMenuItem mami)
{
if (mami?.Control?.parameter?.name == null) return null;
return new ControlCondition
{
Parameter = mami.Control.parameter.name,

View File

@ -131,6 +131,14 @@ namespace nadena.dev.modular_avatar.core.editor
var cursor = controllingObject?.transform;
// Only look at the menu item we're directly attached to, to avoid submenus causing issues...
var mami = cursor?.GetComponent<ModularAvatarMenuItem>();
if (mami != null)
{
var mami_condition = ParameterAssignerPass.AssignMenuItemParameter(context, mami);
if (mami_condition != null) conditions.Add(mami_condition);
}
while (cursor != null && !RuntimeUtil.IsAvatarRoot(cursor))
{
conditions.Add(new ControlCondition
@ -144,9 +152,6 @@ namespace nadena.dev.modular_avatar.core.editor
ReferenceObject = cursor.gameObject
});
foreach (var mami in cursor.GetComponents<ModularAvatarMenuItem>())
conditions.Add(ParameterAssignerPass.AssignMenuItemParameter(context, mami));
cursor = cursor.parent;
}
@ -203,6 +208,8 @@ namespace nadena.dev.modular_avatar.core.editor
AnalyzeConstants(shapes);
ResolveToggleInitialStates(shapes);
PreprocessShapes(shapes, out var initialStates, out var deletedShapes);
ProcessInitialStates(initialStates);
@ -218,6 +225,7 @@ namespace nadena.dev.modular_avatar.core.editor
private void AnalyzeConstants(Dictionary<TargetProp, PropGroup> shapes)
{
var asc = context.Extension<AnimationServicesContext>();
HashSet<GameObject> toggledObjects = new();
foreach (var targetProp in shapes.Keys)
@ -230,16 +238,21 @@ namespace nadena.dev.modular_avatar.core.editor
{
foreach (var condition in actionGroup.ControllingConditions)
if (condition.ReferenceObject != null && !toggledObjects.Contains(condition.ReferenceObject))
condition.IsConstant = true;
condition.IsConstant = asc.AnimationDatabase.ClipsForPath(asc.PathMappings.GetObjectIdentifier(condition.ReferenceObject)).IsEmpty;
var firstAlwaysOn =
actionGroup.ControllingConditions.FindIndex(c => c.InitiallyActive && c.IsConstant);
if (firstAlwaysOn > 0) actionGroup.ControllingConditions.RemoveRange(0, firstAlwaysOn - 1);
var i = 0;
// Remove redundant conditions
actionGroup.ControllingConditions.RemoveAll(c => c.IsConstant && c.InitiallyActive && (i++ != 0));
}
// Remove any action groups with always-off conditions
group.actionGroups.RemoveAll(agk =>
agk.ControllingConditions.Any(c => !c.InitiallyActive && c.IsConstant));
// Remove all action groups up until the last one where we're always on
var lastAlwaysOnGroup = group.actionGroups.FindLastIndex(ag => ag.IsConstantOn);
if (lastAlwaysOnGroup > 0)
group.actionGroups.RemoveRange(0, lastAlwaysOnGroup - 1);
}
// Remove shapes with no action groups
@ -305,8 +318,81 @@ namespace nadena.dev.modular_avatar.core.editor
}
}
private void ResolveToggleInitialStates(Dictionary<TargetProp, PropGroup> groups)
{
var asc = context.Extension<AnimationServicesContext>();
Dictionary<string, bool> propStates = new Dictionary<string, bool>();
Dictionary<string, bool> nextPropStates = new Dictionary<string, bool>();
int loopLimit = 5;
bool unsettled = true;
while (unsettled && loopLimit-- > 0)
{
unsettled = false;
foreach (var group in groups.Values)
{
if (group.TargetProp.PropertyName != "m_IsActive") continue;
if (!(group.TargetProp.TargetObject is GameObject targetObject)) continue;
var pathKey = asc.GetActiveSelfProxy(targetObject);
bool state;
if (!propStates.TryGetValue(pathKey, out state)) state = targetObject.activeSelf;
foreach (var actionGroup in group.actionGroups)
{
bool evaluated = true;
foreach (var condition in actionGroup.ControllingConditions)
{
if (!propStates.TryGetValue(condition.Parameter, out var propCondition))
{
propCondition = condition.InitiallyActive;
}
if (!propCondition)
{
evaluated = false;
break;
}
}
if (evaluated)
{
state = actionGroup.Value > 0.5f;
}
}
nextPropStates[pathKey] = state;
if (!propStates.TryGetValue(pathKey, out var oldState) || oldState != state)
{
unsettled = true;
}
}
propStates = nextPropStates;
nextPropStates = new();
}
foreach (var group in groups.Values)
{
foreach (var action in group.actionGroups)
{
foreach (var condition in action.ControllingConditions)
{
if (propStates.TryGetValue(condition.Parameter, out var state))
condition.InitialValue = state ? 1.0f : 0.0f;
}
}
}
}
private void ProcessInitialStates(Dictionary<TargetProp, float> initialStates)
{
var asc = context.Extension<AnimationServicesContext>();
// We need to track _two_ initial states: the initial state we'll apply at build time (which applies
// when animations are disabled) and the animation base state. Confusingly, the animation base state
// should be the state that is currently applied to the object...
@ -362,6 +448,7 @@ namespace nadena.dev.modular_avatar.core.editor
var prop = serializedObject.FindProperty(key.PropertyName);
if (prop != null)
{
switch (prop.propertyType)
{
case SerializedPropertyType.Boolean:
@ -373,6 +460,9 @@ namespace nadena.dev.modular_avatar.core.editor
prop.floatValue = initialState;
break;
}
serializedObject.ApplyModifiedPropertiesWithoutUndo();
}
}
var curve = new AnimationCurve();
@ -386,6 +476,17 @@ namespace nadena.dev.modular_avatar.core.editor
);
AnimationUtility.SetEditorCurve(_initialStateClip, binding, curve);
if (componentType == typeof(GameObject) && key.PropertyName == "m_IsActive")
{
binding = EditorCurveBinding.FloatCurve(
"",
typeof(Animator),
asc.GetActiveSelfProxy((GameObject)key.TargetObject)
);
AnimationUtility.SetEditorCurve(_initialStateClip, binding, curve);
}
}
}
@ -464,6 +565,7 @@ namespace nadena.dev.modular_avatar.core.editor
position = new Vector3(x, y),
state = initialState
});
asc.AnimationDatabase.RegisterState(states[^1].state);
var lastConstant = info.actionGroups.FindLastIndex(agk => agk.IsConstant);
var transitionBuffer = new List<(AnimatorState, List<AnimatorStateTransition>)>();
@ -511,6 +613,7 @@ namespace nadena.dev.modular_avatar.core.editor
position = new Vector3(x, y),
state = state
});
asc.AnimationDatabase.RegisterState(states[^1].state);
var transitionList = new List<AnimatorStateTransition>();
transitionBuffer.Add((state, transitionList));