mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-29 18:55:06 +08:00
Initial implementation of the 1p-visibility trait component
This commit is contained in:
parent
578e7a565c
commit
1ce110cb08
@ -109,12 +109,12 @@ namespace net.fushizen.modular_avatar.core.editor
|
||||
|
||||
new RenameParametersHook().OnPreprocessAvatar(avatarGameObject);
|
||||
new MenuInstallHook().OnPreprocessAvatar(avatarGameObject);
|
||||
|
||||
new MergeArmatureHook().OnPreprocessAvatar(avatarGameObject);
|
||||
new RetargetMeshes().OnPreprocessAvatar(avatarGameObject);
|
||||
new BoneProxyProcessor().OnPreprocessAvatar(avatarGameObject);
|
||||
new MergeAnimatorProcessor().OnPreprocessAvatar(avatarGameObject);
|
||||
new BlendshapeSyncAnimationProcessor().OnPreprocessAvatar(avatarGameObject);
|
||||
new FirstPersonVisibleProcessor(avatarGameObject.GetComponent<VRCAvatarDescriptor>()).Process();
|
||||
|
||||
AfterProcessing?.Invoke(avatarGameObject);
|
||||
|
||||
|
@ -0,0 +1,135 @@
|
||||
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
|
||||
{
|
||||
internal class FirstPersonVisibleProcessor
|
||||
{
|
||||
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;
|
||||
|
||||
private Dictionary<Transform, Transform> _proxyBones = new Dictionary<Transform, Transform>();
|
||||
|
||||
public FirstPersonVisibleProcessor(VRCAvatarDescriptor avatar)
|
||||
{
|
||||
_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()
|
||||
{
|
||||
foreach (var target in _avatar.GetComponentsInChildren<ModularAvatarFirstPersonVisible>(true))
|
||||
{
|
||||
Process(target);
|
||||
}
|
||||
}
|
||||
|
||||
void Process(ModularAvatarFirstPersonVisible target)
|
||||
{
|
||||
if (Validate(target) == ReadyStatus.Ready)
|
||||
{
|
||||
var proxy = CreateProxy(_headBone);
|
||||
|
||||
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);
|
||||
|
||||
target.transform.SetParent(proxy, true);
|
||||
}
|
||||
|
||||
Object.DestroyImmediate(target);
|
||||
}
|
||||
|
||||
private Transform CreateProxy(Transform src)
|
||||
{
|
||||
if (_proxyBones.TryGetValue(src, out var proxy)) return proxy;
|
||||
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};
|
||||
|
||||
_proxyBones.Add(src, obj.transform);
|
||||
|
||||
return obj.transform;
|
||||
}
|
||||
|
||||
internal ReadyStatus Validate(ModularAvatarFirstPersonVisible target)
|
||||
{
|
||||
ReadyStatus status = ReadyStatus.NotUnderHead;
|
||||
Transform node = target.transform.parent;
|
||||
|
||||
if (_activeBones.Contains(target.transform)) return ReadyStatus.InPhysBoneChain;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
if (node.GetComponent<ModularAvatarFirstPersonVisible>()) return ReadyStatus.ParentMarked;
|
||||
if (_activeBones.Contains(node)) return ReadyStatus.InPhysBoneChain;
|
||||
|
||||
if (node == _headBone)
|
||||
{
|
||||
status = ReadyStatus.Ready;
|
||||
break;
|
||||
}
|
||||
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0466a62b8e2b4dd39117b650dcd1c312
|
||||
timeCreated: 1667775370
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace net.fushizen.modular_avatar.core.editor
|
||||
{
|
||||
[CustomEditor(typeof(ModularAvatarFirstPersonVisible))]
|
||||
public class FirstPersonVisibleEditor : Editor
|
||||
{
|
||||
private FirstPersonVisibleProcessor _processor;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
var target = (ModularAvatarFirstPersonVisible) this.target;
|
||||
var avatar = RuntimeUtil.FindAvatarInParents(target.transform);
|
||||
|
||||
if (avatar != null) _processor = new FirstPersonVisibleProcessor(avatar);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var target = (ModularAvatarFirstPersonVisible) this.target;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
EditorGUILayout.HelpBox(Localization.S("fpvisible.quest"), MessageType.Warning);
|
||||
|
||||
#else
|
||||
|
||||
if (_processor != null)
|
||||
{
|
||||
var status = _processor.Validate(target);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case FirstPersonVisibleProcessor.ReadyStatus.Ready:
|
||||
case FirstPersonVisibleProcessor.ReadyStatus.ParentMarked:
|
||||
EditorGUILayout.HelpBox(Localization.S("fpvisible.normal"), MessageType.Info);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
var label = "fpvisible." + status;
|
||||
EditorGUILayout.HelpBox(Localization.S(label), MessageType.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Localization.ShowLanguageUI();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b478fb92b39b4df58f10aef1db3b2ffd
|
||||
timeCreated: 1667774854
|
@ -37,5 +37,9 @@
|
||||
"merge_animator.path_mode": "Path Mode",
|
||||
"merge_animator.path_mode.tooltip": "How to interpret paths in animations. Using relative mode lets you record animations from an animator on this object.",
|
||||
"merge_animator.match_avatar_write_defaults": "Match Avatar Write Defaults",
|
||||
"merge_animator.match_avatar_write_defaults.tooltip": "Match the write defaults setting used on the avatar's animator. If the avatar's write defaults settings are inconsistent, the settings on the animator will be left alone."
|
||||
"merge_animator.match_avatar_write_defaults.tooltip": "Match the write defaults setting used on the avatar's animator. If the avatar's write defaults settings are inconsistent, the settings on the animator will be left alone.",
|
||||
"fpvisible.normal": "This object will be visible in your first person view.",
|
||||
"fpvisible.NotUnderHead": "This component has no effect when not placed under the head bone.",
|
||||
"fpvisible.quest": "This component is not compatible with the standalone Oculus Quest and will have no effect.",
|
||||
"fpvisible.InPhysBoneChain": "This object is controlled by a Physics Bone chain and cannot be made visible in first person safely. Select the start of the chain instead."
|
||||
}
|
@ -37,5 +37,9 @@
|
||||
"merge_animator.path_mode": "パースモード",
|
||||
"merge_animator.path_mode.tooltip": "アニメーション内のパースを解釈するモード。相対的にすると、このオブジェクトについているアニメーターでアニメーション編集できます",
|
||||
"merge_animator.match_avatar_write_defaults": "アバターのWrite Defaults設定に合わせる",
|
||||
"merge_animator.match_avatar_write_defaults.tooltip": "アバターの該当アニメーターのWrite Defaults設定に合わせます。アバター側の設定が矛盾する場合は、統合されるアニメーターのWD値がそのまま採用されます。"
|
||||
"merge_animator.match_avatar_write_defaults.tooltip": "アバターの該当アニメーターのWrite Defaults設定に合わせます。アバター側の設定が矛盾する場合は、統合されるアニメーターのWD値がそのまま採用されます。",
|
||||
"fpvisible.normal": "このオブジェクトは一人視点で表示されます。",
|
||||
"fpvisible.quest": "このコンポーネントはクエスト単体非対応のため無効化となっています。",
|
||||
"fpvisible.NotUnderHead": "このコンポーネントはヘッドボーン外では効果がありません。",
|
||||
"fpvisible.InPhysBoneChain": "このオブジェクトはPhysBoneに制御されているため、一人視点で表示できません。PhysBoneの始点を指定してください。"
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace net.fushizen.modular_avatar.core
|
||||
{
|
||||
public class ModularAvatarFirstPersonVisible : AvatarTagComponent
|
||||
{
|
||||
// no configuration needed
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33dac8cfeaeb4c399ddd90597f849f70
|
||||
timeCreated: 1667774788
|
Loading…
Reference in New Issue
Block a user