diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index 82ba6ec2a..8f972500c 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed +- incorrect AutoMergeSkinnedMesh with material property animations `#1271` ### Security diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4709da2..2ae708171 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog]. ### Removed ### Fixed +- incorrect AutoMergeSkinnedMesh with material property animations `#1271` ### Security diff --git a/Editor/AnimatorParserV2/AnimationParser.cs b/Editor/AnimatorParserV2/AnimationParser.cs index c895381bd..d82b66a00 100644 --- a/Editor/AnimatorParserV2/AnimationParser.cs +++ b/Editor/AnimatorParserV2/AnimationParser.cs @@ -12,6 +12,9 @@ namespace Anatawa12.AvatarOptimizer.AnimatorParsersV2 { class AnimationParser { + // AAO 1.7 hotfix. see https://github.com/anatawa12/AvatarOptimizer/issues/1244 + public Dictionary> SkinnedMeshAnimations = new Dictionary>(); + internal ImmutableNodeContainer ParseMotion([NotNull] GameObject root, [CanBeNull] Motion motion, [NotNull] IReadOnlyDictionary mapping) { @@ -101,7 +104,7 @@ internal ImmutableNodeContainer GetParsedAnimation([NotNull] GameObject root, [C return parsed; } - public static ImmutableNodeContainer ParseAnimation([NotNull] GameObject root, [NotNull] AnimationClip clip) + public ImmutableNodeContainer ParseAnimation([NotNull] GameObject root, [NotNull] AnimationClip clip) { var nodes = new ImmutableNodeContainer(); @@ -119,6 +122,13 @@ public static ImmutableNodeContainer ParseAnimation([NotNull] GameObject root, [ if (binding.type == typeof(Behaviour) && propertyName == "m_Enabled") propertyName = Props.EnabledFor(obj); + if (componentOrGameObject.Value is SkinnedMeshRenderer renderer) + { + if (!SkinnedMeshAnimations.TryGetValue(renderer, out var set)) + SkinnedMeshAnimations.Add(renderer, set = new HashSet()); + set.Add(propertyName); + } + var node = FloatAnimationCurveNode.Create(clip, binding); if (node == null) continue; nodes.Set(componentOrGameObject, propertyName, node); diff --git a/Editor/AnimatorParserV2/AnimatorParser.cs b/Editor/AnimatorParserV2/AnimatorParser.cs index c733ea8ca..85ac24a82 100644 --- a/Editor/AnimatorParserV2/AnimatorParser.cs +++ b/Editor/AnimatorParserV2/AnimatorParser.cs @@ -19,6 +19,7 @@ class AnimatorParser { private readonly bool _mmdWorldCompatibility; private readonly AnimationParser _animationParser = new AnimationParser(); + public Dictionary> SkinnedMeshAnimations => _animationParser.SkinnedMeshAnimations; public AnimatorParser(bool mmdWorldCompatibility) { diff --git a/Editor/Processors/ParseAnimator.cs b/Editor/Processors/ParseAnimator.cs index 13277fe09..1ca58a254 100644 --- a/Editor/Processors/ParseAnimator.cs +++ b/Editor/Processors/ParseAnimator.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using Anatawa12.AvatarOptimizer.AnimatorParsersV2; using Anatawa12.AvatarOptimizer.ndmf; using nadena.dev.ndmf; +using UnityEngine; namespace Anatawa12.AvatarOptimizer.Processors { @@ -14,11 +16,19 @@ protected override void Execute(BuildContext context) if (!context.GetState().Enabled) return; var traceAndOptimize = context.GetState(); - var modifications = new AnimatorParser(traceAndOptimize.MmdWorldCompatibility) - .GatherAnimationModifications(context); + var parser = new AnimatorParser(traceAndOptimize.MmdWorldCompatibility); + var modifications = parser.GatherAnimationModifications(context); + context.GetState().SkinnedMeshAnimations = + parser.SkinnedMeshAnimations; context.Extension() .MappingBuilder .ImportModifications(modifications); } } -} \ No newline at end of file + + // AAO 1.7 hotfix. see https://github.com/anatawa12/AvatarOptimizer/issues/1244 + class MaterialAnimationWeightZeroEffectHotFixState + { + public Dictionary> SkinnedMeshAnimations = new Dictionary>(); + } +} diff --git a/Editor/Processors/TraceAndOptimize/AutoMergeSkinnedMesh.cs b/Editor/Processors/TraceAndOptimize/AutoMergeSkinnedMesh.cs index d31074f11..c333fbb4d 100644 --- a/Editor/Processors/TraceAndOptimize/AutoMergeSkinnedMesh.cs +++ b/Editor/Processors/TraceAndOptimize/AutoMergeSkinnedMesh.cs @@ -102,7 +102,7 @@ protected override void Execute(BuildContext context, TraceAndOptimizeState stat var (activeness, activenessAnimationLocations) = activenessInfo.Value; var rendererAnimationLocations = - GetAnimationLocationsForRendererAnimation(context, meshInfo2.SourceRenderer); + GetAnimationLocationsForRendererAnimation(context, (SkinnedMeshRenderer) meshInfo2.SourceRenderer); if (rendererAnimationLocations == null) continue; // animating renderer properties with non animator is not supported @@ -377,11 +377,12 @@ private static (Activeness, EqualsHashSet<(bool initial, EqualsHashSet + private static EqualsHashSet<(float inital, string property, AnimationLocation location)> GetAnimationLocationsForRendererAnimation( - BuildContext context, Component component) + BuildContext context, SkinnedMeshRenderer component) { - var locations = new HashSet<(string property, AnimationLocation location)>(); + var animations = context.GetState().SkinnedMeshAnimations; + var locations = new HashSet<(float inital, string property, AnimationLocation location)>(); var animationComponent = context.GetAnimationComponent(component); foreach (var (property, node) in animationComponent.GetAllFloatProperties()) @@ -389,11 +390,71 @@ private static (Activeness, EqualsHashSet<(bool initial, EqualsHashSet !(x is AnimatorParsersV2.AnimatorPropModNode))) return null; + var initial = GetInitialValue(property, context, component); + if (initial == null) return null; locations.UnionWith(AnimationLocation.CollectAnimationLocation(node) - .Select(location => (property, location))); + .Select(location => (initial.Value, property, location))); } - return new EqualsHashSet<(string property, AnimationLocation location)>(locations); + if (component is SkinnedMeshRenderer renderer && animations.TryGetValue(renderer, out var properties)) + { + var animatedProperties = new HashSet(properties); + animatedProperties.Remove("m_Enabled"); + var foundProperties = new HashSet(locations.Select(x => x.property)); + if (!animatedProperties.SetEquals(foundProperties)) + return null; + } + + return new EqualsHashSet<(float inital, string property, AnimationLocation location)>(locations); + } + + private static float? GetInitialValue(string property, BuildContext context, SkinnedMeshRenderer component) + { + var meshInfo = context.GetMeshInfoFor(component); + if (property.StartsWith("material.", StringComparison.Ordinal)) + { + var materialProperty = property.Substring("material.".Length); + var material = meshInfo + .SubMeshes.FirstOrDefault() + ?.SharedMaterials?.FirstOrDefault(); + + // according to experiment, if the material is not set, the value becomes 0 + if (material == null) return 0; + + if (materialProperty.Length > 3 && materialProperty[materialProperty.Length - 2] == '.') + { + // xyzw or rgba + var propertyName = materialProperty.Substring(0, materialProperty.Length - 2); + var channel = materialProperty[materialProperty.Length - 1]; + + switch (channel) + { + case 'x': return material.GetVector(propertyName).x; + case 'y': return material.GetVector(propertyName).y; + case 'z': return material.GetVector(propertyName).z; + case 'w': return material.GetVector(propertyName).w; + case 'r': return material.GetColor(propertyName).r; + case 'g': return material.GetColor(propertyName).g; + case 'b': return material.GetColor(propertyName).b; + case 'a': return material.GetColor(propertyName).a; + default: return null; + }; + } + else + { + // float + return material.GetFloat(materialProperty); + } + } + + if (property.StartsWith("blendShapes.", StringComparison.Ordinal)) + { + var blendShapeName = property.Substring("blendShapes.".Length); + return meshInfo.BlendShapes.FirstOrDefault(x => x.name == blendShapeName).weight; + } + + UnityEngine.Debug.LogError($"AAO forgot to implement handling for {property}"); + return null; } private SkinnedMeshRenderer CreateNewRenderer( @@ -649,7 +710,7 @@ private struct CategorizationKey : IEquatable public EqualsHashSet<(bool initial, EqualsHashSet animation)> ActivenessAnimationLocations; - public EqualsHashSet<(string property, AnimationLocation location)> RendererAnimationLocations; + public EqualsHashSet<(float inital, string property, AnimationLocation location)> RendererAnimationLocations; public Activeness Activeness; // renderer properties @@ -672,7 +733,7 @@ public CategorizationKey( MeshInfo2 meshInfo2, Activeness activeness, EqualsHashSet<(bool initial, EqualsHashSet animation)> activenessAnimationLocations, - EqualsHashSet<(string property, AnimationLocation location)> rendererAnimationLocations + EqualsHashSet<(float inital, string property, AnimationLocation location)> rendererAnimationLocations ) { var renderer = (SkinnedMeshRenderer)meshInfo2.SourceRenderer;