mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 02:35:06 +08:00
fix: propagate shape changer effects through BlendshapeSync (#1267)
Closes: #1259
This commit is contained in:
parent
656a401684
commit
828e6b4548
@ -1,12 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class AnimatedProperty
|
||||
{
|
||||
public TargetProp TargetProp { get; }
|
||||
public string ControlParam { get; set; }
|
||||
|
||||
public object currentState;
|
||||
|
||||
@ -24,5 +25,30 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
TargetProp = key;
|
||||
this.currentState = currentState;
|
||||
}
|
||||
|
||||
protected bool Equals(AnimatedProperty other)
|
||||
{
|
||||
return Equals(currentState, other.currentState) && actionGroups.SequenceEqual(other.actionGroups) &&
|
||||
TargetProp.Equals(other.TargetProp);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((AnimatedProperty)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var actionGroupHash = 0;
|
||||
foreach (var ag in actionGroups)
|
||||
{
|
||||
actionGroupHash = HashCode.Combine(actionGroupHash, ag);
|
||||
}
|
||||
|
||||
return HashCode.Combine(currentState, actionGroupHash, TargetProp);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
internal class ControlCondition
|
||||
{
|
||||
public string Parameter;
|
||||
public UnityEngine.Object DebugReference;
|
||||
public Object DebugReference;
|
||||
|
||||
public string DebugName;
|
||||
public bool IsConstant;
|
||||
@ -14,5 +16,31 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
public bool IsConstantActive => InitiallyActive && IsConstant;
|
||||
|
||||
public GameObject ReferenceObject;
|
||||
|
||||
protected bool Equals(ControlCondition other)
|
||||
{
|
||||
return Parameter == other.Parameter
|
||||
&& Equals(DebugReference, other.DebugReference)
|
||||
&& DebugName == other.DebugName
|
||||
&& IsConstant == other.IsConstant
|
||||
&& ParameterValueLo.Equals(other.ParameterValueLo)
|
||||
&& ParameterValueHi.Equals(other.ParameterValueHi)
|
||||
&& InitialValue.Equals(other.InitialValue)
|
||||
&& Equals(ReferenceObject, other.ReferenceObject);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((ControlCondition)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Parameter, DebugReference, DebugName, IsConstant, ParameterValueLo,
|
||||
ParameterValueHi, InitialValue, ReferenceObject);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace nadena.dev.modular_avatar.core.editor
|
||||
{
|
||||
@ -59,5 +61,33 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool Equals(ReactionRule other)
|
||||
{
|
||||
return TargetProp.Equals(other.TargetProp)
|
||||
&& Equals(Value, other.Value)
|
||||
&& Equals(ControllingObject, other.ControllingObject)
|
||||
&& ControllingConditions.SequenceEqual(other.ControllingConditions)
|
||||
&& Inverted == other.Inverted;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is null) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != GetType()) return false;
|
||||
return Equals((ReactionRule)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var ccHash = 0;
|
||||
foreach (var cc in ControllingConditions)
|
||||
{
|
||||
ccHash = HashCode.Combine(ccHash, cc);
|
||||
}
|
||||
|
||||
return HashCode.Combine(TargetProp, Value, ControllingObject, ccHash, Inverted);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using nadena.dev.ndmf.preview;
|
||||
using UnityEngine;
|
||||
@ -38,6 +39,77 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
return param;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<(SkinnedMeshRenderer, string), HashSet<(SkinnedMeshRenderer, string)>>
|
||||
_blendshapeSyncMappings = new();
|
||||
|
||||
private void LocateBlendshapeSyncs(GameObject root)
|
||||
{
|
||||
var components = _computeContext.GetComponentsInChildren<ModularAvatarBlendshapeSync>(root, true);
|
||||
|
||||
foreach (var bss in components)
|
||||
{
|
||||
var localMesh = _computeContext.GetComponent<SkinnedMeshRenderer>(bss.gameObject);
|
||||
if (localMesh == null) continue;
|
||||
|
||||
foreach (var entry in _computeContext.Observe(bss, bss_ => bss_.Bindings.ToImmutableList(),
|
||||
Enumerable.SequenceEqual))
|
||||
{
|
||||
var src = entry.ReferenceMesh.Get(bss);
|
||||
if (src == null) continue;
|
||||
|
||||
var srcMesh = _computeContext.GetComponent<SkinnedMeshRenderer>(src);
|
||||
|
||||
var localBlendshape = entry.LocalBlendshape;
|
||||
if (string.IsNullOrWhiteSpace(localBlendshape))
|
||||
{
|
||||
localBlendshape = entry.Blendshape;
|
||||
}
|
||||
|
||||
var srcBinding = (srcMesh, entry.Blendshape);
|
||||
var dstBinding = (localMesh, localBlendshape);
|
||||
|
||||
if (!_blendshapeSyncMappings.TryGetValue(srcBinding, out var dstSet))
|
||||
{
|
||||
dstSet = new HashSet<(SkinnedMeshRenderer, string)>();
|
||||
_blendshapeSyncMappings[srcBinding] = dstSet;
|
||||
}
|
||||
|
||||
dstSet.Add(dstBinding);
|
||||
}
|
||||
}
|
||||
|
||||
// For recursive blendshape syncs, we need to precompute the full set of affected blendshapes.
|
||||
foreach (var (src, dsts) in _blendshapeSyncMappings)
|
||||
{
|
||||
var visited = new HashSet<(SkinnedMeshRenderer, string)>();
|
||||
foreach (var item in Visit(src, visited).ToList())
|
||||
{
|
||||
dsts.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<(SkinnedMeshRenderer, string)> Visit(
|
||||
(SkinnedMeshRenderer, string) key,
|
||||
HashSet<(SkinnedMeshRenderer, string)> visited
|
||||
)
|
||||
{
|
||||
if (!visited.Add(key)) yield break;
|
||||
|
||||
if (_blendshapeSyncMappings.TryGetValue(key, out var children))
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
foreach (var item in Visit(child, visited))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return key;
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildConditions(Component controllingComponent, ReactionRule rule)
|
||||
{
|
||||
@ -130,8 +202,34 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
var currentValue = renderer.GetBlendShapeWeight(shapeId);
|
||||
var value = shape.ChangeType == ShapeChangeType.Delete ? 100 : shape.Value;
|
||||
|
||||
RegisterAction(key, renderer, currentValue, value, changer, shape);
|
||||
RegisterAction(key, currentValue, value, changer);
|
||||
|
||||
if (_blendshapeSyncMappings.TryGetValue((renderer, shape.ShapeName), out var bindings))
|
||||
{
|
||||
// Propagate the new value through any Blendshape Syncs we might have.
|
||||
// Note that we don't propagate deletes; it's common to e.g. want to delete breasts from the
|
||||
// base model while retaining outerwear that matches the breast size.
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
var bindingKey = new TargetProp
|
||||
{
|
||||
TargetObject = binding.Item1,
|
||||
PropertyName = BlendshapePrefix + binding.Item2
|
||||
};
|
||||
var bindingRenderer = binding.Item1;
|
||||
|
||||
var bindingMesh = bindingRenderer.sharedMesh;
|
||||
if (bindingMesh == null) continue;
|
||||
|
||||
var bindingShapeIndex = bindingMesh.GetBlendShapeIndex(binding.Item2);
|
||||
if (bindingShapeIndex < 0) continue;
|
||||
|
||||
var bindingInitialState = bindingRenderer.GetBlendShapeWeight(bindingShapeIndex);
|
||||
|
||||
RegisterAction(bindingKey, bindingInitialState, value, changer);
|
||||
}
|
||||
}
|
||||
|
||||
key = new TargetProp
|
||||
{
|
||||
TargetObject = renderer,
|
||||
@ -139,14 +237,13 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
};
|
||||
|
||||
value = shape.ChangeType == ShapeChangeType.Delete ? 1 : 0;
|
||||
RegisterAction(key, renderer, 0, value, changer, shape);
|
||||
RegisterAction(key, 0, value, changer);
|
||||
}
|
||||
}
|
||||
|
||||
return shapeKeys;
|
||||
|
||||
void RegisterAction(TargetProp key, SkinnedMeshRenderer renderer, float currentValue, float value,
|
||||
ModularAvatarShapeChanger changer, ChangedShape shape)
|
||||
void RegisterAction(TargetProp key, float currentValue, float value, ModularAvatarShapeChanger changer)
|
||||
{
|
||||
if (!shapeKeys.TryGetValue(key, out var info))
|
||||
{
|
||||
|
@ -101,6 +101,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
||||
result.InitialStates = new();
|
||||
return result;
|
||||
}
|
||||
|
||||
LocateBlendshapeSyncs(root);
|
||||
|
||||
Dictionary<TargetProp, AnimatedProperty> shapes = FindShapes(root);
|
||||
FindObjectToggles(shapes, root);
|
||||
|
Loading…
Reference in New Issue
Block a user