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;
|
|
|
|
|
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;
|
|
|
|
|
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
|
|
|
|
{
|
2022-08-28 07:54:59 +08:00
|
|
|
|
private Dictionary<Transform, Transform> BoneRemappings = new Dictionary<Transform, Transform>();
|
2022-10-03 09:58:22 +08:00
|
|
|
|
private HashSet<GameObject> ToDelete = new HashSet<GameObject>();
|
|
|
|
|
private HashSet<IConstraint> AddedConstraints = new HashSet<IConstraint>();
|
2022-08-28 07:54:59 +08:00
|
|
|
|
|
2022-09-12 05:19:05 +08:00
|
|
|
|
internal bool OnPreprocessAvatar(GameObject avatarGameObject)
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2022-09-10 11:36:04 +08:00
|
|
|
|
BoneRemappings.Clear();
|
|
|
|
|
ToDelete.Clear();
|
2022-10-03 09:58:22 +08:00
|
|
|
|
AddedConstraints.Clear();
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-08-28 06:04:39 +08:00
|
|
|
|
var mergeArmatures = avatarGameObject.transform.GetComponentsInChildren<ModularAvatarMergeArmature>(true);
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
BoneRemappings.Clear();
|
|
|
|
|
ToDelete.Clear();
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-08-28 04:38:52 +08:00
|
|
|
|
foreach (var mergeArmature in mergeArmatures)
|
|
|
|
|
{
|
|
|
|
|
MergeArmature(mergeArmature);
|
|
|
|
|
UnityEngine.Object.DestroyImmediate(mergeArmature);
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-04 10:19:43 +08:00
|
|
|
|
foreach (var renderer in avatarGameObject.transform.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
|
|
|
|
var bones = renderer.bones;
|
2022-10-03 09:58:22 +08:00
|
|
|
|
for (int i = 0; i < bones.Length; i++) bones[i] = MapBoneReference(bones[i], Retargetable.Ignore);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
renderer.bones = bones;
|
2022-10-03 11:20:29 +08:00
|
|
|
|
renderer.rootBone = MapBoneReference(renderer.rootBone, Retargetable.Ignore);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
renderer.probeAnchor = MapBoneReference(renderer.probeAnchor);
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
UpdateBoneReferences(c);
|
|
|
|
|
}
|
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;
|
|
|
|
|
UpdateBoneReferences(c);
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
UpdateBoneReferences(c);
|
|
|
|
|
}
|
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
|
|
|
|
{
|
|
|
|
|
if (!AddedConstraints.Contains(c))
|
|
|
|
|
{
|
|
|
|
|
FixupConstraint(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
foreach (var bone in ToDelete) UnityEngine.Object.DestroyImmediate(bone);
|
|
|
|
|
|
2022-08-28 04:38:52 +08:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-03 09:58:22 +08:00
|
|
|
|
private void FixupConstraint(IConstraint constraint)
|
|
|
|
|
{
|
|
|
|
|
int nSources = constraint.sourceCount;
|
|
|
|
|
for (int i = 0; i < nSources; i++)
|
|
|
|
|
{
|
|
|
|
|
var source = constraint.GetSource(i);
|
2022-12-10 13:11:06 +08:00
|
|
|
|
source.sourceTransform = MapConstraintSource(source.sourceTransform);
|
2022-10-03 09:58:22 +08:00
|
|
|
|
constraint.SetSource(i, source);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 13:14:52 +08:00
|
|
|
|
if (constraint is AimConstraint aimConstraint)
|
|
|
|
|
{
|
|
|
|
|
aimConstraint.worldUpObject = MapConstraintSource(aimConstraint.worldUpObject);
|
|
|
|
|
}
|
2022-12-09 12:19:15 +08:00
|
|
|
|
|
2022-12-10 13:14:52 +08:00
|
|
|
|
if (constraint is LookAtConstraint lookAtConstraint)
|
|
|
|
|
{
|
|
|
|
|
lookAtConstraint.worldUpObject = MapConstraintSource(lookAtConstraint.worldUpObject);
|
|
|
|
|
}
|
2022-12-10 13:11:06 +08:00
|
|
|
|
}
|
2022-12-09 12:19:15 +08:00
|
|
|
|
|
2022-12-10 13:11:06 +08:00
|
|
|
|
private Transform MapConstraintSource(Transform transform)
|
|
|
|
|
{
|
|
|
|
|
if (transform == null) return null;
|
|
|
|
|
if (!BoneRemappings.TryGetValue(transform, out var remap)) return transform;
|
|
|
|
|
var retarget = BoneDatabase.GetRetargetedBone(remap);
|
|
|
|
|
return retarget != null ? retarget : remap;
|
2022-12-09 12:19:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-03 09:58:22 +08:00
|
|
|
|
private void UpdateBoneReferences(Component c, Retargetable retargetable = Retargetable.Disable)
|
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)
|
|
|
|
|
{
|
2022-10-03 09:58:22 +08:00
|
|
|
|
var mapped = MapBoneReference(t, retargetable);
|
|
|
|
|
|
|
|
|
|
iter.objectReferenceValue = mapped;
|
|
|
|
|
ClearToDeleteFlag(mapped);
|
|
|
|
|
}
|
|
|
|
|
else if (iter.objectReferenceValue is GameObject go)
|
|
|
|
|
{
|
|
|
|
|
var mapped = MapBoneReference(go.transform, retargetable);
|
|
|
|
|
|
|
|
|
|
iter.objectReferenceValue = mapped?.gameObject;
|
|
|
|
|
ClearToDeleteFlag(mapped);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
so.ApplyModifiedPropertiesWithoutUndo();
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-03 09:58:22 +08:00
|
|
|
|
private void ClearToDeleteFlag(Transform t)
|
|
|
|
|
{
|
|
|
|
|
while (t != null && ToDelete.Contains(t.gameObject))
|
|
|
|
|
{
|
|
|
|
|
ToDelete.Remove(t.gameObject);
|
|
|
|
|
t = t.parent;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Retargetable
|
|
|
|
|
{
|
|
|
|
|
Disable,
|
|
|
|
|
Ignore,
|
|
|
|
|
Use
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Transform MapBoneReference(Transform bone, Retargetable retargetable = Retargetable.Disable)
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
|
|
|
|
if (bone != null && BoneRemappings.TryGetValue(bone, out var newBone))
|
|
|
|
|
{
|
2022-10-03 09:58:22 +08:00
|
|
|
|
if (retargetable == Retargetable.Disable) BoneDatabase.MarkNonRetargetable(newBone);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
bone = newBone;
|
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-10-03 09:58:22 +08:00
|
|
|
|
if (bone != null && retargetable == Retargetable.Use)
|
|
|
|
|
{
|
|
|
|
|
var retargeted = BoneDatabase.GetRetargetedBone(bone);
|
|
|
|
|
if (retargeted != null) bone = retargeted;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
return bone;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-03 07:43:01 +08:00
|
|
|
|
private bool HasAdditionalComponents(GameObject go, out Type constraintType)
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
|
|
|
|
bool hasComponents = false;
|
2022-10-03 07:43:01 +08:00
|
|
|
|
bool needsConstraint = false;
|
|
|
|
|
bool hasPositionConstraint = false;
|
|
|
|
|
bool hasRotationConstraint = false;
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
foreach (Component c in go.GetComponents<Component>())
|
|
|
|
|
{
|
|
|
|
|
switch (c)
|
|
|
|
|
{
|
|
|
|
|
case Transform _: break;
|
|
|
|
|
case ModularAvatarMergeArmature _: break;
|
2022-10-03 07:43:01 +08:00
|
|
|
|
case VRCPhysBone _:
|
|
|
|
|
case VRCPhysBoneCollider _:
|
|
|
|
|
hasComponents = true;
|
|
|
|
|
break;
|
|
|
|
|
case AimConstraint _:
|
|
|
|
|
case LookAtConstraint _:
|
|
|
|
|
case RotationConstraint _:
|
|
|
|
|
hasRotationConstraint = true;
|
|
|
|
|
needsConstraint = true;
|
|
|
|
|
hasComponents = true;
|
|
|
|
|
break;
|
|
|
|
|
case PositionConstraint _:
|
|
|
|
|
hasPositionConstraint = true;
|
|
|
|
|
needsConstraint = true;
|
|
|
|
|
hasComponents = true;
|
|
|
|
|
break;
|
|
|
|
|
case ParentConstraint _:
|
|
|
|
|
needsConstraint = false;
|
|
|
|
|
hasPositionConstraint = hasRotationConstraint = true;
|
|
|
|
|
hasComponents = true;
|
2022-08-28 07:54:59 +08:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
hasComponents = true;
|
|
|
|
|
needsConstraint = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-03 07:43:01 +08:00
|
|
|
|
if (!needsConstraint || (hasPositionConstraint && hasRotationConstraint))
|
|
|
|
|
{
|
|
|
|
|
constraintType = null;
|
|
|
|
|
}
|
|
|
|
|
else if (hasPositionConstraint)
|
|
|
|
|
{
|
|
|
|
|
constraintType = typeof(RotationConstraint);
|
|
|
|
|
}
|
|
|
|
|
else if (hasRotationConstraint)
|
|
|
|
|
{
|
|
|
|
|
constraintType = typeof(PositionConstraint);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
constraintType = typeof(ParentConstraint);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
return hasComponents;
|
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
2022-08-28 04:38:52 +08:00
|
|
|
|
private void MergeArmature(ModularAvatarMergeArmature mergeArmature)
|
|
|
|
|
{
|
|
|
|
|
// TODO: error reporting framework?
|
2022-10-03 09:16:58 +08:00
|
|
|
|
if (mergeArmature.mergeTargetObject == null) return;
|
2022-08-28 04:38:52 +08:00
|
|
|
|
|
2022-10-03 09:16:58 +08:00
|
|
|
|
RecursiveMerge(mergeArmature, mergeArmature.gameObject, mergeArmature.mergeTargetObject.gameObject, true);
|
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.
|
|
|
|
|
*/
|
2022-10-03 09:58:22 +08:00
|
|
|
|
private bool RecursiveMerge(
|
|
|
|
|
ModularAvatarMergeArmature config,
|
|
|
|
|
GameObject src,
|
|
|
|
|
GameObject newParent,
|
|
|
|
|
bool zipMerge
|
|
|
|
|
)
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2022-11-30 01:58:12 +08:00
|
|
|
|
if (src == newParent)
|
|
|
|
|
{
|
|
|
|
|
throw new Exception("[ModularAvatar] Attempted to merge an armature into itself! Aborting build...");
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
GameObject mergedSrcBone = new GameObject(src.name + "@" + GUID.Generate());
|
|
|
|
|
mergedSrcBone.transform.SetParent(src.transform.parent);
|
|
|
|
|
mergedSrcBone.transform.localPosition = src.transform.localPosition;
|
|
|
|
|
mergedSrcBone.transform.localRotation = src.transform.localRotation;
|
|
|
|
|
mergedSrcBone.transform.localScale = src.transform.localScale;
|
|
|
|
|
mergedSrcBone.transform.SetParent(newParent.transform, true);
|
|
|
|
|
|
2022-12-10 04:39:39 +08:00
|
|
|
|
if (zipMerge) PruneDuplicatePhysBones(newParent, src);
|
|
|
|
|
|
2022-10-03 07:43:01 +08:00
|
|
|
|
bool retain = HasAdditionalComponents(src, out var constraintType);
|
|
|
|
|
if (constraintType != null)
|
2022-08-28 07:54:59 +08:00
|
|
|
|
{
|
2022-10-03 07:43:01 +08:00
|
|
|
|
IConstraint constraint = (IConstraint) src.AddComponent(constraintType);
|
2022-10-03 09:58:22 +08:00
|
|
|
|
AddedConstraints.Add(constraint);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
constraint.AddSource(new ConstraintSource()
|
|
|
|
|
{
|
|
|
|
|
weight = 1,
|
|
|
|
|
sourceTransform = mergedSrcBone.transform
|
|
|
|
|
});
|
2022-10-03 07:43:01 +08:00
|
|
|
|
Matrix4x4 targetToSrc = src.transform.worldToLocalMatrix * newParent.transform.localToWorldMatrix;
|
|
|
|
|
if (constraint is ParentConstraint pc)
|
|
|
|
|
{
|
|
|
|
|
pc.translationOffsets = new Vector3[] {targetToSrc.MultiplyPoint(Vector3.zero)};
|
|
|
|
|
pc.rotationOffsets = new Vector3[] {targetToSrc.rotation.eulerAngles};
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-28 07:54:59 +08:00
|
|
|
|
constraint.locked = true;
|
|
|
|
|
constraint.constraintActive = true;
|
|
|
|
|
}
|
2022-10-03 07:43:01 +08:00
|
|
|
|
|
|
|
|
|
if ((constraintType != null && constraintType != typeof(ParentConstraint))
|
|
|
|
|
|| (constraintType == null && src.GetComponent<IConstraint>() != null))
|
|
|
|
|
{
|
2022-10-03 11:20:29 +08:00
|
|
|
|
return true;
|
2022-10-03 07:43:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 08:47:48 +08:00
|
|
|
|
if (zipMerge)
|
|
|
|
|
{
|
|
|
|
|
BoneDatabase.AddMergedBone(mergedSrcBone.transform);
|
|
|
|
|
var srcPath = RuntimeUtil.AvatarRootPath(src);
|
|
|
|
|
PathMappings.Remap(srcPath, new PathMappings.MappingEntry()
|
|
|
|
|
{
|
|
|
|
|
transformPath = RuntimeUtil.AvatarRootPath(newParent),
|
|
|
|
|
path = srcPath
|
|
|
|
|
});
|
2022-12-17 15:35:50 +08:00
|
|
|
|
// The new merged leaf (if it's retained below) can break parent bone physbones. Add a PB Blocker
|
|
|
|
|
// to prevent this becoming an issue.
|
|
|
|
|
mergedSrcBone.GetOrAddComponent<ModularAvatarPBBlocker>();
|
2022-10-05 08:47:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BoneRemappings[src.transform] = mergedSrcBone.transform;
|
|
|
|
|
|
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
|
|
|
|
|
2022-08-28 04:38:52 +08:00
|
|
|
|
foreach (Transform child in children)
|
|
|
|
|
{
|
|
|
|
|
var childGameObject = child.gameObject;
|
|
|
|
|
var childName = childGameObject.name;
|
2022-08-28 07:54:59 +08:00
|
|
|
|
GameObject childNewParent = mergedSrcBone;
|
|
|
|
|
bool shouldZip = zipMerge;
|
|
|
|
|
|
|
|
|
|
if (shouldZip && childName.StartsWith(config.prefix) && childName.EndsWith(config.suffix))
|
2022-08-28 04:38:52 +08:00
|
|
|
|
{
|
2022-08-28 07:54:59 +08:00
|
|
|
|
var targetObjectName = childName.Substring(config.prefix.Length,
|
2022-08-28 04:38:52 +08:00
|
|
|
|
childName.Length - config.prefix.Length - config.suffix.Length);
|
2022-08-28 07:54:59 +08:00
|
|
|
|
var targetObject = newParent.transform.Find(targetObjectName);
|
2022-08-28 04:38:52 +08:00
|
|
|
|
if (targetObject != null)
|
|
|
|
|
{
|
2022-08-28 07:54:59 +08:00
|
|
|
|
childNewParent = targetObject.gameObject;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
shouldZip = false;
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-28 07:54:59 +08:00
|
|
|
|
|
|
|
|
|
var retainChild = RecursiveMerge(config, childGameObject, childNewParent, shouldZip);
|
|
|
|
|
retain = retain || retainChild;
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
2022-08-28 07:54:59 +08:00
|
|
|
|
|
|
|
|
|
if (!retain) ToDelete.Add(src);
|
|
|
|
|
|
|
|
|
|
return retain;
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
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.
|
|
|
|
|
*
|
|
|
|
|
* For simplicity - we currently only detect the case where the physbone references the component it's on.
|
|
|
|
|
* TODO - detect duplicate colliders, contacts, et - these can cause perf issues but usually not quite as large
|
|
|
|
|
* of a correctness issue.
|
|
|
|
|
*/
|
|
|
|
|
private void PruneDuplicatePhysBones(GameObject baseBone, GameObject mergeBone)
|
|
|
|
|
{
|
|
|
|
|
bool hasSelfReferencePB = false;
|
|
|
|
|
|
|
|
|
|
foreach (var pb in baseBone.GetComponents<VRCPhysBone>())
|
|
|
|
|
{
|
|
|
|
|
var target = pb.rootTransform;
|
|
|
|
|
if (target == null || target == baseBone.transform)
|
|
|
|
|
{
|
|
|
|
|
hasSelfReferencePB = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!hasSelfReferencePB) return;
|
|
|
|
|
|
|
|
|
|
foreach (var pb in mergeBone.GetComponents<VRCPhysBone>())
|
|
|
|
|
{
|
|
|
|
|
var target = pb.rootTransform;
|
|
|
|
|
if (target == null || target == baseBone.transform)
|
|
|
|
|
{
|
|
|
|
|
Object.DestroyImmediate(pb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-28 04:38:52 +08:00
|
|
|
|
}
|
2022-12-09 12:19:15 +08:00
|
|
|
|
}
|