diff options
Diffstat (limited to 'VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor')
37 files changed, 3920 insertions, 0 deletions
diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3.meta new file mode 100644 index 00000000..0f1c2060 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f2f3ebb885866d644b715283275265c3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorLocomotionControlEditor.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorLocomotionControlEditor.cs new file mode 100644 index 00000000..fbe1e6d3 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorLocomotionControlEditor.cs @@ -0,0 +1,60 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using VRC.SDK3.Avatars.Components; + +[CustomEditor(typeof(VRCAnimatorLocomotionControl))] +public class VRCAnimatorLocomotionControlEditor : Editor +{ + VRCAnimatorLocomotionControl control; + GUIStyle styleButtonActive; + GUIStyle styleButtonInactive; + + public void OnEnable() + { + if (target == null) + return; + + if (control == null) + control = (VRCAnimatorLocomotionControl)target; + + styleButtonActive = new GUIStyle(EditorStyles.miniButton); + styleButtonInactive = new GUIStyle(EditorStyles.miniButton); + styleButtonActive.fixedWidth = 80; + styleButtonInactive.fixedWidth = 80; + styleButtonActive.normal.textColor = Color.green; + styleButtonInactive.normal.textColor = Color.gray; + + serializedObject.ApplyModifiedProperties(); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Locomotion Control", GUILayout.MaxWidth(150)); + if (control.disableLocomotion) + { + GUILayout.Button("Disable", styleButtonActive); + if (GUILayout.Button("Enable", styleButtonInactive)) + control.disableLocomotion = false; + } + else + { + if (GUILayout.Button("Disable", styleButtonInactive)) + control.disableLocomotion = true; + GUILayout.Button("Enable", styleButtonActive); + } + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + control.debugString = EditorGUILayout.TextField("Debug String", control.debugString); + + serializedObject.ApplyModifiedProperties(); + + //if (_repaint) + // EditorUtility.SetDirty(target); + } +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorLocomotionControlEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorLocomotionControlEditor.cs.meta new file mode 100644 index 00000000..3008968a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorLocomotionControlEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f54e8dca9d13504faf74cc6961ebb98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorRemeasureAvatarEditor.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorRemeasureAvatarEditor.cs new file mode 100644 index 00000000..31b8dd39 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorRemeasureAvatarEditor.cs @@ -0,0 +1,36 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using VRC.SDK3.Avatars.Components; + +//Will be revisiting this, removed temporarily Kiro - Aug/5/2020 +/*[CustomEditor(typeof(VRCAnimatorRemeasureAvatar))] +public class VRCAnimatorRemeasureAvatarEditor : Editor +{ + VRCAnimatorRemeasureAvatar view; + + public void OnEnable() + { + if (target == null) return; + + if (view == null) + view = (VRCAnimatorRemeasureAvatar)target; + + serializedObject.ApplyModifiedProperties(); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.PropertyField(serializedObject.FindProperty("fixedDelay")); + EditorGUILayout.PropertyField(serializedObject.FindProperty("delayTime"), new GUIContent(view.fixedDelay ? "Delay Time (s)" : "Delay Time (%)")); + EditorGUILayout.PropertyField(serializedObject.FindProperty("debugString")); + + serializedObject.ApplyModifiedProperties(); + + //if (_repaint) + // EditorUtility.SetDirty(target); + } +}*/ +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorRemeasureAvatarEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorRemeasureAvatarEditor.cs.meta new file mode 100644 index 00000000..815d1f32 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorRemeasureAvatarEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f28ca733d7af7e74da947f0a814fb6cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTemporaryPoseSpaceEditor.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTemporaryPoseSpaceEditor.cs new file mode 100644 index 00000000..4101f359 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTemporaryPoseSpaceEditor.cs @@ -0,0 +1,55 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using VRC.SDK3.Avatars.Components; + +[CustomEditor(typeof(VRCAnimatorTemporaryPoseSpace))] +public class VRCAnimatorSetViewEditor : Editor +{ + VRCAnimatorTemporaryPoseSpace view; + GUIStyle styleButtonActive; + GUIStyle styleButtonInactive; + + public void OnEnable() + { + if (target == null) return; + + if (view == null) + view = (VRCAnimatorTemporaryPoseSpace)target; + + styleButtonActive = new GUIStyle(EditorStyles.miniButton); + styleButtonInactive = new GUIStyle(EditorStyles.miniButton); + styleButtonActive.fixedWidth = 50; + styleButtonInactive.fixedWidth = 50; + styleButtonActive.normal.textColor = Color.green; + styleButtonInactive.normal.textColor = Color.gray; + + serializedObject.ApplyModifiedProperties(); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.Space(); + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(new GUIContent("Pose Space", "Enter or exit a pose space based on the avatar's current pose."), GUILayout.MaxWidth(150)); + + if (GUILayout.Button("Enter", view.enterPoseSpace ? styleButtonActive : styleButtonInactive)) + view.enterPoseSpace = true; + if (GUILayout.Button("Exit", !view.enterPoseSpace ? styleButtonActive : styleButtonInactive)) + view.enterPoseSpace = false; + + EditorGUILayout.EndHorizontal(); + EditorGUILayout.Space(); + EditorGUILayout.PropertyField(serializedObject.FindProperty("fixedDelay")); + EditorGUILayout.PropertyField(serializedObject.FindProperty("delayTime"), new GUIContent(view.fixedDelay ? "Delay Time (s)" : "Delay Time (%)")); + EditorGUILayout.PropertyField(serializedObject.FindProperty("debugString")); + + serializedObject.ApplyModifiedProperties(); + + //if (_repaint) + // EditorUtility.SetDirty(target); + } +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTemporaryPoseSpaceEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTemporaryPoseSpaceEditor.cs.meta new file mode 100644 index 00000000..8e8d12f0 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTemporaryPoseSpaceEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ca174ef82b69e74a84454229b5b39a0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTrackingControlEditor.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTrackingControlEditor.cs new file mode 100644 index 00000000..a01e74a8 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTrackingControlEditor.cs @@ -0,0 +1,130 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using VRC.SDK3.Avatars.Components; + +[CustomEditor(typeof(VRCAnimatorTrackingControl))] +public class VRCAnimatorTrackingControlEditor : Editor +{ + VRCAnimatorTrackingControl control; + const float columnWidth = 64f; + VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType trackingAll; + + string[] PopupOptions = new string[3] + { + "Tracking", + "Animation", + "None", + }; + + public void OnEnable() + { + if (target==null) + return; + + if (control == null) + control = (VRCAnimatorTrackingControl)target; + + serializedObject.ApplyModifiedProperties(); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + EditorGUILayout.LabelField("Tracking Control"); + EditorGUILayout.BeginVertical(GUI.skin.box); + + //Labels + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(""); + EditorGUILayout.LabelField("No Change", GUILayout.MinWidth(columnWidth)); + EditorGUILayout.LabelField("Tracking", GUILayout.MinWidth(columnWidth)); + EditorGUILayout.LabelField("Animation", GUILayout.MinWidth(columnWidth)); + EditorGUILayout.EndHorizontal(); + + //Force all + var trackingAll = CheckAll(); + var lastAll = trackingAll; + DrawTrackingOption("All", ref trackingAll); + if (lastAll != trackingAll) + { + control.trackingHead = + control.trackingLeftHand = + control.trackingRightHand = + control.trackingHip = + control.trackingLeftFoot = + control.trackingRightFoot = + control.trackingLeftFingers = + control.trackingRightFingers = + control.trackingEyes = + control.trackingMouth = trackingAll; + } + EditorGUILayout.Space(); + + //Individual + EditorGUI.BeginChangeCheck(); + { + DrawTrackingOption("Head", ref control.trackingHead); + DrawTrackingOption("Left Hand", ref control.trackingLeftHand); + DrawTrackingOption("Right Hand", ref control.trackingRightHand); + DrawTrackingOption("Hip", ref control.trackingHip); + DrawTrackingOption("Left Foot", ref control.trackingLeftFoot); + DrawTrackingOption("Right Foot", ref control.trackingRightFoot); + DrawTrackingOption("Left Fingers", ref control.trackingLeftFingers); + DrawTrackingOption("Right Fingers", ref control.trackingRightFingers); + DrawTrackingOption("Eyes & Eyelids", ref control.trackingEyes); + DrawTrackingOption("Mouth & Jaw", ref control.trackingMouth); + } + if (EditorGUI.EndChangeCheck()) + trackingAll = (VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType)999; + + EditorGUILayout.EndVertical(); + + control.debugString = EditorGUILayout.TextField("Debug String", control.debugString); + + serializedObject.ApplyModifiedProperties(); + + //if (_repaint) + // EditorUtility.SetDirty(target); + } + void DrawTrackingOption(string name, ref VRCAnimatorTrackingControl.TrackingType value) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(name); + bool result; + + //No Change + result = EditorGUILayout.Toggle(value == VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.NoChange, GUILayout.MinWidth(columnWidth)); + if (result) + value = VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.NoChange; + + //Tracking + result = EditorGUILayout.Toggle(value == VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.Tracking, GUILayout.MinWidth(columnWidth)); + if (result) + value = VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.Tracking; + + //Animation + result = EditorGUILayout.Toggle(value == VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.Animation, GUILayout.MinWidth(columnWidth)); + if (result) + value = VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType.Animation; + + EditorGUILayout.EndHorizontal(); + } + VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType CheckAll() + { + var type = control.trackingHead; + bool same = (control.trackingHead == type && + control.trackingLeftHand == type && + control.trackingRightHand == type && + control.trackingHip == type && + control.trackingLeftFoot == type && + control.trackingRightFoot == type && + control.trackingLeftFingers == type && + control.trackingRightFingers == type && + control.trackingEyes == type && + control.trackingMouth == type); + return same ? type : (VRC.SDKBase.VRC_AnimatorTrackingControl.TrackingType)999; + } +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTrackingControlEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTrackingControlEditor.cs.meta new file mode 100644 index 00000000..e9868313 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAnimatorTrackingControlEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a313c54d760ecd445aa1a536c2c2b238 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3.cs new file mode 100644 index 00000000..28cd5239 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3.cs @@ -0,0 +1,187 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using VRC.SDK3.Avatars.Components; +using VRC.SDK3.Editor; +using VRC.SDKBase.Editor; + +[CustomEditor(typeof(VRCAvatarDescriptor))] +public partial class AvatarDescriptorEditor3 : Editor +{ + + VRCAvatarDescriptor avatarDescriptor; + VRC.Core.PipelineManager pipelineManager; + + static bool _repaint = false; + + public void OnEnable() + { + /* + if (EyelidSkinnedMesh == null) + EyelidSkinnedMesh = (target as Component).GetComponentInChildren<SkinnedMeshRenderer>(true); + if (EyelidBlendShapes == null) + EyelidBlendShapes = GetBlendShapeNames(EyelidSkinnedMesh); + */ + + if (avatarDescriptor == null) + avatarDescriptor = (VRCAvatarDescriptor)target; + + if (pipelineManager == null) + { + pipelineManager = avatarDescriptor.GetComponent<VRC.Core.PipelineManager>(); + if (pipelineManager == null) + avatarDescriptor.gameObject.AddComponent<VRC.Core.PipelineManager>(); + } + + if (!_doCustomizeAnimLayers.boolValue) + ResetAnimLayersToDefault(); + + EnforceAnimLayerSetup(true); + serializedObject.ApplyModifiedProperties(); + + InitEyeLook(); + Init_Expressions(); + + } + + public void OnSceneGUI() + { + serializedObject.Update(); + + //Draw + DrawSceneViewpoint(); + DrawSceneEyeLook(); + DrawSceneLipSync(); + + serializedObject.ApplyModifiedProperties(); + } + + public override void OnInspectorGUI() + { + serializedObject.Update(); + + if(VRCSdkControlPanel.window != null) + { + if( GUILayout.Button( "Select this avatar in the SDK control panel" ) ) + VRCSdkControlPanelAvatarBuilder.SelectAvatar(avatarDescriptor); + } + + DrawView(); + DrawLipSync(); + DrawEyeLook(); + DrawPlayableLayers(); + DrawLowerBodySettings(); + DrawInspector_Expressions(); + DrawFooter(); + + serializedObject.ApplyModifiedProperties(); + + if (_repaint) + EditorUtility.SetDirty(target); + } + + void DrawFooter() + { + if (!string.IsNullOrEmpty(avatarDescriptor.unityVersion)) + EditorGUILayout.LabelField("Unity Version: ", avatarDescriptor.unityVersion); + if (_animator) + EditorGUILayout.LabelField("Rig Type: ", (_animator.isHuman ? "Humanoid" : "Non-humanoid")); + GUILayout.Space(5); + } + + #region GUIHelperMethods + + static bool Foldout(string editorPrefsKey, string label, bool deft = false) + { + bool prevState = EditorPrefs.GetBool(editorPrefsKey, deft); + bool state = EditorGUILayout.Foldout(prevState, label); + if (state != prevState) + EditorPrefs.SetBool(editorPrefsKey, state); + return state; + } + + public static void Separator() + { + GUILayout.Space(5); + Rect r = EditorGUILayout.GetControlRect(GUILayout.Height(2)); + r.height = 2; + r.x -= 10; + r.width += 20; + EditorGUI.DrawRect(r, new Color(0, 0, 0, 0.15f)); + GUILayout.Space(5); + } + + void MinMaxSlider(string label, SerializedProperty minFloat, SerializedProperty maxFloat, float minLimit, float maxLimit, bool rounded = true) + { + float min = minFloat.floatValue; + float max = maxFloat.floatValue; + + EditorGUI.BeginChangeCheck(); + MinMaxSlider(label, ref min, ref max, minLimit, maxLimit, rounded); + if (EditorGUI.EndChangeCheck()) + { + minFloat.floatValue = min; + maxFloat.floatValue = max; + } + } + + void MinMaxSlider(string label, ref float minValue, ref float maxValue, float minLimit, float maxLimit, bool rounded = true) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.PrefixLabel(label); + minValue = Mathf.Clamp(System.Convert.ToSingle(EditorGUILayout.TextField(minValue.ToString(), GUILayout.MaxWidth(30))), minLimit, maxValue); + EditorGUILayout.MinMaxSlider( + ref minValue, + ref maxValue, minLimit, maxLimit); + maxValue = Mathf.Clamp(System.Convert.ToSingle(EditorGUILayout.TextField(maxValue.ToString(), GUILayout.MaxWidth(30))), minValue, maxLimit); + if (rounded) + { + minValue = Mathf.Round(minValue); + maxValue = Mathf.Round(maxValue); + } + EditorGUILayout.EndHorizontal(); + } + + static GUIStyle boxStyle; + static void BeginBox(string label, bool hasFoldouts = false) + { + Rect rect = EditorGUILayout.BeginVertical(); + GUILayout.Space(ANIM_LAYER_LIST_MARGIN * 2); + EditorGUILayout.BeginHorizontal(GUI.skin.box); + GUILayout.Space(ANIM_LAYER_LIST_MARGIN + (hasFoldouts ? 8:0)); + EditorGUILayout.BeginVertical(); + + if (!string.IsNullOrEmpty(label)) + { + GUILayout.BeginHorizontal(); + GUILayout.Space(-(hasFoldouts ? 8 : 0)); + GUILayout.Label(label, EditorStyles.boldLabel); + GUILayout.EndHorizontal(); + } + } + + static void EndBox() + { + EditorGUILayout.EndVertical(); + GUILayout.Space(ANIM_LAYER_LIST_MARGIN); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + + // encompassing boxes must be drawn last + /*Rect rect2 = GUILayoutUtility.GetLastRect(); + rect2.y -= ANIM_LAYER_LIST_MARGIN; + rect2.height += (ANIM_LAYER_LIST_MARGIN * 2.35f); + GUI.color = boxColor; + for (int v = 0; v < (boxColor == Color.black ? 3 : 1); v++) + { + GUI.Box(rect2, GUIContent.none); + } + GUI.color = Color.white; + EditorGUILayout.EndVertical(); + GUILayout.Space(ANIM_LAYER_LIST_MARGIN * 2);*/ + } + + #endregion + +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3.cs.meta new file mode 100644 index 00000000..9ad60d53 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d37a92a9c3e21e74f8a3055444d21490 +timeCreated: 1450462624 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerGui.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerGui.cs new file mode 100644 index 00000000..07733285 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerGui.cs @@ -0,0 +1,162 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using UnityEditorInternal; +using System.Collections.Generic; +using VRC.SDK3.Avatars.Components; +using System.Reflection; + +public partial class AvatarDescriptorEditor3 : Editor +{ + + SerializedProperty _baseAnimLayers { get { return serializedObject.FindProperty("baseAnimationLayers"); } } + SerializedProperty _specialAnimLayers { get { return serializedObject.FindProperty("specialAnimationLayers"); } } + SerializedProperty _doCustomizeAnimLayers { get { return serializedObject.FindProperty("customizeAnimationLayers"); } } + + Animator _animator + { + get + { + if (_cachedAnimator == null) + _cachedAnimator = (target as Component).GetComponent<Animator>(); + return _cachedAnimator; + } + } + private Animator _cachedAnimator = null; + + const float ANIM_LAYER_LIST_MARGIN = 5f; + + void DrawPlayableLayers() + { + if (Foldout("VRCSDK3_AvatarDescriptorEditor3_AnimationFoldout", "Playable Layers")) + { + if (_doCustomizeAnimLayers.boolValue) + { + if (GUILayout.Button("Reset to Default")) + { + if (EditorUtility.DisplayDialog("Reset to Default", "This will erase any custom layer settings. Are you sure?", "OK", "Cancel")) + { + ResetAnimLayersToDefault(); + _doCustomizeAnimLayers.boolValue = false; + } + } + + DrawAnimLayerList("Base", _baseAnimLayers, Color.white); + DrawAnimLayerList("Special", _specialAnimLayers, Color.black); + + GUILayout.Space(10); + + if (GUI.changed) + EnforceAnimLayerSetup(); + } + else + { + if (GUILayout.Button("Customize")) + { + ResetAnimLayersToDefault(); + _doCustomizeAnimLayers.boolValue = true; + } + } + + Separator(); + } + } + + void DrawLowerBodySettings() + { + if (_animator && _animator.isHuman) + { + if (Foldout("VRCSDK3_AvatarDescriptorEditor3_LowerBodyFoldout", "Lower Body")) + { + var autoFoot = serializedObject.FindProperty("autoFootsteps"); + var autoLoco = serializedObject.FindProperty("autoLocomotion"); + autoFoot.boolValue = EditorGUILayout.ToggleLeft("Use Auto-Footsteps for 3 and 4 point tracking", autoFoot.boolValue); + autoLoco.boolValue = EditorGUILayout.ToggleLeft("Force Locomotion animations for 6 point tracking", autoLoco.boolValue); + } + + Separator(); + } + } + + void DrawAnimLayerList(string label, SerializedProperty list, Color boxColor, string buttonProperty = null) + { + BeginBox(label); + + bool isBaseLayer = (list.name == "baseAnimationLayers"); + bool isNonHumanBaseLayer = (isBaseLayer && (_animator && !_animator.isHuman)); + + for (int v = 0; v < list.arraySize; v++) + { + SerializedProperty layer = list.GetArrayElementAtIndex(v); + var type = layer.FindPropertyRelative("type"); + + DrawAnimLayerListElement(" " + System.Enum.GetName(typeof(VRCAvatarDescriptor.AnimLayerType), type.enumValueIndex), layer, isNonHumanBaseLayer); + } + + GUILayout.Space(10); + + EndBox(); + } + + void DeleteAnimLayers(SerializedProperty list, List<int> layersToDelete) + { + for (int v = (list.arraySize - 1); v > -1; v--) + { + if(layersToDelete.Contains(v)) + list.DeleteArrayElementAtIndex(v); + } + } + + // returns false if layer should be deleted + bool DrawAnimLayerListElement(string label, SerializedProperty layer, bool isNonHumanBaseLayer = false) + { + bool keepLayer = true; + + GUILayout.BeginVertical(); + GUILayout.BeginHorizontal(); + GUILayout.Label(label, GUILayout.Width(100)); + + var isDefault = layer.FindPropertyRelative("isDefault"); + var type = layer.FindPropertyRelative("type"); + var controller = layer.FindPropertyRelative("animatorController"); + + if (isDefault.boolValue && !isNonHumanBaseLayer) + { + // set next control name & focus the button if pressed + // (this is to reset 'Special' layer buttons to default if left empty in 'EnforceAnimLayerSetup') + string buttonName = GetAnimLayerButtonName((VRCAvatarDescriptor.AnimLayerType)type.enumValueIndex); + GUI.SetNextControlName(buttonName); + if (GUILayout.Button(buttonName, GUILayout.MinWidth(0))) // minwidth 0 prevents buttons from breaking margin on small inspector size + { + isDefault.boolValue = false; + GUI.FocusControl(buttonName); + } + } + else + { + float maxWidth = (Screen.width -170); // ObjectField with SerializedProperty requires some scaling lore + + GUILayout.BeginHorizontal(); + Object prev = controller.objectReferenceValue; + EditorGUILayout.ObjectField(controller, typeof(RuntimeAnimatorController), GUIContent.none, GUILayout.MaxWidth(maxWidth)); + if(controller.objectReferenceValue != prev) + isDefault.boolValue = !controller.objectReferenceValue; + GUILayout.EndHorizontal(); + } + if (isDefault.boolValue) + GUI.enabled = false; + if (GUILayout.Button("x", EditorStyles.miniButton, GUILayout.Width(20))) + { + GUI.FocusControl(null); // resets 'Special' layer buttons to default if left empty in 'EnforceAnimLayerSetup' + controller.objectReferenceValue = null; + isDefault.boolValue = true; + } + GUI.enabled = true; + GUILayout.EndHorizontal(); + GUILayout.EndVertical(); + + return keepLayer; + } + +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerGui.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerGui.cs.meta new file mode 100644 index 00000000..183ac27c --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerGui.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a3db97b1a895fe4389274438e808fc3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerInit.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerInit.cs new file mode 100644 index 00000000..0e543024 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerInit.cs @@ -0,0 +1,197 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using UnityEditor.Animations; +using System.Collections.Generic; +using VRC.SDK3.Avatars.Components; + +public partial class AvatarDescriptorEditor3 : Editor +{ + + List<EditorAnimLayerInfo> baseAnimLayerTypes = new List<EditorAnimLayerInfo> + { + {new EditorAnimLayerInfo("Locomotion", VRCAvatarDescriptor.AnimLayerType.Base)}, + {new EditorAnimLayerInfo("Idle", VRCAvatarDescriptor.AnimLayerType.Additive)}, + {new EditorAnimLayerInfo("Gesture", VRCAvatarDescriptor.AnimLayerType.Gesture)}, + {new EditorAnimLayerInfo("Action", VRCAvatarDescriptor.AnimLayerType.Action)}, + {new EditorAnimLayerInfo("Non-Transform", VRCAvatarDescriptor.AnimLayerType.FX)}, + }; + + List<EditorAnimLayerInfo> specialAnimLayerTypes = new List<EditorAnimLayerInfo> + { + {new EditorAnimLayerInfo("Sitting", VRCAvatarDescriptor.AnimLayerType.Sitting)}, + {new EditorAnimLayerInfo("TPose", VRCAvatarDescriptor.AnimLayerType.TPose)}, + {new EditorAnimLayerInfo("IKPose", VRCAvatarDescriptor.AnimLayerType.IKPose)}, + }; + + Dictionary<VRCAvatarDescriptor.AnimLayerType, string> _animLayerButtonNames = new Dictionary<VRCAvatarDescriptor.AnimLayerType, string>(); + + struct EditorAnimLayerInfo + { + public EditorAnimLayerInfo(string label, VRCAvatarDescriptor.AnimLayerType type) + { + this.label = label; + this.type = type; + } + + public string label; + public VRCAvatarDescriptor.AnimLayerType type; + } + + string GetAnimLayerButtonName(VRCAvatarDescriptor.AnimLayerType layerType) + { + string name = null; + _animLayerButtonNames.TryGetValue(layerType, out name); + if (string.IsNullOrEmpty(name)) + { + foreach (EditorAnimLayerInfo info in baseAnimLayerTypes) + { + if (info.type == layerType) + { + if (_animator && (!_animator.isHuman) && info.label == "Face") + name = "Default BlendShapes"; + else + name = ("Default " + info.label); + _animLayerButtonNames.Add(layerType, name); + return name; + } + } + foreach (EditorAnimLayerInfo info in specialAnimLayerTypes) + { + if (info.type == layerType) + { + name = ("Default " + info.label); + _animLayerButtonNames.Add(layerType, name); + return name; + } + } + } + return name; + } + + bool IsHumanOnlyAnimLayer(VRCAvatarDescriptor.AnimLayerType layerType) + { + switch (layerType) + { + case VRCAvatarDescriptor.AnimLayerType.Additive: return true; + case VRCAvatarDescriptor.AnimLayerType.Gesture: return true; + } + return false; + } + + void ResetAnimLayersToDefault() + { + var b = serializedObject.FindProperty("baseAnimationLayers"); + b.ClearArray(); + var s = serializedObject.FindProperty("specialAnimationLayers"); + s.ClearArray(); + + foreach (EditorAnimLayerInfo info in baseAnimLayerTypes) + { + if (_animator && (!_animator.isHuman) && IsHumanOnlyAnimLayer(info.type)) + continue; + InitAnimLayer(serializedObject.FindProperty("baseAnimationLayers"), info.type, true); + } + + foreach (EditorAnimLayerInfo info in specialAnimLayerTypes) + { + if (_animator && (!_animator.isHuman) && IsHumanOnlyAnimLayer(info.type)) + continue; + InitAnimLayer(serializedObject.FindProperty("specialAnimationLayers"), info.type, true); + } + + serializedObject.ApplyModifiedProperties(); + } + + void SetLayerMaskFromController(SerializedProperty layer) + { +// VRC DaveT: avoid exception when observing during runtime +#if !VRC_CLIENT + var isDefault = layer.FindPropertyRelative("isDefault").boolValue; + if (!isDefault) + { + var maskProp = layer.FindPropertyRelative("mask"); + var controller = layer.FindPropertyRelative("animatorController").objectReferenceValue as AnimatorController; + if (controller != null) + { + maskProp.objectReferenceValue = controller.layers[0].avatarMask; + } + } +#endif + } + + void InitAnimLayer(SerializedProperty list, VRCAvatarDescriptor.AnimLayerType type, bool isDefault, int index = -1) + { + list.InsertArrayElementAtIndex(list.arraySize); + var element = list.GetArrayElementAtIndex((index == -1) ? (list.arraySize - 1) : index); + + element.FindPropertyRelative("type").enumValueIndex = (int)type; + element.FindPropertyRelative("isDefault").boolValue = isDefault; + } + + void EnforceAnimLayerSetup(bool isOnEnable = false) + { + if (_animator) + { + if (_animator.isHuman) + { + // human -> add missing base layers from previous non-human setup + bool haveAdditive = false; + bool haveGesture = false; + + for (int v = 0; v < _baseAnimLayers.arraySize; v++) + { + SerializedProperty layer = _baseAnimLayers.GetArrayElementAtIndex(v); + SerializedProperty type = layer.FindPropertyRelative("type"); + if ((VRCAvatarDescriptor.AnimLayerType)type.enumValueIndex == VRCAvatarDescriptor.AnimLayerType.Additive) + { + haveAdditive = true; + } + if ((VRCAvatarDescriptor.AnimLayerType)type.enumValueIndex == VRCAvatarDescriptor.AnimLayerType.Gesture) + { + haveGesture = true; + SetLayerMaskFromController(layer); + } + } + + if (!haveAdditive) + InitAnimLayer(_baseAnimLayers, VRCAvatarDescriptor.AnimLayerType.Additive, true, 1); + + if (!haveGesture) + InitAnimLayer(_baseAnimLayers, VRCAvatarDescriptor.AnimLayerType.Gesture, true, 2); + } + else + { + // non-human -> remove excess base layers from previous human setup + List<int> baseLayersToDelete = new List<int>(); + for (int v = 0; v < _baseAnimLayers.arraySize; v++) + { + SerializedProperty layer = _baseAnimLayers.GetArrayElementAtIndex(v); + SerializedProperty type = layer.FindPropertyRelative("type"); + VRCAvatarDescriptor.AnimLayerType t = (VRCAvatarDescriptor.AnimLayerType)type.enumValueIndex; + if (IsHumanOnlyAnimLayer(t)) + baseLayersToDelete.Add(v); + } + if (baseLayersToDelete.Count > 0) + DeleteAnimLayers(_baseAnimLayers, baseLayersToDelete); + } + } + + // reset 'Special' layer buttons to default when left empty + for (int v = 0; v < _specialAnimLayers.arraySize; v++) + { + SerializedProperty layer = _specialAnimLayers.GetArrayElementAtIndex(v); + SerializedProperty type = layer.FindPropertyRelative("type"); + SerializedProperty isDefault = layer.FindPropertyRelative("isDefault"); + SerializedProperty animatorController = layer.FindPropertyRelative("animatorController"); + + if ((!isDefault.boolValue) && (animatorController.objectReferenceValue == null)) + { + if (isOnEnable || (GUI.GetNameOfFocusedControl() != GetAnimLayerButtonName((VRCAvatarDescriptor.AnimLayerType)type.enumValueIndex))) + isDefault.boolValue = true; + } + } + } + +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerInit.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerInit.cs.meta new file mode 100644 index 00000000..81ed392c --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3AnimLayerInit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ab182f5d6e71e14f80b694dbf918a17 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3EyeLook.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3EyeLook.cs new file mode 100644 index 00000000..b3a7479a --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3EyeLook.cs @@ -0,0 +1,804 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using UnityEditorInternal; +using System.Collections.Generic; +using VRC.SDK3.Avatars.Components; +using System; +using System.Linq; + + +public partial class AvatarDescriptorEditor3 : Editor +{ + static string _eyeLookFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_EyeLookFoldout"; + static string _eyeMovementFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_EyeLookFoldout_Movement"; + static string _eyeTransformFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_EyeTransformFoldout"; + static string _eyeRotationFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_EyeRotationFoldout"; + static string _eyelidTransformFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_EyelidTransformFoldout"; + static string _eyelidRotationFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_EyelidRotationFoldout"; + static string _eyelidBlendshapesFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_EyeLookFoldout_EyelidBlendshapes"; + + static string _linkEyelidsDialog = "Link Left + Right for '{0}'?\n(using values of: {1})"; + + static string _activeProperty = null; + static Color _activeButtonColor = new Color(0.5f, 1, 0.5f, 1); + + SkinnedMeshRenderer _currentEyelidsMesh; + string[] _eyelidBlendshapeNames; + + static Texture _linkIcon; + + static List<System.Action> activePropertyRestore = new List<System.Action>(); + + void InitEyeLook() + { + if (_linkIcon == null) + _linkIcon = Resources.Load<Texture>("EditorUI_Icons/EditorUI_Link"); + + EditorPrefs.SetBool(_eyeTransformFoldoutPrefsKey, true); + EditorPrefs.SetBool(_eyelidTransformFoldoutPrefsKey, true); + } + + void DrawEyeLook() + { + if (Foldout(_eyeLookFoldoutPrefsKey, "Eye Look")) + { + SerializedProperty p = serializedObject.FindProperty("enableEyeLook"); + + bool toggle = GUILayout.Button(p.boolValue ? "Disable" : "Enable"); + + if (toggle) + p.boolValue = !p.boolValue; + + if (p.boolValue) + { + var eyeSettings = serializedObject.FindProperty("customEyeLookSettings"); + + EditorGUILayout.BeginVertical(); + + DrawEyesGeneralBox(eyeSettings); + DrawEyesBox(eyeSettings); + DrawEyelidsBox(eyeSettings); + + EditorGUILayout.EndVertical(); + } + + Separator(); + } + } + + static void DrawEyesGeneralBox(SerializedProperty eyeSettings) + { + + BeginBox("General", true); + + if (Foldout(_eyeMovementFoldoutPrefsKey, "Eye Movements")) + { + var eyeMovement = eyeSettings.FindPropertyRelative("eyeMovement"); + var confidence = eyeMovement.FindPropertyRelative("confidence"); + var excitement = eyeMovement.FindPropertyRelative("excitement"); + + EyeLookMovementSlider(excitement, "Calm", "Excited"); + EyeLookMovementSlider(confidence, "Shy", "Confident"); + } + + EndBox(); + } + + void DrawEyesBox(SerializedProperty eyeSettings) + { + BeginBox("Eyes", true); + + var leftEye = eyeSettings.FindPropertyRelative("leftEye"); + var rightEye = eyeSettings.FindPropertyRelative("rightEye"); + + if (Foldout(_eyeTransformFoldoutPrefsKey, "Transforms", true)) + { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(leftEye, new GUIContent("Left Eye Bone")); + EditorGUILayout.PropertyField(rightEye, new GUIContent("Right Eye Bone")); + if(EditorGUI.EndChangeCheck()) + SetActiveProperty(null); //Disable active property, maintains proper preview/undo state + } + Separator(); + + EditorGUI.BeginDisabledGroup(leftEye.objectReferenceValue == null || rightEye.objectReferenceValue == null); + if (Foldout(_eyeRotationFoldoutPrefsKey, "Rotation States")) + { + var eyesLookingStraight = eyeSettings.FindPropertyRelative("eyesLookingStraight"); + var eyesLookingUp = eyeSettings.FindPropertyRelative("eyesLookingUp"); + var eyesLookingDown = eyeSettings.FindPropertyRelative("eyesLookingDown"); + var eyesLookingLeft = eyeSettings.FindPropertyRelative("eyesLookingLeft"); + var eyesLookingRight = eyeSettings.FindPropertyRelative("eyesLookingRight"); + + RotationFieldEyeLook(eyesLookingStraight, leftEye, rightEye, "Looking Straight"); + RotationFieldEyeLook(eyesLookingUp, leftEye, rightEye, "Looking Up", () => { BeginEyesUpDown(true); }); + RotationFieldEyeLook(eyesLookingDown, leftEye, rightEye, "Looking Down", () => { BeginEyesUpDown(false); }); + RotationFieldEyeLook(eyesLookingLeft, leftEye, rightEye, "Looking Left"); + RotationFieldEyeLook(eyesLookingRight, leftEye, rightEye, "Looking Right"); + } + EditorGUI.EndDisabledGroup(); + + EndBox(); + } + void RotationFieldEyeLook(SerializedProperty property, SerializedProperty leftEyeBone, SerializedProperty rightEyeBone, string label, System.Action onSetActive=null) + { + RotationField(property, leftEyeBone, rightEyeBone, label, isActive: IsActiveProperty(property), SetActive: SetActive); + void SetActive() + { + //Set + SetActiveProperty(property); + + //Check for transforms + if (((Transform)leftEyeBone.objectReferenceValue) == null || ((Transform)rightEyeBone.objectReferenceValue) == null) + return; + + //Record + RecordEyeRotations(property, leftEyeBone, rightEyeBone); + + //Other + onSetActive?.Invoke(); + } + } + + void DrawEyelidsBox(SerializedProperty eyeSettings) + { + BeginBox("Eyelids", true); + + DrawEyelidType(eyeSettings); + + if (avatarDescriptor.customEyeLookSettings.eyelidType == VRCAvatarDescriptor.EyelidType.Blendshapes) + { + DrawEyelidBlendshapeDropdowns(eyeSettings); + } + else if (avatarDescriptor.customEyeLookSettings.eyelidType == VRCAvatarDescriptor.EyelidType.Bones) + { + DrawEyelidBoneRotations(eyeSettings); + _currentEyelidsMesh = null; + } + else + { + _currentEyelidsMesh = null; + } + + EndBox(); + } + void DrawEyelidType(SerializedProperty eyeSettings) + { + EditorGUI.BeginChangeCheck(); + var eyelidType = eyeSettings.FindPropertyRelative("eyelidType"); + EditorGUILayout.PropertyField(eyelidType); + if (EditorGUI.EndChangeCheck()) + { + if (eyelidType.enumValueIndex == (int)VRCAvatarDescriptor.EyelidType.Blendshapes) + EditorPrefs.SetBool(_eyelidBlendshapesFoldoutPrefsKey, true); + } + } + void DrawEyelidBoneRotations(SerializedProperty eyeSettings) + { + Separator(); + + var upperLeftEyelid = eyeSettings.FindPropertyRelative("upperLeftEyelid"); + var upperRightEyelid = eyeSettings.FindPropertyRelative("upperRightEyelid"); + var lowerLeftEyelid = eyeSettings.FindPropertyRelative("lowerLeftEyelid"); + var lowerRightEyelid = eyeSettings.FindPropertyRelative("lowerRightEyelid"); + + if (Foldout(_eyelidTransformFoldoutPrefsKey, "Transforms", true)) + { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(upperLeftEyelid); + EditorGUILayout.PropertyField(upperRightEyelid); + EditorGUILayout.PropertyField(lowerLeftEyelid); + EditorGUILayout.PropertyField(lowerRightEyelid); + if (EditorGUI.EndChangeCheck()) + SetActiveProperty(null); //Disable active property, maintains proper preview/undo state + } + Separator(); + if (Foldout(_eyelidRotationFoldoutPrefsKey, "Rotation States")) + { + var eyelidsDefault = eyeSettings.FindPropertyRelative("eyelidsDefault"); + var eyelidsClosed = eyeSettings.FindPropertyRelative("eyelidsClosed"); + var eyelidsLookingUp = eyeSettings.FindPropertyRelative("eyelidsLookingUp"); + var eyelidsLookingDown = eyeSettings.FindPropertyRelative("eyelidsLookingDown"); + + GUILayout.BeginHorizontal(); + GUILayout.Space(16); + GUILayout.BeginVertical(); + RotationFieldEyelids(eyelidsDefault, upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid, "Default"); + RotationFieldEyelids(eyelidsClosed, upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid, "Closed"); + RotationFieldEyelids(eyelidsLookingUp, upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid, "Looking Up", () => { BeginEyesUpDown(true); } ); + RotationFieldEyelids(eyelidsLookingDown, upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid, "Looking Down", () => { BeginEyesUpDown(false); }); + + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + } + void RotationFieldEyelids(SerializedProperty property, + SerializedProperty upperLeftBone, SerializedProperty upperRightBone, + SerializedProperty lowerLeftBone, SerializedProperty lowerRightBone, + string label = null, + System.Action onSetActive=null) + { + var upperProperty = property.FindPropertyRelative("upper"); + var lowerProperty = property.FindPropertyRelative("lower"); + GUILayout.BeginHorizontal(); + if (EditorGUILayout.PropertyField(property, new GUIContent(label), GUILayout.MinWidth(100))) + { + Button(); + GUILayout.EndHorizontal(); + GUILayout.BeginVertical(); + + RotationField(upperProperty, upperLeftBone, upperRightBone, "Upper Eyelids", showButton:false, isActive:IsActiveProperty(property), SetActive:SetActive); + RotationField(lowerProperty, lowerLeftBone, lowerRightBone, "Lower Eyelids", showButton:false, isActive:IsActiveProperty(property), SetActive:SetActive); + GUILayout.EndVertical(); + } + else + { + Button(); + GUILayout.EndHorizontal(); + } + + void SetActive() + { + SetActiveProperty(property); + + //Record + RecordEyeRotations(upperProperty, upperLeftBone, upperRightBone); + RecordEyeRotations(lowerProperty, lowerLeftBone, lowerRightBone); + + //Other + onSetActive?.Invoke(); + } + + void Button() + { + bool isActiveProperty = IsActiveProperty(property); + GUI.backgroundColor = isActiveProperty ? _activeButtonColor : Color.white; + if (GUILayout.Button(isActiveProperty ? "Return" : "Preview", EditorStyles.miniButton, GUILayout.MaxWidth(PreviewButtonWidth), GUILayout.Height(PreviewButtonHeight))) + { + if(isActiveProperty) + { + SetActiveProperty(null); + } + else + { + SetActive(); + } + } + GUI.backgroundColor = Color.white; + } + } + void DrawEyelidBlendshapeDropdowns(SerializedProperty eyeSettings) + { + Separator(); + + var eyelidsMeshProp = eyeSettings.FindPropertyRelative("eyelidsSkinnedMesh"); + eyelidsMeshProp.objectReferenceValue = (SkinnedMeshRenderer)EditorGUILayout.ObjectField("Eyelids Mesh", eyelidsMeshProp.objectReferenceValue, typeof(SkinnedMeshRenderer), true); + + if (eyelidsMeshProp.objectReferenceValue == null) + { + _eyelidBlendshapeNames = null; + return; + } + + if (_currentEyelidsMesh == null) + { + _currentEyelidsMesh = (SkinnedMeshRenderer)eyelidsMeshProp.objectReferenceValue; + _eyelidBlendshapeNames = GetBlendShapeNames(_currentEyelidsMesh); + } + + var eyelidsBlendshapes = eyeSettings.FindPropertyRelative("eyelidsBlendshapes"); + + if (Foldout(_eyelidBlendshapesFoldoutPrefsKey, "Blendshape States")) + { + + if (eyelidsBlendshapes.arraySize != 3) + eyelidsBlendshapes.arraySize = 3; + int[] indices = new int[] { eyelidsBlendshapes.GetArrayElementAtIndex(0).intValue, + eyelidsBlendshapes.GetArrayElementAtIndex(1).intValue, + eyelidsBlendshapes.GetArrayElementAtIndex(2).intValue}; + + PreviewBlendshapeField("Blink", 0, eyelidsBlendshapes.GetArrayElementAtIndex(0)); + PreviewBlendshapeField("Looking Up", 1, eyelidsBlendshapes.GetArrayElementAtIndex(1), () => { BeginEyesUpDown(true); }); + PreviewBlendshapeField("Looking Down", 2, eyelidsBlendshapes.GetArrayElementAtIndex(2), () => { BeginEyesUpDown(false); }); + } + } + + /*static float NormalizedDegAngle ( float degrees ) + { + int factor = (int) (degrees/360); + degrees -= factor * 360; + if ( degrees > 180 ) + return degrees - 360; + + if ( degrees < -180 ) + return degrees + 360; + + return degrees; + } + static Vector3 NormalizedEulers(Vector3 eulers) + { + Vector3 normEulers; + normEulers.x = NormalizedDegAngle(eulers.x); + normEulers.y = NormalizedDegAngle(eulers.y); + normEulers.z = NormalizedDegAngle(eulers.z); + + return normEulers; + }*/ + static int PreviewButtonWidth = 55; + static int PreviewButtonHeight = 24; + + static void RecordEyeRotations(SerializedProperty property, SerializedProperty leftEyeBone, SerializedProperty rightEyeBone) + { + //Record restore point + var transformL = (Transform)leftEyeBone.objectReferenceValue; + var transformR = (Transform)rightEyeBone.objectReferenceValue; + var prevRotationL = transformL != null ? transformL.localRotation : Quaternion.identity; + var prevRotationR = transformR != null ? transformR.localRotation : Quaternion.identity; + System.Action restore = () => + { + if (transformL != null) + transformL.localRotation = prevRotationL; + if (transformR != null) + transformR.localRotation = prevRotationR; + }; + activePropertyRestore.Add(restore); + + //Set to value + var leftRotation = property.FindPropertyRelative("left"); + var rightRotation = property.FindPropertyRelative("right"); + if(transformL != null) + transformL.localRotation = leftRotation.quaternionValue; + if (transformR != null) + transformR.localRotation = rightRotation.quaternionValue; + } + void BeginEyesUpDown(bool isUp) + { + var eyeSettings = serializedObject.FindProperty("customEyeLookSettings"); + + //Record - Eye Up + { + var eyesLooking = eyeSettings.FindPropertyRelative(isUp ? "eyesLookingUp" : "eyesLookingDown"); + var leftEye = eyeSettings.FindPropertyRelative("leftEye"); + var rightEye = eyeSettings.FindPropertyRelative("rightEye"); + RecordEyeRotations(eyesLooking, leftEye, rightEye); + } + + //Record - Eyelid Up Bones + if (avatarDescriptor.customEyeLookSettings.eyelidType == VRCAvatarDescriptor.EyelidType.Bones) + { + var upperLeftEyelid = eyeSettings.FindPropertyRelative("upperLeftEyelid"); + var upperRightEyelid = eyeSettings.FindPropertyRelative("upperRightEyelid"); + var lowerLeftEyelid = eyeSettings.FindPropertyRelative("lowerLeftEyelid"); + var lowerRightEyelid = eyeSettings.FindPropertyRelative("lowerRightEyelid"); + + var eyelidsLooking = eyeSettings.FindPropertyRelative(isUp ? "eyelidsLookingUp" : "eyelidsLookingDown"); + var upperProperty = eyelidsLooking.FindPropertyRelative("upper"); + var lowerProperty = eyelidsLooking.FindPropertyRelative("lower"); + + RecordEyeRotations(upperProperty, upperLeftEyelid, upperRightEyelid); + RecordEyeRotations(lowerProperty, lowerLeftEyelid, lowerRightEyelid); + } + + //Record - Eyelid Blendshapes + if (avatarDescriptor.customEyeLookSettings.eyelidType == VRCAvatarDescriptor.EyelidType.Blendshapes) + { + var eyelidsBlendshapes = eyeSettings.FindPropertyRelative("eyelidsBlendshapes"); + var blendshapeIndex = eyelidsBlendshapes.GetArrayElementAtIndex(isUp ? 1 : 2); + + RecordBlendShape(blendshapeIndex.intValue); + } + } + + static Vector3 EditorQuaternionToVector3(Quaternion value) + { + var result = value.eulerAngles; + + //Fix the axis flipping + if(Mathf.Approximately(value.eulerAngles.y, 180) && Mathf.Approximately(value.eulerAngles.z, 180)) + { + if (result.x < 90.0f) + result.x = 90f + (90f - result.x); + else + result.x = 270f + (270f - result.x); + result.y = 0; + result.z = 0; + } + + //Represent angle as -180 to 180 + if (result.x > 180.0f) + result.x = -(360f-result.x); + if (result.y > 180.0f) + result.y = -(360f - result.y); + if (result.z > 180.0f) + result.z = -(360f - result.z); + + //Prevent small number in editor, they arn't nessecary here + if (result.x < 0.001f && result.x > -0.001f) + result.x = 0f; + if (result.y < 0.001f && result.y > -0.001f) + result.y = 0f; + if (result.z < 0.001f && result.z > -0.001f) + result.z = 0f; + + //Return + return result; + } + static bool RotationField(SerializedProperty property, SerializedProperty leftEyeBone, SerializedProperty rightEyeBone, string label = null, bool showButton = true, bool isActive=false, System.Action SetActive=null) + { + bool dirty = false; + + EditorGUI.BeginChangeCheck(); + GUILayout.BeginHorizontal(); + var leftRotation = property.FindPropertyRelative("left"); + var rightRotation = property.FindPropertyRelative("right"); + var linked = property.FindPropertyRelative("linked"); + GUILayout.BeginVertical(); + label = string.IsNullOrEmpty(label) ? property.displayName : label; + if (GUILayout.Button(label, EditorStyles.label)) + dirty = true; + if (linked.boolValue) + { + GUILayout.BeginHorizontal(); + { + //Link button + GUI.color = GUI.skin.label.normal.textColor; + if (GUILayout.Button(new GUIContent(_linkIcon), GUI.skin.label, GUILayout.MaxWidth(16))) + linked.boolValue = false; + GUI.color = Color.white; + + var testA1 = Quaternion.Euler(new Vector3(20, 0, 0)).eulerAngles; + var testA2 = Quaternion.Euler(new Vector3(45, 0, 0)).eulerAngles; + var testA3 = Quaternion.Euler(new Vector3(90, 0, 0)).eulerAngles; + var testA4 = Quaternion.Euler(new Vector3(95, 0, 0)).eulerAngles; + var testA5 = Quaternion.Euler(new Vector3(120, 0, 0)).eulerAngles; + + var testB1 = EditorQuaternionToVector3(Quaternion.Euler(new Vector3(20, 0, 0))); + var testB2 = EditorQuaternionToVector3(Quaternion.Euler(new Vector3(45, 0, 0))); + var testB3 = EditorQuaternionToVector3(Quaternion.Euler(new Vector3(90, 0, 0))); + var testB4 = EditorQuaternionToVector3(Quaternion.Euler(new Vector3(95, 0, 0))); + var testB5 = EditorQuaternionToVector3(Quaternion.Euler(new Vector3(120, 0, 0))); + + //Values + //leftRotation.quaternionValue = EditorGUILayout.Vector3Field(GUIContent.none, QuaternionToVector3(leftRotation.quaternionValue), GUILayout.MinWidth(100))); + leftRotation.quaternionValue = Quaternion.Euler( EditorGUILayout.Vector3Field(GUIContent.none, EditorQuaternionToVector3(leftRotation.quaternionValue), GUILayout.MinWidth(100)) ); + rightRotation.quaternionValue = leftRotation.quaternionValue; + } + GUILayout.EndHorizontal(); + } + else + { + GUIStyle style = new GUIStyle(EditorStyles.boldLabel); + style.contentOffset = new Vector2(0, -4); + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("L", style, GUILayout.MaxHeight(16))) + { + string message = string.Format(_linkEyelidsDialog, label, "L"); + if ((rightRotation.quaternionValue == leftRotation.quaternionValue) + || EditorUtility.DisplayDialog("Collapse?", message, "Yes", "No")) + { + linked.boolValue = true; + rightRotation.quaternionValue = leftRotation.quaternionValue; + } + } + leftRotation.quaternionValue = Quaternion.Euler(EditorGUILayout.Vector3Field(GUIContent.none, EditorQuaternionToVector3(leftRotation.quaternionValue), GUILayout.MinWidth(100))); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + if (GUILayout.Button("R", style, GUILayout.MaxHeight(16))) + { + string message = string.Format(_linkEyelidsDialog, label, "R"); + if ((leftRotation.quaternionValue == rightRotation.quaternionValue) + || (EditorUtility.DisplayDialog("Collapse?", message, "Yes", "No"))) + { + linked.boolValue = true; + leftRotation.quaternionValue = rightRotation.quaternionValue; + } + } + rightRotation.quaternionValue = Quaternion.Euler(EditorGUILayout.Vector3Field(GUIContent.none, EditorQuaternionToVector3(rightRotation.quaternionValue), GUILayout.MinWidth(100))); + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + GUILayout.BeginVertical(); + bool changed = EditorGUI.EndChangeCheck(); + + //Edit button + if (showButton) + { + GUI.backgroundColor = (isActive ? _activeButtonColor : Color.white); + GUILayout.BeginVertical(); + GUILayout.Space(linked.boolValue ? 4 : 20); + if (GUILayout.Button(isActive ? "Return" : "Preview", EditorStyles.miniButton, GUILayout.Width(PreviewButtonWidth), GUILayout.Height(PreviewButtonHeight))) + { + if (isActive) + { + SetActiveProperty(null); + isActive = false; + } + else + { + SetActive(); + isActive = true; + } + dirty = _repaint = true; + } + GUILayout.EndVertical(); + GUI.backgroundColor = Color.white; + } + + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + + //Mark active if changed + if(changed) + { + //Set active if not already + if (!isActive) + { + SetActive(); + isActive = true; + } + + //Mark dirty + dirty = _repaint = true; + } + + //Update values, always update if active not only on change. + //We do this because the user may change values with a control-z/undo operation + if (isActive) + { + //Left + var leftEyeTransform = (Transform)leftEyeBone.objectReferenceValue; + if (leftEyeTransform != null) + leftEyeTransform.localRotation = leftRotation.quaternionValue; + + //Right + var rightEyeTransform = (Transform)rightEyeBone.objectReferenceValue; + if (rightEyeTransform != null) + rightEyeTransform.localRotation = rightRotation.quaternionValue; + } + + //Return + return dirty; + } + + void PreviewBlendshapeField(string label, int buttonIndex, SerializedProperty blendShapeIndex, System.Action onSetActive=null) + { + if (_eyelidBlendshapeNames == null) + return; + + bool setActive = false; + GUILayout.BeginHorizontal(); + { + //Dropdown + EditorGUI.BeginChangeCheck(); + blendShapeIndex.intValue = EditorGUILayout.Popup(label, blendShapeIndex.intValue + 1, _eyelidBlendshapeNames) - 1; + if (EditorGUI.EndChangeCheck()) + { + SetActiveProperty(null); + setActive = true; + } + + //Preview + bool isActiveProperty = IsActiveProperty(blendShapeIndex); + GUI.backgroundColor = isActiveProperty ? _activeButtonColor : Color.white; + if (GUILayout.Button(isActiveProperty ? "Return" : "Preview", EditorStyles.miniButton, GUILayout.MaxWidth(PreviewButtonWidth)) || setActive) + { + if (isActiveProperty) + { + SetActiveProperty(null); + } + else + { + onSetActive?.Invoke(); + SetActiveProperty(blendShapeIndex); + + //Record + RecordBlendShape(blendShapeIndex.intValue); + + //Other + onSetActive?.Invoke(); + } + } + GUI.backgroundColor = Color.white; + } + GUILayout.EndHorizontal(); + } + void RecordBlendShape(int index, float newWeight = 100.0f) + { + //Validate + if (avatarDescriptor.customEyeLookSettings.eyelidsSkinnedMesh == null || index < 0 || index >= avatarDescriptor.customEyeLookSettings.eyelidsSkinnedMesh.sharedMesh.blendShapeCount) + return; + + //Record old position + int oldIndex = index; + float oldWeight = avatarDescriptor.customEyeLookSettings.eyelidsSkinnedMesh.GetBlendShapeWeight(index); + System.Action restore = () => + { + avatarDescriptor.customEyeLookSettings.eyelidsSkinnedMesh.SetBlendShapeWeight(oldIndex, oldWeight); + }; + activePropertyRestore.Add(restore); + + //Set new weight + avatarDescriptor.customEyeLookSettings.eyelidsSkinnedMesh.SetBlendShapeWeight(index, newWeight); + } + + void ResetBlendshapes(int[] indices) + { + for (int v = 0; v < indices.Length; v++) + { + if (indices[v] < 0) continue; + avatarDescriptor.customEyeLookSettings.eyelidsSkinnedMesh.SetBlendShapeWeight(indices[v], 0); + } + } + + static void EyeLookMovementSlider(SerializedProperty property, string minLabel, string maxLabel) + { + + GUIStyle style = new GUIStyle(EditorStyles.miniLabel); + style.alignment = TextAnchor.MiddleRight; + style.padding.left = 10; + style.padding.right = 10; + + GUILayout.BeginHorizontal(); + + GUILayout.Label(GUIContent.none); + Rect r = GUILayoutUtility.GetLastRect(); + + // left word + float leftLabelWidth = r.width * 0.2f; + float rightLabelWidth = r.width * 0.3f; + float sliderWidth = r.width * 0.5f; + r.width = leftLabelWidth; + GUI.Label(r, minLabel, style); + r.x += r.width; + + // slider + r.width = sliderWidth; + property.floatValue = GUI.HorizontalSlider(r, property.floatValue, 0f, 1f); + property.floatValue = Mathf.Round(property.floatValue * 10) * 0.1f; + r.x += r.width; + + // right word + r.width = rightLabelWidth; + style.alignment = TextAnchor.MiddleLeft; + GUI.Label(r, maxLabel, style); + + GUILayout.EndHorizontal(); + } + + static bool IsActiveProperty(SerializedProperty property) + { + return (_activeProperty != null) && _activeProperty.Equals(property.propertyPath, System.StringComparison.Ordinal); + } + static void SetActiveProperty(SerializedProperty property) + { + if (_activeProperty == property?.propertyPath) + return; + + //Set + _activeProperty = (property?.propertyPath); + + //Restore previous state + activePropertyRestore.Reverse(); //Iterate from last to first + foreach (var restore in activePropertyRestore) + restore(); + activePropertyRestore.Clear(); + + //Redraw + _repaint = true; + } + + public static string[] GetBlendShapeNames(SkinnedMeshRenderer skinnedMeshRenderer) + { + if (!skinnedMeshRenderer) + return null; + string[] names = new string[skinnedMeshRenderer.sharedMesh.blendShapeCount+1]; + names[0] = "-none-"; + for (int v = 0; v < skinnedMeshRenderer.sharedMesh.blendShapeCount; v++) + { + names[v+1] = skinnedMeshRenderer.sharedMesh.GetBlendShapeName(v); + } + return names; + } + + void DrawSceneViewpoint() + { + var viewPosition = serializedObject.FindProperty("ViewPosition"); + if(IsActiveProperty(viewPosition)) + { + viewPosition.vector3Value = Handles.PositionHandle(viewPosition.vector3Value, Quaternion.identity); + } + } + + void DrawSceneEyeLook() + { + var eyeSettings = serializedObject.FindProperty("customEyeLookSettings"); + var leftEye = eyeSettings.FindPropertyRelative("leftEye"); + var rightEye = eyeSettings.FindPropertyRelative("rightEye"); + + //Eye Rotation State + DrawSceneEyes(eyeSettings.FindPropertyRelative("eyesLookingStraight"), leftEye, rightEye); + DrawSceneEyes(eyeSettings.FindPropertyRelative("eyesLookingUp"), leftEye, rightEye); + DrawSceneEyes(eyeSettings.FindPropertyRelative("eyesLookingDown"), leftEye, rightEye); + DrawSceneEyes(eyeSettings.FindPropertyRelative("eyesLookingLeft"), leftEye, rightEye); + DrawSceneEyes(eyeSettings.FindPropertyRelative("eyesLookingRight"), leftEye, rightEye); + + //Eyelid Rotation States + if(avatarDescriptor.customEyeLookSettings.eyelidType == VRCAvatarDescriptor.EyelidType.Bones) + { + var upperLeftEyelid = eyeSettings.FindPropertyRelative("upperLeftEyelid"); + var upperRightEyelid = eyeSettings.FindPropertyRelative("upperRightEyelid"); + var lowerLeftEyelid = eyeSettings.FindPropertyRelative("lowerLeftEyelid"); + var lowerRightEyelid = eyeSettings.FindPropertyRelative("lowerRightEyelid"); + + DrawSceneEyelids(eyeSettings.FindPropertyRelative("eyelidsDefault"), upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid); + DrawSceneEyelids(eyeSettings.FindPropertyRelative("eyelidsClosed"), upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid); + DrawSceneEyelids(eyeSettings.FindPropertyRelative("eyelidsLookingUp"), upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid); + DrawSceneEyelids(eyeSettings.FindPropertyRelative("eyelidsLookingDown"), upperLeftEyelid, upperRightEyelid, lowerLeftEyelid, lowerRightEyelid); + } + } + void DrawSceneEyes(SerializedProperty property, SerializedProperty leftEye, SerializedProperty rightEye, bool checkActive=true) + { + if (checkActive && !IsActiveProperty(property)) + return; + + var leftRotation = property.FindPropertyRelative("left"); + var rightRotation = property.FindPropertyRelative("right"); + var linked = property.FindPropertyRelative("linked").boolValue; + + bool changeL = DrawRotationHandles(leftEye, leftRotation); + bool changeR = DrawRotationHandles(rightEye, rightRotation); + + if(linked) + { + if(changeL) + { + var rotation = leftRotation.quaternionValue; + (rightEye.objectReferenceValue as Transform).localRotation = rotation; + rightRotation.quaternionValue = rotation; + } + else if (changeR) + { + var rotation = rightRotation.quaternionValue; + (leftEye.objectReferenceValue as Transform).localRotation = rotation; + leftRotation.quaternionValue = rotation; + } + } + } + void DrawSceneEyelids(SerializedProperty property, SerializedProperty upperLeftEyelid, SerializedProperty upperRightEyelid, SerializedProperty lowerLeftEyelid, SerializedProperty lowerRightEyelid) + { + if (!IsActiveProperty(property)) + return; + + var upperProperty = property.FindPropertyRelative("upper"); + var lowerProperty = property.FindPropertyRelative("lower"); + + DrawSceneEyes(upperProperty, upperLeftEyelid, upperRightEyelid, false); + DrawSceneEyes(lowerProperty, lowerLeftEyelid, lowerRightEyelid, false); + } + bool DrawRotationHandles(SerializedProperty transformProperty, SerializedProperty rotationProperty) + { + var transform = transformProperty.objectReferenceValue as Transform; + if (transform == null) + return false; + Handles.matrix = Matrix4x4.TRS(transform.position, transform.parent.rotation, Vector3.one); + + transform.localRotation = rotationProperty.quaternionValue; + var result = Handles.RotationHandle(transform.localRotation, Vector3.zero); + if (result != transform.localRotation) + { + transform.localRotation = result; + rotationProperty.quaternionValue = result; + return true; + } + + Handles.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); + Handles.color = new Color(1, 1, 1, 0.5f); + Handles.DrawWireDisc(Vector3.zero, Vector3.forward, 0.01f); + + return false; + } +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3EyeLook.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3EyeLook.cs.meta new file mode 100644 index 00000000..c2b709a5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3EyeLook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9318bd935bb1bc24183ddf154b1fa7a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3LipSync.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3LipSync.cs new file mode 100644 index 00000000..334a5670 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3LipSync.cs @@ -0,0 +1,402 @@ +#if VRC_SDK_VRCSDK3 +using System; +using UnityEngine; +using UnityEditor; +using UnityEditorInternal; +using System.Collections.Generic; +using VRC.SDK3.Avatars.Components; +using VRC.SDKBase; + +public partial class AvatarDescriptorEditor3 : Editor +{ + + SkinnedMeshRenderer selectedMesh; + List<string> blendShapeNames = null; + + bool shouldRefreshVisemes = false; + + bool lipsyncFoldout; + + public void DrawLipSync() + { + if (Foldout("VRCSDK3_AvatarDescriptorEditor3_LipSyncFoldout", "LipSync")) + { + + avatarDescriptor.lipSync = (VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle)EditorGUILayout.EnumPopup("Mode", avatarDescriptor.lipSync); + switch (avatarDescriptor.lipSync) + { + + case VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.Default: + if (GUILayout.Button("Auto Detect!")) + AutoDetectLipSync(); + break; + + case VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape: + avatarDescriptor.VisemeSkinnedMesh = (SkinnedMeshRenderer)EditorGUILayout.ObjectField("Face Mesh", avatarDescriptor.VisemeSkinnedMesh, typeof(SkinnedMeshRenderer), true); + if (avatarDescriptor.VisemeSkinnedMesh != null) + { + DetermineBlendShapeNames(); + + int current = -1; + for (int b = 0; b < blendShapeNames.Count; ++b) + if (avatarDescriptor.MouthOpenBlendShapeName == blendShapeNames[b]) + current = b; + + string title = "Jaw Flap Blend Shape"; + int next = EditorGUILayout.Popup(title, current, blendShapeNames.ToArray()); + if (next >= 0) + avatarDescriptor.MouthOpenBlendShapeName = blendShapeNames[next]; + } + break; + + case VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.JawFlapBone: + DrawJawBone(); + break; + + case VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape: + SkinnedMeshRenderer prev = avatarDescriptor.VisemeSkinnedMesh; + avatarDescriptor.VisemeSkinnedMesh = (SkinnedMeshRenderer)EditorGUILayout.ObjectField("Face Mesh", avatarDescriptor.VisemeSkinnedMesh, typeof(SkinnedMeshRenderer), true); + if (avatarDescriptor.VisemeSkinnedMesh != prev) + shouldRefreshVisemes = true; + if (avatarDescriptor.VisemeSkinnedMesh != null) + { + DetermineBlendShapeNames(); + + if (avatarDescriptor.VisemeBlendShapes == null || avatarDescriptor.VisemeBlendShapes.Length != (int)VRC.SDKBase.VRC_AvatarDescriptor.Viseme.Count) + avatarDescriptor.VisemeBlendShapes = new string[(int)VRC.SDKBase.VRC_AvatarDescriptor.Viseme.Count]; + for (int i = 0; i < (int)VRC.SDKBase.VRC_AvatarDescriptor.Viseme.Count; ++i) + { + int current = -1; + for (int b = 0; b < blendShapeNames.Count; ++b) + if (avatarDescriptor.VisemeBlendShapes[i] == blendShapeNames[b]) + current = b; + + string title = "Viseme: " + ((VRC.SDKBase.VRC_AvatarDescriptor.Viseme)i).ToString(); + int next = EditorGUILayout.Popup(title, current, blendShapeNames.ToArray()); + if (next >= 0) + avatarDescriptor.VisemeBlendShapes[i] = blendShapeNames[next]; + } + + if (shouldRefreshVisemes) + AutoDetectVisemes(); + } + break; + + case VRC.SDKBase.VRC_AvatarDescriptor.LipSyncStyle.VisemeParameterOnly: + break; + } + Separator(); + } + } + public void DrawSceneLipSync() + { + var transformProp = serializedObject.FindProperty("lipSyncJawBone"); + DrawRotationState(serializedObject.FindProperty("lipSyncJawClosed")); + DrawRotationState(serializedObject.FindProperty("lipSyncJawOpen")); + + void DrawRotationState(SerializedProperty property) + { + //Check if active + if (!IsActiveProperty(property)) + return; + + //Draw handle + DrawRotationHandles(transformProp, property); + } + } + + void DrawJawBone() + { + var lipSyncJawClosed = serializedObject.FindProperty("lipSyncJawClosed"); + var lipSyncJawOpen = serializedObject.FindProperty("lipSyncJawOpen"); + + //Transform + EditorGUI.BeginChangeCheck(); + avatarDescriptor.lipSyncJawBone = (Transform)EditorGUILayout.ObjectField("Jaw Bone", avatarDescriptor.lipSyncJawBone, typeof(Transform), true); + if(EditorGUI.EndChangeCheck()) + { + if(avatarDescriptor.lipSyncJawBone != null) + { + lipSyncJawClosed.quaternionValue = avatarDescriptor.lipSyncJawBone.localRotation; + lipSyncJawOpen.quaternionValue = avatarDescriptor.lipSyncJawBone.localRotation; + } + } + + //Rotation states + EditorGUILayout.LabelField("Rotation States"); + EditorGUI.indentLevel += 1; + GUI.enabled = avatarDescriptor.lipSyncJawBone != null; + { + DrawRotationState("Closed", lipSyncJawClosed); + DrawRotationState("Open", lipSyncJawOpen); + } + GUI.enabled = true; + EditorGUI.indentLevel -= 1; + + + void DrawRotationState(string name, SerializedProperty property) + { + GUILayout.BeginHorizontal(); + { + //Vector + EditorGUI.BeginChangeCheck(); + var result = EditorGUILayout.Vector3Field(name, EditorQuaternionToVector3(property.quaternionValue)); + if (EditorGUI.EndChangeCheck()) + { + property.quaternionValue = Quaternion.Euler(result); + SetActive(); + } + + //Edit Button + bool isActiveProperty = IsActiveProperty(property); + GUI.backgroundColor = isActiveProperty ? _activeButtonColor : Color.white; + if (GUILayout.Button(isActiveProperty ? "Return" : "Preview", EditorStyles.miniButton, GUILayout.MaxWidth(PreviewButtonWidth), GUILayout.Height(PreviewButtonHeight))) + { + if (isActiveProperty) + SetActiveProperty(null); + else + SetActive(); + } + GUI.backgroundColor = Color.white; + } + GUILayout.EndHorizontal(); + + void SetActive() + { + //Set active + SetActiveProperty(property); + + //Record restore point + var prevBone = avatarDescriptor.lipSyncJawBone; + var prevRotation = prevBone.transform.localRotation; + System.Action restore = () => + { + if (prevBone != null) + prevBone.localRotation = prevRotation; + }; + activePropertyRestore.Add(restore); + + //Set + avatarDescriptor.lipSyncJawBone.transform.localRotation = property.quaternionValue; + } + } + } + + void DetermineBlendShapeNames() + { + if (avatarDescriptor.VisemeSkinnedMesh != null && + avatarDescriptor.VisemeSkinnedMesh != selectedMesh) + { + blendShapeNames = new List<string>(); + blendShapeNames.Add("-none-"); + selectedMesh = avatarDescriptor.VisemeSkinnedMesh; + for (int i = 0; i < selectedMesh.sharedMesh.blendShapeCount; ++i) + blendShapeNames.Add(selectedMesh.sharedMesh.GetBlendShapeName(i)); + } + } + + void AutoDetectVisemes() + { + // prioritize strict - but fallback to looser - naming and don't touch user-overrides + + List<string> blendShapes = new List<string>(blendShapeNames); + blendShapes.Remove("-none-"); + + for (int v = 0; v < avatarDescriptor.VisemeBlendShapes.Length; v++) + { + if (string.IsNullOrEmpty(avatarDescriptor.VisemeBlendShapes[v])) + { + string viseme = ((VRC.SDKBase.VRC_AvatarDescriptor.Viseme)v).ToString().ToLowerInvariant(); + + foreach (string s in blendShapes) + { + if (s.ToLowerInvariant() == "vrc.v_" + viseme) + { + avatarDescriptor.VisemeBlendShapes[v] = s; + goto next; + } + } + foreach (string s in blendShapes) + { + if (s.ToLowerInvariant() == "v_" + viseme) + { + avatarDescriptor.VisemeBlendShapes[v] = s; + goto next; + } + } + foreach (string s in blendShapes) + { + if (s.ToLowerInvariant().EndsWith(viseme)) + { + avatarDescriptor.VisemeBlendShapes[v] = s; + goto next; + } + } + foreach (string s in blendShapes) + { + if (s.ToLowerInvariant() == viseme) + { + avatarDescriptor.VisemeBlendShapes[v] = s; + goto next; + } + } + foreach (string s in blendShapes) + { + if (s.ToLowerInvariant().Contains(viseme)) + { + avatarDescriptor.VisemeBlendShapes[v] = s; + goto next; + } + } + next: { } + } + + } + + shouldRefreshVisemes = false; + } + + class SearchComparer : IComparer<string> + { + string ReplaceFirst(string text, string search, string replace) + { + int pos = text.IndexOf(search, StringComparison.Ordinal); + if (pos < 0) + { + return text; + } + return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + } + + public SearchComparer(string searchString) + { + _searchString = searchString; + } + private readonly string _searchString; + public int Compare(string x, string y) + { + if (x == null || y == null) + { + return 0; + } + //-1 is they're out of order, 0 is order doesn't matter, 1 is they're in order + + x = ReplaceFirst(x, "const ", ""); + y = ReplaceFirst(y, "const ", ""); + + int xIndex = x.IndexOf(_searchString, StringComparison.InvariantCultureIgnoreCase); + int yIndex = y.IndexOf(_searchString, StringComparison.InvariantCultureIgnoreCase); + int compareIndex = xIndex.CompareTo(yIndex); + if (compareIndex != 0) return compareIndex; + + string xDiff = ReplaceFirst(x, _searchString, ""); + string yDiff = ReplaceFirst(y, _searchString, ""); + return string.Compare(xDiff, yDiff, StringComparison.InvariantCultureIgnoreCase); + } + } + + private void AutoDetectLipSync() + { + SkinnedMeshRenderer[] renderers = avatarDescriptor.GetComponentsInChildren<SkinnedMeshRenderer>(); + + string[] baseVisemeNames = Enum.GetNames(typeof(VRC_AvatarDescriptor.Viseme)); + int visemeCount = baseVisemeNames.Length - 1; + string[] reversedVisemeNames = new string[visemeCount]; + string[] reversedVVisemeNames = new string[visemeCount]; + for (int i = 0; i < visemeCount; i++) + { + string visemeName = baseVisemeNames[i]; + char[] tmpArray = visemeName.ToLowerInvariant().ToCharArray(); + Array.Reverse(tmpArray); + reversedVisemeNames[i] = new string(tmpArray); + reversedVVisemeNames[i] = $"{reversedVisemeNames[i]}_v"; + } + + foreach (SkinnedMeshRenderer renderer in renderers) + { + if (renderer.sharedMesh.blendShapeCount <= 0) continue; + + if (renderer.sharedMesh.blendShapeCount >= visemeCount) + { + string[] rendererBlendShapeNames = new string[renderer.sharedMesh.blendShapeCount]; + for (int i = 0; i < renderer.sharedMesh.blendShapeCount; i++) + { + rendererBlendShapeNames[i] = renderer.sharedMesh.GetBlendShapeName(i); + } + + string[] visemeStrings = new string[visemeCount]; + int foundVisemes = 0; + + string[] reversedRendererNames = new string[rendererBlendShapeNames.Length]; + Dictionary<string, string> reverseMap = new Dictionary<string, string>(); + + for (int i = 0; i < rendererBlendShapeNames.Length; i++) + { + string rendererBlendShapeName = rendererBlendShapeNames[i]; + char[] tmpArray = rendererBlendShapeName.ToLowerInvariant().ToCharArray(); + Array.Reverse(tmpArray); + reversedRendererNames[i] = new string(tmpArray); + if (reverseMap.ContainsKey(reversedRendererNames[i])) + { + continue; + } + reverseMap.Add(reversedRendererNames[i], rendererBlendShapeName); + } + + for (int i = 0; i < reversedVisemeNames.Length; i++) + { + string visemeName = reversedVisemeNames[i]; + string vVisemeName = reversedVVisemeNames[i]; + + List<string> matchingStrings = new List<string>(); + foreach (string reversedRendererName in reversedRendererNames) + { + if (reversedRendererName.Contains(vVisemeName)) + { + matchingStrings.Add(reversedRendererName); + } + } + if (matchingStrings.Count == 0) + { + foreach (string reversedRendererName in reversedRendererNames) + { + if (reversedRendererName.Contains(visemeName)) + { + matchingStrings.Add(reversedRendererName); + } + } + } + + matchingStrings.Sort(new SearchComparer(visemeName)); + + if (matchingStrings.Count <= 0) continue; + + visemeStrings[i] = reverseMap[matchingStrings[0]]; + foundVisemes++; + } + + //Threshold to see if we did a good enough job to bother showing the user + if (foundVisemes > 2) + { + avatarDescriptor.lipSync = VRC_AvatarDescriptor.LipSyncStyle.VisemeBlendShape; + avatarDescriptor.VisemeSkinnedMesh = renderer; + avatarDescriptor.VisemeBlendShapes = visemeStrings; + avatarDescriptor.lipSyncJawBone = null; + return; + } + } + + avatarDescriptor.lipSync = VRC_AvatarDescriptor.LipSyncStyle.JawFlapBlendShape; + avatarDescriptor.VisemeSkinnedMesh = renderer; + avatarDescriptor.lipSyncJawBone = null; + return; + } + + + if (avatarDescriptor.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Jaw) == null) return; + avatarDescriptor.lipSync = VRC_AvatarDescriptor.LipSyncStyle.JawFlapBone; + avatarDescriptor.lipSyncJawBone = avatarDescriptor.GetComponent<Animator>().GetBoneTransform(HumanBodyBones.Jaw); + avatarDescriptor.VisemeSkinnedMesh = null; + } +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3LipSync.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3LipSync.cs.meta new file mode 100644 index 00000000..57e330a9 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3LipSync.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c3292ff65aa089469766dcb0ac8a4f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3View.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3View.cs new file mode 100644 index 00000000..e4cba296 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3View.cs @@ -0,0 +1,43 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using UnityEditorInternal; +using System.Collections.Generic; +using VRC.SDK3.Avatars.Components; + +public partial class AvatarDescriptorEditor3 : Editor +{ + + void DrawView() + { + + if (Foldout("VRCSDK3_AvatarDescriptorEditor3_ViewFoldout", "View")) + { + var viewPosition = serializedObject.FindProperty("ViewPosition"); + + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.PropertyField(viewPosition); + + bool isActive = IsActiveProperty(viewPosition); + if(isActive) + GUI.backgroundColor = _activeButtonColor; + if (GUILayout.Button(isActive ? "Return" : "Edit", EditorStyles.miniButton, GUILayout.MaxWidth(PreviewButtonWidth))) + { + if (isActive) + SetActiveProperty(null); + else + SetActiveProperty(viewPosition); + _repaint = true; + } + GUI.backgroundColor = Color.white; + } + EditorGUILayout.EndHorizontal(); + Separator(); + } + + } + + +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3View.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3View.cs.meta new file mode 100644 index 00000000..07047669 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3View.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b6bea6a051798844944df1d6e53fa9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3_Expressions.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3_Expressions.cs new file mode 100644 index 00000000..50a41a84 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3_Expressions.cs @@ -0,0 +1,49 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using VRC.SDK3.Avatars.Components; + +public partial class AvatarDescriptorEditor3 : Editor +{ + static string _ExpressionsFoldoutPrefsKey = "VRCSDK3_AvatarDescriptorEditor3_ExpressionsFoldout"; + void Init_Expressions() + { + } + void DrawInspector_Expressions() + { + var menu = serializedObject.FindProperty("expressionsMenu"); + var parameters = serializedObject.FindProperty("expressionParameters"); + var customize = serializedObject.FindProperty("customExpressions"); + + if (Foldout(_ExpressionsFoldoutPrefsKey, "Expressions", false)) + { + if(customize.boolValue) + { + if(GUILayout.Button("Reset To Default")) + { + if (EditorUtility.DisplayDialog("Reset to Default", "This will erase any custom expression settings. Are you sure?", "OK", "Cancel")) + { + menu.objectReferenceValue = null; + parameters.objectReferenceValue = null; + customize.boolValue = false; + } + } + + //Menu + EditorGUILayout.PropertyField(menu, new GUIContent("Menu")); + + //Parameters + EditorGUILayout.PropertyField(parameters, new GUIContent("Parameters")); + } + else + { + if (GUILayout.Button("Customize")) + { + customize.boolValue = true; + } + } + } + Separator(); + } +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3_Expressions.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3_Expressions.cs.meta new file mode 100644 index 00000000..392f6b89 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarDescriptorEditor3_Expressions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39f53ee3c68113d4fa788cd2809981be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarParameterDriverEditor.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarParameterDriverEditor.cs new file mode 100644 index 00000000..86c89a11 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarParameterDriverEditor.cs @@ -0,0 +1,221 @@ +#if VRC_SDK_VRCSDK3 +using UnityEngine; +using UnityEditor; +using VRC.SDK3.Avatars.Components; +using static VRC.SDKBase.VRC_AvatarParameterDriver; +using Boo.Lang; +using System; + +[CustomEditor(typeof(VRCAvatarParameterDriver))] +public class AvatarParameterDriverEditor : Editor +{ + VRCAvatarParameterDriver driver; + string[] parameterNames; + AnimatorControllerParameterType[] parameterTypes; + + enum ChangeTypeBool + { + Set = 0, + Random = 2, + } + + public void OnEnable() + { + var driver = target as VRCAvatarParameterDriver; + + //Build parameter names + var controller = GetCurrentController(); + if (controller != null) + { + //Standard + List<string> names = new List<string>(); + List<AnimatorControllerParameterType> types = new List<AnimatorControllerParameterType>(); + foreach (var item in controller.parameters) + { + names.Add(item.name); + types.Add(item.type); + } + parameterNames = names.ToArray(); + parameterTypes = types.ToArray(); + } + } + + static UnityEditor.Animations.AnimatorController GetCurrentController() + { + UnityEditor.Animations.AnimatorController controller = null; + var toolType = Type.GetType("UnityEditor.Graphs.AnimatorControllerTool, UnityEditor.Graphs"); + var tool = EditorWindow.GetWindow(toolType); + var controllerProperty = toolType.GetProperty("animatorController", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + if (controllerProperty != null) + { + controller = controllerProperty.GetValue(tool, null) as UnityEditor.Animations.AnimatorController; + } + else + Debug.LogError("Unable to find animator window.", tool); + return controller; + } + + public override void OnInspectorGUI() + { + EditorGUI.BeginChangeCheck(); + serializedObject.Update(); + var driver = target as VRCAvatarParameterDriver; + + //Info + EditorGUILayout.HelpBox("This behaviour modifies parameters on this and all other animation controllers referenced on the avatar descriptor.", MessageType.Info); + EditorGUILayout.HelpBox("You should primarily be driving expression parameters as they are the only variables that sync across the network. Changes to any other parameter will not be synced across the network.", MessageType.Info); + + //Data + driver.localOnly = EditorGUILayout.Toggle("Local Only", driver.localOnly); + + //Check for info message + bool usesAddOrRandom = false; + foreach(var param in driver.parameters) + { + if (param.type != ChangeType.Set) + usesAddOrRandom = true; + } + if(usesAddOrRandom && !driver.localOnly) + EditorGUILayout.HelpBox("Using Add & Random may not produce the same result when run on remote instance of the avatar. When using these modes it's suggested you use a synced parameter and use the local only option.", MessageType.Warning); + + //Parameters + for (int i = 0; i < driver.parameters.Count; i++) + { + EditorGUILayout.BeginVertical(GUI.skin.box); + { + var param = driver.parameters[i]; + var index = IndexOf(parameterNames, param.name); + + //Name + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Name"); + if (parameterNames != null) + { + EditorGUI.BeginChangeCheck(); + index = EditorGUILayout.Popup(index, parameterNames); + if (EditorGUI.EndChangeCheck() && index >= 0) + param.name = parameterNames[index]; + } + param.name = EditorGUILayout.TextField(param.name); + EditorGUILayout.EndHorizontal(); + + //Value + if (index >= 0) + { + var type = parameterTypes[index]; + if (type == AnimatorControllerParameterType.Int) + { + //Type + param.type = (VRCAvatarParameterDriver.ChangeType)EditorGUILayout.EnumPopup("Change Type", param.type); + + //Value + if (param.type == ChangeType.Set) + param.value = Mathf.Clamp(EditorGUILayout.IntField("Value", (int)param.value), 0, 255); + else if (param.type == ChangeType.Add) + param.value = Mathf.Clamp(EditorGUILayout.IntField("Value", (int)param.value), -255, 255); + else if (param.type == ChangeType.Random) + { + param.valueMin = Mathf.Clamp(EditorGUILayout.IntField("Min Value", (int)param.valueMin), 0, 255); + param.valueMax = Mathf.Clamp(EditorGUILayout.IntField("Max Value", (int)param.valueMax), param.valueMin, 255); + } + } + else if (type == AnimatorControllerParameterType.Float) + { + //Type + param.type = (VRCAvatarParameterDriver.ChangeType)EditorGUILayout.EnumPopup("Change Type", param.type); + + //Value + if (param.type == ChangeType.Set || param.type == ChangeType.Add) + param.value = Mathf.Clamp(EditorGUILayout.FloatField("Value", param.value), -1f, 1); + else if (param.type == ChangeType.Random) + { + param.valueMin = Mathf.Clamp(EditorGUILayout.FloatField("Min Value", param.valueMin), -1f, 1); + param.valueMax = Mathf.Clamp(EditorGUILayout.FloatField("Max Value", param.valueMax), param.valueMin, 1); + } + } + else if (type == AnimatorControllerParameterType.Bool) + { + //Type + param.type = (VRCAvatarParameterDriver.ChangeType)EditorGUILayout.EnumPopup("Change Type", (ChangeTypeBool)param.type); + + //Value + if (param.type == ChangeType.Set) + param.value = EditorGUILayout.Toggle("Value", param.value != 0) ? 1f : 0f; + else + param.chance = Mathf.Clamp(EditorGUILayout.FloatField("Chance", param.chance), 0f, 1f); + } + else if (type == AnimatorControllerParameterType.Trigger) + { + //Type + param.type = (VRCAvatarParameterDriver.ChangeType)EditorGUILayout.EnumPopup("Change Type", (ChangeTypeBool)param.type); + + //Chance + if (param.type == ChangeType.Random) + param.chance = Mathf.Clamp(EditorGUILayout.FloatField("Chance", param.chance), 0f, 1f); + } + } + else + { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.EnumPopup(param.type); + if(param.type == ChangeType.Random) + { + EditorGUILayout.FloatField("Min Value", param.valueMin); + EditorGUILayout.FloatField("Max Value", param.valueMax); + } + else + { + EditorGUILayout.FloatField("Value", param.value); + } + EditorGUI.EndDisabledGroup(); + DrawInfoBox("WARNING: Parameter not found. Make sure you defined it on the animation controller."); + } + + //Delete + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Delete")) + { + driver.parameters.RemoveAt(i); + i--; + } + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.EndHorizontal(); + } + + //Add + if (GUILayout.Button("Add Parameter")) + { + var parameter = new Parameter(); + if (parameterNames != null && parameterNames.Length > 0) + parameter.name = parameterNames[0]; + driver.parameters.Add(parameter); + } + + int IndexOf(string[] array, string value) + { + if (array == null) + return -1; + for(int i=0; i<array.Length; i++) + { + if (array[i] == value) + return i; + } + return -1; + } + + void DrawInfoBox(string text) + { + EditorGUI.indentLevel += 2; + EditorGUILayout.LabelField(text, EditorStyles.textArea); + EditorGUI.indentLevel -= 2; + } + + //End + serializedObject.ApplyModifiedProperties(); + if (EditorGUI.EndChangeCheck()) + EditorUtility.SetDirty(this); + } +} +#endif diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarParameterDriverEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarParameterDriverEditor.cs.meta new file mode 100644 index 00000000..c833e1ab --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarParameterDriverEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 10dd087510ec9264c81586b24b0dc7b0 +timeCreated: 1450462624 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarPlayerStationEditor3.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarPlayerStationEditor3.cs new file mode 100644 index 00000000..5fb98ef5 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarPlayerStationEditor3.cs @@ -0,0 +1,32 @@ +#if VRC_SDK_VRCSDK3 + +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using System; + +[CustomEditor(typeof(VRC.SDK3.Avatars.Components.VRCStation))] +public class VRCAvatarPlayerStationEditor3 : Editor +{ + VRC.SDK3.Avatars.Components.VRCStation myTarget; + + void OnEnable() + { + if(myTarget == null) + myTarget = (VRC.SDK3.Avatars.Components.VRCStation)target; + } + + public override void OnInspectorGUI() + { + myTarget.PlayerMobility = (VRC.SDKBase.VRCStation.Mobility)EditorGUILayout.EnumPopup("Player Mobility", myTarget.PlayerMobility); + myTarget.canUseStationFromStation = EditorGUILayout.Toggle("Can Use Station From Station", myTarget.canUseStationFromStation); + myTarget.animatorController = (RuntimeAnimatorController) EditorGUILayout.ObjectField("Animator Controller", myTarget.animatorController, typeof(RuntimeAnimatorController), false ); + myTarget.disableStationExit = EditorGUILayout.Toggle("Disable Station Exit", myTarget.disableStationExit ); + myTarget.seated = EditorGUILayout.Toggle("Seated", myTarget.seated); + myTarget.stationEnterPlayerLocation = (Transform)EditorGUILayout.ObjectField("Player Enter Location", myTarget.stationEnterPlayerLocation, typeof(Transform), true); + myTarget.stationExitPlayerLocation = (Transform)EditorGUILayout.ObjectField("Player Exit Location", myTarget.stationExitPlayerLocation, typeof(Transform), true); + myTarget.controlsObject = (VRC.SDKBase.VRC_ObjectApi)EditorGUILayout.ObjectField("API Object", myTarget.controlsObject, typeof(VRC.SDKBase.VRC_ObjectApi), false); + } +} +#endif
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarPlayerStationEditor3.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarPlayerStationEditor3.cs.meta new file mode 100644 index 00000000..9ee8b718 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarPlayerStationEditor3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02850b4562f17f44db2c16e8a84e10a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarSpatialAudioSourceEditor3.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarSpatialAudioSourceEditor3.cs new file mode 100644 index 00000000..a6466c92 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarSpatialAudioSourceEditor3.cs @@ -0,0 +1,58 @@ +#if VRC_SDK_VRCSDK3 +#if UNITY_EDITOR + +using UnityEngine; +using UnityEditor; + +namespace VRC.SDK3.Editor +{ + [CustomEditor(typeof(VRC.SDK3.Avatars.Components.VRCSpatialAudioSource))] + public class VRCAvatarSpatialAudioSourceEditor3 : UnityEditor.Editor + { + private bool showAdvancedOptions = false; + private SerializedProperty gainProperty; + private SerializedProperty nearProperty; + private SerializedProperty farProperty; + private SerializedProperty volRadiusProperty; + private SerializedProperty enableSpatialProperty; + private SerializedProperty useCurveProperty; + + public override void OnInspectorGUI() + { + gainProperty = serializedObject.FindProperty("Gain"); + nearProperty = serializedObject.FindProperty("Near"); + farProperty = serializedObject.FindProperty("Far"); + volRadiusProperty = serializedObject.FindProperty("VolumetricRadius"); + enableSpatialProperty = serializedObject.FindProperty("EnableSpatialization"); + useCurveProperty = serializedObject.FindProperty("UseAudioSourceVolumeCurve"); + + serializedObject.Update(); + + VRC.SDKBase.VRC_SpatialAudioSource target = serializedObject.targetObject as VRC.SDKBase.VRC_SpatialAudioSource; + AudioSource source = target.GetComponent<AudioSource>(); + + EditorGUILayout.BeginVertical(); + + EditorGUILayout.PropertyField(gainProperty, new GUIContent("Gain")); + EditorGUILayout.PropertyField(farProperty, new GUIContent("Far")); + showAdvancedOptions = EditorGUILayout.Foldout(showAdvancedOptions, "Advanced Options"); + bool enableSp = enableSpatialProperty.boolValue; + if (showAdvancedOptions) + { + EditorGUILayout.PropertyField(nearProperty, new GUIContent("Near")); + EditorGUILayout.PropertyField(volRadiusProperty, new GUIContent("Volumetric Radius")); + EditorGUILayout.PropertyField(enableSpatialProperty, new GUIContent("Enable Spatialization")); + if (enableSp) + EditorGUILayout.PropertyField(useCurveProperty, new GUIContent("Use AudioSource Volume Curve")); + } + + EditorGUILayout.EndVertical(); + + if (source != null) + source.spatialize = enableSp; + serializedObject.ApplyModifiedProperties(); + } + } +} +#endif +#endif
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarSpatialAudioSourceEditor3.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarSpatialAudioSourceEditor3.cs.meta new file mode 100644 index 00000000..b7802d8b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCAvatarSpatialAudioSourceEditor3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f1d254457370f4468776bf1a49d945d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionMenuEditor.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionMenuEditor.cs new file mode 100644 index 00000000..4aa9841b --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionMenuEditor.cs @@ -0,0 +1,408 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using ExpressionsMenu = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu; +using ExpressionControl = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu.Control; +using ExpressionParameters = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters; +using VRC.SDK3.Avatars.ScriptableObjects; +using System.Reflection.Emit; + +[CustomEditor(typeof(VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionsMenu))] +public class VRCExpressionsMenuEditor : Editor +{ + static string[] ToggleStyles = { "Pip-Slot", "Animation" }; + + List<UnityEngine.Object> foldoutList = new List<UnityEngine.Object>(); + public void Start() + { + + } + public void OnDisable() + { + SelectAvatarDescriptor(null); + } + public override void OnInspectorGUI() + { + serializedObject.Update(); + + SelectAvatarDescriptor(); + + if(activeDescriptor == null) + { + EditorGUILayout.HelpBox("No active avatar descriptor found in scene.", MessageType.Error); + } + EditorGUILayout.Space(); + + //Controls + EditorGUI.BeginDisabledGroup(activeDescriptor == null); + EditorGUILayout.LabelField("Controls"); + EditorGUI.indentLevel += 1; + { + var controls = serializedObject.FindProperty("controls"); + for (int i = 0; i < controls.arraySize; i++) + { + var control = controls.GetArrayElementAtIndex(i); + DrawControl(controls, control as SerializedProperty, i); + } + + //Add + EditorGUI.BeginDisabledGroup(controls.arraySize >= ExpressionsMenu.MAX_CONTROLS); + if (GUILayout.Button("Add Control")) + { + var menu = serializedObject.targetObject as ExpressionsMenu; + + var control = new ExpressionControl(); + control.name = "New Control"; + menu.controls.Add(control); + } + EditorGUI.EndDisabledGroup(); + } + EditorGUI.indentLevel -= 1; + EditorGUI.EndDisabledGroup(); + + serializedObject.ApplyModifiedProperties(); + } + void DrawControl(SerializedProperty controls, SerializedProperty control, int index) + { + var name = control.FindPropertyRelative("name"); + var icon = control.FindPropertyRelative("icon"); + var type = control.FindPropertyRelative("type"); + var parameter = control.FindPropertyRelative("parameter"); + var value = control.FindPropertyRelative("value"); + var subMenu = control.FindPropertyRelative("subMenu"); + + var subParameters = control.FindPropertyRelative("subParameters"); + var labels = control.FindPropertyRelative("labels"); + + //Foldout + EditorGUI.BeginChangeCheck(); + control.isExpanded = EditorGUILayout.Foldout(control.isExpanded, name.stringValue); + if (!control.isExpanded) + return; + + //Box + GUILayout.BeginVertical(GUI.skin.box); + { + //Up, Down, Delete + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Up", GUILayout.Width(64))) + { + if (index > 0) + controls.MoveArrayElement(index, index - 1); + } + if (GUILayout.Button("Down", GUILayout.Width(64))) + { + if (index < controls.arraySize - 1) + controls.MoveArrayElement(index, index + 1); + } + if (GUILayout.Button("Delete", GUILayout.Width(64))) + { + controls.DeleteArrayElementAtIndex(index); + return; + } + GUILayout.EndHorizontal(); + + //Generic params + EditorGUI.indentLevel += 1; + { + EditorGUILayout.PropertyField(name); + EditorGUILayout.PropertyField(icon); + EditorGUILayout.PropertyField(type); + + //Type Info + var controlType = (ExpressionControl.ControlType)type.intValue; + switch (controlType) + { + case ExpressionControl.ControlType.Button: + EditorGUILayout.HelpBox("Click or hold to activate. The button remains active for a minimum 0.2s.\nWhile active the (Parameter) is set to (Value).\nWhen inactive the (Parameter) is reset to zero.", MessageType.Info); + break; + case ExpressionControl.ControlType.Toggle: + EditorGUILayout.HelpBox("Click to toggle on or off.\nWhen turned on the (Parameter) is set to (Value).\nWhen turned off the (Parameter) is reset to zero.", MessageType.Info); + break; + case ExpressionControl.ControlType.SubMenu: + EditorGUILayout.HelpBox("Opens another expression menu.\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); + break; + case ExpressionControl.ControlType.TwoAxisPuppet: + EditorGUILayout.HelpBox("Puppet menu that maps the joystick to two parameters (-1 to +1).\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); + break; + case ExpressionControl.ControlType.FourAxisPuppet: + EditorGUILayout.HelpBox("Puppet menu that maps the joystick to four parameters (0 to 1).\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); + break; + case ExpressionControl.ControlType.RadialPuppet: + EditorGUILayout.HelpBox("Puppet menu that sets a value based on joystick rotation. (0 to 1)\nWhen opened the (Parameter) is set to (Value).\nWhen closed (Parameter) is reset to zero.", MessageType.Info); + break; + } + + //Param + switch (controlType) + { + case ExpressionControl.ControlType.Button: + case ExpressionControl.ControlType.Toggle: + case ExpressionControl.ControlType.SubMenu: + case ExpressionControl.ControlType.TwoAxisPuppet: + case ExpressionControl.ControlType.FourAxisPuppet: + case ExpressionControl.ControlType.RadialPuppet: + DrawParameterDropDown(parameter, "Parameter"); + DrawParameterValue(parameter, value); + break; + } + EditorGUILayout.LabelField("", GUI.skin.horizontalSlider); + + //Style + /*if (controlType == ExpressionsControl.ControlType.Toggle) + { + style.intValue = EditorGUILayout.Popup("Visual Style", style.intValue, ToggleStyles); + }*/ + + //Sub menu + if (controlType == ExpressionControl.ControlType.SubMenu) + { + EditorGUILayout.PropertyField(subMenu); + } + + //Puppet Parameter Set + switch (controlType) + { + case ExpressionControl.ControlType.TwoAxisPuppet: + subParameters.arraySize = 2; + labels.arraySize = 4; + + DrawParameterDropDown(subParameters.GetArrayElementAtIndex(0), "Parameter Horizontal", false); + DrawParameterDropDown(subParameters.GetArrayElementAtIndex(1), "Parameter Vertical", false); + + DrawLabel(labels.GetArrayElementAtIndex(0), "Label Up"); + DrawLabel(labels.GetArrayElementAtIndex(1), "Label Right"); + DrawLabel(labels.GetArrayElementAtIndex(2), "Label Down"); + DrawLabel(labels.GetArrayElementAtIndex(3), "Label Left"); + break; + case ExpressionControl.ControlType.FourAxisPuppet: + subParameters.arraySize = 4; + labels.arraySize = 4; + + DrawParameterDropDown(subParameters.GetArrayElementAtIndex(0), "Parameter Up", false); + DrawParameterDropDown(subParameters.GetArrayElementAtIndex(1), "Parameter Right", false); + DrawParameterDropDown(subParameters.GetArrayElementAtIndex(2), "Parameter Down", false); + DrawParameterDropDown(subParameters.GetArrayElementAtIndex(3), "Parameter Left", false); + + DrawLabel(labels.GetArrayElementAtIndex(0), "Label Up"); + DrawLabel(labels.GetArrayElementAtIndex(1), "Label Right"); + DrawLabel(labels.GetArrayElementAtIndex(2), "Label Down"); + DrawLabel(labels.GetArrayElementAtIndex(3), "Label Left"); + break; + case ExpressionControl.ControlType.RadialPuppet: + subParameters.arraySize = 1; + labels.arraySize = 0; + + DrawParameterDropDown(subParameters.GetArrayElementAtIndex(0), "Paramater Rotation", false); + break; + default: + subParameters.arraySize = 0; + labels.arraySize = 0; + break; + } + } + EditorGUI.indentLevel -= 1; + } + GUILayout.EndVertical(); + } + void DrawLabel(SerializedProperty subControl, string name) + { + var nameProp = subControl.FindPropertyRelative("name"); + var icon = subControl.FindPropertyRelative("icon"); + + EditorGUILayout.LabelField(name); + EditorGUI.indentLevel += 2; + EditorGUILayout.PropertyField(nameProp); + EditorGUILayout.PropertyField(icon); + EditorGUI.indentLevel -= 2; + } + + void DrawInfoHover(string text) + { + GUILayout.Button(new GUIContent("?", text), GUILayout.MaxWidth(32)); + } + void DrawInfo(string text) + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Label(text, GUI.skin.textArea, GUILayout.MaxWidth(400)); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + } + + VRC.SDK3.Avatars.Components.VRCAvatarDescriptor activeDescriptor = null; + string[] parameterNames; + void SelectAvatarDescriptor() + { + var descriptors = GameObject.FindObjectsOfType<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>(); + if (descriptors.Length > 0) + { + //Compile list of names + string[] names = new string[descriptors.Length]; + for(int i=0; i<descriptors.Length; i++) + names[i] = descriptors[i].gameObject.name; + + //Select + var currentIndex = System.Array.IndexOf(descriptors, activeDescriptor); + var nextIndex = EditorGUILayout.Popup("Active Avatar", currentIndex, names); + if(nextIndex < 0) + nextIndex = 0; + if (nextIndex != currentIndex) + SelectAvatarDescriptor(descriptors[nextIndex]); + } + else + SelectAvatarDescriptor(null); + } + void SelectAvatarDescriptor(VRC.SDK3.Avatars.Components.VRCAvatarDescriptor desc) + { + if (desc == activeDescriptor) + return; + + activeDescriptor = desc; + if(activeDescriptor != null) + { + //Init stage parameters + int paramCount = desc.GetExpressionParameterCount(); + parameterNames = new string[paramCount + 1]; + parameterNames[0] = "[None]"; + for (int i = 0; i < paramCount; i++) + { + var param = desc.GetExpressionParameter(i); + string name = "[None]"; + if (param != null && !string.IsNullOrEmpty(param.name)) + name = string.Format("{0}, {1}", param.name, param.valueType.ToString(), i + 1); + parameterNames[i + 1] = name; + } + } + else + { + parameterNames = null; + } + } + int GetExpressionParametersCount() + { + if (activeDescriptor != null && activeDescriptor.expressionParameters != null && activeDescriptor.expressionParameters.parameters != null) + return activeDescriptor.expressionParameters.parameters.Length; + return 0; + } + ExpressionParameters.Parameter GetExpressionParameter(int i) + { + if (activeDescriptor != null) + return activeDescriptor.GetExpressionParameter(i); + return null; + } + void DrawParameterDropDown(SerializedProperty parameter, string name, bool allowBool=true) + { + var parameterName = parameter.FindPropertyRelative("name"); + VRCExpressionParameters.Parameter param = null; + string value = parameterName.stringValue; + + bool parameterFound = false; + EditorGUILayout.BeginHorizontal(); + { + if(activeDescriptor != null) + { + //Dropdown + int currentIndex; + if (string.IsNullOrEmpty(value)) + { + currentIndex = -1; + parameterFound = true; + } + else + { + currentIndex = -2; + for (int i = 0; i < GetExpressionParametersCount(); i++) + { + var item = activeDescriptor.GetExpressionParameter(i); + if (item.name == value) + { + param = item; + parameterFound = true; + currentIndex = i; + break; + } + } + } + + //Dropdown + EditorGUI.BeginChangeCheck(); + currentIndex = EditorGUILayout.Popup(name, currentIndex + 1, parameterNames); + if (EditorGUI.EndChangeCheck()) + { + if (currentIndex == 0) + parameterName.stringValue = ""; + else + parameterName.stringValue = GetExpressionParameter(currentIndex - 1).name; + } + } + else + { + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.Popup(0, new string[0]); + EditorGUI.EndDisabledGroup(); + } + + //Text field + parameterName.stringValue = EditorGUILayout.TextField(parameterName.stringValue, GUILayout.MaxWidth(200)); + } + EditorGUILayout.EndHorizontal(); + + if (!parameterFound) + { + EditorGUILayout.HelpBox("Parameter not found on the active avatar descriptor.", MessageType.Warning); + } + + if(!allowBool && param != null && param.valueType == ExpressionParameters.ValueType.Bool) + { + EditorGUILayout.HelpBox("Bool parameters not valid for this choice.", MessageType.Error); + } + } + void DrawParameterValue(SerializedProperty parameter, SerializedProperty value) + { + string paramName = parameter.FindPropertyRelative("name").stringValue; + if (!string.IsNullOrEmpty(paramName)) + { + var paramDef = FindExpressionParameterDef(paramName); + if (paramDef != null) + { + if (paramDef.valueType == ExpressionParameters.ValueType.Int) + { + value.floatValue = EditorGUILayout.IntField("Value", Mathf.Clamp((int)value.floatValue, 0, 255)); + } + else if (paramDef.valueType == ExpressionParameters.ValueType.Float) + { + value.floatValue = EditorGUILayout.FloatField("Value", Mathf.Clamp(value.floatValue, -1f, 1f)); + } + else if(paramDef.valueType == ExpressionParameters.ValueType.Bool) + { + value.floatValue = 1f; + } + } + else + { + EditorGUI.BeginDisabledGroup(true); + value.floatValue = EditorGUILayout.FloatField("Value", value.floatValue); + EditorGUI.EndDisabledGroup(); + } + } + } + + ExpressionParameters.Parameter FindExpressionParameterDef(string name) + { + if (activeDescriptor == null || string.IsNullOrEmpty(name)) + return null; + + //Find + int length = GetExpressionParametersCount(); + for(int i=0; i<length; i++) + { + var item = GetExpressionParameter(i); + if (item != null && item.name == name) + return item; + } + return null; + } +}
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionMenuEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionMenuEditor.cs.meta new file mode 100644 index 00000000..98e83dac --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionMenuEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b41ae1eaabb436f4e96c4580c946d37b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionParametersEditor.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionParametersEditor.cs new file mode 100644 index 00000000..2f51b6c7 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionParametersEditor.cs @@ -0,0 +1,253 @@ +using UnityEngine; +using UnityEditor; +using ExpressionParameters = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters; +using ExpressionParameter = VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters.Parameter; +using UnityEngine.UI; + +[CustomEditor(typeof(VRC.SDK3.Avatars.ScriptableObjects.VRCExpressionParameters))] +public class VRCExpressionParametersEditor : Editor +{ + int selected = -1; + GUIStyle boxNormal; + GUIStyle boxSelected; + + void InitStyles() + { + //Normal + if(boxNormal == null) + boxNormal = new GUIStyle(GUI.skin.box); + + //Selected + if(boxSelected == null) + { + boxSelected = new GUIStyle(GUI.skin.box); + boxSelected.normal.background = MakeStyleBackground(new Color(0.0f, 0.5f, 1f, 0.5f)); + } + } + Texture2D MakeStyleBackground(Color color) + { + var texture = new Texture2D(1, 1); + texture.SetPixel(0, 0, color); + texture.Apply(); + return texture; + } + + void SelectParam(int value) + { + selected = value; + Repaint(); + } + public void OnEnable() + { + //Init parameters + var expressionParameters = target as ExpressionParameters; + if (expressionParameters.parameters == null) + InitExpressionParameters(true); + + SelectParam(-1); + } + public override void OnInspectorGUI() + { + InitStyles(); + + serializedObject.Update(); + { + EditorGUILayout.LabelField("Parameters"); + var parameters = serializedObject.FindProperty("parameters"); + + //Controls + EditorGUILayout.BeginHorizontal(); + { + //Add + if (GUILayout.Button("Add")) + parameters.arraySize = parameters.arraySize + 1; + + EditorGUI.BeginDisabledGroup(selected < 0); + { + //Move Up + if (GUILayout.Button("Up")) + { + if(selected > 0) + { + SwapParams(selected, selected - 1); + selected = selected - 1; + Repaint(); + } + } + + //Move Down + if (GUILayout.Button("Down")) + { + if (selected < parameters.arraySize-1) + { + SwapParams(selected, selected + 1); + selected = selected + 1; + Repaint(); + } + } + + void SwapParams(int indexA, int indexB) + { + var script = (ExpressionParameters)target; + var itemA = script.parameters[indexA]; + var itemB = script.parameters[indexB]; + script.parameters[indexA] = itemB; + script.parameters[indexB] = itemA; + + serializedObject.Update(); + } + + //Delete + if (GUILayout.Button("Delete")) + { + parameters.DeleteArrayElementAtIndex(selected); + SelectParam(-1); + } + } + EditorGUI.EndDisabledGroup(); + } + EditorGUILayout.EndHorizontal(); + + + //Labels + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.LabelField(" Name", GUILayout.MinWidth(100)); + EditorGUILayout.LabelField(" Type", GUILayout.Width(100)); + EditorGUILayout.LabelField("Default", GUILayout.Width(64)); + EditorGUILayout.LabelField("Saved", GUILayout.Width(64)); + } + EditorGUILayout.EndHorizontal(); + + //Parameters + int count = parameters.arraySize; + for(int paramIter=0; paramIter< parameters.arraySize; paramIter++) + { + DrawExpressionParameter(parameters, paramIter); + + /*var item = parameters.GetArrayElementAtIndex(paramIter); + var name = item.FindPropertyRelative("name"); + var valueType = item.FindPropertyRelative("valueType"); + + //Draw + EditorGUI.indentLevel += 1; + EditorGUILayout.BeginHorizontal(); + { + EditorGUILayout.PropertyField(name, new GUIContent("")); + EditorGUILayout.PropertyField(valueType, new GUIContent("")); + if(GUILayout.Button("X", GUILayout.Width(32))) + { + parameters.DeleteArrayElementAtIndex(paramIter); + paramIter -= 1; + } + } + EditorGUILayout.EndHorizontal(); + EditorGUI.indentLevel -= 1;*/ + } + + //Cost + int cost = (target as ExpressionParameters).CalcTotalCost(); + if(cost <= ExpressionParameters.MAX_PARAMETER_COST) + EditorGUILayout.HelpBox($"Total Memory: {cost}/{ExpressionParameters.MAX_PARAMETER_COST}", MessageType.Info); + else + EditorGUILayout.HelpBox($"Total Memory: {cost}/{ExpressionParameters.MAX_PARAMETER_COST}\nParameters use too much memory. Remove parameters or use bools which use less memory.", MessageType.Error); + + //Info + EditorGUILayout.HelpBox("Only parameters defined here can be used by expression menus, sync between all playable layers and sync across the network to remote clients.", MessageType.Info); + EditorGUILayout.HelpBox("The parameter name and type should match a parameter defined on one or more of your animation controllers.", MessageType.Info); + EditorGUILayout.HelpBox("Parameters used by the default animation controllers (Optional)\nVRCEmote, Int\nVRCFaceBlendH, Float\nVRCFaceBlendV, Float", MessageType.Info); + + //Clear + if (GUILayout.Button("Clear Parameters")) + { + if (EditorUtility.DisplayDialogComplex("Warning", "Are you sure you want to clear all expression parameters?", "Clear", "Cancel", "") == 0) + { + InitExpressionParameters(false); + } + } + if (GUILayout.Button("Default Parameters")) + { + if (EditorUtility.DisplayDialogComplex("Warning", "Are you sure you want to reset all expression parameters to default?", "Reset", "Cancel", "") == 0) + { + InitExpressionParameters(true); + } + } + } + serializedObject.ApplyModifiedProperties(); + } + void DrawExpressionParameter(SerializedProperty parameters, int index) + { + if (parameters.arraySize < index + 1) + parameters.InsertArrayElementAtIndex(index); + var item = parameters.GetArrayElementAtIndex(index); + + var name = item.FindPropertyRelative("name"); + var valueType = item.FindPropertyRelative("valueType"); + var defaultValue = item.FindPropertyRelative("defaultValue"); + var saved = item.FindPropertyRelative("saved"); + + bool isSelected = selected == index; + + EditorGUI.indentLevel += 1; + var rect = EditorGUILayout.BeginHorizontal(isSelected ? boxSelected : boxNormal); + { + EditorGUILayout.PropertyField(name, new GUIContent(""), GUILayout.MinWidth(100)); + EditorGUILayout.PropertyField(valueType, new GUIContent(""), GUILayout.Width(100)); + var type = (ExpressionParameters.ValueType)valueType.intValue; + switch(type) + { + case ExpressionParameters.ValueType.Int: + defaultValue.floatValue = Mathf.Clamp(EditorGUILayout.IntField((int)defaultValue.floatValue, GUILayout.Width(64)), 0, 255); + break; + case ExpressionParameters.ValueType.Float: + defaultValue.floatValue = Mathf.Clamp(EditorGUILayout.FloatField(defaultValue.floatValue, GUILayout.Width(64)), -1f, 1f); + break; + case ExpressionParameters.ValueType.Bool: + defaultValue.floatValue = EditorGUILayout.Toggle(defaultValue.floatValue != 0 ? true : false, GUILayout.Width(64)) ? 1f : 0f; + break; + } + EditorGUILayout.PropertyField(saved, new GUIContent(""), GUILayout.Width(64)); + } + EditorGUILayout.EndHorizontal(); + EditorGUI.indentLevel -= 1; + + //Select + if(Event.current.type == EventType.MouseDown) + { + if(rect.Contains(Event.current.mousePosition)) + { + SelectParam(index); + Event.current.Use(); + } + } + } + void InitExpressionParameters(bool populateWithDefault) + { + var expressionParameters = target as ExpressionParameters; + serializedObject.Update(); + { + if (populateWithDefault) + { + expressionParameters.parameters = new ExpressionParameter[3]; + + expressionParameters.parameters[0] = new ExpressionParameter(); + expressionParameters.parameters[0].name = "VRCEmote"; + expressionParameters.parameters[0].valueType = ExpressionParameters.ValueType.Int; + + expressionParameters.parameters[1] = new ExpressionParameter(); + expressionParameters.parameters[1].name = "VRCFaceBlendH"; + expressionParameters.parameters[1].valueType = ExpressionParameters.ValueType.Float; + + expressionParameters.parameters[2] = new ExpressionParameter(); + expressionParameters.parameters[2].name = "VRCFaceBlendV"; + expressionParameters.parameters[2].valueType = ExpressionParameters.ValueType.Float; + } + else + { + //Empty + expressionParameters.parameters = new ExpressionParameter[0]; + } + } + serializedObject.ApplyModifiedProperties(); + } +}
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionParametersEditor.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionParametersEditor.cs.meta new file mode 100644 index 00000000..89ff8316 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/Components3/VRCExpressionParametersEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 873187ac1700b0045aff6dc3786116f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/SDK3AImportFix.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/SDK3AImportFix.cs new file mode 100644 index 00000000..ba98c5f7 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/SDK3AImportFix.cs @@ -0,0 +1,45 @@ +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace VRCSDK.SDK3.Editor +{ + [InitializeOnLoad] + public class SDK3AImportFix + { + private const string packageRuntimePluginsFolder = "Packages/com.vrchat.avatars/Runtime/VRCSDK/Plugins"; + private const string legacyRuntimePluginsFolder = "Assets/VRCSDK/Plugins/"; + private const string SDK3_IMPORTS_FIXED = "SDK3AImportsFixed"; + + static SDK3AImportFix() + { + // Only run once per project + string key = Path.Combine(Application.dataPath, SDK3_IMPORTS_FIXED); + + if (EditorPrefs.HasKey(key)) + return; + + EditorPrefs.SetBool(key, true); + Run(); + } + + public static void Run(){ + if (Directory.Exists(packageRuntimePluginsFolder)) + { + AssetDatabase.ImportAsset($"{packageRuntimePluginsFolder}/VRCSDK3A.dll", + ImportAssetOptions.ForceSynchronousImport); + AssetDatabase.ImportAsset($"{packageRuntimePluginsFolder}/VRCSDK3A-Editor.dll", + ImportAssetOptions.ForceSynchronousImport); + AssetDatabase.ImportPackage($"Packages/com.vrchat.avatars/Samples~/AV3 Demo Assets/SDK3A.unitypackage", + false); + } + else if (Directory.Exists(legacyRuntimePluginsFolder)) + { + AssetDatabase.ImportAsset($"{legacyRuntimePluginsFolder}/VRCSDK3A.dll", + ImportAssetOptions.ForceSynchronousImport); + AssetDatabase.ImportAsset($"{legacyRuntimePluginsFolder}/VRCSDK3A-Editor.dll", + ImportAssetOptions.ForceSynchronousImport); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/SDK3AImportFix.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/SDK3AImportFix.cs.meta new file mode 100644 index 00000000..9265492e --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/SDK3AImportFix.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 66c5ee5adf1c4a339c4f7b1d907046c1 +timeCreated: 1632527647
\ No newline at end of file diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/VRCSdkControlPanelAvatarBuilder3A.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/VRCSdkControlPanelAvatarBuilder3A.cs new file mode 100644 index 00000000..68782f86 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/VRCSdkControlPanelAvatarBuilder3A.cs @@ -0,0 +1,586 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; +using VRC.SDKBase.Editor; +using VRC.SDK3.Avatars.Components; +using VRC.SDK3.Avatars.ScriptableObjects; +using VRC.SDK3.Editor; +using VRC.SDKBase; +using VRC.SDKBase.Editor.BuildPipeline; +using VRC.SDKBase.Validation.Performance; +using VRC.SDKBase.Validation; +using VRC.SDKBase.Validation.Performance.Stats; +using VRCStation = VRC.SDK3.Avatars.Components.VRCStation; +using VRC.SDK3.Validation; + +[assembly: VRCSdkControlPanelBuilder(typeof(VRCSdkControlPanelAvatarBuilder3A))] + +namespace VRC.SDK3.Editor +{ + public class VRCSdkControlPanelAvatarBuilder3A : VRCSdkControlPanelAvatarBuilder + { + public override void ValidateFeatures(VRC_AvatarDescriptor avatar, Animator anim, AvatarPerformanceStats perfStats) + { + //Create avatar debug hashset + VRCAvatarDescriptor avatarSDK3 = avatar as VRCAvatarDescriptor; + if (avatarSDK3 != null) + { + avatarSDK3.animationHashSet.Clear(); + + foreach (VRCAvatarDescriptor.CustomAnimLayer animLayer in avatarSDK3.baseAnimationLayers) + { + AnimatorController controller = animLayer.animatorController as AnimatorController; + if (controller != null) + { + foreach (AnimatorControllerLayer layer in controller.layers) + { + ProcessStateMachine(layer.stateMachine, ""); + void ProcessStateMachine(AnimatorStateMachine stateMachine, string prefix) + { + //Update prefix + prefix = prefix + stateMachine.name + "."; + + //States + foreach (var state in stateMachine.states) + { + VRCAvatarDescriptor.DebugHash hash = new VRCAvatarDescriptor.DebugHash(); + string fullName = prefix + state.state.name; + hash.hash = Animator.StringToHash(fullName); + hash.name = fullName.Remove(0, layer.stateMachine.name.Length + 1); + avatarSDK3.animationHashSet.Add(hash); + } + + //Sub State Machines + foreach (var subMachine in stateMachine.stateMachines) + ProcessStateMachine(subMachine.stateMachine, prefix); + } + } + } + } + } + + //Validate Playable Layers + if (avatarSDK3 != null && avatarSDK3.customizeAnimationLayers) + { + VRCAvatarDescriptor.CustomAnimLayer gestureLayer = avatarSDK3.baseAnimationLayers[2]; + if (anim != null + && anim.isHuman + && gestureLayer.animatorController != null + && gestureLayer.type == VRCAvatarDescriptor.AnimLayerType.Gesture + && !gestureLayer.isDefault) + { + AnimatorController controller = gestureLayer.animatorController as AnimatorController; + if (controller != null && controller.layers[0].avatarMask == null) + _builder.OnGUIError(avatar, "Gesture Layer needs valid mask on first animator layer", + delegate { OpenAnimatorControllerWindow(controller); }, null); + } + } + + //Expression menu images + if (avatarSDK3 != null) + { + bool ValidateTexture(Texture2D texture) + { + string path = AssetDatabase.GetAssetPath(texture); + TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; + if (importer == null) + return true; + TextureImporterPlatformSettings settings = importer.GetDefaultPlatformTextureSettings(); + + //Max texture size + if ((texture.width > MAX_ACTION_TEXTURE_SIZE || texture.height > MAX_ACTION_TEXTURE_SIZE) && + settings.maxTextureSize > MAX_ACTION_TEXTURE_SIZE) + return false; + + //Compression + if (settings.textureCompression == TextureImporterCompression.Uncompressed) + return false; + + //Success + return true; + } + + void FixTexture(Texture2D texture) + { + string path = AssetDatabase.GetAssetPath(texture); + TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; + if (importer == null) + return; + TextureImporterPlatformSettings settings = importer.GetDefaultPlatformTextureSettings(); + + //Max texture size + if (texture.width > MAX_ACTION_TEXTURE_SIZE || texture.height > MAX_ACTION_TEXTURE_SIZE) + settings.maxTextureSize = Math.Min(settings.maxTextureSize, MAX_ACTION_TEXTURE_SIZE); + + //Compression + if (settings.textureCompression == TextureImporterCompression.Uncompressed) + settings.textureCompression = TextureImporterCompression.Compressed; + + //Set & Reimport + importer.SetPlatformTextureSettings(settings); + AssetDatabase.ImportAsset(path); + } + + //Find all textures + List<Texture2D> textures = new List<Texture2D>(); + List<VRCExpressionsMenu> menuStack = new List<VRCExpressionsMenu>(); + FindTextures(avatarSDK3.expressionsMenu); + + void FindTextures(VRCExpressionsMenu menu) + { + if (menu == null || menuStack.Contains(menu)) //Prevent recursive menu searching + return; + menuStack.Add(menu); + + //Check controls + foreach (VRCExpressionsMenu.Control control in menu.controls) + { + AddTexture(control.icon); + if (control.labels != null) + { + foreach (VRCExpressionsMenu.Control.Label label in control.labels) + AddTexture(label.icon); + } + + if (control.subMenu != null) + FindTextures(control.subMenu); + } + + void AddTexture(Texture2D texture) + { + if (texture != null) + textures.Add(texture); + } + } + + //Validate + bool isValid = true; + foreach (Texture2D texture in textures) + { + if (!ValidateTexture(texture)) + isValid = false; + } + + if (!isValid) + _builder.OnGUIError(avatar, "Images used for Actions & Moods are too large.", + delegate { Selection.activeObject = avatar.gameObject; }, FixTextures); + + //Fix + void FixTextures() + { + foreach (Texture2D texture in textures) + FixTexture(texture); + } + } + + //Expression menu parameters + if (avatarSDK3 != null) + { + //Check for expression menu/parameters object + if (avatarSDK3.expressionsMenu != null || avatarSDK3.expressionParameters != null) + { + //Menu + if (avatarSDK3.expressionsMenu == null) + _builder.OnGUIError(avatar, "VRCExpressionsMenu object reference is missing.", + delegate { Selection.activeObject = avatarSDK3; }, null); + + //Parameters + if (avatarSDK3.expressionParameters == null) + _builder.OnGUIError(avatar, "VRCExpressionParameters object reference is missing.", + delegate { Selection.activeObject = avatarSDK3; }, null); + } + + //Check if parameters is valid + if (avatarSDK3.expressionParameters != null && avatarSDK3.expressionParameters.CalcTotalCost() > VRCExpressionParameters.MAX_PARAMETER_COST) + { + _builder.OnGUIError(avatar, "VRCExpressionParameters has too many parameters defined.", + delegate { Selection.activeObject = avatarSDK3.expressionParameters; }, null); + } + + //Find all existing parameters + if (avatarSDK3.expressionsMenu != null && avatarSDK3.expressionParameters != null) + { + List<VRCExpressionsMenu> menuStack = new List<VRCExpressionsMenu>(); + List<string> parameters = new List<string>(); + List<VRCExpressionsMenu> selects = new List<VRCExpressionsMenu>(); + FindParameters(avatarSDK3.expressionsMenu); + + void FindParameters(VRCExpressionsMenu menu) + { + if (menu == null || menuStack.Contains(menu)) //Prevent recursive menu searching + return; + menuStack.Add(menu); + + //Check controls + foreach (VRCExpressionsMenu.Control control in menu.controls) + { + AddParameter(control.parameter); + if (control.subParameters != null) + { + foreach (VRCExpressionsMenu.Control.Parameter subParameter in control.subParameters) + { + AddParameter(subParameter); + } + } + + if (control.subMenu != null) + FindParameters(control.subMenu); + } + + void AddParameter(VRCExpressionsMenu.Control.Parameter parameter) + { + if (parameter != null) + { + parameters.Add(parameter.name); + selects.Add(menu); + } + } + } + + //Validate parameters + for (int i = 0; i < parameters.Count; i++) + { + string parameter = parameters[i]; + VRCExpressionsMenu select = selects[i]; + + //Find + bool exists = string.IsNullOrEmpty(parameter) || avatarSDK3.expressionParameters.FindParameter(parameter) != null; + if (!exists) + { + _builder.OnGUIError(avatar, + "VRCExpressionsMenu uses a parameter that is not defined.\nParameter: " + parameter, + delegate { Selection.activeObject = select; }, null); + } + } + + //Validate param choices + foreach (var menu in menuStack) + { + foreach (var control in menu.controls) + { + bool isValid = true; + if (control.type == VRCExpressionsMenu.Control.ControlType.FourAxisPuppet) + { + isValid &= ValidateNonBoolParam(control.subParameters[0].name); + isValid &= ValidateNonBoolParam(control.subParameters[1].name); + isValid &= ValidateNonBoolParam(control.subParameters[2].name); + isValid &= ValidateNonBoolParam(control.subParameters[3].name); + } + else if (control.type == VRCExpressionsMenu.Control.ControlType.RadialPuppet) + { + isValid &= ValidateNonBoolParam(control.subParameters[0].name); + } + else if (control.type == VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet) + { + isValid &= ValidateNonBoolParam(control.subParameters[0].name); + isValid &= ValidateNonBoolParam(control.subParameters[1].name); + } + if (!isValid) + { + _builder.OnGUIError(avatar, + "VRCExpressionsMenu uses an invalid parameter for a control.\nControl: " + control.name, + delegate { Selection.activeObject = menu; }, null); + } + } + + bool ValidateNonBoolParam(string name) + { + VRCExpressionParameters.Parameter param = string.IsNullOrEmpty(name) ? null : avatarSDK3.expressionParameters.FindParameter(name); + if (param != null && param.valueType == VRCExpressionParameters.ValueType.Bool) + return false; + return true; + } + } + } + } + + List<Component> componentsToRemove = AvatarValidation.FindIllegalComponents(avatar.gameObject).ToList(); + + // create a list of the PipelineSaver component(s) + List<Component> toRemoveSilently = new List<Component>(); + foreach (Component c in componentsToRemove) + { + if (c.GetType().Name == "PipelineSaver") + { + toRemoveSilently.Add(c); + } + } + + // delete PipelineSaver(s) from the list of the Components we will destroy now + foreach (Component c in toRemoveSilently) + { + componentsToRemove.Remove(c); + } + + HashSet<string> componentsToRemoveNames = new HashSet<string>(); + List<Component> toRemove = componentsToRemove as List<Component> ?? componentsToRemove; + foreach (Component c in toRemove) + { + if (componentsToRemoveNames.Contains(c.GetType().Name) == false) + componentsToRemoveNames.Add(c.GetType().Name); + } + + if (componentsToRemoveNames.Count > 0) + _builder.OnGUIError(avatar, + "The following component types are found on the Avatar and will be removed by the client: " + + string.Join(", ", componentsToRemoveNames.ToArray()), + delegate { ShowRestrictedComponents(toRemove); }, + delegate { FixRestrictedComponents(toRemove); }); + + List<AudioSource> audioSources = + avatar.gameObject.GetComponentsInChildren<AudioSource>(true).ToList(); + if (audioSources.Count > 0) + _builder.OnGUIWarning(avatar, + "Audio sources found on Avatar, they will be adjusted to safe limits, if necessary.", + GetAvatarSubSelectAction(avatar, typeof(AudioSource)), null); + + List<VRCStation> stations = + avatar.gameObject.GetComponentsInChildren<VRCStation>(true).ToList(); + if (stations.Count > 0) + _builder.OnGUIWarning(avatar, "Stations found on Avatar, they will be adjusted to safe limits, if necessary.", + GetAvatarSubSelectAction(avatar, typeof(VRCStation)), null); + + if (VRCSdkControlPanel.HasSubstances(avatar.gameObject)) + { + _builder.OnGUIWarning(avatar, + "This avatar has one or more Substance materials, which is not supported and may break in-game. Please bake your Substances to regular materials.", + () => { Selection.objects = VRCSdkControlPanel.GetSubstanceObjects(avatar.gameObject); }, + null); + } + + CheckAvatarMeshesForLegacyBlendShapesSetting(avatar); + CheckAvatarMeshesForMeshReadWriteSetting(avatar); + +#if UNITY_ANDROID + IEnumerable<Shader> illegalShaders = AvatarValidation.FindIllegalShaders(avatar.gameObject); + foreach (Shader s in illegalShaders) + { + _builder.OnGUIError(avatar, "Avatar uses unsupported shader '" + s.name + "'. You can only use the shaders provided in 'VRChat/Mobile' for Quest avatars.", delegate () { Selection.activeObject + = avatar.gameObject; }, null); + } +#endif + + foreach (AvatarPerformanceCategory perfCategory in Enum.GetValues(typeof(AvatarPerformanceCategory))) + { + if (perfCategory == AvatarPerformanceCategory.Overall || + perfCategory == AvatarPerformanceCategory.PolyCount || + perfCategory == AvatarPerformanceCategory.AABB || + perfCategory == AvatarPerformanceCategory.AvatarPerformanceCategoryCount) + { + continue; + } + + Action show = null; + + switch (perfCategory) + { + case AvatarPerformanceCategory.AnimatorCount: + show = GetAvatarSubSelectAction(avatar, typeof(Animator)); + break; + case AvatarPerformanceCategory.AudioSourceCount: + show = GetAvatarSubSelectAction(avatar, typeof(AudioSource)); + break; + case AvatarPerformanceCategory.BoneCount: + show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer)); + break; + case AvatarPerformanceCategory.ClothCount: + show = GetAvatarSubSelectAction(avatar, typeof(Cloth)); + break; + case AvatarPerformanceCategory.ClothMaxVertices: + show = GetAvatarSubSelectAction(avatar, typeof(Cloth)); + break; + case AvatarPerformanceCategory.LightCount: + show = GetAvatarSubSelectAction(avatar, typeof(Light)); + break; + case AvatarPerformanceCategory.LineRendererCount: + show = GetAvatarSubSelectAction(avatar, typeof(LineRenderer)); + break; + case AvatarPerformanceCategory.MaterialCount: + show = GetAvatarSubSelectAction(avatar, + new[] {typeof(MeshRenderer), typeof(SkinnedMeshRenderer)}); + break; + case AvatarPerformanceCategory.MeshCount: + show = GetAvatarSubSelectAction(avatar, + new[] {typeof(MeshRenderer), typeof(SkinnedMeshRenderer)}); + break; + case AvatarPerformanceCategory.ParticleCollisionEnabled: + show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); + break; + case AvatarPerformanceCategory.ParticleMaxMeshPolyCount: + show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); + break; + case AvatarPerformanceCategory.ParticleSystemCount: + show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); + break; + case AvatarPerformanceCategory.ParticleTotalCount: + show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); + break; + case AvatarPerformanceCategory.ParticleTrailsEnabled: + show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem)); + break; + case AvatarPerformanceCategory.PhysicsColliderCount: + show = GetAvatarSubSelectAction(avatar, typeof(Collider)); + break; + case AvatarPerformanceCategory.PhysicsRigidbodyCount: + show = GetAvatarSubSelectAction(avatar, typeof(Rigidbody)); + break; + case AvatarPerformanceCategory.PolyCount: + show = GetAvatarSubSelectAction(avatar, + new[] {typeof(MeshRenderer), typeof(SkinnedMeshRenderer)}); + break; + case AvatarPerformanceCategory.SkinnedMeshCount: + show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer)); + break; + case AvatarPerformanceCategory.TrailRendererCount: + show = GetAvatarSubSelectAction(avatar, typeof(TrailRenderer)); + break; + } + + // we can only show these buttons if DynamicBone is installed + + Type dynamicBoneType = typeof(AvatarValidation).Assembly.GetType("DynamicBone"); + Type dynamicBoneColliderType = typeof(AvatarValidation).Assembly.GetType("DynamicBoneCollider"); + if ((dynamicBoneType != null) && (dynamicBoneColliderType != null)) + { + switch (perfCategory) + { + case AvatarPerformanceCategory.DynamicBoneColliderCount: + show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType); + break; + case AvatarPerformanceCategory.DynamicBoneCollisionCheckCount: + show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType); + break; + case AvatarPerformanceCategory.DynamicBoneComponentCount: + show = GetAvatarSubSelectAction(avatar, dynamicBoneType); + break; + case AvatarPerformanceCategory.DynamicBoneSimulatedBoneCount: + show = GetAvatarSubSelectAction(avatar, dynamicBoneType); + break; + } + } + + OnGUIPerformanceInfo(avatar, perfStats, perfCategory, show, null); + } + + _builder.OnGUILink(avatar, "Avatar Optimization Tips", VRCSdkControlPanel.AVATAR_OPTIMIZATION_TIPS_URL); + + } + + public override void OnGUIAvatar(VRC_AvatarDescriptor avatar) + { + EditorGUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle); + EditorGUILayout.BeginHorizontal(); + + EditorGUILayout.BeginVertical(GUILayout.Width(300)); + EditorGUILayout.Space(); + + GUI.enabled = (EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows64) && + (_builder.NoGuiErrorsOrIssues() || Core.APIUser.CurrentUser.developerType == + Core.APIUser.DeveloperType.Internal); + + GUILayout.Label("Offline Testing", VRCSdkControlPanel.infoGuiStyle); + if (GUI.enabled) + { + GUILayout.Label( + "Before uploading your avatar you may build and test it in the VRChat client. Other users will not able to see the test avatar.", + VRCSdkControlPanel.infoGuiStyle); + } + else + { + GUILayout.Label( + "(Not available for Android build target)", + VRCSdkControlPanel.infoGuiStyle); + } + + EditorGUILayout.EndVertical(); + + EditorGUILayout.BeginVertical(GUILayout.Width(200)); + EditorGUILayout.Space(); + + if (GUILayout.Button("Build & Test")) + { + if (Core.APIUser.CurrentUser.canPublishAvatars) + { + VRC_SdkBuilder.ExportAndTestAvatarBlueprint(avatar.gameObject); + + EditorUtility.DisplayDialog("VRChat SDK", "Test Avatar Built", "OK"); + } + else + { + VRCSdkControlPanel.ShowContentPublishPermissionsDialog(); + } + } + + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.Separator(); + + EditorGUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle); + EditorGUILayout.BeginHorizontal(); + + EditorGUILayout.BeginVertical(GUILayout.Width(300)); + EditorGUILayout.Space(); + + GUILayout.Label("Online Publishing", VRCSdkControlPanel.infoGuiStyle); + GUILayout.Label( + "In order for other people to see your avatar in VRChat it must be built and published to our game servers.", + VRCSdkControlPanel.infoGuiStyle); + + EditorGUILayout.EndVertical(); + + EditorGUILayout.BeginVertical(GUILayout.Width(200)); + EditorGUILayout.Space(); + + GUI.enabled = _builder.NoGuiErrorsOrIssues() || + Core.APIUser.CurrentUser.developerType == Core.APIUser.DeveloperType.Internal; + if (GUILayout.Button(VRCSdkControlPanel.GetBuildAndPublishButtonString())) + { + bool buildBlocked = !VRCBuildPipelineCallbacks.OnVRCSDKBuildRequested(VRCSDKRequestedBuildType.Avatar); + if (!buildBlocked) + { + if (Core.APIUser.CurrentUser.canPublishAvatars) + { + EnvConfig.FogSettings originalFogSettings = EnvConfig.GetFogSettings(); + EnvConfig.SetFogSettings( + new EnvConfig.FogSettings(EnvConfig.FogSettings.FogStrippingMode.Custom, true, true, true)); + +#if UNITY_ANDROID + EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", true); +#else + EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", false); +#endif + + VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled; + VRC_SdkBuilder.ExportAndUploadAvatarBlueprint(avatar.gameObject); + + EnvConfig.SetFogSettings(originalFogSettings); + + // this seems to workaround a Unity bug that is clearing the formatting of two levels of Layout + // when we call the upload functions + return; + } + else + { + VRCSdkControlPanel.ShowContentPublishPermissionsDialog(); + } + } + } + + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + + GUI.enabled = true; + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/VRCSdkControlPanelAvatarBuilder3A.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/VRCSdkControlPanelAvatarBuilder3A.cs.meta new file mode 100644 index 00000000..4e951837 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor/VRCSdkControlPanelAvatarBuilder3A.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 65c5cbf8795c47529aec9652925e434a +timeCreated: 1605045127
\ No newline at end of file |