mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2025-01-01 20:25:07 +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 System;
|
||||||
using UnityEngine;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
{
|
{
|
||||||
internal class AnimatedProperty
|
internal class AnimatedProperty
|
||||||
{
|
{
|
||||||
public TargetProp TargetProp { get; }
|
public TargetProp TargetProp { get; }
|
||||||
public string ControlParam { get; set; }
|
|
||||||
|
|
||||||
public object currentState;
|
public object currentState;
|
||||||
|
|
||||||
@ -24,5 +25,30 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
TargetProp = key;
|
TargetProp = key;
|
||||||
this.currentState = currentState;
|
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
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
{
|
{
|
||||||
internal class ControlCondition
|
internal class ControlCondition
|
||||||
{
|
{
|
||||||
public string Parameter;
|
public string Parameter;
|
||||||
public UnityEngine.Object DebugReference;
|
public Object DebugReference;
|
||||||
|
|
||||||
public string DebugName;
|
public string DebugName;
|
||||||
public bool IsConstant;
|
public bool IsConstant;
|
||||||
@ -14,5 +16,31 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
public bool IsConstantActive => InitiallyActive && IsConstant;
|
public bool IsConstantActive => InitiallyActive && IsConstant;
|
||||||
|
|
||||||
public GameObject ReferenceObject;
|
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 System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace nadena.dev.modular_avatar.core.editor
|
namespace nadena.dev.modular_avatar.core.editor
|
||||||
{
|
{
|
||||||
@ -59,5 +61,33 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
|
|
||||||
return true;
|
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.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using nadena.dev.ndmf.preview;
|
using nadena.dev.ndmf.preview;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -39,6 +40,77 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
private void BuildConditions(Component controllingComponent, ReactionRule rule)
|
||||||
{
|
{
|
||||||
rule.ControllingObject = controllingComponent;
|
rule.ControllingObject = controllingComponent;
|
||||||
@ -130,7 +202,33 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
var currentValue = renderer.GetBlendShapeWeight(shapeId);
|
var currentValue = renderer.GetBlendShapeWeight(shapeId);
|
||||||
var value = shape.ChangeType == ShapeChangeType.Delete ? 100 : shape.Value;
|
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
|
key = new TargetProp
|
||||||
{
|
{
|
||||||
@ -139,14 +237,13 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
};
|
};
|
||||||
|
|
||||||
value = shape.ChangeType == ShapeChangeType.Delete ? 1 : 0;
|
value = shape.ChangeType == ShapeChangeType.Delete ? 1 : 0;
|
||||||
RegisterAction(key, renderer, 0, value, changer, shape);
|
RegisterAction(key, 0, value, changer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return shapeKeys;
|
return shapeKeys;
|
||||||
|
|
||||||
void RegisterAction(TargetProp key, SkinnedMeshRenderer renderer, float currentValue, float value,
|
void RegisterAction(TargetProp key, float currentValue, float value, ModularAvatarShapeChanger changer)
|
||||||
ModularAvatarShapeChanger changer, ChangedShape shape)
|
|
||||||
{
|
{
|
||||||
if (!shapeKeys.TryGetValue(key, out var info))
|
if (!shapeKeys.TryGetValue(key, out var info))
|
||||||
{
|
{
|
||||||
|
@ -102,6 +102,8 @@ namespace nadena.dev.modular_avatar.core.editor
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocateBlendshapeSyncs(root);
|
||||||
|
|
||||||
Dictionary<TargetProp, AnimatedProperty> shapes = FindShapes(root);
|
Dictionary<TargetProp, AnimatedProperty> shapes = FindShapes(root);
|
||||||
FindObjectToggles(shapes, root);
|
FindObjectToggles(shapes, root);
|
||||||
FindMaterialSetters(shapes, root);
|
FindMaterialSetters(shapes, root);
|
||||||
|
Loading…
Reference in New Issue
Block a user