diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 81c7d3ee6..1dbf479fb 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog]. ### Fixed - Box Editor of Remove Mesh in Box can be broke with scale of Skinned Mesh Renderer `#1019` +- Automatic Merge Skinned Mesh is broken with BlendTree `#1020` ### Security diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eab210ff..02a969001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ The format is based on [Keep a Changelog]. - That's why AAO now configures `Clamp BlendShapes (Deprecated)` to false in edit mode and true in play mode. - PlayMode is usually used for testing the avatar behavior so it's better to have the same setting as VRChat client. - If you want not to change this setting, please disable `Tools/Avatar Optimizer/Configure Clamp BlendShape Weight`. -- Automatic Merge Skinned Mesh `#952` `#972` `#1010` +- Automatic Merge Skinned Mesh `#952` `#972` `#1010` `#1020` - Trace and Optimize now automatically merges Skinned Meshes if possible. - Trace and Optimize will merge your mesh if the material properties or enablement of the mesh is animated similarly and has no BlendShapes. - Components API for Scripting Usage `#976` diff --git a/Editor/AnimatorParserV2/AnimationParser.cs b/Editor/AnimatorParserV2/AnimationParser.cs index d296d3a7f..1f21e7d83 100644 --- a/Editor/AnimatorParserV2/AnimationParser.cs +++ b/Editor/AnimatorParserV2/AnimationParser.cs @@ -51,7 +51,7 @@ private ImmutableNodeContainer ParseBlendTree(GameObject root, BlendTree blendTr return NodesMerger.Merge< ImmutableNodeContainer, ImmutablePropModNode, ImmutablePropModNode, - ImmutablePropModNode, ImmutablePropModNode, + BlendTreeElement, BlendTreeElement, ImmutableNodeContainer, ImmutableNodeContainer, ImmutablePropModNode, ImmutablePropModNode, BlendTreeMergeProperty @@ -62,7 +62,7 @@ private ImmutableNodeContainer ParseBlendTree(GameObject root, BlendTree blendTr internal readonly struct BlendTreeMergeProperty : IMergeProperty1< ImmutableNodeContainer, ImmutablePropModNode, ImmutablePropModNode, - ImmutablePropModNode, ImmutablePropModNode, + BlendTreeElement, BlendTreeElement, ImmutableNodeContainer, ImmutableNodeContainer, ImmutablePropModNode, ImmutablePropModNode > @@ -77,16 +77,16 @@ public BlendTreeMergeProperty(BlendTreeType blendType) public ImmutableNodeContainer CreateContainer() => new ImmutableNodeContainer(); public ImmutableNodeContainer GetContainer(ImmutableNodeContainer source) => source; - public ImmutablePropModNode GetIntermediate(ImmutableNodeContainer source, - ImmutablePropModNode node, int index) => node; + public BlendTreeElement GetIntermediate(ImmutableNodeContainer source, + ImmutablePropModNode node, int index) => new BlendTreeElement(index, node); - public ImmutablePropModNode GetIntermediate(ImmutableNodeContainer source, - ImmutablePropModNode node, int index) => node; + public BlendTreeElement GetIntermediate(ImmutableNodeContainer source, + ImmutablePropModNode node, int index) => new BlendTreeElement(index, node); - public ImmutablePropModNode MergeNode(List> nodes, int sourceCount) => + public ImmutablePropModNode MergeNode(List> nodes, int sourceCount) => new BlendTreeNode(nodes, _blendType, partial: nodes.Count != sourceCount); - public ImmutablePropModNode MergeNode(List> nodes, int sourceCount) => + public ImmutablePropModNode MergeNode(List> nodes, int sourceCount) => new BlendTreeNode(nodes, _blendType, partial: nodes.Count != sourceCount); } diff --git a/Editor/AnimatorParserV2/AnimatorParserDebugWindow.cs b/Editor/AnimatorParserV2/AnimatorParserDebugWindow.cs index a5b50a44e..0de98910d 100644 --- a/Editor/AnimatorParserV2/AnimatorParserDebugWindow.cs +++ b/Editor/AnimatorParserV2/AnimatorParserDebugWindow.cs @@ -33,6 +33,8 @@ private void OnGUI() { if (GUILayout.Button("Copy Parsed Text")) GUIUtility.systemCopyBuffer = CreateText(); + if (GUILayout.Button("Copy Detailed Parsed Text")) + GUIUtility.systemCopyBuffer = CreateText(true); } if (Container == null) return; @@ -63,7 +65,7 @@ private void OnGUI() GUILayout.EndScrollView(); } - private string CreateText() + private string CreateText(bool detailed = false) { var root = parsedRootObject.transform; var resultText = new StringBuilder(); @@ -86,7 +88,9 @@ private string CreateText() else propStateInfo += "Variable"; - resultText.Append(propName).Append(": ").Append(propStateInfo).Append('\n'); + resultText.Append(" ").Append(propName).Append(": ").Append(propStateInfo).Append('\n'); + if (detailed) + AppendNodeRecursive(propState, resultText, " "); } resultText.Append('\n'); @@ -95,6 +99,71 @@ private string CreateText() return resultText.ToString(); } + private void AppendNodeRecursive(PropModNode propState, StringBuilder resultText, string indent) + { + switch (propState) + { + case AnimatorControllerPropModNode animCont: + resultText.Append($"{indent}AnimatorController: \n"); + foreach (var layerInfo in animCont.LayersReversed) + { + resultText.Append($"{indent} Layer {layerInfo.LayerIndex}: {layerInfo.Weight}, {layerInfo.BlendingMode}\n"); + AppendNodeRecursive(layerInfo.Node, resultText, indent + " "); + } + break; + case AnimationComponentPropModNode animation: + resultText.Append($"{indent}Animation: {animation.Component.name}\n"); + AppendNodeRecursive(animation.Animation, resultText, indent + " "); + break; + case AnimatorPropModNode animator: + resultText.Append($"{indent}Animator: {animator.Component.name}\n"); + foreach (var layerInfo in animator.LayersReversed) + { + resultText.Append($"{indent} Layer {layerInfo.LayerIndex}: {layerInfo.Weight}, {layerInfo.BlendingMode}\n"); + AppendNodeRecursive(layerInfo.Node, resultText, indent + " "); + } + break; + case HumanoidAnimatorPropModNode humanoid: + resultText.Append($"{indent}Humanoid: {humanoid.Component.name}\n"); + break; + case VariableComponentPropModNode variable: + resultText.Append($"{indent}Variable({variable.Component.GetType().Name}): {variable.Component.name}\n"); + break; + case AnimatorLayerPropModNode animatorLayer: + resultText.Append($"{indent}AnimatorLayer:\n"); + foreach (var childNode in animatorLayer.Children) + AppendNodeRecursive(childNode, resultText, indent + " "); + break; + case AnimatorStatePropModNode stateNode: + resultText.Append($"{indent}AnimatorState: {stateNode.State.name}\n"); + AppendNodeRecursive(stateNode.Node, resultText, indent + " "); + break; + case BlendTreeNode blendTreeNode: + resultText.Append($"{indent}BlendTree:\n"); + foreach (var childNode in blendTreeNode.Children) + { + resultText.Append($"{indent} BlendTreeElement({childNode.Index}):\n"); + AppendNodeRecursive(childNode.Node, resultText, indent + " "); + } + break; + case FloatAnimationCurveNode curve: + resultText.Append($"{indent}AnimationCurve: {curve.Clip.name}\n"); + break; + case RootPropModNode rootNode: + resultText.Append($"{indent}Root:\n"); + foreach (var rootNodeChild in rootNode.Children) + { + resultText.Append($"{indent} {rootNodeChild.Component.name}:\n"); + AppendNodeRecursive(rootNodeChild.Node, resultText, indent + " "); + } + break; + default: + resultText.Append($"{indent}Unknown: {propState.GetType().Name}\n"); + break; + } + } + + private static void NarrowValueLabelField(string label0, string value) { var position = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(true, 18f)); @@ -173,4 +242,4 @@ public enum ParserSource Motion } } -} \ No newline at end of file +} diff --git a/Editor/AnimatorParserV2/PropModNode.cs b/Editor/AnimatorParserV2/PropModNode.cs index af6747a9d..0676e197a 100644 --- a/Editor/AnimatorParserV2/PropModNode.cs +++ b/Editor/AnimatorParserV2/PropModNode.cs @@ -209,7 +209,7 @@ internal interface ILayer : ILayer internal sealed class RootPropModNode : PropModNode, IErrorContext { - readonly struct ComponentInfo + internal readonly struct ComponentInfo { public readonly ComponentPropModNodeBase Node; public readonly bool AlwaysApplied; @@ -227,6 +227,8 @@ public ComponentInfo(ComponentPropModNodeBase node, bool alwaysApplied) private readonly List _children = new List(); + public IEnumerable Children => _children; + public override bool AppliedAlways => _children.All(x => x.AppliedAlways); public override IEnumerable ContextReferences => _children.SelectMany(x => x.ContextReferences); public override ValueInfo Value => NodeImplUtils.ConstantInfoForSideBySide(_children.Select(x => x.Node)); @@ -354,13 +356,25 @@ private static ValueInfo ParseProperty(ObjectReferenceKeyframe[] frames) new ValueInfo(frames.Select(x => x.value).Distinct().ToArray()); } + internal struct BlendTreeElement + { + public int Index; + public ImmutablePropModNode Node; + + public BlendTreeElement(int index, [NotNull] ImmutablePropModNode node) + { + Index = index; + Node = node ?? throw new ArgumentNullException(nameof(node)); + } + } + internal class BlendTreeNode : ImmutablePropModNode { - private readonly List> _children; + private readonly List> _children; private readonly BlendTreeType _blendTreeType; private readonly bool _partial; - public BlendTreeNode([NotNull] [ItemNotNull] List> children, BlendTreeType blendTreeType, bool partial) + public BlendTreeNode([NotNull] List> children, BlendTreeType blendTreeType, bool partial) { // expected to pass list or array // ReSharper disable once PossibleMultipleEnumeration @@ -373,14 +387,14 @@ public BlendTreeNode([NotNull] [ItemNotNull] List> child private bool WeightSumIsOne => _blendTreeType != BlendTreeType.Direct; - public IReadOnlyList> Children => _children; - public override bool AppliedAlways => WeightSumIsOne && !_partial && _children.All(x => x.AppliedAlways); + public IReadOnlyList> Children => _children; + public override bool AppliedAlways => WeightSumIsOne && !_partial && _children.All(x => x.Node.AppliedAlways); public override ValueInfo Value => !WeightSumIsOne ? ValueInfo.Variable - : NodeImplUtils.ConstantInfoForSideBySide(_children); + : NodeImplUtils.ConstantInfoForSideBySide(_children.Select(x => x.Node)); public override IEnumerable ContextReferences => - _children.SelectMany(x => x.ContextReferences); + _children.SelectMany(x => x.Node.ContextReferences); } abstract class ComponentPropModNodeBase : PropModNode @@ -422,11 +436,11 @@ public VariableComponentPropModNode([NotNull] Component component) : base(compon class AnimationComponentPropModNode : ComponentPropModNode { - private readonly ImmutablePropModNode _animation; + public ImmutablePropModNode Animation { get; } public AnimationComponentPropModNode([NotNull] Animation component, ImmutablePropModNode animation) : base(component) { - _animation = animation; + Animation = animation; _constantInfo = new Lazy>(() => animation.Value, isThreadSafe: false); } @@ -436,6 +450,6 @@ public AnimationComponentPropModNode([NotNull] Animation component, ImmutablePro public override ValueInfo Value => _constantInfo.Value; public override IEnumerable ContextReferences => - base.ContextReferences.Concat(_animation.ContextReferences); + base.ContextReferences.Concat(Animation.ContextReferences); } } diff --git a/Editor/Utils/AnimationLocation.cs b/Editor/Utils/AnimationLocation.cs index 411a580fb..48c0397ee 100644 --- a/Editor/Utils/AnimationLocation.cs +++ b/Editor/Utils/AnimationLocation.cs @@ -74,11 +74,11 @@ private static IEnumerable CollectAnimationLocationSlow(Anima { var (blendTree, location) = queue.Dequeue(); - for (var i = 0; i < blendTree.Children.Count; i++) + foreach (var element in blendTree.Children) { var newLocation = location; - ArrayUtility.Add(ref newLocation, i); - switch (blendTree.Children[i]) + ArrayUtility.Add(ref newLocation, element.Index); + switch (element.Node) { case FloatAnimationCurveNode floatNode: yield return new AnimationLocation(animator, playableLayer, animatorLayer, state, @@ -90,7 +90,7 @@ private static IEnumerable CollectAnimationLocationSlow(Anima default: throw new InvalidOperationException( "Unexpected node type: " + - blendTree.Children[i].GetType().FullName); + element.Node.GetType().FullName); } } }