diff --git a/Editor/OptimizerProcessor.cs b/Editor/OptimizerProcessor.cs index 01e59a521..f3cc3f695 100644 --- a/Editor/OptimizerProcessor.cs +++ b/Editor/OptimizerProcessor.cs @@ -124,10 +124,10 @@ public static void ProcessObject(OptimizerSession session) finally { _processing = false; - foreach (var component in session.GetComponents()) - UnityEngine.Object.DestroyImmediate(component); - foreach (var activator in session.GetComponents()) - UnityEngine.Object.DestroyImmediate(activator); + //foreach (var component in session.GetComponents()) + // UnityEngine.Object.DestroyImmediate(component); + //foreach (var activator in session.GetComponents()) + // UnityEngine.Object.DestroyImmediate(activator); session.MarkDirtyAll(); } @@ -137,13 +137,13 @@ public static void ProcessObject(OptimizerSession session) private static void DoProcessObject(OptimizerSession session) { new Processors.AutomaticConfigurationProcessor().Process(session); - new Processors.ClearEndpointPositionProcessor().Process(session); - new Processors.MergePhysBoneProcessor().Process(session); - new Processors.EditSkinnedMeshComponentProcessor().Process(session); - new Processors.MergeBoneProcessor().Process(session); - new Processors.MakeChildrenProcessor().Process(session); + //new Processors.ClearEndpointPositionProcessor().Process(session); + //new Processors.MergePhysBoneProcessor().Process(session); + //new Processors.EditSkinnedMeshComponentProcessor().Process(session); + //new Processors.MergeBoneProcessor().Process(session); + //new Processors.MakeChildrenProcessor().Process(session); - new Processors.ApplyObjectMapping().Apply(session); + //new Processors.ApplyObjectMapping().Apply(session); session.MarkDirtyAll(); } diff --git a/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.AutoFreezeBlendShape.cs b/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.AutoFreezeBlendShape.cs index 30bd1ca7a..d3cbbe9d8 100644 --- a/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.AutoFreezeBlendShape.cs +++ b/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.AutoFreezeBlendShape.cs @@ -18,9 +18,24 @@ private void AutoFreezeBlendShape() if (skinnedMeshRenderer.GetComponent()) continue; var modifies = GetModifiedProperties(skinnedMeshRenderer); + var blendShapeValues = Enumerable.Range(0, mesh.blendShapeCount) + .Select(i => skinnedMeshRenderer.GetBlendShapeWeight(i)).ToArray(); var notChanged = Enumerable.Range(0, mesh.blendShapeCount) .Select(i => mesh.GetBlendShapeName(i)) - .Where(name => !modifies.Contains($"blendShape.{name}")) + .Where((name, i) => + { + if (!modifies.TryGetValue($"blendShape.{name}", out var prop)) return true; + + if (!prop.IsConst) return false; + + if (prop.IsAlwaysApplied) + { + blendShapeValues[i] = prop.ConstValue; + return true; + } + + return prop.ConstValue.CompareTo(blendShapeValues[i]) == 0; + }) .ToArray(); if (notChanged.Length == 0) continue; diff --git a/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.CollectUsages.cs b/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.CollectUsages.cs index a3188d19b..32b8d69fa 100644 --- a/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.CollectUsages.cs +++ b/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.CollectUsages.cs @@ -40,8 +40,9 @@ private void GatherAnimationModifications() { var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh; if (!_modifiedProperties.TryGetValue(skinnedMeshRenderer, out var set)) - _modifiedProperties.Add(skinnedMeshRenderer, set = new HashSet()); - set.UnionWith(descriptor.VisemeBlendShapes.Select(x => $"blendShape.{x}")); + _modifiedProperties.Add(skinnedMeshRenderer, set = new Dictionary()); + foreach (var prop in descriptor.VisemeBlendShapes.Select(x => $"blendShape.{x}")) + set[prop] = AnimationProperty.Variable(); break; } case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null: @@ -50,8 +51,8 @@ private void GatherAnimationModifications() var shape = descriptor.MouthOpenBlendShapeName; if (!_modifiedProperties.TryGetValue(skinnedMeshRenderer, out var set)) - _modifiedProperties.Add(skinnedMeshRenderer, set = new HashSet()); - set.Add($"blendShape.{shape}"); + _modifiedProperties.Add(skinnedMeshRenderer, set = new Dictionary()); + set[$"blendShape.{shape}"] = AnimationProperty.Variable(); break; } } @@ -65,13 +66,13 @@ private void GatherAnimationModifications() var mesh = skinnedMeshRenderer.sharedMesh; if (!_modifiedProperties.TryGetValue(skinnedMeshRenderer, out var set)) - _modifiedProperties.Add(skinnedMeshRenderer, set = new HashSet()); + _modifiedProperties.Add(skinnedMeshRenderer, set = new Dictionary()); - set.UnionWith( - from index in descriptor.customEyeLookSettings.eyelidsBlendshapes - where 0 <= index && index < mesh.blendShapeCount - let name = mesh.GetBlendShapeName(index) - select $"blendShape.{name}"); + foreach (var prop in from index in descriptor.customEyeLookSettings.eyelidsBlendshapes + where 0 <= index && index < mesh.blendShapeCount + let name = mesh.GetBlendShapeName(index) + select $"blendShape.{name}") + set[prop] = AnimationProperty.Variable(); } var bodySkinnedMesh = descriptor.transform.Find("Body")?.GetComponent(); @@ -79,9 +80,9 @@ from index in descriptor.customEyeLookSettings.eyelidsBlendshapes if (bodySkinnedMesh) { if (!_modifiedProperties.TryGetValue(bodySkinnedMesh, out var set)) - _modifiedProperties.Add(bodySkinnedMesh, set = new HashSet()); + _modifiedProperties.Add(bodySkinnedMesh, set = new Dictionary()); - set.UnionWith(new[] + var mmdShapes = new[] { // https://booth.pm/ja/items/3341221 // https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/i/0b7b5e4b-c62e-41f7-8ced-1f3e58c4f5bf/d5nbmvp-5779f5ac-d476-426c-8ee6-2111eff8e76c.png @@ -210,26 +211,82 @@ from index in descriptor.customEyeLookSettings.eyelidsBlendshapes "しいたけ", "照れ", "涙", - }); + }; + + foreach (var shape in mmdShapes) + set[$"blendShape.{shape}"] = AnimationProperty.Variable(); } } } + private readonly Dictionary<(GameObject, AnimationClip), ParsedAnimation> _parsedAnimationCache = + new Dictionary<(GameObject, AnimationClip), ParsedAnimation>(); + private void GatherAnimationModificationsInController(GameObject root, RuntimeAnimatorController controller) { if (controller == null) return; + foreach (var clip in controller.animationClips) { + if (!_parsedAnimationCache.TryGetValue((root, clip), out var parsed)) + _parsedAnimationCache.Add((root, clip), parsed = ParsedAnimation.Parse(root, clip)); + + foreach (var keyValuePair in parsed.Components) + { + if (!_modifiedProperties.TryGetValue(keyValuePair.Key, out var properties)) + _modifiedProperties.Add(keyValuePair.Key, properties = new Dictionary()); + foreach (var prop in keyValuePair.Value) + { + + if (properties.TryGetValue(prop.Key, out var property)) + properties[prop.Key] = property.Merge(prop.Value.PartiallyApplied()); + else + properties.Add(prop.Key, prop.Value.PartiallyApplied()); + } + } + } + } + + readonly struct ParsedAnimation + { + public readonly IReadOnlyDictionary> Components; + + public ParsedAnimation(IReadOnlyDictionary> components) + { + Components = components; + } + + public static ParsedAnimation Parse(GameObject root, AnimationClip clip) + { + var components = new Dictionary>(); + foreach (var binding in AnimationUtility.GetCurveBindings(clip)) { if (!typeof(Component).IsAssignableFrom(binding.type)) continue; var obj = (Component)AnimationUtility.GetAnimatedObject(root, binding); if (obj == null) continue; - if (!_modifiedProperties.TryGetValue(obj, out var set)) - _modifiedProperties.Add(obj, set = new HashSet()); - set.Add(binding.propertyName); + var curve = AnimationUtility.GetEditorCurve(clip, binding); + var currentPropertyMayNull = AnimationProperty.ParseProperty(curve); + + if (!(currentPropertyMayNull is AnimationProperty currentProperty)) continue; + + if (currentProperty.IsConst) + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (curve[0].time == 0 && curve[curve.length - 1].time == clip.length) + currentProperty = currentProperty.AlwaysApplied(); + + if (!components.TryGetValue(obj, out var propertiesItf)) + components.Add(obj, propertiesItf = new Dictionary()); + var properties = (Dictionary)propertiesItf; + + if (properties.TryGetValue(binding.propertyName, out var property)) + properties[binding.propertyName] = property.Merge(currentProperty); + else + properties.Add(binding.propertyName, currentProperty); } + + return new ParsedAnimation(components); } } } diff --git a/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.cs b/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.cs index 1f9b339e3..8e77233cc 100644 --- a/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.cs +++ b/Editor/Processors/AutomaticConfiguration/AutomaticConfigurationProcessor.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using UnityEngine; namespace Anatawa12.AvatarOptimizer.Processors @@ -9,8 +10,8 @@ internal partial class AutomaticConfigurationProcessor private AutomaticConfiguration _config; private OptimizerSession _session; - private Dictionary> _modifiedProperties = - new Dictionary>(); + private Dictionary> _modifiedProperties = + new Dictionary>(); public void Process(OptimizerSession session) { @@ -23,11 +24,80 @@ public void Process(OptimizerSession session) AutoFreezeBlendShape(); } - private IReadOnlyCollection GetModifiedProperties(Component component) + private IReadOnlyDictionary GetModifiedProperties(Component component) { - return _modifiedProperties.TryGetValue(component, out var value) - ? (IReadOnlyCollection)value - : Array.Empty(); + return _modifiedProperties.TryGetValue(component, out var value) ? value : EmptyProperties; + } + + private static readonly IReadOnlyDictionary EmptyProperties = + new ReadOnlyDictionary(new Dictionary()); + + readonly struct AnimationProperty + { + public readonly AnimationPropertyFlags Flags; + public bool IsConst => (Flags & AnimationPropertyFlags.Constant) != 0; + public bool IsAlwaysApplied => (Flags & AnimationPropertyFlags.AlwaysApplied) != 0; + public readonly float ConstValue; + + private AnimationProperty(AnimationPropertyFlags flags, float constValue) => + (Flags, ConstValue) = (flags, constValue); + + public static AnimationProperty Const(float value) => + new AnimationProperty(AnimationPropertyFlags.Constant, value); + + public static AnimationProperty Variable() => + new AnimationProperty(AnimationPropertyFlags.Variable, float.NaN); + + public AnimationProperty Merge(AnimationProperty b) + { + var isConstant = IsConst && b.IsConst && ConstValue.CompareTo(b.ConstValue) == 0; + var isAlwaysApplied = IsAlwaysApplied && b.IsAlwaysApplied; + + return new AnimationProperty( + (isConstant ? AnimationPropertyFlags.Constant : AnimationPropertyFlags.Variable) + | (isAlwaysApplied ? AnimationPropertyFlags.AlwaysApplied : AnimationPropertyFlags.Variable), + ConstValue); + } + + public static AnimationProperty? ParseProperty(AnimationCurve curve) + { + if (curve.keys.Length == 0) return null; + if (curve.keys.Length == 1) + return Const(curve.keys[0].value); + + float constValue = 0; + foreach (var (preKey, postKey) in curve.keys.ZipWithNext()) + { + var preWeighted = preKey.weightedMode == WeightedMode.Out || preKey.weightedMode == WeightedMode.Both; + var postWeighted = postKey.weightedMode == WeightedMode.In || postKey.weightedMode == WeightedMode.Both; + + if (preKey.value.CompareTo(postKey.value) != 0) return Variable(); + constValue = preKey.value; + // it's constant + if (float.IsInfinity(preKey.outWeight) || float.IsInfinity(postKey.inTangent)) + continue; + if (preKey.outTangent == 0 && postKey.inTangent == 0) + continue; + if (preWeighted && postWeighted && preKey.outWeight == 0 && postKey.inWeight == 0) + continue; + return Variable(); + } + + return Const(constValue); + } + + public AnimationProperty AlwaysApplied() => + new AnimationProperty(Flags | AnimationPropertyFlags.AlwaysApplied, ConstValue); + public AnimationProperty PartiallyApplied() => + new AnimationProperty(Flags & ~AnimationPropertyFlags.AlwaysApplied, ConstValue); + } + + [Flags] + enum AnimationPropertyFlags + { + Variable = 0, + Constant = 1, + AlwaysApplied = 2, } } }