Skip to content

Commit

Permalink
Merge pull request #1271 from anatawa12/auto-merge-smr-weight-0-affec…
Browse files Browse the repository at this point in the history
…tness

fix: incorrect AutoMergeSkinnedMesh with material property animations (1.7)
  • Loading branch information
anatawa12 authored Oct 16, 2024
2 parents 638c6af + 4b9d037 commit bf81151
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-PRERELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog].
### Removed

### Fixed
- incorrect AutoMergeSkinnedMesh with material property animations `#1271`

### Security

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog].
### Removed

### Fixed
- incorrect AutoMergeSkinnedMesh with material property animations `#1271`

### Security

Expand Down
12 changes: 11 additions & 1 deletion Editor/AnimatorParserV2/AnimationParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace Anatawa12.AvatarOptimizer.AnimatorParsersV2
{
class AnimationParser
{
// AAO 1.7 hotfix. see https://github.com/anatawa12/AvatarOptimizer/issues/1244
public Dictionary<SkinnedMeshRenderer, HashSet<string>> SkinnedMeshAnimations = new Dictionary<SkinnedMeshRenderer, HashSet<string>>();

internal ImmutableNodeContainer ParseMotion([NotNull] GameObject root, [CanBeNull] Motion motion,
[NotNull] IReadOnlyDictionary<AnimationClip, AnimationClip> mapping)
{
Expand Down Expand Up @@ -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();

Expand All @@ -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<string>());
set.Add(propertyName);
}

var node = FloatAnimationCurveNode.Create(clip, binding);
if (node == null) continue;
nodes.Set(componentOrGameObject, propertyName, node);
Expand Down
1 change: 1 addition & 0 deletions Editor/AnimatorParserV2/AnimatorParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class AnimatorParser
{
private readonly bool _mmdWorldCompatibility;
private readonly AnimationParser _animationParser = new AnimationParser();
public Dictionary<SkinnedMeshRenderer, HashSet<string>> SkinnedMeshAnimations => _animationParser.SkinnedMeshAnimations;

public AnimatorParser(bool mmdWorldCompatibility)
{
Expand Down
16 changes: 13 additions & 3 deletions Editor/Processors/ParseAnimator.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -14,11 +16,19 @@ protected override void Execute(BuildContext context)
if (!context.GetState<AAOEnabled>().Enabled) return;

var traceAndOptimize = context.GetState<TraceAndOptimizes.TraceAndOptimizeState>();
var modifications = new AnimatorParser(traceAndOptimize.MmdWorldCompatibility)
.GatherAnimationModifications(context);
var parser = new AnimatorParser(traceAndOptimize.MmdWorldCompatibility);
var modifications = parser.GatherAnimationModifications(context);
context.GetState<MaterialAnimationWeightZeroEffectHotFixState>().SkinnedMeshAnimations =
parser.SkinnedMeshAnimations;
context.Extension<ObjectMappingContext>()
.MappingBuilder
.ImportModifications(modifications);
}
}
}

// AAO 1.7 hotfix. see https://github.com/anatawa12/AvatarOptimizer/issues/1244
class MaterialAnimationWeightZeroEffectHotFixState
{
public Dictionary<SkinnedMeshRenderer, HashSet<string>> SkinnedMeshAnimations = new Dictionary<SkinnedMeshRenderer, HashSet<string>>();
}
}
77 changes: 69 additions & 8 deletions Editor/Processors/TraceAndOptimize/AutoMergeSkinnedMesh.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -377,23 +377,84 @@ private static (Activeness, EqualsHashSet<(bool initial, EqualsHashSet<Animation


[CanBeNull]
private static EqualsHashSet<(string property, AnimationLocation location)>
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<MaterialAnimationWeightZeroEffectHotFixState>().SkinnedMeshAnimations;
var locations = new HashSet<(float inital, string property, AnimationLocation location)>();
var animationComponent = context.GetAnimationComponent(component);

foreach (var (property, node) in animationComponent.GetAllFloatProperties())
{
if (property == Props.EnabledFor(typeof(SkinnedMeshRenderer))) continue; // m_Enabled is proceed separatedly
if (node.ComponentNodes.Any(x => !(x is AnimatorParsersV2.AnimatorPropModNode<float>)))
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<string>(properties);
animatedProperties.Remove("m_Enabled");
var foundProperties = new HashSet<string>(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(
Expand Down Expand Up @@ -649,7 +710,7 @@ private struct CategorizationKey : IEquatable<CategorizationKey>
public EqualsHashSet<(bool initial, EqualsHashSet<AnimationLocation> animation)>
ActivenessAnimationLocations;

public EqualsHashSet<(string property, AnimationLocation location)> RendererAnimationLocations;
public EqualsHashSet<(float inital, string property, AnimationLocation location)> RendererAnimationLocations;
public Activeness Activeness;

// renderer properties
Expand All @@ -672,7 +733,7 @@ public CategorizationKey(
MeshInfo2 meshInfo2,
Activeness activeness,
EqualsHashSet<(bool initial, EqualsHashSet<AnimationLocation> animation)> activenessAnimationLocations,
EqualsHashSet<(string property, AnimationLocation location)> rendererAnimationLocations
EqualsHashSet<(float inital, string property, AnimationLocation location)> rendererAnimationLocations
)
{
var renderer = (SkinnedMeshRenderer)meshInfo2.SourceRenderer;
Expand Down

0 comments on commit bf81151

Please sign in to comment.