Skip to content

Commit

Permalink
chore: improve constant BlendShape detection
Browse files Browse the repository at this point in the history
  • Loading branch information
anatawa12 committed Jul 17, 2023
1 parent f4d75f3 commit 67abbb6
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 33 deletions.
20 changes: 10 additions & 10 deletions Editor/OptimizerProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ public static void ProcessObject(OptimizerSession session)
finally
{
_processing = false;
foreach (var component in session.GetComponents<AvatarTagComponent>())
UnityEngine.Object.DestroyImmediate(component);
foreach (var activator in session.GetComponents<AvatarActivator>())
UnityEngine.Object.DestroyImmediate(activator);
//foreach (var component in session.GetComponents<AvatarTagComponent>())
// UnityEngine.Object.DestroyImmediate(component);
//foreach (var activator in session.GetComponents<AvatarActivator>())
// UnityEngine.Object.DestroyImmediate(activator);

session.MarkDirtyAll();
}
Expand All @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,24 @@ private void AutoFreezeBlendShape()
if (skinnedMeshRenderer.GetComponent<FreezeBlendShape>()) 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ private void GatherAnimationModifications()
{
var skinnedMeshRenderer = descriptor.VisemeSkinnedMesh;
if (!_modifiedProperties.TryGetValue(skinnedMeshRenderer, out var set))
_modifiedProperties.Add(skinnedMeshRenderer, set = new HashSet<string>());
set.UnionWith(descriptor.VisemeBlendShapes.Select(x => $"blendShape.{x}"));
_modifiedProperties.Add(skinnedMeshRenderer, set = new Dictionary<string, AnimationProperty>());
foreach (var prop in descriptor.VisemeBlendShapes.Select(x => $"blendShape.{x}"))
set[prop] = AnimationProperty.Variable();
break;
}
case VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape when descriptor.VisemeSkinnedMesh != null:
Expand All @@ -50,8 +51,8 @@ private void GatherAnimationModifications()
var shape = descriptor.MouthOpenBlendShapeName;

if (!_modifiedProperties.TryGetValue(skinnedMeshRenderer, out var set))
_modifiedProperties.Add(skinnedMeshRenderer, set = new HashSet<string>());
set.Add($"blendShape.{shape}");
_modifiedProperties.Add(skinnedMeshRenderer, set = new Dictionary<string, AnimationProperty>());
set[$"blendShape.{shape}"] = AnimationProperty.Variable();
break;
}
}
Expand All @@ -65,23 +66,23 @@ private void GatherAnimationModifications()
var mesh = skinnedMeshRenderer.sharedMesh;

if (!_modifiedProperties.TryGetValue(skinnedMeshRenderer, out var set))
_modifiedProperties.Add(skinnedMeshRenderer, set = new HashSet<string>());
_modifiedProperties.Add(skinnedMeshRenderer, set = new Dictionary<string, AnimationProperty>());

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<SkinnedMeshRenderer>();

if (bodySkinnedMesh)
{
if (!_modifiedProperties.TryGetValue(bodySkinnedMesh, out var set))
_modifiedProperties.Add(bodySkinnedMesh, set = new HashSet<string>());
_modifiedProperties.Add(bodySkinnedMesh, set = new Dictionary<string, AnimationProperty>());

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
Expand Down Expand Up @@ -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<string, AnimationProperty>());
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<Component, IReadOnlyDictionary<string, AnimationProperty>> Components;

public ParsedAnimation(IReadOnlyDictionary<Component, IReadOnlyDictionary<string, AnimationProperty>> components)
{
Components = components;
}

public static ParsedAnimation Parse(GameObject root, AnimationClip clip)
{
var components = new Dictionary<Component, IReadOnlyDictionary<string, AnimationProperty>>();

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<string>());
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<string, AnimationProperty>());
var properties = (Dictionary<string, AnimationProperty>)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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;

namespace Anatawa12.AvatarOptimizer.Processors
Expand All @@ -9,8 +10,8 @@ internal partial class AutomaticConfigurationProcessor
private AutomaticConfiguration _config;
private OptimizerSession _session;

private Dictionary<Component, HashSet<string>> _modifiedProperties =
new Dictionary<Component, HashSet<string>>();
private Dictionary<Component, Dictionary<string, AnimationProperty>> _modifiedProperties =
new Dictionary<Component, Dictionary<string, AnimationProperty>>();

public void Process(OptimizerSession session)
{
Expand All @@ -23,11 +24,80 @@ public void Process(OptimizerSession session)
AutoFreezeBlendShape();
}

private IReadOnlyCollection<string> GetModifiedProperties(Component component)
private IReadOnlyDictionary<string, AnimationProperty> GetModifiedProperties(Component component)
{
return _modifiedProperties.TryGetValue(component, out var value)
? (IReadOnlyCollection<string>)value
: Array.Empty<string>();
return _modifiedProperties.TryGetValue(component, out var value) ? value : EmptyProperties;
}

private static readonly IReadOnlyDictionary<string, AnimationProperty> EmptyProperties =
new ReadOnlyDictionary<string, AnimationProperty>(new Dictionary<string, AnimationProperty>());

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,
}
}
}

0 comments on commit 67abbb6

Please sign in to comment.