2022-09-09 11:40:52 +08:00
|
|
|
|
/*
|
|
|
|
|
* MIT License
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2022 bd_
|
|
|
|
|
*
|
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
* in the Software without restriction, including without limitation the rights
|
|
|
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
* furnished to do so, subject to the following conditions:
|
|
|
|
|
*
|
|
|
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
|
|
|
* copies or substantial portions of the Software.
|
|
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
|
|
|
* SOFTWARE.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-10-03 07:43:01 +08:00
|
|
|
|
using System;
|
2022-08-28 07:54:59 +08:00
|
|
|
|
using System.Collections.Generic;
|
2023-01-05 20:10:22 +08:00
|
|
|
|
using System.Linq;
|
2023-01-19 20:32:44 +08:00
|
|
|
|
using nadena.dev.modular_avatar.editor.ErrorReporting;
|
2022-08-28 07:54:59 +08:00
|
|
|
|
using UnityEditor;
|
2022-08-28 04:38:52 +08:00
|
|
|
|
using UnityEngine;
|
2022-08-28 07:54:59 +08:00
|
|
|
|
using UnityEngine.Animations;
|
|
|
|
|
using VRC.Dynamics;
|
2023-01-05 20:10:22 +08:00
|
|
|
|
using VRC.SDK3.Avatars.Components;
|
2022-08-28 07:54:59 +08:00
|
|
|
|
using VRC.SDK3.Dynamics.PhysBone.Components;
|
2022-10-03 09:58:22 +08:00
|
|
|
|
using Object = UnityEngine.Object;
|
2022-08-28 04:38:52 +08:00
|
|
|
|
|
2022-11-11 12:39:58 +08:00
|
|
|
|
namespace nadena.dev.modular_avatar.core.editor
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2022-11-28 05:51:39 +08:00
|
|
|
|
internal class MergeArmatureHook
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
private BuildContext context;
|
2023-01-16 22:50:24 +08:00
|
|
|
|
private HashSet<Transform> mergedObjects = new HashSet<Transform>();
|
|
|
|
|
private HashSet<Transform> thisPassAdded = new HashSet<Transform>();
|
2022-08-28 07:54:59 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
internal void OnPreprocessAvatar(BuildContext context, GameObject avatarGameObject)
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
this.context = context;
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2023-07-29 17:04:59 +08:00
|
|
|
|
var mergeArmatures =
|
|
|
|
|
avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2023-07-29 17:04:59 +08:00
|
|
|
|
TopoProcessMergeArmatures(mergeArmatures);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
|
2022-11-04 10:19:43 +08:00
|
|
|
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBone>(true))
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
|
|
|
|
if (c.rootTransform == null) c.rootTransform = c.transform;
|
2023-01-05 20:10:22 +08:00
|
|
|
|
RetainBoneReferences(c);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-11-04 10:19:43 +08:00
|
|
|
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<VRCPhysBoneCollider>(true))
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
|
|
|
|
if (c.rootTransform == null) c.rootTransform = c.transform;
|
2023-01-05 20:10:22 +08:00
|
|
|
|
RetainBoneReferences(c);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-04 10:19:43 +08:00
|
|
|
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<ContactBase>(true))
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
|
|
|
|
if (c.rootTransform == null) c.rootTransform = c.transform;
|
2023-01-05 20:10:22 +08:00
|
|
|
|
RetainBoneReferences(c);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-11-04 10:19:43 +08:00
|
|
|
|
foreach (var c in avatarGameObject.transform.GetComponentsInChildren<IConstraint>(true))
|
2022-10-03 09:58:22 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
RetainBoneReferences(c as Component);
|
2022-10-03 09:58:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-05 20:30:01 +08:00
|
|
|
|
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject, context);
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-29 17:04:59 +08:00
|
|
|
|
private void TopoProcessMergeArmatures(ModularAvatarMergeArmature[] mergeArmatures)
|
|
|
|
|
{
|
|
|
|
|
Dictionary<ModularAvatarMergeArmature, List<ModularAvatarMergeArmature>> runsBefore
|
|
|
|
|
= new Dictionary<ModularAvatarMergeArmature, List<ModularAvatarMergeArmature>>();
|
|
|
|
|
|
|
|
|
|
foreach (var config in mergeArmatures)
|
|
|
|
|
{
|
|
|
|
|
// TODO - assert that we're not nesting merge armatures?
|
|
|
|
|
|
|
|
|
|
var target = config.mergeTargetObject;
|
|
|
|
|
if (target == null)
|
|
|
|
|
{
|
|
|
|
|
// TODO - report error
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var parentConfig = target.GetComponentInParent<ModularAvatarMergeArmature>();
|
|
|
|
|
if (parentConfig != null)
|
|
|
|
|
{
|
|
|
|
|
if (!runsBefore.ContainsKey(parentConfig))
|
|
|
|
|
{
|
|
|
|
|
runsBefore[parentConfig] = new List<ModularAvatarMergeArmature>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
runsBefore[parentConfig].Add(config);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HashSet<ModularAvatarMergeArmature> visited = new HashSet<ModularAvatarMergeArmature>();
|
|
|
|
|
Stack<ModularAvatarMergeArmature> visitStack = new Stack<ModularAvatarMergeArmature>();
|
|
|
|
|
foreach (var next in mergeArmatures)
|
|
|
|
|
{
|
|
|
|
|
TopoLoop(next);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void TopoLoop(ModularAvatarMergeArmature config)
|
|
|
|
|
{
|
|
|
|
|
if (visited.Contains(config)) return;
|
|
|
|
|
if (visitStack.Contains(config))
|
|
|
|
|
{
|
|
|
|
|
BuildReport.LogFatal("merge_armature.circular_dependency", new string[0], config);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visitStack.Push(config);
|
|
|
|
|
var target = config.mergeTargetObject;
|
|
|
|
|
|
|
|
|
|
if (target != null)
|
|
|
|
|
{
|
|
|
|
|
if (runsBefore.TryGetValue(config, out var predecessors))
|
|
|
|
|
{
|
|
|
|
|
foreach (var priorConfig in predecessors)
|
|
|
|
|
{
|
|
|
|
|
TopoLoop(priorConfig);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MergeArmatureWithReporting(config);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visitStack.Pop();
|
|
|
|
|
visited.Add(config);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void MergeArmatureWithReporting(ModularAvatarMergeArmature config)
|
|
|
|
|
{
|
|
|
|
|
var target = config.mergeTargetObject;
|
|
|
|
|
|
|
|
|
|
while (BoneDatabase.IsRetargetable(target.transform))
|
|
|
|
|
{
|
|
|
|
|
target = target.transform.parent.gameObject;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BuildReport.ReportingObject(config, () =>
|
|
|
|
|
{
|
|
|
|
|
mergedObjects.Clear();
|
|
|
|
|
thisPassAdded.Clear();
|
|
|
|
|
MergeArmature(config, target);
|
|
|
|
|
PruneDuplicatePhysBones();
|
|
|
|
|
UnityEngine.Object.DestroyImmediate(config);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
private void RetainBoneReferences(Component c)
|
2022-10-03 09:58:22 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
if (c == null) return;
|
2022-12-09 12:19:15 +08:00
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
SerializedObject so = new SerializedObject(c);
|
|
|
|
|
SerializedProperty iter = so.GetIterator();
|
|
|
|
|
|
|
|
|
|
bool enterChildren = true;
|
|
|
|
|
while (iter.Next(enterChildren))
|
|
|
|
|
{
|
|
|
|
|
enterChildren = true;
|
|
|
|
|
switch (iter.propertyType)
|
|
|
|
|
{
|
2022-10-03 07:43:01 +08:00
|
|
|
|
case SerializedPropertyType.String:
|
|
|
|
|
enterChildren = false;
|
2022-08-28 07:54:59 +08:00
|
|
|
|
break;
|
|
|
|
|
case SerializedPropertyType.ObjectReference:
|
2022-10-03 09:58:22 +08:00
|
|
|
|
if (iter.name == "m_GameObject") break;
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
if (iter.objectReferenceValue is Transform t)
|
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
BoneDatabase.RetainMergedBone(t);
|
2022-10-03 09:58:22 +08:00
|
|
|
|
}
|
|
|
|
|
else if (iter.objectReferenceValue is GameObject go)
|
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
BoneDatabase.RetainMergedBone(go.transform);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
so.ApplyModifiedPropertiesWithoutUndo();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
private bool HasAdditionalComponents(GameObject go)
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
|
|
|
|
bool hasComponents = false;
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
foreach (Component c in go.GetComponents<Component>())
|
|
|
|
|
{
|
|
|
|
|
switch (c)
|
|
|
|
|
{
|
|
|
|
|
case Transform _: break;
|
|
|
|
|
case ModularAvatarMergeArmature _: break;
|
|
|
|
|
default:
|
|
|
|
|
hasComponents = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hasComponents;
|
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
/// <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>
|
|
|
|
|
/// Name of the intermediate object. Used to name proxy objects.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string name;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The original path of this intermediate object.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string originPath;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether this object is initially active.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool active;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<IntermediateObj> intermediateObjects = new List<IntermediateObj>();
|
2023-01-24 20:40:22 +08:00
|
|
|
|
|
|
|
|
|
private Dictionary<string, List<GameObject>>
|
|
|
|
|
activationPathMappings = new Dictionary<string, List<GameObject>>();
|
2023-01-05 20:10:22 +08:00
|
|
|
|
|
2023-07-29 17:04:59 +08:00
|
|
|
|
private void MergeArmature(ModularAvatarMergeArmature mergeArmature, GameObject mergeTargetObject)
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2023-07-29 17:04:59 +08:00
|
|
|
|
// TODO: error reporting?
|
|
|
|
|
if (mergeTargetObject == null) return;
|
2022-08-28 04:38:52 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
GatherActiveStatePaths(mergeArmature.transform);
|
|
|
|
|
|
2023-07-29 17:04:59 +08:00
|
|
|
|
RecursiveMerge(mergeArmature, mergeArmature.gameObject, mergeTargetObject, true);
|
2023-01-05 20:10:22 +08:00
|
|
|
|
|
|
|
|
|
FixupAnimations();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private AnimationCurve GetActiveBinding(AnimationClip clip, string path)
|
|
|
|
|
{
|
|
|
|
|
return AnimationUtility.GetEditorCurve(clip,
|
|
|
|
|
EditorCurveBinding.FloatCurve(path, typeof(GameObject), "m_IsActive"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void FixupAnimations()
|
|
|
|
|
{
|
|
|
|
|
foreach (var kvp in activationPathMappings)
|
|
|
|
|
{
|
|
|
|
|
var path = kvp.Key;
|
|
|
|
|
var mappings = kvp.Value;
|
|
|
|
|
|
|
|
|
|
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 mappings)
|
|
|
|
|
{
|
2023-01-24 20:40:22 +08:00
|
|
|
|
clip.SetCurve(PathMappings.GetObjectIdentifier(mapping), typeof(GameObject), "m_IsActive",
|
|
|
|
|
curve);
|
2023-01-05 20:10:22 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void GatherActiveStatePaths(Transform root)
|
|
|
|
|
{
|
|
|
|
|
intermediateObjects.Clear();
|
|
|
|
|
activationPathMappings.Clear();
|
|
|
|
|
|
|
|
|
|
List<IntermediateObj> rootPath = new List<IntermediateObj>();
|
|
|
|
|
|
|
|
|
|
while (root != null && root.GetComponent<VRCAvatarDescriptor>() == null)
|
|
|
|
|
{
|
|
|
|
|
rootPath.Insert(0, new IntermediateObj()
|
|
|
|
|
{
|
|
|
|
|
name = root.name,
|
|
|
|
|
originPath = RuntimeUtil.AvatarRootPath(root.gameObject),
|
|
|
|
|
active = root.gameObject.activeSelf
|
|
|
|
|
});
|
|
|
|
|
root = root.parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var prefix = "";
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= rootPath.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
var srcPrefix = string.Join("/", rootPath.Take(i).Select(p => p.name));
|
|
|
|
|
if (context.AnimationDatabase.ClipsForPath(srcPrefix).Any(clip =>
|
|
|
|
|
GetActiveBinding(clip.CurrentClip as AnimationClip, srcPrefix) != null
|
|
|
|
|
))
|
|
|
|
|
{
|
|
|
|
|
var intermediate = rootPath[i - 1].name + "$" + Guid.NewGuid().ToString();
|
|
|
|
|
var originPath = rootPath[i - 1].originPath;
|
|
|
|
|
intermediateObjects.Add(new IntermediateObj()
|
|
|
|
|
{
|
|
|
|
|
name = intermediate,
|
|
|
|
|
originPath = originPath,
|
|
|
|
|
active = rootPath[i - 1].active
|
|
|
|
|
});
|
|
|
|
|
if (prefix.Length > 0) prefix += "/";
|
|
|
|
|
prefix += intermediate;
|
2023-01-24 20:40:22 +08:00
|
|
|
|
activationPathMappings[originPath] = new List<GameObject>();
|
2023-01-05 20:10:22 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
/**
|
|
|
|
|
* (Attempts to) merge the source gameobject into the target gameobject. Returns true if the merged source
|
|
|
|
|
* object must be retained.
|
|
|
|
|
*/
|
2023-01-05 20:10:22 +08:00
|
|
|
|
private void RecursiveMerge(ModularAvatarMergeArmature config,
|
2022-10-03 09:58:22 +08:00
|
|
|
|
GameObject src,
|
|
|
|
|
GameObject newParent,
|
2023-01-05 20:10:22 +08:00
|
|
|
|
bool zipMerge)
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2022-11-30 01:58:12 +08:00
|
|
|
|
if (src == newParent)
|
|
|
|
|
{
|
2023-01-19 20:32:44 +08:00
|
|
|
|
// Error reported by validation framework
|
|
|
|
|
return;
|
2022-11-30 01:58:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-16 22:50:24 +08:00
|
|
|
|
if (zipMerge)
|
|
|
|
|
{
|
|
|
|
|
mergedObjects.Add(src.transform);
|
|
|
|
|
thisPassAdded.Add(src.transform);
|
|
|
|
|
}
|
2022-12-10 04:39:39 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
bool retain = HasAdditionalComponents(src) || !zipMerge;
|
|
|
|
|
zipMerge = zipMerge && src.GetComponent<IConstraint>() == null;
|
|
|
|
|
|
|
|
|
|
GameObject mergedSrcBone = newParent;
|
|
|
|
|
if (retain)
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
mergedSrcBone = newParent;
|
|
|
|
|
var switchPath = "";
|
|
|
|
|
foreach (var intermediate in intermediateObjects)
|
2022-10-03 07:43:01 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
var preexisting = mergedSrcBone.transform.Find(intermediate.name);
|
|
|
|
|
if (preexisting != null)
|
|
|
|
|
{
|
|
|
|
|
mergedSrcBone = preexisting.gameObject;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
var switchObj = new GameObject(intermediate.name);
|
|
|
|
|
switchObj.transform.SetParent(mergedSrcBone.transform, false);
|
|
|
|
|
switchObj.transform.localPosition = Vector3.zero;
|
|
|
|
|
switchObj.transform.localRotation = Quaternion.identity;
|
|
|
|
|
switchObj.transform.localScale = Vector3.one;
|
|
|
|
|
switchObj.SetActive(intermediate.active);
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
if (switchPath.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
switchPath += "/";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// This new leaf can break parent bone physbones. Add a PB Blocker
|
|
|
|
|
// to prevent this becoming an issue.
|
|
|
|
|
switchObj.GetOrAddComponent<ModularAvatarPBBlocker>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switchPath += intermediate.name;
|
|
|
|
|
|
2023-01-24 20:40:22 +08:00
|
|
|
|
activationPathMappings[intermediate.originPath].Add(switchObj);
|
2023-01-05 20:10:22 +08:00
|
|
|
|
|
|
|
|
|
mergedSrcBone = switchObj;
|
|
|
|
|
|
|
|
|
|
// Ensure mesh retargeting looks through this
|
|
|
|
|
BoneDatabase.AddMergedBone(mergedSrcBone.transform);
|
|
|
|
|
BoneDatabase.RetainMergedBone(mergedSrcBone.transform);
|
|
|
|
|
PathMappings.MarkTransformLookthrough(mergedSrcBone);
|
2023-01-16 22:50:24 +08:00
|
|
|
|
thisPassAdded.Add(mergedSrcBone.transform);
|
2023-01-05 20:10:22 +08:00
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
src.transform.SetParent(mergedSrcBone.transform, true);
|
2023-07-29 17:04:59 +08:00
|
|
|
|
if (config.mangleNames)
|
|
|
|
|
{
|
|
|
|
|
src.name = src.name + "$" + Guid.NewGuid();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
src.GetOrAddComponent<ModularAvatarPBBlocker>();
|
|
|
|
|
mergedSrcBone = src;
|
|
|
|
|
|
2022-10-05 08:47:48 +08:00
|
|
|
|
if (zipMerge)
|
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
PathMappings.MarkTransformLookthrough(src);
|
|
|
|
|
BoneDatabase.AddMergedBone(src.transform);
|
2022-10-05 08:47:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 04:38:52 +08:00
|
|
|
|
List<Transform> children = new List<Transform>();
|
|
|
|
|
foreach (Transform child in src.transform)
|
|
|
|
|
{
|
|
|
|
|
children.Add(child);
|
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
if (zipMerge)
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
foreach (Transform child in children)
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
var childGameObject = child.gameObject;
|
|
|
|
|
var childName = childGameObject.name;
|
|
|
|
|
GameObject childNewParent = mergedSrcBone;
|
|
|
|
|
bool shouldZip = false;
|
|
|
|
|
|
|
|
|
|
if (childName.StartsWith(config.prefix) && childName.EndsWith(config.suffix))
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
2023-01-05 20:10:22 +08:00
|
|
|
|
var targetObjectName = childName.Substring(config.prefix.Length,
|
|
|
|
|
childName.Length - config.prefix.Length - config.suffix.Length);
|
|
|
|
|
var targetObject = newParent.transform.Find(targetObjectName);
|
|
|
|
|
if (targetObject != null)
|
|
|
|
|
{
|
|
|
|
|
childNewParent = targetObject.gameObject;
|
|
|
|
|
shouldZip = true;
|
|
|
|
|
}
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
2022-08-28 07:54:59 +08:00
|
|
|
|
|
2023-01-05 20:10:22 +08:00
|
|
|
|
RecursiveMerge(config, childGameObject, childNewParent, shouldZip);
|
|
|
|
|
}
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-10 04:39:39 +08:00
|
|
|
|
|
2023-01-16 22:50:24 +08:00
|
|
|
|
Transform FindOriginalParent(Transform merged)
|
|
|
|
|
{
|
|
|
|
|
while (merged != null && thisPassAdded.Contains(merged)) merged = merged.parent;
|
|
|
|
|
return merged;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 04:39:39 +08:00
|
|
|
|
/**
|
|
|
|
|
* Sometimes outfit authors copy the entire armature, including PhysBones components. If we merge these and
|
|
|
|
|
* end up with multiple PB components referencing the same target, PB refuses to animate the bone. So detect
|
|
|
|
|
* and prune this case.
|
|
|
|
|
*
|
|
|
|
|
* TODO - detect duplicate colliders, contacts, et - these can cause perf issues but usually not quite as large
|
|
|
|
|
* of a correctness issue.
|
|
|
|
|
*/
|
2023-01-16 22:50:24 +08:00
|
|
|
|
private void PruneDuplicatePhysBones()
|
2022-12-10 04:39:39 +08:00
|
|
|
|
{
|
2023-01-16 22:50:24 +08:00
|
|
|
|
foreach (var obj in mergedObjects)
|
2022-12-10 04:39:39 +08:00
|
|
|
|
{
|
2023-01-16 22:50:24 +08:00
|
|
|
|
if (obj.GetComponent<VRCPhysBone>() == null) continue;
|
|
|
|
|
var baseObj = FindOriginalParent(obj);
|
|
|
|
|
if (baseObj == null || baseObj.GetComponent<VRCPhysBone>() == null) continue;
|
|
|
|
|
|
|
|
|
|
HashSet<Transform> baseTargets = new HashSet<Transform>();
|
|
|
|
|
foreach (var component in baseObj.GetComponents<VRCPhysBone>())
|
2022-12-10 04:39:39 +08:00
|
|
|
|
{
|
2023-01-16 22:50:24 +08:00
|
|
|
|
var target = component.rootTransform == null ? baseObj.transform : component.rootTransform;
|
|
|
|
|
baseTargets.Add(target);
|
2022-12-10 04:39:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-01-16 22:50:24 +08:00
|
|
|
|
foreach (var component in obj.GetComponents<VRCPhysBone>())
|
2022-12-10 04:39:39 +08:00
|
|
|
|
{
|
2023-01-16 22:50:24 +08:00
|
|
|
|
var target = component.rootTransform == null
|
|
|
|
|
? baseObj.transform
|
|
|
|
|
: FindOriginalParent(component.rootTransform);
|
|
|
|
|
if (baseTargets.Contains(target))
|
|
|
|
|
{
|
|
|
|
|
Object.DestroyImmediate(component);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
BoneDatabase.RetainMergedBone(component.transform);
|
|
|
|
|
}
|
2022-12-10 04:39:39 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
2023-01-05 20:10:22 +08:00
|
|
|
|
}
|