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