2022-11-07 09:03:06 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.Animations;
|
|
|
|
|
using VRC.SDK3.Avatars.Components;
|
|
|
|
|
using VRC.SDK3.Dynamics.PhysBone.Components;
|
|
|
|
|
|
|
|
|
|
namespace net.fushizen.modular_avatar.core.editor
|
|
|
|
|
{
|
2022-11-10 09:39:52 +08:00
|
|
|
|
internal class VisibleHeadAccessoryProcessor
|
2022-11-07 09:03:06 +08:00
|
|
|
|
{
|
|
|
|
|
private const double EPSILON = 0.01;
|
|
|
|
|
|
|
|
|
|
internal enum ReadyStatus
|
|
|
|
|
{
|
|
|
|
|
Ready,
|
|
|
|
|
ParentMarked,
|
|
|
|
|
NotUnderHead,
|
|
|
|
|
InPhysBoneChain
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private VRCAvatarDescriptor _avatar;
|
|
|
|
|
private HashSet<Transform> _activeBones = new HashSet<Transform>();
|
|
|
|
|
private Transform _headBone;
|
|
|
|
|
|
2022-11-07 10:44:05 +08:00
|
|
|
|
private HashSet<Transform> _visibleBones = new HashSet<Transform>();
|
|
|
|
|
private Transform _proxyHead;
|
2022-11-07 09:03:06 +08:00
|
|
|
|
|
2022-11-10 09:39:52 +08:00
|
|
|
|
public VisibleHeadAccessoryProcessor(VRCAvatarDescriptor avatar)
|
2022-11-07 09:03:06 +08:00
|
|
|
|
{
|
|
|
|
|
_avatar = avatar;
|
|
|
|
|
|
|
|
|
|
var animator = avatar.GetComponent<Animator>();
|
|
|
|
|
_headBone = animator != null ? animator.GetBoneTransform(HumanBodyBones.Head) : null;
|
|
|
|
|
|
|
|
|
|
foreach (var physBone in avatar.GetComponentsInChildren<VRCPhysBone>(true))
|
|
|
|
|
{
|
|
|
|
|
var boneRoot = physBone.rootTransform != null ? physBone.rootTransform : physBone.transform;
|
|
|
|
|
var ignored = new HashSet<Transform>(physBone.ignoreTransforms);
|
|
|
|
|
|
|
|
|
|
foreach (Transform child in boneRoot)
|
|
|
|
|
{
|
|
|
|
|
Traverse(child, ignored);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Traverse(Transform bone, HashSet<Transform> ignored)
|
|
|
|
|
{
|
|
|
|
|
if (ignored.Contains(bone)) return;
|
|
|
|
|
_activeBones.Add(bone);
|
|
|
|
|
|
|
|
|
|
foreach (Transform child in bone)
|
|
|
|
|
{
|
|
|
|
|
Traverse(child, ignored);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Process()
|
|
|
|
|
{
|
2022-11-07 10:44:05 +08:00
|
|
|
|
bool didWork = false;
|
|
|
|
|
|
2022-11-10 09:39:52 +08:00
|
|
|
|
foreach (var target in _avatar.GetComponentsInChildren<ModularAvatarVisibleHeadAccessory>(true))
|
2022-11-07 09:03:06 +08:00
|
|
|
|
{
|
2022-11-07 10:44:05 +08:00
|
|
|
|
var w = Process(target);
|
|
|
|
|
didWork = didWork || w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (didWork)
|
|
|
|
|
{
|
|
|
|
|
// Process meshes
|
|
|
|
|
foreach (var smr in _avatar.GetComponentsInChildren<SkinnedMeshRenderer>(true))
|
|
|
|
|
{
|
2022-11-10 09:39:52 +08:00
|
|
|
|
new VisibleHeadAccessoryMeshProcessor(smr, _visibleBones, _proxyHead).Retarget();
|
2022-11-07 10:44:05 +08:00
|
|
|
|
}
|
2022-11-07 09:03:06 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-10 09:39:52 +08:00
|
|
|
|
bool Process(ModularAvatarVisibleHeadAccessory target)
|
2022-11-07 09:03:06 +08:00
|
|
|
|
{
|
2022-11-07 10:44:05 +08:00
|
|
|
|
bool didWork = false;
|
|
|
|
|
|
2022-11-07 09:03:06 +08:00
|
|
|
|
if (Validate(target) == ReadyStatus.Ready)
|
|
|
|
|
{
|
2022-11-07 10:44:05 +08:00
|
|
|
|
var proxy = CreateProxy();
|
2022-11-07 09:03:06 +08:00
|
|
|
|
|
|
|
|
|
var xform = target.transform;
|
|
|
|
|
|
|
|
|
|
var pscale = proxy.lossyScale;
|
|
|
|
|
var oscale = xform.lossyScale;
|
|
|
|
|
xform.localScale = new Vector3(oscale.x / pscale.x, oscale.y / pscale.y, oscale.z / pscale.z);
|
|
|
|
|
|
2022-11-08 12:21:15 +08:00
|
|
|
|
var oldPath = RuntimeUtil.AvatarRootPath(target.gameObject);
|
2022-11-07 09:03:06 +08:00
|
|
|
|
target.transform.SetParent(proxy, true);
|
2022-11-08 12:21:15 +08:00
|
|
|
|
var newPath = RuntimeUtil.AvatarRootPath(target.gameObject);
|
|
|
|
|
PathMappings.Remap(oldPath, new PathMappings.MappingEntry()
|
|
|
|
|
{
|
|
|
|
|
path = newPath,
|
|
|
|
|
transformPath = newPath
|
|
|
|
|
});
|
2022-11-07 10:44:05 +08:00
|
|
|
|
|
|
|
|
|
didWork = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (didWork)
|
|
|
|
|
{
|
|
|
|
|
foreach (var xform in target.GetComponentsInChildren<Transform>(true))
|
|
|
|
|
{
|
|
|
|
|
_visibleBones.Add(xform);
|
|
|
|
|
}
|
2022-11-07 09:03:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Object.DestroyImmediate(target);
|
2022-11-07 10:44:05 +08:00
|
|
|
|
|
|
|
|
|
return didWork;
|
2022-11-07 09:03:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
2022-11-07 10:44:05 +08:00
|
|
|
|
private Transform CreateProxy()
|
2022-11-07 09:03:06 +08:00
|
|
|
|
{
|
2022-11-07 10:44:05 +08:00
|
|
|
|
if (_proxyHead != null) return _proxyHead;
|
|
|
|
|
|
|
|
|
|
var src = _headBone;
|
2022-11-07 09:03:06 +08:00
|
|
|
|
GameObject obj = new GameObject(src.name + " (FirstPersonVisible)");
|
|
|
|
|
|
|
|
|
|
Transform parent = _headBone.parent;
|
|
|
|
|
|
|
|
|
|
obj.transform.SetParent(parent, false);
|
|
|
|
|
obj.transform.localPosition = src.localPosition;
|
|
|
|
|
obj.transform.localRotation = src.localRotation;
|
|
|
|
|
obj.transform.localScale = src.localScale;
|
|
|
|
|
|
|
|
|
|
var constraint = obj.AddComponent<ParentConstraint>();
|
|
|
|
|
constraint.AddSource(new ConstraintSource()
|
|
|
|
|
{
|
|
|
|
|
weight = 1.0f,
|
|
|
|
|
sourceTransform = src
|
|
|
|
|
});
|
|
|
|
|
constraint.constraintActive = true;
|
|
|
|
|
constraint.locked = true;
|
|
|
|
|
constraint.rotationOffsets = new[] {Vector3.zero};
|
|
|
|
|
constraint.translationOffsets = new[] {Vector3.zero};
|
|
|
|
|
|
2022-11-07 10:44:05 +08:00
|
|
|
|
_proxyHead = obj.transform;
|
2022-11-07 09:03:06 +08:00
|
|
|
|
|
|
|
|
|
return obj.transform;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-10 09:39:52 +08:00
|
|
|
|
internal ReadyStatus Validate(ModularAvatarVisibleHeadAccessory target)
|
2022-11-07 09:03:06 +08:00
|
|
|
|
{
|
|
|
|
|
ReadyStatus status = ReadyStatus.NotUnderHead;
|
|
|
|
|
Transform node = target.transform.parent;
|
|
|
|
|
|
|
|
|
|
if (_activeBones.Contains(target.transform)) return ReadyStatus.InPhysBoneChain;
|
|
|
|
|
|
|
|
|
|
while (node != null)
|
|
|
|
|
{
|
2022-11-10 09:39:52 +08:00
|
|
|
|
if (node.GetComponent<ModularAvatarVisibleHeadAccessory>()) return ReadyStatus.ParentMarked;
|
2022-11-07 09:03:06 +08:00
|
|
|
|
if (_activeBones.Contains(node)) return ReadyStatus.InPhysBoneChain;
|
|
|
|
|
|
|
|
|
|
if (node == _headBone)
|
|
|
|
|
{
|
|
|
|
|
status = ReadyStatus.Ready;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node = node.parent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|