diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger2.cs b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger2.cs index 9c840b211..95e305f3a 100644 --- a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger2.cs +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger2.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using UnityEngine; @@ -6,63 +7,87 @@ namespace UniVRM10 { internal sealed class MorphTargetBindingMerger2 { - private readonly Dictionary> _weights = new(ExpressionKey.Comparer); - private readonly Dictionary _accumulatedWeights = new(RuntimeMorphTargetBinding.Comparer); - private readonly RuntimeMorphTargetBinding[] _targetIds; + private readonly ExpressionKey[] _keyOrder; + // NOTE: Dictionary Access is very slow. So we use this only. + private readonly Dictionary _keyIndexReverseMap = new(ExpressionKey.Comparer); + // NOTE: idx dimension0: _keyOrder, idx dimension1: no specific order + private readonly RuntimeMorphTargetBinding[][] _bindings; + + private readonly MorphTargetIdentifier[] _morphTargetOrder; + // NOTE: idx: _morphTargetOrder + private readonly float?[] _accumulatedWeights; public MorphTargetBindingMerger2(Dictionary expressions, Transform modelRoot) { - foreach (var (expressionKey, expression) in expressions) + _keyOrder = expressions.Keys.ToArray(); + for (var keyIdx = 0; keyIdx < _keyOrder.Length; ++keyIdx) { - _weights.Add(expressionKey, new Dictionary(RuntimeMorphTargetBinding.Comparer)); - foreach (var binding in expression.MorphTargetBindings) - { - var id = RuntimeMorphTargetBinding.Create(binding, modelRoot); - if (id == null) - { - Debug.LogWarning($"Invalid MorphTargetBinding found: {binding}"); - continue; - } - if (_weights[expressionKey].ContainsKey(id.Value)) - { - Debug.LogWarning($"Duplicate MorphTargetBinding found: {binding}"); - continue; - } - _weights[expressionKey].Add(id.Value, binding.Weight * MorphTargetBinding.VRM_TO_UNITY); - _accumulatedWeights.TryAdd(id.Value, (false, 0f)); - } + _keyIndexReverseMap.Add(_keyOrder[keyIdx], keyIdx); } - _targetIds = _accumulatedWeights.Keys.ToArray(); + + var morphTargetList = new List(); + _bindings = new RuntimeMorphTargetBinding[_keyOrder.Length][]; + for (var keyIdx = 0; keyIdx < _keyOrder.Length; ++keyIdx) + { + var key = _keyOrder[keyIdx]; + var expression = expressions[key]; + + var bindingsPerKey = new List(); + foreach (var rawBinding in expression.MorphTargetBindings) + { + var morphTarget = MorphTargetIdentifier.Create(rawBinding, modelRoot); + if (!morphTarget.HasValue) + { + Debug.LogWarning($"Invalid {nameof(MorphTargetBinding)} found: {rawBinding}"); + continue; + } + if (bindingsPerKey.FindIndex(x => morphTarget.Value.Equals(x.TargetIdentifier)) >= 0) + { + Debug.LogWarning($"Duplicate MorphTargetBinding found: {rawBinding}"); + continue; + } + + var morphTargetIdx = morphTargetList.FindIndex(x => morphTarget.Value.Equals(x)); + if (morphTargetIdx < 0) + { + morphTargetIdx = morphTargetList.Count; + morphTargetList.Add(morphTarget.Value); + } + + var bindingWeight = rawBinding.Weight * MorphTargetBinding.VRM_TO_UNITY; + bindingsPerKey.Add(new RuntimeMorphTargetBinding(morphTarget.Value, inputWeight => + { + _accumulatedWeights[morphTargetIdx] = (_accumulatedWeights[morphTargetIdx] ?? 0f) + bindingWeight * inputWeight; + })); + } + _bindings[keyIdx] = bindingsPerKey.ToArray(); + } + _morphTargetOrder = morphTargetList.ToArray(); + _accumulatedWeights = new float?[_morphTargetOrder.Length]; } public void AccumulateValue(ExpressionKey key, float value) { - if (_weights.TryGetValue(key, out var weightsPerBinding)) + if (!_keyIndexReverseMap.TryGetValue(key, out var idx)) return; + + foreach (var binding in _bindings[idx]) { - foreach (var (id, weight) in weightsPerBinding) - { - var (_, currentWeight) = _accumulatedWeights[id]; - _accumulatedWeights[id] = (true, currentWeight + weight * value); - } + binding.WeightApplier(value); } } public void Apply() { - foreach (var (id, (isAccumulated, totalWeight)) in _accumulatedWeights) + for (var idx = 0; idx < _morphTargetOrder.Length; ++idx) { - if (isAccumulated) + var morphTarget = _morphTargetOrder[idx]; + var weight = _accumulatedWeights[idx]; + if (!weight.HasValue) continue; + if (morphTarget.TargetRenderer) { - if (id.TargetRenderer == null) continue; - - id.TargetRenderer.SetBlendShapeWeight(id.TargetBlendShapeIndex, totalWeight); + morphTarget.TargetRenderer.SetBlendShapeWeight(morphTarget.TargetBlendShapeIndex, weight.Value); } - } - - // NOTE: Reset - foreach (var id in _targetIds) - { - _accumulatedWeights[id] = (false, 0f); + _accumulatedWeights[idx] = null; } } } diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetIdentifier.cs b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetIdentifier.cs new file mode 100644 index 000000000..c230a708b --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetIdentifier.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + internal readonly struct MorphTargetIdentifier : IEquatable + { + public static MorphTargetIdentifier? Create(MorphTargetBinding binding, Transform modelRoot) + { + if (modelRoot == null) return null; + + var targetGameObject = modelRoot.Find(binding.RelativePath); + if (targetGameObject == null) return null; + + var targetRenderer = targetGameObject.GetComponent(); + if (targetRenderer == null) return null; + if (targetRenderer.sharedMesh == null) return null; + if (targetRenderer.sharedMesh.blendShapeCount <= binding.Index) return null; + + return new MorphTargetIdentifier(targetRenderer, binding.Index); + } + + public SkinnedMeshRenderer TargetRenderer { get; } + public int TargetRendererInstanceId { get; } + public int TargetBlendShapeIndex { get; } + + public MorphTargetIdentifier(SkinnedMeshRenderer targetRenderer, int targetBlendShapeIndex) + { + TargetRenderer = targetRenderer; + TargetRendererInstanceId = targetRenderer.GetInstanceID(); + TargetBlendShapeIndex = targetBlendShapeIndex; + } + + public bool Equals(MorphTargetIdentifier other) + { + return TargetRendererInstanceId == other.TargetRendererInstanceId && TargetBlendShapeIndex == other.TargetBlendShapeIndex; + } + + public override bool Equals(object obj) + { + return obj is MorphTargetIdentifier other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(TargetRendererInstanceId, TargetBlendShapeIndex); + } + + #region IEqualityComparer + public static IEqualityComparer Comparer { get; } = new EqualityComparer(); + + private sealed class EqualityComparer : IEqualityComparer + { + public bool Equals(MorphTargetIdentifier x, MorphTargetIdentifier y) + { + return x.TargetRendererInstanceId == y.TargetRendererInstanceId && x.TargetBlendShapeIndex == y.TargetBlendShapeIndex; + } + + public int GetHashCode(MorphTargetIdentifier obj) + { + return HashCode.Combine(obj.TargetRendererInstanceId, obj.TargetBlendShapeIndex); + } + } + #endregion + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetIdentifier.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetIdentifier.cs.meta new file mode 100644 index 000000000..7a6652743 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetIdentifier.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2d943de827f04f72aa4ea46868826987 +timeCreated: 1692191013 \ No newline at end of file diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs index 42103e4ed..0657cc055 100644 --- a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs @@ -4,64 +4,15 @@ using UnityEngine; namespace UniVRM10 { - internal readonly struct RuntimeMorphTargetBinding : IEquatable + internal readonly struct RuntimeMorphTargetBinding { - public static RuntimeMorphTargetBinding? Create(MorphTargetBinding binding, Transform modelRoot) + public MorphTargetIdentifier TargetIdentifier { get; } + public Action WeightApplier { get; } + + public RuntimeMorphTargetBinding(MorphTargetIdentifier targetIdentifier, Action weightApplier) { - if (modelRoot == null) return null; - - var targetGameObject = modelRoot.Find(binding.RelativePath); - if (targetGameObject == null) return null; - - var targetRenderer = targetGameObject.GetComponent(); - if (targetRenderer == null) return null; - if (targetRenderer.sharedMesh == null) return null; - if (targetRenderer.sharedMesh.blendShapeCount <= binding.Index) return null; - - return new RuntimeMorphTargetBinding(targetRenderer, binding.Index, binding.Weight * MorphTargetBinding.VRM_TO_UNITY); - } - - public SkinnedMeshRenderer TargetRenderer { get; } - public int TargetRendererInstanceId { get; } // NOTE: Compare key - public int TargetBlendShapeIndex { get; } // NOTE: Compare key - public float Weight { get; } - - private RuntimeMorphTargetBinding(SkinnedMeshRenderer targetRenderer, int targetBlendShapeIndex, float weight) - { - TargetRenderer = targetRenderer; - TargetRendererInstanceId = TargetRenderer.GetInstanceID(); - TargetBlendShapeIndex = targetBlendShapeIndex; - Weight = weight; - } - - public bool Equals(RuntimeMorphTargetBinding other) - { - return TargetRendererInstanceId == other.TargetRendererInstanceId && TargetBlendShapeIndex == other.TargetBlendShapeIndex; - } - - public override bool Equals(object obj) - { - return obj is RuntimeMorphTargetBinding other && Equals(other); - } - - public override int GetHashCode() - { - return HashCode.Combine(TargetRendererInstanceId, TargetBlendShapeIndex); - } - - public static IEqualityComparer Comparer { get; } = new EqualityComparer(); - - private sealed class EqualityComparer : IEqualityComparer - { - public bool Equals(RuntimeMorphTargetBinding x, RuntimeMorphTargetBinding y) - { - return x.TargetRendererInstanceId == y.TargetRendererInstanceId && x.TargetBlendShapeIndex == y.TargetBlendShapeIndex; - } - - public int GetHashCode(RuntimeMorphTargetBinding obj) - { - return HashCode.Combine(obj.TargetRendererInstanceId, obj.TargetBlendShapeIndex); - } + TargetIdentifier = targetIdentifier; + WeightApplier = weightApplier; } } } \ No newline at end of file