implement MorphTargetBindingMerger2 with minimum Dictionary access.

This commit is contained in:
Masataka SUMI
2023-08-16 23:01:12 +09:00
parent 8950900bb9
commit 9574c15966
4 changed files with 142 additions and 96 deletions

View File

@@ -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<ExpressionKey, Dictionary<RuntimeMorphTargetBinding, float>> _weights = new(ExpressionKey.Comparer);
private readonly Dictionary<RuntimeMorphTargetBinding, (bool, float)> _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<ExpressionKey, int> _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<ExpressionKey, VRM10Expression> 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, float>(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<MorphTargetIdentifier>();
_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<RuntimeMorphTargetBinding>();
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;
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10
{
internal readonly struct MorphTargetIdentifier : IEquatable<MorphTargetIdentifier>
{
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<SkinnedMeshRenderer>();
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<MorphTargetIdentifier> Comparer { get; } = new EqualityComparer();
private sealed class EqualityComparer : IEqualityComparer<MorphTargetIdentifier>
{
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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2d943de827f04f72aa4ea46868826987
timeCreated: 1692191013

View File

@@ -4,64 +4,15 @@ using UnityEngine;
namespace UniVRM10
{
internal readonly struct RuntimeMorphTargetBinding : IEquatable<RuntimeMorphTargetBinding>
internal readonly struct RuntimeMorphTargetBinding
{
public static RuntimeMorphTargetBinding? Create(MorphTargetBinding binding, Transform modelRoot)
public MorphTargetIdentifier TargetIdentifier { get; }
public Action<float> WeightApplier { get; }
public RuntimeMorphTargetBinding(MorphTargetIdentifier targetIdentifier, Action<float> weightApplier)
{
if (modelRoot == null) return null;
var targetGameObject = modelRoot.Find(binding.RelativePath);
if (targetGameObject == null) return null;
var targetRenderer = targetGameObject.GetComponent<SkinnedMeshRenderer>();
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<RuntimeMorphTargetBinding> Comparer { get; } = new EqualityComparer();
private sealed class EqualityComparer : IEqualityComparer<RuntimeMorphTargetBinding>
{
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;
}
}
}