diff --git a/Assets/VRM10/Runtime/Components/Expression/DefaultExpressionValidator.cs b/Assets/VRM10/Runtime/Components/Expression/DefaultExpressionValidator.cs index b56e3d318..b671a1608 100644 --- a/Assets/VRM10/Runtime/Components/Expression/DefaultExpressionValidator.cs +++ b/Assets/VRM10/Runtime/Components/Expression/DefaultExpressionValidator.cs @@ -14,8 +14,14 @@ namespace UniVRM10 private DefaultExpressionValidator(VRM10ObjectExpression expressionAvatar) { - _keys = expressionAvatar.Clips.Select(x => expressionAvatar.CreateKey(x.Clip)).ToArray(); - _expressions = expressionAvatar.Clips.ToDictionary(x => expressionAvatar.CreateKey(x.Clip), x => x.Clip); + _keys = expressionAvatar.Clips + .Select(x => expressionAvatar.CreateKey(x.Clip)) + .ToArray(); + _expressions = expressionAvatar.Clips.ToDictionary( + x => expressionAvatar.CreateKey(x.Clip), + x => x.Clip, + ExpressionKey.Comparer + ); } public void Validate(IReadOnlyDictionary inputWeights, IDictionary actualWeights, diff --git a/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs b/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs index f0f36f54d..a31f0e8ff 100644 --- a/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs +++ b/Assets/VRM10/Runtime/Components/Expression/ExpressionKey.cs @@ -156,7 +156,7 @@ namespace UniVRM10 if (Preset != other.Preset) return false; if (Preset != ExpressionPreset.custom) return true; - return Name == other.Name; + return Name.Equals(other.Name, StringComparison.Ordinal); } public override bool Equals(object obj) diff --git a/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs b/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs index 64fb82b0d..c67515913 100644 --- a/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs +++ b/Assets/VRM10/Runtime/Components/Expression/ExpressionMerger.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -using VrmLib; - namespace UniVRM10 { @@ -63,7 +61,7 @@ namespace UniVRM10 return; } - m_morphTargetBindingMerger.AccumulateValue(clip, value); + m_morphTargetBindingMerger.AccumulateValue(key, value); m_materialValueBindingMerger.AccumulateValue(clip, value); } diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs deleted file mode 100644 index f9758414f..000000000 --- a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace UniVRM10 -{ - /// - /// A.Value * A.Weight + B.Value * B.Weight ... - /// - internal sealed class MorphTargetBindingMerger - { - class DictionaryKeyMorphTargetBindingComparer : IEqualityComparer - { - public bool Equals(MorphTargetBinding x, MorphTargetBinding y) - { - return x.RelativePath == y.RelativePath - && x.Index == y.Index; - } - - public int GetHashCode(MorphTargetBinding obj) - { - return obj.RelativePath.GetHashCode() + obj.Index; - } - } - - private static DictionaryKeyMorphTargetBindingComparer comparer = new DictionaryKeyMorphTargetBindingComparer(); - - /// - /// MorphTargetBinding の適用値を蓄積する - /// - /// - /// - /// - Dictionary m_morphTargetValueMap = new Dictionary(comparer); - - /// - /// - /// - /// - Dictionary> m_morphTargetSetterMap = new Dictionary>(comparer); - - public MorphTargetBindingMerger(Dictionary clipMap, Transform root) - { - foreach (var kv in clipMap) - { - foreach (var binding in kv.Value.MorphTargetBindings) - { - if (!m_morphTargetSetterMap.ContainsKey(binding)) - { - var _target = root.Find(binding.RelativePath); - SkinnedMeshRenderer target = null; - if (_target != null) - { - target = _target.GetComponent(); - } - if (target != null) - { - if (binding.Index >= 0 && binding.Index < target.sharedMesh.blendShapeCount) - { - m_morphTargetSetterMap.Add(binding, x => - { - if (target == null) - { - // recompile in editor ? - return; - } - // VRM-1.0 weight is 0-1 - target.SetBlendShapeWeight(binding.Index, x * MorphTargetBinding.VRM_TO_UNITY); - }); - } - else - { - Debug.LogWarningFormat("Invalid morphTarget binding: {0}: {1}", target.name, binding.Index); - } - - } - else - { - Debug.LogWarningFormat("SkinnedMeshRenderer: {0} not found", binding.RelativePath); - } - } - } - } - } - - public void AccumulateValue(VRM10Expression clip, float value) - { - foreach (var binding in clip.MorphTargetBindings) - { - float acc; - if (m_morphTargetValueMap.TryGetValue(binding, out acc)) - { - m_morphTargetValueMap[binding] = acc + binding.Weight * value; - } - else - { - m_morphTargetValueMap[binding] = binding.Weight * value; - } - } - } - - public void Apply() - { - foreach (var kv in m_morphTargetValueMap) - { - Action setter; - if (m_morphTargetSetterMap.TryGetValue(kv.Key, out setter)) - { - setter(kv.Value); - } - } - m_morphTargetValueMap.Clear(); - } - } -} diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs.meta deleted file mode 100644 index 6c9b5f681..000000000 --- a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b955b3371ff699842a355d0e1348a859 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.meta b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.meta new file mode 100644 index 000000000..e8493187f --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6ced9964a99042e383f60c2f065fa807 +timeCreated: 1692091240 \ No newline at end of file diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger.cs b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger.cs new file mode 100644 index 000000000..3c99a1a72 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// Optimized implementation of MorphTarget binding merger. + /// + /// Dictionary を使用すると、その GetEnumerator()(foreach) や get/set を 100,1000 オーダーで呼び出すことになる。 + /// するとモバイル環境ではかなりの定常負荷になってしまうため、その使用を避けて実装する。 + /// + internal sealed class MorphTargetBindingMerger + { + private readonly ExpressionKey[] _keyOrder; + private readonly Dictionary _keyIndexReverseMap = new(ExpressionKey.Comparer); + + /// + /// index access with [_keyOrder][*] + /// + private readonly RuntimeMorphTargetBinding[][] _bindings; + private readonly MorphTargetIdentifier[] _morphTargetOrder; + + /// + /// index access with [_morphTargetOrder] + /// + private readonly float?[] _accumulatedWeights; + + public MorphTargetBindingMerger(Dictionary expressions, Transform modelRoot) + { + _keyOrder = expressions.Keys.ToArray(); + for (var keyIdx = 0; keyIdx < _keyOrder.Length; ++keyIdx) + { + _keyIndexReverseMap.Add(_keyOrder[keyIdx], keyIdx); + } + + 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 (!_keyIndexReverseMap.TryGetValue(key, out var idx)) return; + + foreach (var binding in _bindings[idx]) + { + binding.WeightApplier(value); + } + } + + public void Apply() + { + for (var idx = 0; idx < _morphTargetOrder.Length; ++idx) + { + var morphTarget = _morphTargetOrder[idx]; + var weight = _accumulatedWeights[idx]; + if (!weight.HasValue) continue; + if (morphTarget.TargetRenderer) + { + morphTarget.TargetRenderer.SetBlendShapeWeight(morphTarget.TargetBlendShapeIndex, weight.Value); + } + _accumulatedWeights[idx] = null; + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger.cs.meta new file mode 100644 index 000000000..881314e68 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/MorphTargetBindingMerger.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 166681d1a9384d8ba5ed2ee1f8c127a5 +timeCreated: 1692089928 \ No newline at end of file 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 new file mode 100644 index 000000000..0657cc055 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + internal readonly struct RuntimeMorphTargetBinding + { + public MorphTargetIdentifier TargetIdentifier { get; } + public Action WeightApplier { get; } + + public RuntimeMorphTargetBinding(MorphTargetIdentifier targetIdentifier, Action weightApplier) + { + TargetIdentifier = targetIdentifier; + WeightApplier = weightApplier; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs.meta b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs.meta new file mode 100644 index 000000000..c98efb4ac --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Expression/MorphTargetBindingMerger/RuntimeMorphTargetBinding.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 80f2db6641bf44f192a67c98006db559 +timeCreated: 1692091250 \ No newline at end of file