diff options
author | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
---|---|---|
committer | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
commit | eb84bb298d2b95aec7b2ae12cbf25ac64f25379a (patch) | |
tree | efd616a157df06ab661c6d56651853431ac6b08b /VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A | |
download | unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.gz unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.bz2 unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.zip |
move to self host
Diffstat (limited to 'VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A')
41 files changed, 5025 insertions, 0 deletions
diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor.meta new file mode 100644 index 00000000..5a83dba1 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4b6a3da21992c4e4d9be5a724530a412 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: 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 diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime.meta new file mode 100644 index 00000000..c007d73f --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f54806a5d143a3741b4ab30c731f05e3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime/AvatarValidation.cs b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime/AvatarValidation.cs new file mode 100644 index 00000000..37d76013 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime/AvatarValidation.cs @@ -0,0 +1,1078 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Unity.Profiling; +using UnityEngine; +using UnityEngine.Profiling; +using VRC.SDKBase; +using VRC.SDKBase.Validation; + +// ReSharper disable RedundantNameQualifier + +namespace VRC.SDK3.Validation +{ + public static class AvatarValidation + { + private const int MAX_STATIONS_PER_AVATAR = 6; + private const float MAX_STATION_ACTIVATE_DISTANCE = 0f; + private const float MAX_STATION_LOCATION_DISTANCE = 2f; + private const float MAX_STATION_COLLIDER_DIMENSION = 2f; + + private static ProfilerMarker _clampRenderQueuesProfilerMarker = new ProfilerMarker("AvatarValidation.ClampRenderQueues"); + private static readonly List<Material> _clampRenderQueuesMaterialsTempList = new List<Material>(); + + public static readonly string[] ComponentTypeWhiteListCommon = new string[] + { + #if UNITY_STANDALONE + "DynamicBone", + "DynamicBoneCollider", + "RootMotion.FinalIK.IKExecutionOrder", + "RootMotion.FinalIK.VRIK", + "RootMotion.FinalIK.FullBodyBipedIK", + "RootMotion.FinalIK.LimbIK", + "RootMotion.FinalIK.AimIK", + "RootMotion.FinalIK.BipedIK", + "RootMotion.FinalIK.GrounderIK", + "RootMotion.FinalIK.GrounderFBBIK", + "RootMotion.FinalIK.GrounderVRIK", + "RootMotion.FinalIK.GrounderQuadruped", + "RootMotion.FinalIK.TwistRelaxer", + "RootMotion.FinalIK.ShoulderRotator", + "RootMotion.FinalIK.FBBIKArmBending", + "RootMotion.FinalIK.FBBIKHeadEffector", + "RootMotion.FinalIK.FABRIK", + "RootMotion.FinalIK.FABRIKChain", + "RootMotion.FinalIK.FABRIKRoot", + "RootMotion.FinalIK.CCDIK", + "RootMotion.FinalIK.RotationLimit", + "RootMotion.FinalIK.RotationLimitHinge", + "RootMotion.FinalIK.RotationLimitPolygonal", + "RootMotion.FinalIK.RotationLimitSpline", + "UnityEngine.Cloth", + "UnityEngine.Light", + "UnityEngine.BoxCollider", + "UnityEngine.SphereCollider", + "UnityEngine.CapsuleCollider", + "UnityEngine.Rigidbody", + "UnityEngine.Joint", + "UnityEngine.Animations.AimConstraint", + "UnityEngine.Animations.LookAtConstraint", + "UnityEngine.Animations.ParentConstraint", + "UnityEngine.Animations.PositionConstraint", + "UnityEngine.Animations.RotationConstraint", + "UnityEngine.Animations.ScaleConstraint", + "UnityEngine.Camera", + "UnityEngine.AudioSource", + "ONSPAudioSource", + #endif + #if !VRC_CLIENT + "VRC.Core.PipelineSaver", + #endif + "VRC.Core.PipelineManager", + "UnityEngine.Transform", + "UnityEngine.Animator", + "UnityEngine.SkinnedMeshRenderer", + "LimbIK", // our limbik based on Unity ik + "LoadingAvatarTextureAnimation", + "UnityEngine.MeshFilter", + "UnityEngine.MeshRenderer", + "UnityEngine.Animation", + "UnityEngine.ParticleSystem", + "UnityEngine.ParticleSystemRenderer", + "UnityEngine.TrailRenderer", + "UnityEngine.FlareLayer", + "UnityEngine.GUILayer", + "UnityEngine.LineRenderer", + "RealisticEyeMovements.EyeAndHeadAnimator", + "RealisticEyeMovements.LookTargetController", + }; + + public static readonly string[] ComponentTypeWhiteListSdk2 = new string[] + { + #if UNITY_STANDALONE + "VRCSDK2.VRC_SpatialAudioSource", + #endif + "VRCSDK2.VRC_AvatarDescriptor", + "VRCSDK2.VRC_AvatarVariations", + "VRCSDK2.VRC_IKFollower", + "VRCSDK2.VRC_Station", + }; + + public static readonly string[] ComponentTypeWhiteListSdk3 = new string[] + { + #if UNITY_STANDALONE + "VRC.SDK3.Avatars.Components.VRCSpatialAudioSource", + #endif + "VRC.SDK3.VRCTestMarker", + "VRC.SDK3.Avatars.Components.VRCAvatarDescriptor", + "VRC.SDK3.Avatars.Components.VRCStation", + }; + +#pragma warning disable 0414 + + private static string[] CombinedComponentTypeWhiteListSdk2 = null; + private static string[] CombinedComponentTypeWhiteListSdk3 = null; + +#pragma warning restore 0414 + + public static readonly string[] ShaderWhiteList = new string[] + { + "VRChat/Mobile/Standard Lite", + "VRChat/Mobile/Diffuse", + "VRChat/Mobile/Bumped Diffuse", + "VRChat/Mobile/Bumped Mapped Specular", + "VRChat/Mobile/Toon Lit", + "VRChat/Mobile/MatCap Lit", + + "VRChat/Mobile/Particles/Additive", + "VRChat/Mobile/Particles/Multiply", + }; + + public static bool ps_limiter_enabled = false; + public static int ps_max_particles = 50000; + public static int ps_max_systems = 200; + public static int ps_max_emission = 5000; + public static int ps_max_total_emission = 40000; + public static int ps_mesh_particle_divider = 50; + public static int ps_mesh_particle_poly_limit = 50000; + public static int ps_collision_penalty_high = 120; + public static int ps_collision_penalty_med = 60; + public static int ps_collision_penalty_low = 10; + public static int ps_trails_penalty = 10; + public static int ps_max_particle_force = 0; // can not be disabled + + private static HashSet<System.Type> GetWhitelistForSDK(GameObject avatar) + { + VRC.SDKBase.VRC_AvatarDescriptor descriptor = avatar.GetComponent<VRC.SDKBase.VRC_AvatarDescriptor>(); + + #if VRC_SDK_VRCSDK2 + if(descriptor is VRCSDK2.VRC_AvatarDescriptor) + { + if(CombinedComponentTypeWhiteListSdk2 == null) + { + List<string> concatenation = new List<string>(ComponentTypeWhiteListCommon); + concatenation.AddRange(ComponentTypeWhiteListSdk2); + CombinedComponentTypeWhiteListSdk2 = concatenation.ToArray(); + } + + return ValidationUtils.WhitelistedTypes("avatar-sdk2", CombinedComponentTypeWhiteListSdk2); + } + #endif + #if VRC_SDK_VRCSDK3 + if(descriptor is VRC.SDK3.Avatars.Components.VRCAvatarDescriptor) + { + if(CombinedComponentTypeWhiteListSdk3 == null) + { + List<string> concatenation = new List<string>(ComponentTypeWhiteListCommon); + concatenation.AddRange(ComponentTypeWhiteListSdk3); + CombinedComponentTypeWhiteListSdk3 = concatenation.ToArray(); + } + + return ValidationUtils.WhitelistedTypes("avatar-sdk3", CombinedComponentTypeWhiteListSdk3); + } + #endif + //throw new System.Exception("Malformed avatar"); + // instead of exception, log error, and return empty whitelist + Debug.LogError("Malformed avatar"); + return new HashSet<System.Type>(); + } + + public static void RemoveIllegalComponents(GameObject target, bool retry = true) + { + ValidationUtils.RemoveIllegalComponents(target, GetWhitelistForSDK(target), retry); + } + + public static IEnumerable<Component> FindIllegalComponents(GameObject target) + { + return ValidationUtils.FindIllegalComponents(target, GetWhitelistForSDK(target)); + } + + private static ProfilerMarker _enforceAudioSourceLimitsProfilerMarker = new ProfilerMarker("AvatarValidation.EnforceAudioSourceLimits"); + + public static void EnforceAudioSourceLimits(GameObject currentAvatar) + { + using(_enforceAudioSourceLimitsProfilerMarker.Auto()) + { + if(currentAvatar == null) + { + return; + } + + Queue<GameObject> children = new Queue<GameObject>(); + if(currentAvatar != null) + { + children.Enqueue(currentAvatar.gameObject); + } + + while(children.Count > 0) + { + GameObject child = children.Dequeue(); + if(child == null) + { + continue; + } + + int childCount = child.transform.childCount; + for(int idx = 0; idx < childCount; ++idx) + { + children.Enqueue(child.transform.GetChild(idx).gameObject); + } + + #if VRC_CLIENT + if(child.GetComponent<USpeaker>() != null) + { + continue; + } + #endif + + AudioSource[] sources = child.transform.GetComponents<AudioSource>(); + if(sources == null || sources.Length <= 0) + { + continue; + } + + AudioSource audioSource = sources[0]; + if(audioSource == null) + { + continue; + } + + + #if VRC_CLIENT + audioSource.outputAudioMixerGroup = VRCAudioManager.GetAvatarGroup(); + audioSource.priority = Mathf.Clamp(audioSource.priority, 200, 255); + #else + ProcessSpatialAudioSources(audioSource); + #endif //!VRC_CLIENT + + if(sources.Length <= 1) + { + continue; + } + + Debug.LogError("Disabling extra AudioSources on GameObject(" + child.name + "). Only one is allowed per GameObject."); + for(int i = 1; i < sources.Length; i++) + { + if(sources[i] == null) + { + Profiler.EndSample(); + continue; + } + + #if VRC_CLIENT + sources[i].enabled = false; + sources[i].clip = null; + #else + ValidationUtils.RemoveComponent(sources[i]); + #endif //!VRC_CLIENT + } + } + } + } + + public static void EnforceClothLimits(GameObject avatarGameObject) + { + const int clothMaxSolverFrequency = 240; + foreach(Cloth cloth in avatarGameObject.GetComponentsInChildren<Cloth>(true)) + { + if(cloth.clothSolverFrequency > clothMaxSolverFrequency) + { + cloth.clothSolverFrequency = clothMaxSolverFrequency; + } + } + } + + #if VRC_CLIENT + public static void EnforceAimIKLimits(GameObject avatarGameObject) + { + const int aimIKMaxSolverFrequency = 64; + foreach (RootMotion.FinalIK.AimIK aimIK in avatarGameObject.GetComponentsInChildren<RootMotion.FinalIK.AimIK>(true)) + { + if (aimIK.solver.maxIterations > aimIKMaxSolverFrequency) + { + aimIK.solver.maxIterations = aimIKMaxSolverFrequency; + } + } + } + #endif + + #if !VRC_CLIENT + private static void ProcessSpatialAudioSources(AudioSource audioSource) + { + #if VRC_SDK_VRCSDK2 + VRC_SpatialAudioSource vrcSpatialAudioSource2 = audioSource.gameObject.GetComponent<VRC_SpatialAudioSource>(); + if(vrcSpatialAudioSource2 == null) + { + // user has not yet added VRC_SpatialAudioSource (or ONSP) + // so set up some defaults + vrcSpatialAudioSource2 = audioSource.gameObject.AddComponent<VRC_SpatialAudioSource>(); + vrcSpatialAudioSource2.Gain = AudioManagerSettings.AvatarAudioMaxGain; + vrcSpatialAudioSource2.Far = AudioManagerSettings.AvatarAudioMaxRange; + vrcSpatialAudioSource2.Near = 0f; + vrcSpatialAudioSource2.VolumetricRadius = 0f; + vrcSpatialAudioSource2.EnableSpatialization = true; + vrcSpatialAudioSource2.enabled = true; + audioSource.spatialize = true; + audioSource.priority = Mathf.Clamp(audioSource.priority, 200, 255); + audioSource.bypassEffects = false; + audioSource.bypassListenerEffects = false; + audioSource.spatialBlend = 1f; + audioSource.spread = 0; + + // user is allowed to change, but for now put a safe default + audioSource.maxDistance = AudioManagerSettings.AvatarAudioMaxRange; + audioSource.minDistance = audioSource.maxDistance / 500f; + audioSource.rolloffMode = AudioRolloffMode.Logarithmic; + } + #elif VRC_SDK_VRCSDK3 + VRC.SDK3.Avatars.Components.VRCSpatialAudioSource vrcSpatialAudioSource2 = audioSource.gameObject.GetComponent<VRC.SDK3.Avatars.Components.VRCSpatialAudioSource>(); + if (vrcSpatialAudioSource2 == null) + { + // user has not yet added VRC_SpatialAudioSource (or ONSP) + // so set up some defaults + vrcSpatialAudioSource2 = audioSource.gameObject.AddComponent<VRC.SDK3.Avatars.Components.VRCSpatialAudioSource>(); + vrcSpatialAudioSource2.Gain = AudioManagerSettings.AvatarAudioMaxGain; + vrcSpatialAudioSource2.Far = AudioManagerSettings.AvatarAudioMaxRange; + vrcSpatialAudioSource2.Near = 0f; + vrcSpatialAudioSource2.VolumetricRadius = 0f; + vrcSpatialAudioSource2.EnableSpatialization = true; + vrcSpatialAudioSource2.enabled = true; + audioSource.spatialize = true; + audioSource.priority = Mathf.Clamp(audioSource.priority, 200, 255); + audioSource.bypassEffects = false; + audioSource.bypassListenerEffects = false; + audioSource.spatialBlend = 1f; + audioSource.spread = 0; + + // user is allowed to change, but for now put a safe default + audioSource.maxDistance = AudioManagerSettings.AvatarAudioMaxRange; + audioSource.minDistance = audioSource.maxDistance / 500f; + audioSource.rolloffMode = AudioRolloffMode.Logarithmic; + } + #endif + } + #endif + + public static void EnforceRealtimeParticleSystemLimits(Dictionary<ParticleSystem, int> particleSystems, bool includeDisabled = false, bool stopSystems = true) + { + float totalEmission = 0; + ParticleSystem ps = null; + int max = 0; + int em_penalty = 1; + ParticleSystem.EmissionModule em; + float emission = 0; + ParticleSystem.Burst[] bursts; + + foreach(KeyValuePair<ParticleSystem, int> kp in particleSystems) + { + if(kp.Key == null) + continue; + + if(!kp.Key.isPlaying && !includeDisabled) + continue; + + ps = kp.Key; + max = kp.Value; + em_penalty = 1; + if(ps.collision.enabled) + { + // particle force is always restricted (not dependent on ps_limiter_enabled) + var restrictedCollision = ps.collision; + restrictedCollision.colliderForce = ps_max_particle_force; + + if(ps_limiter_enabled) + { + switch(ps.collision.quality) + { + case ParticleSystemCollisionQuality.High: + max = max / ps_collision_penalty_high; + em_penalty += 3; + break; + case ParticleSystemCollisionQuality.Medium: + max = max / ps_collision_penalty_med; + em_penalty += 2; + break; + case ParticleSystemCollisionQuality.Low: + max = max / ps_collision_penalty_low; + em_penalty += 2; + break; + } + } + } + + if(ps_limiter_enabled && ps.trails.enabled) + { + max = max / ps_trails_penalty; + em_penalty += 3; + } + + if(ps_limiter_enabled && ps.emission.enabled) + { + em = ps.emission; + emission = 0; + emission += GetCurveMax(em.rateOverTime); + emission += GetCurveMax(em.rateOverDistance); + + bursts = new ParticleSystem.Burst[em.burstCount]; + em.GetBursts(bursts); + for(int i = 0; i < bursts.Length; i++) + { + float adjMax = bursts[i].repeatInterval > 1 ? bursts[i].maxCount : bursts[i].maxCount * bursts[i].repeatInterval; + if(adjMax > ps_max_emission) + bursts[i].maxCount = (short)Mathf.Clamp(adjMax, 0, ps_max_emission); + } + + em.SetBursts(bursts); + + emission *= em_penalty; + totalEmission += emission; + if((emission > ps_max_emission || totalEmission > ps_max_total_emission) && stopSystems) + { + kp.Key.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + // Debug.LogWarning("Particle system named " + kp.Key.gameObject.name + " breached particle emission limits, it has been stopped"); + } + } + + if(ps_limiter_enabled && ps.main.maxParticles > Mathf.Clamp(max, 1, kp.Value)) + { + ParticleSystem.MainModule psm = ps.main; + psm.maxParticles = Mathf.Clamp(psm.maxParticles, 1, max); + if(stopSystems) + kp.Key.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + + Debug.LogWarning("Particle system named " + kp.Key.gameObject.name + " breached particle limits, it has been limited"); + } + } + } + + private static ProfilerMarker _enforceAvatarStationLimitsProfilerMarker = new ProfilerMarker("AvatarValidation.EnforceAudioSourceLimits"); + + public static void EnforceAvatarStationLimits(GameObject currentAvatar) + { + using(_enforceAvatarStationLimitsProfilerMarker.Auto()) + { + int stationCount = 0; + foreach(VRC.SDKBase.VRCStation station in currentAvatar.gameObject.GetComponentsInChildren<VRC.SDKBase.VRCStation>(true)) + { + if(station == null) + { + continue; + } + + #if VRC_CLIENT + VRC_StationInternal stationInternal = station.transform.GetComponent<VRC_StationInternal>(); + #endif + if(stationCount < MAX_STATIONS_PER_AVATAR) + { + #if VRC_CLIENT + bool markedForDestruction = false; + #endif + // keep this station, but limit it + if(station.disableStationExit) + { + Debug.LogError("[" + currentAvatar.name + "]==> Stations on avatars cannot disable station exit. Re-enabled."); + station.disableStationExit = false; + } + + if(station.stationEnterPlayerLocation != null) + { + if(Vector3.Distance(station.stationEnterPlayerLocation.position, station.transform.position) > MAX_STATION_LOCATION_DISTANCE) + { + #if VRC_CLIENT + markedForDestruction = true; + Debug.LogError( + "[" + currentAvatar.name + "]==> Station enter location is too far from station (max dist=" + MAX_STATION_LOCATION_DISTANCE + + "). Station disabled."); + #else + Debug.LogError("Station enter location is too far from station (max dist=" + MAX_STATION_LOCATION_DISTANCE + "). Station will be disabled at runtime."); + #endif + } + + if(Vector3.Distance(station.stationExitPlayerLocation.position, station.transform.position) > MAX_STATION_LOCATION_DISTANCE) + { + #if VRC_CLIENT + markedForDestruction = true; + Debug.LogError( + "[" + currentAvatar.name + "]==> Station exit location is too far from station (max dist=" + MAX_STATION_LOCATION_DISTANCE + + "). Station disabled."); + #else + Debug.LogError("Station exit location is too far from station (max dist=" + MAX_STATION_LOCATION_DISTANCE + "). Station will be disabled at runtime."); + #endif + } + + #if VRC_CLIENT + if(markedForDestruction) + { + + ValidationUtils.RemoveComponent(station); + if(stationInternal != null) + { + ValidationUtils.RemoveComponent(stationInternal); + } + } + #endif + } + } + else + { + #if VRC_CLIENT + Debug.LogError("[" + currentAvatar.name + "]==> Removing station over limit of " + MAX_STATIONS_PER_AVATAR); + ValidationUtils.RemoveComponent(station); + if(stationInternal != null) + { + ValidationUtils.RemoveComponent(stationInternal); + } + #else + Debug.LogError("Too many stations on avatar(" + currentAvatar.name + "). Maximum allowed=" + MAX_STATIONS_PER_AVATAR + ". Extra stations will be removed at runtime."); + #endif + } + + stationCount++; + } + } + } + + public static void RemoveCameras(GameObject currentAvatar, bool localPlayer, bool friend) + { + if(!localPlayer && currentAvatar != null) + { + foreach(Camera camera in currentAvatar.GetComponentsInChildren<Camera>(true)) + { + if(camera == null || camera.gameObject == null) + continue; + + Debug.LogWarning("Removing camera from " + camera.gameObject.name); + + if(friend && camera.targetTexture != null) + { + camera.enabled = false; + } + else + { + camera.enabled = false; + if(camera.targetTexture != null) + camera.targetTexture = new RenderTexture(16, 16, 24); + + ValidationUtils.RemoveComponent(camera); + } + } + } + } + + public static void StripAnimations(GameObject currentAvatar) + { + foreach(Animator anim in currentAvatar.GetComponentsInChildren<Animator>(true)) + { + if(anim == null) + continue; + + StripRuntimeAnimatorController(anim.runtimeAnimatorController); + } + + foreach(VRC.SDKBase.VRCStation station in currentAvatar.GetComponentsInChildren<VRC.SDKBase.VRCStation>(true)) + { + if(station == null) + continue; + + StripRuntimeAnimatorController(station.animatorController); + } + #if VRC_SDK_VRCSDK3 + // also strip any controllers inside the av3 descriptor + var desc3 = currentAvatar.GetComponent<VRC.SDK3.Avatars.Components.VRCAvatarDescriptor>(); + if(desc3 != null) + { + foreach(var layer in desc3.baseAnimationLayers) + StripRuntimeAnimatorController(layer.animatorController); + + foreach(var layer in desc3.specialAnimationLayers) + StripRuntimeAnimatorController(layer.animatorController); + } + #endif + } + + private static void StripRuntimeAnimatorController(RuntimeAnimatorController rc) + { + if(rc == null || rc.animationClips == null) + return; + + foreach(AnimationClip clip in rc.animationClips) + { + if(clip == null) + continue; + + if(clip.events != null && clip.events.Length > 0) + Debug.LogWarning("Removing animation events found on " + clip.name + " on animcontroller " + rc.name); + + clip.events = null; + } + } + + public static void RemoveExtraAnimationComponents(GameObject currentAvatar) + { + if(currentAvatar == null) + return; + + // remove Animator comps + { + Animator mainAnimator = currentAvatar.GetComponent<Animator>(); + bool removeMainAnimator = false; + if(mainAnimator != null) + { + if(!mainAnimator.isHuman || mainAnimator.avatar == null || !mainAnimator.avatar.isValid) + { + removeMainAnimator = true; + } + } + + foreach(Animator anim in currentAvatar.GetComponentsInChildren<Animator>(true)) + { + if(anim == null || anim.gameObject == null) + continue; + + // exclude the main avatar animator + if(anim == mainAnimator) + { + if(!removeMainAnimator) + { + continue; + } + } + + Debug.LogWarning("Removing Animator comp from " + anim.gameObject.name); + + anim.enabled = false; + ValidationUtils.RemoveComponent(anim); + } + } + + ValidationUtils.RemoveComponentsOfType<UnityEngine.Animation>(currentAvatar); + } + + private static Color32 GetTrustLevelColor(VRC.Core.APIUser user) + { + #if VRC_CLIENT + Color32 color = new Color32(255, 255, 255, 255); + if (user == null) + { + return color; + } + + color = VRCPlayer.GetDisplayColorForSocialRank(user); + return color; + #else + // we are in sdk, this is not meaningful anyway + return (Color32)Color.grey; + #endif + } + + private static Material CreateFallbackMaterial(Material originalMaterial, VRC.Core.APIUser user) + { + #if VRC_CLIENT + Material fallbackMaterial; + Color trustCol = user != null ? (Color)GetTrustLevelColor(user) : Color.white; + string displayName = user != null ? user.displayName : "localUser"; + + if (originalMaterial == null || originalMaterial.shader == null) + { + fallbackMaterial = VRC.Core.AssetManagement.CreateMatCap(trustCol * 0.8f + new Color(0.2f, 0.2f, 0.2f)); + fallbackMaterial.name = string.Format("MC_{0}_{1}", fallbackMaterial.shader.name, displayName); + } + else + { + var safeShader = VRC.Core.AssetManagement.GetSafeShader(originalMaterial.shader.name); + if (safeShader == null) + { + fallbackMaterial = VRC.Core.AssetManagement.CreateSafeFallbackMaterial(originalMaterial, trustCol * 0.8f + new Color(0.2f, 0.2f, 0.2f)); + fallbackMaterial.name = string.Format("FB_{0}_{1}_{2}", fallbackMaterial.shader.name, displayName, originalMaterial.name); + } + else + { + //Debug.Log("<color=cyan>*** using safe internal fallback for shader:"+ safeShader.name + "</color>"); + fallbackMaterial = new Material(safeShader); + if (safeShader.name == "Standard" || safeShader.name == "Standard (Specular setup)") + { + VRC.Core.AssetManagement.SetupBlendMode(fallbackMaterial); + } + + fallbackMaterial.CopyPropertiesFromMaterial(originalMaterial); + fallbackMaterial.name = string.Format("INT_{0}_{1}_{2}", fallbackMaterial.shader.name, displayName, originalMaterial.name); + } + } + + return fallbackMaterial; + #else + // we are in sdk, this is not meaningful anyway + return new Material(Shader.Find("Standard")); + #endif + } + + public static void BuildAvatarRenderersList(GameObject currentAvatar, List<Renderer> avatarRenderers) + { + currentAvatar.GetComponentsInChildren(true, avatarRenderers); + } + + // TCL's method of allocation avoidance + private static readonly List<Material> _replaceShadersWorkingList = new List<Material>(); + + public static void ReplaceShaders(VRC.Core.APIUser user, List<Renderer> avatarRenderers, FallbackMaterialCache fallbackMaterialCache, bool debug = false) + { + foreach(Renderer avatarRenderer in avatarRenderers) + { + if(avatarRenderer == null) + { + continue; + } + + avatarRenderer.GetSharedMaterials(_replaceShadersWorkingList); + bool anyReplaced = false; + for(int i = 0; i < _replaceShadersWorkingList.Count; ++i) + { + Material currentMaterial = _replaceShadersWorkingList[i]; + if(currentMaterial == null) + { + continue; + } + + // Check if the material has a cached fallback material if not then create a new one. + if(!fallbackMaterialCache.TryGetFallbackMaterial(currentMaterial, out Material fallbackMaterial)) + { + fallbackMaterial = CreateFallbackMaterial(currentMaterial, user); + + // Map the current material to the fallback and the fallback to itself. + fallbackMaterialCache.AddFallbackMaterial(currentMaterial, fallbackMaterial); + fallbackMaterialCache.AddFallbackMaterial(fallbackMaterial, fallbackMaterial); + + if(debug) + { + Debug.Log($"<color=cyan>*** Creating new fallback: '{fallbackMaterial.shader.name}' </color>"); + } + + if(fallbackMaterial == currentMaterial) + { + continue; + } + + _replaceShadersWorkingList[i] = fallbackMaterial; + anyReplaced = true; + continue; + } + + // If the material is the fallback then we don't need to change it. + if(currentMaterial == fallbackMaterial) + { + continue; + } + + if(debug) + { + Debug.Log($"<color=cyan>*** Using existing fallback: '{fallbackMaterial.shader.name}' </color>"); + } + + _replaceShadersWorkingList[i] = fallbackMaterial; + anyReplaced = true; + } + + if(anyReplaced) + { + avatarRenderer.sharedMaterials = _replaceShadersWorkingList.ToArray(); + } + } + } + + public static void ReplaceShadersRealtime(VRC.Core.APIUser user, List<Renderer> avatarRenderers, FallbackMaterialCache fallbackMaterialCache, bool debug = false) + { + ReplaceShaders(user, avatarRenderers, fallbackMaterialCache, debug); + } + + public static void SetupParticleLimits() + { + ps_limiter_enabled = VRC.Core.ConfigManager.RemoteConfig.GetBool("ps_limiter_enabled", ps_limiter_enabled); + ps_max_particles = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_max_particles", ps_max_particles); + ps_max_systems = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_max_systems", ps_max_systems); + ps_max_emission = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_max_emission", ps_max_emission); + ps_max_total_emission = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_max_total_emission", ps_max_total_emission); + ps_mesh_particle_divider = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_mesh_particle_divider", ps_mesh_particle_divider); + ps_mesh_particle_poly_limit = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_mesh_particle_poly_limit", ps_mesh_particle_poly_limit); + ps_collision_penalty_high = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_collision_penalty_high", ps_collision_penalty_high); + ps_collision_penalty_med = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_collision_penalty_med", ps_collision_penalty_med); + ps_collision_penalty_low = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_collision_penalty_low", ps_collision_penalty_low); + ps_trails_penalty = VRC.Core.ConfigManager.RemoteConfig.GetInt("ps_trails_penalty", ps_trails_penalty); + + if(Application.isMobilePlatform) + { + ps_limiter_enabled = true; + } + else + { + ps_limiter_enabled = VRC.Core.ConfigManager.LocalConfig.GetList("betas").Contains("particle_system_limiter") || ps_limiter_enabled; + ps_max_particles = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_max_particles", ps_max_particles); + ps_max_systems = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_max_systems", ps_max_systems); + ps_max_emission = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_max_emission", ps_max_emission); + ps_max_total_emission = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_max_total_emission", ps_max_total_emission); + ps_mesh_particle_divider = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_mesh_particle_divider", ps_mesh_particle_divider); + ps_mesh_particle_poly_limit = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_mesh_particle_poly_limit", ps_mesh_particle_poly_limit); + ps_collision_penalty_high = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_collision_penalty_high", ps_collision_penalty_high); + ps_collision_penalty_med = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_collision_penalty_med", ps_collision_penalty_med); + ps_collision_penalty_low = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_collision_penalty_low", ps_collision_penalty_low); + ps_trails_penalty = VRC.Core.ConfigManager.LocalConfig.GetInt("ps_trails_penalty", ps_trails_penalty); + } + } + + public static Dictionary<ParticleSystem, int> EnforceParticleSystemLimits(GameObject currentAvatar) + { + Dictionary<ParticleSystem, int> particleSystems = new Dictionary<ParticleSystem, int>(); + + foreach(ParticleSystem ps in currentAvatar.transform.GetComponentsInChildren<ParticleSystem>(true)) + { + int realtime_max = ps_max_particles; + + // always limit collision force + var collision = ps.collision; + if(collision.colliderForce > ps_max_particle_force) + { + collision.colliderForce = ps_max_particle_force; + Debug.LogError("Collision force is restricted on avatars, particle system named " + ps.gameObject.name + " collision force restricted to " + ps_max_particle_force); + } + + if(ps_limiter_enabled) + { + if(particleSystems.Count > ps_max_systems) + { + Debug.LogError("Too many particle systems, #" + particleSystems.Count + " named " + ps.gameObject.name + " deleted"); + ValidationUtils.RemoveComponent(ps); + continue; + } + else + { + var main = ps.main; + var emission = ps.emission; + + ParticleSystemRenderer renderer = ps.GetComponent<ParticleSystemRenderer>(); + if(renderer != null) + { + if(renderer.renderMode == ParticleSystemRenderMode.Mesh) + { + Mesh[] meshes = new Mesh[0]; + int highestPoly = 0; + renderer.GetMeshes(meshes); + if(meshes.Length == 0 && renderer.mesh != null) + { + meshes = new Mesh[] {renderer.mesh}; + } + + // Debug.Log(meshes.Length + " meshes possible emmited meshes from " + ps.gameObject.name); + foreach(Mesh m in meshes) + { + if(m.isReadable) + { + if(m.triangles.Length / 3 > highestPoly) + { + highestPoly = m.triangles.Length / 3; + } + } + else + { + if(1000 > highestPoly) + { + highestPoly = int.MaxValue; + } + } + } + + if(highestPoly > 0) + { + highestPoly = Mathf.Clamp(highestPoly / ps_mesh_particle_divider, 1, highestPoly); + realtime_max = Mathf.FloorToInt((float)realtime_max / highestPoly); + + if(highestPoly > ps_mesh_particle_poly_limit) + { + Debug.LogError("Particle system named " + ps.gameObject.name + " breached polygon limits, it has been deleted"); + ValidationUtils.RemoveComponent(ps); + continue; + } + } + } + } + + + ParticleSystem.MinMaxCurve rate = emission.rateOverTime; + + if(rate.mode == ParticleSystemCurveMode.Constant) + { + rate.constant = Mathf.Clamp(rate.constant, 0, ps_max_emission); + } + else if(rate.mode == ParticleSystemCurveMode.TwoConstants) + { + rate.constantMax = Mathf.Clamp(rate.constantMax, 0, ps_max_emission); + } + else + { + rate.curveMultiplier = Mathf.Clamp(rate.curveMultiplier, 0, ps_max_emission); + } + + emission.rateOverTime = rate; + rate = emission.rateOverDistance; + + if(rate.mode == ParticleSystemCurveMode.Constant) + { + rate.constant = Mathf.Clamp(rate.constant, 0, ps_max_emission); + } + else if(rate.mode == ParticleSystemCurveMode.TwoConstants) + { + rate.constantMax = Mathf.Clamp(rate.constantMax, 0, ps_max_emission); + } + else + { + rate.curveMultiplier = Mathf.Clamp(rate.curveMultiplier, 0, ps_max_emission); + } + + emission.rateOverDistance = rate; + + //Disable collision with PlayerLocal layer + collision.collidesWith &= ~(1 << 10); + } + } + + particleSystems.Add(ps, realtime_max); + } + + EnforceRealtimeParticleSystemLimits(particleSystems, true, false); + + return particleSystems; + } + + public static bool ClearLegacyAnimations(GameObject currentAvatar) + { + bool hasLegacyAnims = false; + foreach(var ani in currentAvatar.GetComponentsInChildren<UnityEngine.Animation>(true)) + { + if(ani.clip != null) + if(ani.clip.legacy) + { + Debug.LogWarningFormat("Legacy animation found named '{0}' on '{1}', removing", ani.clip.name, ani.gameObject.name); + ani.clip = null; + hasLegacyAnims = true; + } + + foreach(AnimationState anistate in ani) + if(anistate.clip.legacy) + { + Debug.LogWarningFormat("Legacy animation found named '{0}' on '{1}', removing", anistate.clip.name, ani.gameObject.name); + ani.RemoveClip(anistate.clip); + hasLegacyAnims = true; + } + } + + return hasLegacyAnims; + } + + private static float GetCurveMax(ParticleSystem.MinMaxCurve minMaxCurve) + { + switch(minMaxCurve.mode) + { + case ParticleSystemCurveMode.Constant: + return minMaxCurve.constant; + case ParticleSystemCurveMode.TwoConstants: + return minMaxCurve.constantMax; + default: + return minMaxCurve.curveMultiplier; + } + } + + public static bool AreAnyParticleSystemsPlaying(Dictionary<ParticleSystem, int> particleSystems) + { + foreach(KeyValuePair<ParticleSystem, int> kp in particleSystems) + { + if(kp.Key != null && kp.Key.isPlaying) + return true; + } + + return false; + } + + public static void StopAllParticleSystems(Dictionary<ParticleSystem, int> particleSystems) + { + foreach(KeyValuePair<ParticleSystem, int> kp in particleSystems) + { + if(kp.Key != null && kp.Key.isPlaying) + { + kp.Key.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + } + } + } + + public static IEnumerable<Shader> FindIllegalShaders(GameObject target) + { + return ShaderValidation.FindIllegalShaders(target, ShaderWhiteList); + } + + /// <summary> + /// NOTE: intended to be called from 'VRCAvatarManager.SafetyCheckAndComponentScan' + /// but temporarily disabled (until we enable texture streaming) + /// </summary> + public static void ReportTexturesWithoutMipMapStreaming(VRC.Core.ApiAvatar avatar, GameObject target) + { + var badTextures = new List<Texture2D>(); + foreach(Renderer r in target.GetComponentsInChildren<Renderer>()) + { + foreach(Material m in r.sharedMaterials) + { + foreach(int i in m.GetTexturePropertyNameIDs()) + { + Texture2D t = m.GetTexture(i) as Texture2D; + if(!t) + continue; + + if((t.mipmapCount > 0) && !t.streamingMipmaps) + badTextures.Add(t); + } + } + } + + if(badTextures.Count > 0) + { + string warning = "[" + avatar.name + "]==> One or more avatar textures have non-streaming mipmaps: "; + foreach(Texture2D t in badTextures) + { + warning += "'" + t.name + "', "; + } + + warning = warning.Remove(warning.LastIndexOf(",", StringComparison.Ordinal)); + Debug.LogWarning(warning + "."); + } + } + + public static void ClampRenderQueues(List<Renderer> avatarRenderers, int minimumRenderQueue, int maximumRenderQueue) + { + using(_clampRenderQueuesProfilerMarker.Auto()) + { + foreach(Renderer avatarRenderer in avatarRenderers) + { + if(avatarRenderer == null) + { + continue; + } + + avatarRenderer.GetSharedMaterials(_clampRenderQueuesMaterialsTempList); + foreach(Material avatarSharedMaterial in _clampRenderQueuesMaterialsTempList) + { + if(avatarSharedMaterial == null) + { + continue; + } + + int renderQueue = avatarSharedMaterial.renderQueue; + if(renderQueue < minimumRenderQueue) + { + avatarSharedMaterial.renderQueue = minimumRenderQueue; + } + else if(renderQueue > maximumRenderQueue) + { + avatarSharedMaterial.renderQueue = maximumRenderQueue; + } + } + } + } + } + } +} diff --git a/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime/AvatarValidation.cs.meta b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime/AvatarValidation.cs.meta new file mode 100644 index 00000000..e85a2f67 --- /dev/null +++ b/VRCSDK3AvatarsLegacy/Assets/VRCSDK/SDK3A/Runtime/AvatarValidation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7965a6ae8ad12e649a226748b4219e81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: |