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