mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55:06 +08:00
162 lines
6.0 KiB
C#
162 lines
6.0 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using nadena.dev.modular_avatar.animation;
|
||
|
using UnityEditor;
|
||
|
using UnityEngine;
|
||
|
using VRC.SDK3.Avatars.Components;
|
||
|
using Object = UnityEngine.Object;
|
||
|
|
||
|
namespace nadena.dev.modular_avatar.core.editor
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The class to retarget m_IsActive animation to moved multiple objects
|
||
|
/// </summary>
|
||
|
internal class ActiveAnimationRetargeter
|
||
|
{
|
||
|
private readonly BuildContext _context;
|
||
|
private readonly BoneDatabase _boneDatabase;
|
||
|
private readonly TrackObjectRenamesContext _pathMappings;
|
||
|
private readonly List<IntermediateObj> _intermediateObjs = new List<IntermediateObj>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tracks an object whose Active state is animated, and which leads up to this Merge Animator component.
|
||
|
/// We use this tracking data to create proxy objects within the main armature, which track the same active
|
||
|
/// state.
|
||
|
/// </summary>
|
||
|
struct IntermediateObj
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The path of original object
|
||
|
/// </summary>
|
||
|
public string OriginalPath;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Name of the intermediate object. Used to name proxy objects.
|
||
|
/// </summary>
|
||
|
public string Name;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Whether this object is initially active.
|
||
|
/// </summary>
|
||
|
public bool InitiallyActive;
|
||
|
|
||
|
/// <summary>
|
||
|
/// List of created Intermediate Objects
|
||
|
/// </summary>
|
||
|
public List<GameObject> Created;
|
||
|
}
|
||
|
|
||
|
public ActiveAnimationRetargeter(
|
||
|
BuildContext context,
|
||
|
BoneDatabase boneDatabase,
|
||
|
Transform root
|
||
|
)
|
||
|
{
|
||
|
_context = context;
|
||
|
_boneDatabase = boneDatabase;
|
||
|
_pathMappings = context.PluginBuildContext.Extension<TrackObjectRenamesContext>();
|
||
|
|
||
|
while (root != null && root.GetComponent<VRCAvatarDescriptor>() == null)
|
||
|
{
|
||
|
var originalPath = RuntimeUtil.AvatarRootPath(root.gameObject);
|
||
|
System.Diagnostics.Debug.Assert(originalPath != null);
|
||
|
|
||
|
if (context.AnimationDatabase.ClipsForPath(originalPath).Any(clip =>
|
||
|
GetActiveBinding(clip.CurrentClip as AnimationClip, originalPath) != null
|
||
|
))
|
||
|
{
|
||
|
_intermediateObjs.Add(new IntermediateObj
|
||
|
{
|
||
|
OriginalPath = originalPath,
|
||
|
Name = $"{root.gameObject.name}${Guid.NewGuid()}",
|
||
|
InitiallyActive = root.gameObject.activeSelf,
|
||
|
Created = new List<GameObject>(),
|
||
|
});
|
||
|
}
|
||
|
|
||
|
root = root.parent;
|
||
|
}
|
||
|
|
||
|
// currently _intermediateObjs is in child -> parent order.
|
||
|
// we want parent -> child order so reverse entire list
|
||
|
_intermediateObjs.Reverse();
|
||
|
}
|
||
|
|
||
|
public IEnumerable<GameObject> AddedGameObjects => _intermediateObjs.SelectMany(x => x.Created);
|
||
|
|
||
|
public GameObject CreateIntermediateObjects(GameObject sourceBone)
|
||
|
{
|
||
|
for (var i = 0; i < _intermediateObjs.Count; i++)
|
||
|
{
|
||
|
var intermediate = _intermediateObjs[i];
|
||
|
var preexisting = sourceBone.transform.Find(intermediate.Name);
|
||
|
if (preexisting != null)
|
||
|
{
|
||
|
sourceBone = preexisting.gameObject;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var switchObj = new GameObject(intermediate.Name);
|
||
|
switchObj.transform.SetParent(sourceBone.transform, false);
|
||
|
switchObj.transform.localPosition = Vector3.zero;
|
||
|
switchObj.transform.localRotation = Quaternion.identity;
|
||
|
switchObj.transform.localScale = Vector3.one;
|
||
|
switchObj.SetActive(intermediate.InitiallyActive);
|
||
|
|
||
|
if (i == 0)
|
||
|
{
|
||
|
// This new leaf can break parent bone physbones. Add a PB Blocker
|
||
|
// to prevent this becoming an issue.
|
||
|
switchObj.GetOrAddComponent<ModularAvatarPBBlocker>();
|
||
|
}
|
||
|
|
||
|
intermediate.Created.Add(switchObj);
|
||
|
|
||
|
sourceBone = switchObj;
|
||
|
|
||
|
// Ensure mesh retargeting looks through this
|
||
|
_boneDatabase.AddMergedBone(sourceBone.transform);
|
||
|
_boneDatabase.RetainMergedBone(sourceBone.transform);
|
||
|
_pathMappings.MarkTransformLookthrough(sourceBone);
|
||
|
}
|
||
|
|
||
|
return sourceBone;
|
||
|
}
|
||
|
|
||
|
public void FixupAnimations()
|
||
|
{
|
||
|
foreach (var intermediate in _intermediateObjs)
|
||
|
{
|
||
|
var path = intermediate.OriginalPath;
|
||
|
|
||
|
foreach (var holder in _context.AnimationDatabase.ClipsForPath(path))
|
||
|
{
|
||
|
if (!Util.IsTemporaryAsset(holder.CurrentClip))
|
||
|
{
|
||
|
holder.CurrentClip = Object.Instantiate(holder.CurrentClip);
|
||
|
}
|
||
|
|
||
|
var clip = holder.CurrentClip as AnimationClip;
|
||
|
if (clip == null) continue;
|
||
|
|
||
|
var curve = GetActiveBinding(clip, path);
|
||
|
if (curve != null)
|
||
|
{
|
||
|
foreach (var mapping in intermediate.Created)
|
||
|
{
|
||
|
clip.SetCurve(_pathMappings.GetObjectIdentifier(mapping), typeof(GameObject), "m_IsActive",
|
||
|
curve);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private AnimationCurve GetActiveBinding(AnimationClip clip, string path)
|
||
|
{
|
||
|
return AnimationUtility.GetEditorCurve(clip,
|
||
|
EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive"));
|
||
|
}
|
||
|
}
|
||
|
}
|