diff --git a/Tools/ACMerge/Editor/MergeAC.cs b/Tools/ACMerge/Editor/MergeAC.cs index 3b8e4ff..e322479 100644 --- a/Tools/ACMerge/Editor/MergeAC.cs +++ b/Tools/ACMerge/Editor/MergeAC.cs @@ -1,542 +1,538 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using UnityEditor; -using UnityEditor.Animations; -using UnityEngine; - -namespace UdonVR.Tools -{ - //using System; - public class MergeAC : EditorWindow - { - public AnimatorController baseController; - public AnimatorController addController; - public bool saveAsNewController = true; - - public string newPath = " "; - private GUILayoutOption[] space = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16) }; - private GUILayoutOption[] noExpandWidth = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16), GUILayout.ExpandWidth(false) }; - private GUIStyle style; - public GUIStyle logoStyle; - private bool[] canAddLayer; - private string[] layerNames; - private bool ChangeCheck; - private bool showLayer = true; - private string currLayer; - private int currLayerNum; - private int canLayerNum; - private AnimatorController tempAddController; - public AnimatorController tempController; - - private bool disableMerge; - private string warning = "None"; - private Vector2 scrollPos; - - [MenuItem("UdonVR/Merge Controllers")] - public static void ShowWindow() - { - MergeAC window = GetWindow("Merge Controllers"); - window.minSize = new Vector2(385, 250); - } - - private void GUIWarning(string text) - { - EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.warnicon")), style); - } - - private void GUIError(string text) - { - disableMerge = true; - EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.erroricon")), style); - } - - private void InitGuiStyles() - { - style = new GUIStyle(EditorStyles.helpBox); - style.richText = true; - style.fontSize = 15; - - logoStyle = new GUIStyle("flow node hex 0") - { - fontSize = 15, - richText = true, - alignment = TextAnchor.MiddleCenter, - hover = ((GUIStyle)"flow node hex 1").normal, - active = ((GUIStyle)"flow node hex 1 on").normal - }; - - logoStyle.padding.top = 17; - logoStyle.normal.textColor = Color.cyan; - } - - private void OnGUI() - { - if (style == null) - InitGuiStyles(); - - EditorGUI.BeginChangeCheck(); - baseController = EditorGUILayout.ObjectField("BaseController", baseController, typeof(AnimatorController), false) as AnimatorController; - bool ChangeBaseCheck = EditorGUI.EndChangeCheck(); - if (baseController != null) - { - EditorGUI.BeginChangeCheck(); - addController = EditorGUILayout.ObjectField("AddController", addController, typeof(AnimatorController), false) as AnimatorController; - ChangeCheck = EditorGUI.EndChangeCheck(); - } - else - { - EditorGUILayout.BeginVertical(space); - EditorGUILayout.Space(); - EditorGUILayout.EndVertical(); - } - if (addController != null) - { - if (canAddLayer == null || ChangeCheck) - { - canAddLayer = Enumerable.Repeat(true, addController.layers.Length).ToArray(); - layerNames = new string[addController.layers.Length]; - } - - EditorGUILayout.BeginHorizontal(); - Rect foldRect = EditorGUILayout.GetControlRect(true); - if (GUILayout.Button("All", noExpandWidth)) - { - canAddLayer = Enumerable.Repeat(true, addController.layers.Length).ToArray(); - } - if (GUILayout.Button("None", noExpandWidth)) - { - canAddLayer = Enumerable.Repeat(false, addController.layers.Length).ToArray(); - } - EditorGUILayout.EndHorizontal(); - showLayer = EditorGUI.Foldout(foldRect, showLayer, "Layers", true); - if (showLayer) - { - scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.ExpandHeight(false)); - for (int i = 0; i < addController.layers.Length; i++) - { - string addLayerName = addController.layers[i].name; - - if (layerNames[i] == null) - { - layerNames[i] = baseController.MakeUniqueLayerName(addLayerName); - } - EditorGUILayout.BeginHorizontal(); - - canAddLayer[i] = EditorGUILayout.ToggleLeft(addLayerName, canAddLayer[i], noExpandWidth); - - layerNames[i] = EditorGUILayout.TextField(layerNames[i]); - - EditorGUILayout.EndHorizontal(); - } - EditorGUILayout.EndScrollView(); - } - } - else - { - canAddLayer = null; - } - EditorGUI.BeginDisabledGroup(baseController == null); - Rect rect = EditorGUILayout.BeginHorizontal(); - EditorGUI.BeginChangeCheck(); - saveAsNewController = EditorGUILayout.Toggle("Save As New Controller", saveAsNewController, noExpandWidth); - if (saveAsNewController) - { - newPath = EditorGUILayout.TextField(newPath); - - if (GUILayout.Button(EditorGUIUtility.IconContent("Folder Icon"), noExpandWidth)) - { - string tempNewPath = UnityEditor.EditorUtility.SaveFilePanelInProject("Save New BaseController", "New" + baseController.name + ".controller", "controller", "Please enter a file name to save the New Controller to", Path.GetDirectoryName(AssetDatabase.GetAssetPath(baseController))); - if (tempNewPath != "") - newPath = tempNewPath; - } - } - bool pathChangeCheck = EditorGUI.EndChangeCheck(); - if (ChangeBaseCheck || baseController != null && newPath.Trim() == "" && !EditorGUIUtility.editingTextField) - { - //Debug.Log("makePath!"); - newPath = AssetDatabase.GenerateUniqueAssetPath(Path.GetDirectoryName(AssetDatabase.GetAssetPath(baseController)) + "/New " + baseController.name + ".controller"); - pathChangeCheck = true; - } - if (pathChangeCheck) - { - if (!saveAsNewController) - { - disableMerge = false; - warning = "SaveToBase"; - } - else - { - if (newPath.StartsWith("Assets/", true, null)) - { - if (newPath.EndsWith(".controller", true, null)) - { - string mvcheck = AssetDatabase.ValidateMoveAsset(AssetDatabase.GetAssetPath(baseController), newPath); - if (mvcheck == "") - { - disableMerge = false; - warning = "None"; - } - else if (mvcheck.EndsWith("Destination path name does already exist")) - { - disableMerge = false; - warning = "Overwrite"; - } - else if (mvcheck.StartsWith("Trying to move asset as a sub directory of a directory that does not exist")) - { - disableMerge = true; - warning = "NoFolder"; - } - else if (mvcheck.StartsWith("Trying to move asset to location it came from")) - { - warning = "SaveOverBase"; - disableMerge = true; - } - } - else - { - disableMerge = true; - warning = "Extension"; - } - } - else - { - disableMerge = true; - warning = "AssetsFolder"; - } - } - } - EditorGUILayout.EndHorizontal(); - EditorGUI.EndDisabledGroup(); - - EditorGUI.BeginDisabledGroup(disableMerge || baseController == null || addController == null); - if (GUILayout.Button("Merge!")) - { - Merge(); - } - EditorGUI.EndDisabledGroup(); - if (baseController == null) - EditorGUILayout.LabelField("Insert a Base AnimatorController to add to.", style); - else if (addController == null) - EditorGUILayout.LabelField("Now add an AnimatorController to put on the Base", style); - else - EditorGUILayout.LabelField("Select Layers you want and click Merge!", style); - - switch (warning) - { - case "AssetsFolder": - GUIError("Can not Save Controller outside of Assets Folder!"); - break; - - case "Extension": - GUIError("Save file extension is not \".controller\"!"); - break; - - case "NoFolder": - GUIError("Can not save to this location, Folder does not exist!"); - break; - - case "Overwrite": - GUIWarning("Overwrite Warning! This will delete the controller at this location and save a new controller at the same location. This will unlink the controller from every thing!"); - break; - - case "SaveOverBase": - GUIError("Overwrite Error! You can not Save a new controller over the Base Controller, Uncheck Save As New Controller if you want to save to the Base Controller."); - break; - - case "SaveToBase": - GUIWarning("This will save to the Base Controller."); - break; - - case "None": - - break; - - default: - GUIError("Unknown Error"); - break; - } - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.Space(); - if (GUILayout.Button("UdonVR\nVRCUdon.com", logoStyle, GUILayout.ExpandWidth(false))) - { - Application.OpenURL("https://VRCUdon.com"); - } - EditorGUILayout.Space(); - EditorGUILayout.EndHorizontal(); - } - - private void OnInspectorUpdate() - { - Repaint(); - } - - private void Merge() - { - try - { - DoMerge(); - } - finally - { - } - } - - public void DoMerge() - { - string path = AssetDatabase.GetAssetPath(baseController); - string pathAdd = AssetDatabase.GetAssetPath(addController); - string tempPath = "Assets/TempController.controller"; - string tempPathAdd = "Assets/TempAddController.controller"; - - canLayerNum = canAddLayer.Count(b => b == true); - - if (saveAsNewController) - AssetDatabase.CopyAsset(path, tempPath); - AssetDatabase.CopyAsset(pathAdd, tempPathAdd); - AssetDatabase.SaveAssets(); - AssetDatabase.Refresh(); - - if (saveAsNewController) - tempController = AssetDatabase.LoadAssetAtPath(tempPath); - else - tempController = baseController; - - tempAddController = AssetDatabase.LoadAssetAtPath(tempPathAdd); - if (tempController == null) - { - Debug.LogError("TempController not found!"); - return; - } - - Debug.Log("Merging Controllers"); - AnimatorControllerParameter[] baseParameters = baseController.parameters; - AnimatorControllerParameter[] addParameters = tempAddController.parameters; - ParameterComparer parameterComparer = new ParameterComparer(); - if (addParameters.Length > 0) - { - //Debug.Log("Merging Stage: Parameters"); - - for (int iPar = 0; iPar < addParameters.Length; iPar++) - { - AnimatorControllerParameter addPar = addParameters[iPar]; - if (!baseParameters.Contains(addPar, parameterComparer)) - { - tempController.AddParameter(addPar); - } - } - } - - AnimatorControllerLayer[] addLayers = tempAddController.layers; - if (addLayers.Length > 0) - { - //Debug.Log("Merging Stage: Layers"); - currLayerNum = 0; - - List baseLayers = baseController.layers.ToList(); - for (int i = 0; i < addLayers.Length; i++) - { - if (canAddLayer[i]) - { - currLayer = layerNames[i]; - currLayerNum++; - - float p = (float)i / addLayers.Length; - UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + " " + p, "Building Layer: " + layerNames[i], p); - AnimatorControllerLayer addLayer = addLayers[i]; - if (i == 0) - addLayer.defaultWeight = 1; - - addLayer.name = baseController.MakeUniqueLayerName(layerNames[i]); - AnimatorStateMachine sm = addLayer.stateMachine; - MoveStateMachine(sm, tempController); - baseLayers.Add(addLayer); - } - } - tempController.layers = baseLayers.ToArray(); - } - UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers", "Done", 1); - AssetDatabase.SaveAssets(); - addController = null; - if (saveAsNewController) - { - AssetDatabase.CopyAsset(tempPath, newPath); - AssetDatabase.DeleteAsset(tempPath); - } - AssetDatabase.DeleteAsset(tempPathAdd); - AssetDatabase.SaveAssets(); - AssetDatabase.Refresh(); - UnityEditor.EditorUtility.ClearProgressBar(); - } - - private string ln; - - private void MoveStateMachine(AnimatorStateMachine sm, AnimatorController tempController) - { - DoMove(sm, tempController); - - ln = " " + currLayerNum + "/" + canLayerNum; - - if (sm.states.Length != 0) - { - int sl = sm.states.Length; - - for (int i = 0; i < sl; i++) - { - var s = sm.states[i]; - - float p = (i + 1) / (float)sl; - - UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States " + p, "Building State: " + s.state.name, p); - DoMove(s.state, tempController); - - if (s.state.motion != null) - { - if (s.state.motion.GetType() == typeof(BlendTree)) - { - BlendTree blendTree = (BlendTree)s.state.motion; - MoveBlendTree(blendTree, tempController); - } - } - - //Debug.Log("transition: " + s.state.name + " > "+ s.state.transitions.Length); - if (s.state.transitions.Length != 0) - { - int l = s.state.transitions.Length; - //Debug.Log("transition: "+ l); - for (int iTrans = 0; iTrans < s.state.transitions.Length; iTrans++) - { - var t = s.state.transitions[iTrans]; - //Debug.Log("Building State: " + s.state.name + " > transition: " + iTrans + "/" + l); - UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States " + p, "Building State: " + s.state.name + " > transition: " + iTrans + "/" + l, p); - DoMove(t, tempController); - } - } - } - } - - //Debug.Log("transition: AnyState > " + sm.anyStateTransitions.Length); - if (sm.anyStateTransitions.Length != 0) - { - MoveTransitions(sm.anyStateTransitions, tempController, "AnyState"); - } - - //Debug.Log("transition: Entry > " + sm.entryTransitions.Length); - if (sm.entryTransitions.Length != 0) - { - MoveTransitions(sm.entryTransitions, tempController, "Entry"); - } - - if (sm.stateMachines.Length != 0) - { - foreach (var csm in sm.stateMachines) - { - AnimatorTransition[] animatorTransitions = sm.GetStateMachineTransitions(csm.stateMachine); - //Debug.Log("transition stateMachine: "+csm.stateMachine.name+" > " + animatorTransitions.Length); - if (animatorTransitions.Length != 0) - MoveTransitions(animatorTransitions, tempController, csm.stateMachine.name); - MoveStateMachine(csm.stateMachine, tempController); - } - } - } - - private void DoMove(Object assObj, AnimatorController tempController) - { - if (AssetDatabase.GetAssetPath(assObj) == AssetDatabase.GetAssetPath(tempAddController)) - { - AssetDatabase.RemoveObjectFromAsset(assObj); - AssetDatabase.SaveAssets(); - - AssetDatabase.AddObjectToAsset(assObj, tempController); - assObj.hideFlags = HideFlags.HideInHierarchy; - //Debug.Log("Adding"); - } - } - - private void MoveTransitions(AnimatorStateTransition[] transitions, AnimatorController tempController, string stateName) - { - //Debug.Log("transition: "+stateName+" > " + transitions.Length); - if (transitions.Length != 0) - { - int l = transitions.Length; - - for (int iTrans = 0; iTrans < l; iTrans++) - { - float p = (iTrans + 1) / (float)l; - var t = transitions[iTrans]; - //Debug.Log("Building State: " + stateName + " > transition: " + iTrans + "/" + l); - UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States ", "Building State: " + stateName + " > transition: " + iTrans + "/" + l, p); - DoMove(t, tempController); - } - } - } - - private void MoveTransitions(AnimatorTransition[] transitions, AnimatorController tempController, string stateName) - { - //Debug.Log("transition: " + stateName + " > " + transitions.Length); - if (transitions.Length != 0) - { - int l = transitions.Length; - - for (int iTrans = 0; iTrans < l; iTrans++) - { - float p = (iTrans + 1) / (float)l; - var t = transitions[iTrans]; - //Debug.Log("Building State: " + stateName + " > transition: " + iTrans + "/" + l); - UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States ", "Building State: " + stateName + " > transition: " + iTrans + "/" + l, p); - DoMove(t, tempController); - } - } - } - - private void MoveBlendTree(BlendTree bT, AnimatorController tempController) - { - DoMove(bT, tempController); - - if (bT.children.Length != 0) - { - foreach (var m in bT.children) - { - if (m.motion != null) - { - if (m.motion.GetType() == typeof(BlendTree)) - { - MoveBlendTree((BlendTree)m.motion, tempController); - } - } - } - } - } - } - - internal class ParameterComparer : IEqualityComparer - { - // AnimatorControllerParameters are equal if their names are equal. - public bool Equals(AnimatorControllerParameter x, AnimatorControllerParameter y) - { - //Check whether the compared objects reference the same data. - if (UnityEngine.Object.ReferenceEquals(x, y)) return true; - - //Check whether any of the compared objects is null. - if (UnityEngine.Object.ReferenceEquals(x, null) || UnityEngine.Object.ReferenceEquals(y, null)) - return false; - - //Check whether the properties are equal. - return x.name == y.name; - } - - // If Equals() returns true for a pair of objects - // then GetHashCode() must return the same value for these objects. - - public int GetHashCode(AnimatorControllerParameter parameter) - { - //Check whether the object is null - if (UnityEngine.Object.ReferenceEquals(parameter, null)) return 0; - - //Get hash code for the Name field if it is not null. - int hashAnimatorControllerParameterName = parameter.nameHash; - - return hashAnimatorControllerParameterName; - } - } +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.Animations; +using UnityEngine; + +using UdonVR.EditorUtility; + +namespace UdonVR.Tools +{ + //using System; + public class MergeAC : EditorWindow + { + public AnimatorController baseController; + public AnimatorController addController; + public bool saveAsNewController = true; + + public string newPath = " "; + private GUILayoutOption[] space = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16) }; + private GUILayoutOption[] noExpandWidth = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16), GUILayout.ExpandWidth(false) }; + private GUIStyle style; + public GUIStyle logoStyle; + private bool[] canAddLayer; + private string[] layerNames; + private bool ChangeCheck; + private bool showLayer = true; + private string currLayer; + private int currLayerNum; + private int canLayerNum; + private AnimatorController tempAddController; + public AnimatorController tempController; + + private bool disableMerge; + private string warning = "None"; + private Vector2 scrollPos; + + [MenuItem("UdonVR/Merge Controllers")] + public static void ShowWindow() + { + MergeAC window = GetWindow("Merge Controllers"); + window.minSize = new Vector2(385, 250); + } + + private void GUIWarning(string text) + { + EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.warnicon")), style); + } + + private void GUIError(string text) + { + disableMerge = true; + EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.erroricon")), style); + } + + private void InitGuiStyles() + { + style = new GUIStyle(EditorStyles.helpBox); + style.richText = true; + style.fontSize = 15; + + logoStyle = new GUIStyle("flow node hex 0") + { + fontSize = 15, + richText = true, + alignment = TextAnchor.MiddleCenter, + hover = ((GUIStyle)"flow node hex 1").normal, + active = ((GUIStyle)"flow node hex 1 on").normal + }; + + logoStyle.padding.top = 17; + logoStyle.normal.textColor = Color.cyan; + } + + private void OnGUI() + { + if (style == null) + InitGuiStyles(); + + EditorGUI.BeginChangeCheck(); + baseController = EditorGUILayout.ObjectField("BaseController", baseController, typeof(AnimatorController), false) as AnimatorController; + bool ChangeBaseCheck = EditorGUI.EndChangeCheck(); + if (baseController != null) + { + EditorGUI.BeginChangeCheck(); + addController = EditorGUILayout.ObjectField("AddController", addController, typeof(AnimatorController), false) as AnimatorController; + ChangeCheck = EditorGUI.EndChangeCheck(); + } + else + { + EditorGUILayout.BeginVertical(space); + EditorGUILayout.Space(); + EditorGUILayout.EndVertical(); + } + if (addController != null) + { + if (canAddLayer == null || ChangeCheck) + { + canAddLayer = Enumerable.Repeat(true, addController.layers.Length).ToArray(); + layerNames = new string[addController.layers.Length]; + } + + EditorGUILayout.BeginHorizontal(); + Rect foldRect = EditorGUILayout.GetControlRect(true); + if (GUILayout.Button("All", noExpandWidth)) + { + canAddLayer = Enumerable.Repeat(true, addController.layers.Length).ToArray(); + } + if (GUILayout.Button("None", noExpandWidth)) + { + canAddLayer = Enumerable.Repeat(false, addController.layers.Length).ToArray(); + } + EditorGUILayout.EndHorizontal(); + showLayer = EditorGUI.Foldout(foldRect, showLayer, "Layers", true); + if (showLayer) + { + scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.ExpandHeight(false)); + for (int i = 0; i < addController.layers.Length; i++) + { + string addLayerName = addController.layers[i].name; + + if (layerNames[i] == null) + { + layerNames[i] = baseController.MakeUniqueLayerName(addLayerName); + } + EditorGUILayout.BeginHorizontal(); + + canAddLayer[i] = EditorGUILayout.ToggleLeft(addLayerName, canAddLayer[i], noExpandWidth); + + layerNames[i] = EditorGUILayout.TextField(layerNames[i]); + + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.EndScrollView(); + } + } + else + { + canAddLayer = null; + } + EditorGUI.BeginDisabledGroup(baseController == null); + Rect rect = EditorGUILayout.BeginHorizontal(); + EditorGUI.BeginChangeCheck(); + saveAsNewController = EditorGUILayout.Toggle("Save As New Controller", saveAsNewController, noExpandWidth); + if (saveAsNewController) + { + newPath = EditorGUILayout.TextField(newPath); + + if (GUILayout.Button(EditorGUIUtility.IconContent("Folder Icon"), noExpandWidth)) + { + string tempNewPath = UnityEditor.EditorUtility.SaveFilePanelInProject("Save New BaseController", "New" + baseController.name + ".controller", "controller", "Please enter a file name to save the New Controller to", Path.GetDirectoryName(AssetDatabase.GetAssetPath(baseController))); + if (tempNewPath != "") + newPath = tempNewPath; + } + } + bool pathChangeCheck = EditorGUI.EndChangeCheck(); + if (ChangeBaseCheck || baseController != null && newPath.Trim() == "" && !EditorGUIUtility.editingTextField) + { + //Debug.Log("makePath!"); + newPath = AssetDatabase.GenerateUniqueAssetPath(Path.GetDirectoryName(AssetDatabase.GetAssetPath(baseController)) + "/New " + baseController.name + ".controller"); + pathChangeCheck = true; + } + if (pathChangeCheck) + { + if (!saveAsNewController) + { + disableMerge = false; + warning = "SaveToBase"; + } + else + { + if (newPath.StartsWith("Assets/", true, null)) + { + if (newPath.EndsWith(".controller", true, null)) + { + string mvcheck = AssetDatabase.ValidateMoveAsset(AssetDatabase.GetAssetPath(baseController), newPath); + if (mvcheck == "") + { + disableMerge = false; + warning = "None"; + } + else if (mvcheck.EndsWith("Destination path name does already exist")) + { + disableMerge = false; + warning = "Overwrite"; + } + else if (mvcheck.StartsWith("Trying to move asset as a sub directory of a directory that does not exist")) + { + disableMerge = true; + warning = "NoFolder"; + } + else if (mvcheck.StartsWith("Trying to move asset to location it came from")) + { + warning = "SaveOverBase"; + disableMerge = true; + } + } + else + { + disableMerge = true; + warning = "Extension"; + } + } + else + { + disableMerge = true; + warning = "AssetsFolder"; + } + } + } + EditorGUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginDisabledGroup(disableMerge || baseController == null || addController == null); + if (GUILayout.Button("Merge!")) + { + Merge(); + } + EditorGUI.EndDisabledGroup(); + if (baseController == null) + EditorGUILayout.LabelField("Insert a Base AnimatorController to add to.", style); + else if (addController == null) + EditorGUILayout.LabelField("Now add an AnimatorController to put on the Base", style); + else + EditorGUILayout.LabelField("Select Layers you want and click Merge!", style); + + switch (warning) + { + case "AssetsFolder": + GUIError("Can not Save Controller outside of Assets Folder!"); + break; + + case "Extension": + GUIError("Save file extension is not \".controller\"!"); + break; + + case "NoFolder": + GUIError("Can not save to this location, Folder does not exist!"); + break; + + case "Overwrite": + GUIWarning("Overwrite Warning! This will delete the controller at this location and save a new controller at the same location. This will unlink the controller from every thing!"); + break; + + case "SaveOverBase": + GUIError("Overwrite Error! You can not Save a new controller over the Base Controller, Uncheck Save As New Controller if you want to save to the Base Controller."); + break; + + case "SaveToBase": + GUIWarning("This will save to the Base Controller."); + break; + + case "None": + + break; + + default: + GUIError("Unknown Error"); + break; + } + + UdonVR_GUI.ShowUdonVRLinks(32, 32, true); + } + + private void OnInspectorUpdate() + { + Repaint(); + } + + private void Merge() + { + try + { + DoMerge(); + } + finally + { + } + } + + public void DoMerge() + { + string path = AssetDatabase.GetAssetPath(baseController); + string pathAdd = AssetDatabase.GetAssetPath(addController); + string tempPath = "Assets/TempController.controller"; + string tempPathAdd = "Assets/TempAddController.controller"; + + canLayerNum = canAddLayer.Count(b => b == true); + + if (saveAsNewController) + AssetDatabase.CopyAsset(path, tempPath); + AssetDatabase.CopyAsset(pathAdd, tempPathAdd); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + + if (saveAsNewController) + tempController = AssetDatabase.LoadAssetAtPath(tempPath); + else + tempController = baseController; + + tempAddController = AssetDatabase.LoadAssetAtPath(tempPathAdd); + if (tempController == null) + { + Debug.LogError("TempController not found!"); + return; + } + + Debug.Log("Merging Controllers"); + AnimatorControllerParameter[] baseParameters = baseController.parameters; + AnimatorControllerParameter[] addParameters = tempAddController.parameters; + ParameterComparer parameterComparer = new ParameterComparer(); + if (addParameters.Length > 0) + { + //Debug.Log("Merging Stage: Parameters"); + + for (int iPar = 0; iPar < addParameters.Length; iPar++) + { + AnimatorControllerParameter addPar = addParameters[iPar]; + if (!baseParameters.Contains(addPar, parameterComparer)) + { + tempController.AddParameter(addPar); + } + } + } + + AnimatorControllerLayer[] addLayers = tempAddController.layers; + if (addLayers.Length > 0) + { + //Debug.Log("Merging Stage: Layers"); + currLayerNum = 0; + + List baseLayers = baseController.layers.ToList(); + for (int i = 0; i < addLayers.Length; i++) + { + if (canAddLayer[i]) + { + currLayer = layerNames[i]; + currLayerNum++; + + float p = (float)i / addLayers.Length; + UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + " " + p, "Building Layer: " + layerNames[i], p); + AnimatorControllerLayer addLayer = addLayers[i]; + if (i == 0) + addLayer.defaultWeight = 1; + + addLayer.name = baseController.MakeUniqueLayerName(layerNames[i]); + AnimatorStateMachine sm = addLayer.stateMachine; + MoveStateMachine(sm, tempController); + baseLayers.Add(addLayer); + } + } + tempController.layers = baseLayers.ToArray(); + } + UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers", "Done", 1); + AssetDatabase.SaveAssets(); + addController = null; + if (saveAsNewController) + { + AssetDatabase.CopyAsset(tempPath, newPath); + AssetDatabase.DeleteAsset(tempPath); + } + AssetDatabase.DeleteAsset(tempPathAdd); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + UnityEditor.EditorUtility.ClearProgressBar(); + } + + private string ln; + + private void MoveStateMachine(AnimatorStateMachine sm, AnimatorController tempController) + { + DoMove(sm, tempController); + + ln = " " + currLayerNum + "/" + canLayerNum; + + if (sm.states.Length != 0) + { + int sl = sm.states.Length; + + for (int i = 0; i < sl; i++) + { + var s = sm.states[i]; + + float p = (i + 1) / (float)sl; + + UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States " + p, "Building State: " + s.state.name, p); + DoMove(s.state, tempController); + + if (s.state.motion != null) + { + if (s.state.motion.GetType() == typeof(BlendTree)) + { + BlendTree blendTree = (BlendTree)s.state.motion; + MoveBlendTree(blendTree, tempController); + } + } + + //Debug.Log("transition: " + s.state.name + " > "+ s.state.transitions.Length); + if (s.state.transitions.Length != 0) + { + int l = s.state.transitions.Length; + //Debug.Log("transition: "+ l); + for (int iTrans = 0; iTrans < s.state.transitions.Length; iTrans++) + { + var t = s.state.transitions[iTrans]; + //Debug.Log("Building State: " + s.state.name + " > transition: " + iTrans + "/" + l); + UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States " + p, "Building State: " + s.state.name + " > transition: " + iTrans + "/" + l, p); + DoMove(t, tempController); + } + } + } + } + + //Debug.Log("transition: AnyState > " + sm.anyStateTransitions.Length); + if (sm.anyStateTransitions.Length != 0) + { + MoveTransitions(sm.anyStateTransitions, tempController, "AnyState"); + } + + //Debug.Log("transition: Entry > " + sm.entryTransitions.Length); + if (sm.entryTransitions.Length != 0) + { + MoveTransitions(sm.entryTransitions, tempController, "Entry"); + } + + if (sm.stateMachines.Length != 0) + { + foreach (var csm in sm.stateMachines) + { + AnimatorTransition[] animatorTransitions = sm.GetStateMachineTransitions(csm.stateMachine); + //Debug.Log("transition stateMachine: "+csm.stateMachine.name+" > " + animatorTransitions.Length); + if (animatorTransitions.Length != 0) + MoveTransitions(animatorTransitions, tempController, csm.stateMachine.name); + MoveStateMachine(csm.stateMachine, tempController); + } + } + } + + private void DoMove(Object assObj, AnimatorController tempController) + { + if (AssetDatabase.GetAssetPath(assObj) == AssetDatabase.GetAssetPath(tempAddController)) + { + AssetDatabase.RemoveObjectFromAsset(assObj); + AssetDatabase.SaveAssets(); + + AssetDatabase.AddObjectToAsset(assObj, tempController); + assObj.hideFlags = HideFlags.HideInHierarchy; + //Debug.Log("Adding"); + } + } + + private void MoveTransitions(AnimatorStateTransition[] transitions, AnimatorController tempController, string stateName) + { + //Debug.Log("transition: "+stateName+" > " + transitions.Length); + if (transitions.Length != 0) + { + int l = transitions.Length; + + for (int iTrans = 0; iTrans < l; iTrans++) + { + float p = (iTrans + 1) / (float)l; + var t = transitions[iTrans]; + //Debug.Log("Building State: " + stateName + " > transition: " + iTrans + "/" + l); + UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States ", "Building State: " + stateName + " > transition: " + iTrans + "/" + l, p); + DoMove(t, tempController); + } + } + } + + private void MoveTransitions(AnimatorTransition[] transitions, AnimatorController tempController, string stateName) + { + //Debug.Log("transition: " + stateName + " > " + transitions.Length); + if (transitions.Length != 0) + { + int l = transitions.Length; + + for (int iTrans = 0; iTrans < l; iTrans++) + { + float p = (iTrans + 1) / (float)l; + var t = transitions[iTrans]; + //Debug.Log("Building State: " + stateName + " > transition: " + iTrans + "/" + l); + UnityEditor.EditorUtility.DisplayProgressBar("Merging Stage: Layers Build Layer: " + currLayer + ln + " > States ", "Building State: " + stateName + " > transition: " + iTrans + "/" + l, p); + DoMove(t, tempController); + } + } + } + + private void MoveBlendTree(BlendTree bT, AnimatorController tempController) + { + DoMove(bT, tempController); + + if (bT.children.Length != 0) + { + foreach (var m in bT.children) + { + if (m.motion != null) + { + if (m.motion.GetType() == typeof(BlendTree)) + { + MoveBlendTree((BlendTree)m.motion, tempController); + } + } + } + } + } + } + + internal class ParameterComparer : IEqualityComparer + { + // AnimatorControllerParameters are equal if their names are equal. + public bool Equals(AnimatorControllerParameter x, AnimatorControllerParameter y) + { + //Check whether the compared objects reference the same data. + if (UnityEngine.Object.ReferenceEquals(x, y)) return true; + + //Check whether any of the compared objects is null. + if (UnityEngine.Object.ReferenceEquals(x, null) || UnityEngine.Object.ReferenceEquals(y, null)) + return false; + + //Check whether the properties are equal. + return x.name == y.name; + } + + // If Equals() returns true for a pair of objects + // then GetHashCode() must return the same value for these objects. + + public int GetHashCode(AnimatorControllerParameter parameter) + { + //Check whether the object is null + if (UnityEngine.Object.ReferenceEquals(parameter, null)) return 0; + + //Get hash code for the Name field if it is not null. + int hashAnimatorControllerParameterName = parameter.nameHash; + + return hashAnimatorControllerParameterName; + } + } } \ No newline at end of file diff --git a/Tools/EasyDoors/PlayerInteractTeleport.cs b/Tools/EasyDoors/PlayerInteractTeleport.cs index 2f3ad14..4f17501 100644 --- a/Tools/EasyDoors/PlayerInteractTeleport.cs +++ b/Tools/EasyDoors/PlayerInteractTeleport.cs @@ -18,6 +18,7 @@ namespace UdonVR.Tools.PlayerTools /// /// [AddComponentMenu("UdonVR/Tools/EasyDoors")] + [UdonBehaviourSyncMode(BehaviourSyncMode.None)] public class PlayerInteractTeleport : UdonSharpBehaviour { [Tooltip("This is the GameObject that the player will teleport to.")] @@ -29,24 +30,37 @@ public class PlayerInteractTeleport : UdonSharpBehaviour const int typeINTERACT = 1 << 0; const int typeENTER = 1 << 1; const int typeEXIT = 1 << 2; + + private void Start() + { + if (!canType(typeINTERACT)) + { + DisableInteractive = true; + transform.GetComponent().isTrigger = true; + } + + } + public override void Interact() { if(canType(typeINTERACT)) - Teleport(); + _Teleport(); } public override void OnPlayerTriggerEnter(VRCPlayerApi player) { + if (player != Networking.LocalPlayer) return; if (canType(typeENTER)) - Teleport(); + _Teleport(); } public override void OnPlayerTriggerExit(VRCPlayerApi player) { + if (player != Networking.LocalPlayer) return; if (canType(typeEXIT)) - Teleport(); + _Teleport(); } - private void Teleport() + private void _Teleport() { Networking.LocalPlayer.TeleportTo(targetLocation.position, targetLocation.rotation, teleportOrientation, lerpOnRemote); } diff --git a/Tools/EasyLocks/EasyLocks_Button.cs b/Tools/EasyLocks/EasyLocks_Button.cs index ce3b520..9db9d3c 100644 --- a/Tools/EasyLocks/EasyLocks_Button.cs +++ b/Tools/EasyLocks/EasyLocks_Button.cs @@ -36,9 +36,9 @@ public class EasyLocks_Button : UdonSharpBehaviour [Space] - [Tooltip("This is what get's toggled when the lock is used.\n\nDefault Off should be used for objects that are off when you upload the world.\n\nThese will turn ON the first time the lock is used.")] + [Tooltip("This is what gets toggled when the lock is used.\n\nDefault Off should be used for objects that are off when you upload the world.\n\nThese will turn ON the first time the lock is used.")] public GameObject[] LockTargetsDefaultOff; - [Tooltip("This is what get's toggled when the lock is used.\n\nDefault On should be used for objects that are on when you upload the world.\n\nThese will turn OFF the first time the lock is used.")] + [Tooltip("This is what gets toggled when the lock is used.\n\nDefault On should be used for objects that are on when you upload the world.\n\nThese will turn OFF the first time the lock is used.")] public GameObject[] LockTargetsDefaultOn; [HideInInspector] diff --git a/Tools/Editor/CreateObjects.cs b/Tools/Editor/CreateObjects.cs new file mode 100644 index 0000000..54e087a --- /dev/null +++ b/Tools/Editor/CreateObjects.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UdonVR.EditorUtility; + +namespace UdonVR.Tools.Utility{ + public class CreateObjects + { + //49 + private const int Menu = 49; + private static void CreateUdonVRPrefab(string filename, MenuCommand _cmd, bool _unPack = false) + { + //Debug.Log("[UdonVR] Trying to load Prefab from file [Assets/_UdonVR/Tools/Utility/Prefabs/" + filename + "]"); + var loadedObject = AssetDatabase.LoadAssetAtPath("Assets/_UdonVR/Tools/Utility/Prefabs/" + filename,typeof(UnityEngine.Object)); + if (loadedObject == null) + { + Debug.LogError("[UdonVR] Failed to find File, did you move the _UdonVR folder? File[" + filename + "]"); + return; + } + CreateObj(loadedObject, _cmd, _unPack); + } + private static void CreatePrefabFromFile(string filename, MenuCommand _cmd, bool _unPack = false) + { + //Debug.Log("[UdonVR] Trying to load Prefab from file [" + filename + "]"); + var loadedObject = AssetDatabase.LoadAssetAtPath(filename, typeof(UnityEngine.Object)); + if (loadedObject == null) + { + Debug.LogError("[UdonVR] Failed to find File at ["+filename+"]?"); + return; + } + CreateObj(loadedObject, _cmd, _unPack); + } + + private static void CreateObj(UnityEngine.Object _loadedObject, MenuCommand _cmd, bool _unPack) + { + GameObject _obj = (GameObject)PrefabUtility.InstantiatePrefab(_loadedObject); + GameObject _target = (_cmd.context as GameObject); + Undo.RegisterCreatedObjectUndo(_obj, "[UdonVR] Created Prefab"); + if (_target != null) + { + _obj.transform.SetParent(_target.transform); + _obj.transform.SetPositionAndRotation(_target.transform.position, _target.transform.rotation); + _obj.layer = _target.layer; + } + _obj.transform.SetAsLastSibling(); + Selection.activeGameObject = _obj; + if (_unPack) + { + PrefabUtility.UnpackPrefabInstance(_obj.gameObject,PrefabUnpackMode.Completely,InteractionMode.AutomatedAction); + } + } + #region VRC + [MenuItem("GameObject/UdonVR/VRC/SceneDescriptor", false, Menu)] + static void CreateSceneDescriptor(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Scene Descriptor"); + CreateUdonVRPrefab("UdonVR_SceneDescriptor.prefab", _cmd); + } + [MenuItem("GameObject/UdonVR/VRC/AvatarPedestal", false, Menu)] + static void CreateAvatarPedestal(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Avatar Pedestal"); + CreatePrefabFromFile("Assets/VRChat Examples/Prefabs/AvatarPedestal.prefab", _cmd); + } + [MenuItem("GameObject/UdonVR/VRC/Chair", false, Menu)] + static void CreateChair(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating VRCChair3"); + CreatePrefabFromFile("Assets/VRChat Examples/Prefabs/VRCChair/VRCChair3.prefab", _cmd); + } + [MenuItem("GameObject/UdonVR/VRC/Mirror", false, Menu)] + static void CreateMirror(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating VRCMirror"); + CreatePrefabFromFile("Assets/VRChat Examples/Prefabs/VRCMirror.prefab", _cmd); + } + [MenuItem("GameObject/UdonVR/VRC/Mirror - No Collider", false, Menu)] + static void CreateMirrorNoCollider(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating VRCMirror - No Collider"); + CreateUdonVRPrefab("VRCMirror_noCollider.prefab", _cmd); + } + + #endregion + #region Meshes + [MenuItem("GameObject/UdonVR/3D Object/Quad (box collider)", false, Menu)] + static void CreateQuad(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Quad (box collider)"); + CreateUdonVRPrefab("Quad (box collider).prefab", _cmd, true); + } + [MenuItem("GameObject/3D Object/Quad (box collider)", false, 6)] + static void CreateQuad2(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Quad (box collider)"); + CreateUdonVRPrefab("Quad (box collider).prefab", _cmd, true); + } + #endregion + #region EasyDoors + [MenuItem("GameObject/UdonVR/EasyDoors/PlayerTeleports", false, Menu)]// + static void CreateDoor_PlayerTeleports(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating PlayerTeleports"); + + PlayerTransforms _transforms = EditorHelper.GetPlayerTransforms(); + if (_transforms != null) + { + Debug.LogWarning("[UdonVR] PlayerTeleports Already Exists"); + Selection.activeObject = _transforms.gameObject; + } else + { + CreatePrefabFromFile("Assets/_UdonVR/Tools/EasyDoors/Prefabs/PlayerTeleports.prefab", _cmd); + } + } + [MenuItem("GameObject/UdonVR/EasyDoors/Spawn", false, Menu)]// + static void CreateDoor_Interact_Spawn(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Door With Spawn"); + CreatePrefabFromFile("Assets/_UdonVR/Tools/EasyDoors/Prefabs/Door+Spawn.prefab", _cmd); + } + [MenuItem("GameObject/UdonVR/EasyDoors/NoSpawn", false, Menu)]// + static void CreateDoor_Interact(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Door"); + CreatePrefabFromFile("Assets/_UdonVR/Tools/EasyDoors/Prefabs/Door.prefab", _cmd); + } + #endregion + + #region Whitelists + [MenuItem("GameObject/UdonVR/Whitelist", false, Menu)] + static void CreateWhitelist(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Whitelist"); + CreatePrefabFromFile("Assets/_UdonVR/Tools/Whitelist/Toggle/Whitelist - Toggles.prefab", _cmd); + } + #endregion + + #region PlayerChimes + [MenuItem("GameObject/UdonVR/PlayerChimes/JoinLeaveSounds", false, Menu)] + static void CreateJoinSounds(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating JoinLeaveSounds"); + CreatePrefabFromFile("Assets/_UdonVR/Tools/Player Chimes/JoinSounds.prefab", _cmd); + } + [MenuItem("GameObject/UdonVR/PlayerChimes/JoinLeaveSounds + Logger", false, Menu)] + static void CreateJoinSoundsLogger(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating JoinLeaveSounds + Logger"); + CreatePrefabFromFile("Assets/_UdonVR/Tools/Player Chimes/PlayerLogger-Sounds.prefab", _cmd); + } + [MenuItem("GameObject/UdonVR/PlayerChimes/Logger", false, Menu)] + static void CreatePlayerLogger(MenuCommand _cmd) + { + Debug.Log("[UdonVR] Creating Player Logger"); + CreatePrefabFromFile("Assets/_UdonVR/Tools/Player Chimes/PlayerLogger.prefab", _cmd); + } + #endregion + } +} \ No newline at end of file diff --git a/Tools/Editor/Functions/UdonVR_Extensions.cs b/Tools/Editor/Functions/UdonVR_Extensions.cs new file mode 100644 index 0000000..005c299 --- /dev/null +++ b/Tools/Editor/Functions/UdonVR_Extensions.cs @@ -0,0 +1,394 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +// created by Hamster9090901 + +namespace UdonVR.EditorUtility +{ + public static class UdonVR_Extensions + { + // created by Hamster9090901 + + #region String + /// + /// Takes a string and makes the first letter uppercase and returns the string. + /// + /// String to uppercase the first letter of. + /// String + public static string ToUpperFirst(this string str) + { + if (str.Length == 1) return str[0].ToString().ToUpper(); + return str[0].ToString().ToUpper() + str.Substring(1); + } + #endregion + + #region Vector + #region Vector2 + /// + /// Set xy components of existing Vector2. + /// + /// + /// + /// + public static Vector2 Set(this Vector2 vector, Vector2 newXY) + { + vector.x = newXY.x; + vector.y = newXY.y; + return vector; + } + #endregion + + #region Vector3 + /// + /// Set xy, z components of existing Vector3. + /// + /// + /// + /// + /// + public static Vector3 Set(this Vector3 vector, Vector2 newXY, float newZ) + { + vector.x = newXY.x; + vector.y = newXY.y; + vector.z = newZ; + return vector; + } + + /// + /// Set x, yz components of existing Vector3. + /// + /// + /// + /// + /// + public static Vector3 Set(this Vector3 vector, float newX, Vector2 newXY) + { + vector.x = newX; + vector.y = newXY.x; + vector.z = newXY.y; + return vector; + } + + /// + /// Set xyz components of existing Vector3. + /// + /// + /// + /// + public static Vector3 Set(this Vector3 vector, Vector3 newXYZ) + { + vector.x = newXYZ.x; + vector.y = newXYZ.y; + vector.z = newXYZ.z; + return vector; + } + #endregion + + #region Vector4 + /// + /// Set xy, z, w components of existing Vector4. + /// + /// + /// + /// + /// + /// + public static Vector4 Set(this Vector4 vector, Vector2 newXY, float newZ, float newW) + { + vector.x = newXY.x; + vector.y = newXY.y; + vector.z = newZ; + vector.w = newW; + return vector; + } + + /// + /// Set x, yz, w components of existing Vector4. + /// + /// + /// + /// + /// + /// + public static Vector4 Set(this Vector4 vector, float newX, Vector2 newYZ, float newW) + { + vector.x = newX; + vector.y = newYZ.x; + vector.z = newYZ.y; + vector.w = newW; + return vector; + } + + /// + /// Set x, y, zw components of existing Vector4. + /// + /// + /// + /// + /// + /// + public static Vector4 Set(this Vector4 vector, float newX, float newY, Vector2 newZW) + { + vector.x = newX; + vector.y = newY; + vector.z = newZW.x; + vector.w = newZW.y; + return vector; + } + + /// + /// Set xy, zw components of existing Vector4. + /// + /// + /// + /// + /// + public static Vector4 Set(this Vector4 vector, Vector2 newXY, Vector2 newZW) + { + vector.x = newXY.x; + vector.y = newXY.y; + vector.z = newZW.x; + vector.w = newZW.y; + return vector; + } + + /// + /// Set xyz, w components of existing Vector4. + /// + /// + /// + /// + /// + public static Vector4 Set(this Vector4 vector, Vector3 newXYZ, float newW) + { + vector.x = newXYZ.x; + vector.y = newXYZ.y; + vector.z = newXYZ.z; + vector.w = newW; + return vector; + } + + /// + /// Set x, yzw components of existing Vector4. + /// + /// + /// + /// + /// + public static Vector4 Set(this Vector4 vector, float newX, Vector3 newYZW) + { + vector.x = newX; + vector.y = newYZW.x; + vector.z = newYZW.y; + vector.w = newYZW.z; + return vector; + } + + /// + /// Set xyzw components of existing Vector4. + /// + /// + /// + /// + public static Vector4 Set(this Vector4 vector, Vector4 newXYZW) + { + vector.x = newXYZW.x; + vector.y = newXYZW.y; + vector.z = newXYZW.z; + vector.w = newXYZW.w; + return vector; + } + #endregion + + #region Vector2Int + /// + /// Set xy components of existing Vector2Int. + /// + /// + /// + /// + public static Vector2Int Set(this Vector2Int vector, Vector2Int newXY) + { + vector.x = newXY.x; + vector.y = newXY.y; + return vector; + } + #endregion + + #region Vector3Int + /// + /// Set xy, z components of existing Vector3Int. + /// + /// + /// + /// + /// + public static Vector3Int Set(this Vector3Int vector, Vector2Int newXY, int newZ) + { + vector.x = newXY.x; + vector.y = newXY.y; + vector.z = newZ; + return vector; + } + + /// + /// Set x, yz components of existing Vector3Int. + /// + /// + /// + /// + /// + public static Vector3Int Set(this Vector3Int vector, int newX, Vector2Int newYZ) + { + vector.x = newX; + vector.y = newYZ.x; + vector.z = newYZ.y; + return vector; + } + + /// + /// Set xyz components of existing Vector3Int. + /// + /// + /// + /// + public static Vector3Int Set(this Vector3Int vector, Vector3Int newXYZ) + { + vector.x = newXYZ.x; + vector.y = newXYZ.y; + vector.z = newXYZ.z; + return vector; + } + #endregion + #endregion + + #region Array + /// + /// Adds an item to the first null spot in the array if that fails it will append the item to the end of the array. + /// + /// + /// + /// Item to add. + /// + public static T[] Add(this T[] array, T item) + { + // loop over array and find the first null spot and replace with the item + for (int _i = 0; _i < array.Length; _i++) + { + if (array[_i] == null) + { + array[_i] = item; + return array; + } + } + + // if there where no empty spots append item to the end of the array + return ArrayHelpers.ArrayAppendItem(array, item); + } + + public static T[] Add(this T[] array, params T[] items) + { + int _itemsIndex = 0; + // loop over array and find the null spots and replace with the items + for (int _i = 0; _i < array.Length; _i++) + { + if (array[_i] == null) + { + array[_i] = items[_itemsIndex]; + _itemsIndex++; + //return array; + } + } + + // if there are left over items append them to the end of the array + if (_itemsIndex < items.Length) + { + T[] _itemsLeft = ArrayHelpers.ArrayRemoveItem(items, 0, _itemsIndex - 1); + array = ArrayHelpers.ArrayAppendItem(array, _itemsLeft); + } + + return array; + } + + /// + /// Append item to the end of the array. + /// + /// + /// + /// Item to append. + /// + public static T[] Append(this T[] array, T item) + { + return ArrayHelpers.ArrayAppendItem(array, item); + } + + /// + /// Append items to the end of the array. + /// + /// + /// + /// Items to append. + /// + public static T[] Append(this T[] array, params T[] items) + { + return ArrayHelpers.ArrayAppendItem(array, items); + } + + /// + /// Clear the array. + /// + /// + /// + /// + public static T[] Clear(this T[] array) + { + return new T[0]; + } + + /// + /// Remove item from the array. + /// + /// + /// + /// Item to remove. + /// + public static T[] Remove(this T[] array, T item) + { + return ArrayHelpers.ArrayRemoveItem(array, item); + } + + /// + /// Remove index from the array. + /// + /// + /// + /// Index to remove. + /// Number of additional indexes / indices to remove. + /// + public static T[] Remove(this T[] array, int index, int count = 0) + { + return ArrayHelpers.ArrayRemoveItem(array, index, count); + } + + /// + /// Find the intex of the first item if item exists. + /// + /// + /// + /// Item to find index of. + /// -1 if no item was found. + public static int FindIndex(this T[] array, T item) + { + for (int _i = 0; _i < array.Length; _i++) + { + if (array[_i].Equals(item)) + { + return _i; + } + } + return -1; + } + #endregion + } +} diff --git a/Tools/Editor/Functions/UdonVR_Functions.cs b/Tools/Editor/Functions/UdonVR_Functions.cs new file mode 100644 index 0000000..bdd44e6 --- /dev/null +++ b/Tools/Editor/Functions/UdonVR_Functions.cs @@ -0,0 +1,85 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using System; +using System.Security.Cryptography; +using System.Text; + +// created by Hamster9090901 + +namespace UdonVR.EditorUtility +{ + public class UdonVR_Functions + { + // created by Hamster9090901 + + private static string[] fileSizes = { "B", "KB", "MB", "GB", "TB" }; + + /// + /// Get a formated string of the inputed fileSize. + /// + /// File size in bytes. + /// + public static string FormatFileSize(float fileSize) + { + int order = 0; + while (fileSize >= 1024 && order < fileSizes.Length - 1) + { + order++; + fileSize = fileSize / 1024; + } + return String.Format("{0:0.##} {1}", fileSize, fileSizes[order]); + } + + /// + /// Gets a random Vector4 color. + /// + /// + public static Vector4 RandomColor() + { + Vector4 output = Vector4.zero; + output.x = UnityEngine.Random.Range(0f, 1f); + output.y = UnityEngine.Random.Range(0f, 1f); + output.z = UnityEngine.Random.Range(0f, 1f); + output.w = 1f; + return output; + } + } + + public class UdonVR_Encryption + { + // created by Hamster9090901 + + #region Get and Create Instance + private static System.Security.Cryptography.MD5 md5; + + /// + /// Get instance / Create one if there wasn't one. + /// + public static System.Security.Cryptography.MD5 MD5 + { + get + { + if (md5 == null) + { + md5 = System.Security.Cryptography.MD5.Create(); + } + return md5; + } + } + #endregion + + /// + /// Create a Guid from a string using MD5 hashing. (Probability of Collision: 2^20.96) + /// + /// String to get a Guid from. + /// + public static Guid GuidFromStringMD5(string input) + { + byte[] hash = MD5.ComputeHash(Encoding.Default.GetBytes(input)); + Guid result = new Guid(hash); + return result; + } + } +} diff --git a/Tools/Editor/Functions/UdonVR_GUI.cs b/Tools/Editor/Functions/UdonVR_GUI.cs new file mode 100644 index 0000000..6005630 --- /dev/null +++ b/Tools/Editor/Functions/UdonVR_GUI.cs @@ -0,0 +1,2441 @@ +using System.Diagnostics.Tracing; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +// created by Hamster9090901 + +namespace UdonVR.EditorUtility +{ + public class UdonVR_GUI_DragAndDrop + { + public static class Controller + { + public static Node SelectedNode = null; + public static Node.InOutData? ConnectionStart = null; + + private static List _nodes = new List(); + public static List Nodes { get { return _nodes; } } + + private static Vector2 _mousePosition = Vector2.zero; + + private static List _removeNodes = new List(); + + public static void Update() + { + if (ConnectionStart == null && Event.current.type == EventType.MouseDown && Event.current.button == 1) + { + _mousePosition = Event.current.mousePosition; + GenericMenu menu = new GenericMenu(); + menu.AddItem(new GUIContent("Node"), false, AddNode, typeof(Node)); + menu.AddItem(new GUIContent("TestNode"), false, AddNode, typeof(TestNode)); + menu.AddItem(new GUIContent("Constant"), false, AddNode, typeof(Constant_Node)); + menu.AddItem(new GUIContent("Debug"), false, AddNode, typeof(Debug_Node)); + menu.AddItem(new GUIContent("Remap"), false, AddNode, typeof(Remap_Node)); + menu.AddItem(new GUIContent("Gate"), false, AddNode, typeof(Gate_Node)); + menu.AddItem(new GUIContent("Clamp"), false, AddNode, typeof(Clamp_Node)); + menu.AddItem(new GUIContent("Math"), false, AddNode, typeof(Math_Node)); + menu.AddItem(new GUIContent("Random"), false, AddNode, typeof(Random_Node)); + menu.AddItem(new GUIContent("Dampening"), false, AddNode, typeof(Dampening_Node)); + menu.AddItem(new GUIContent("Object"), false, AddNode, typeof(Object_Node)); + menu.ShowAsContext(); + Event.current.Use(); + } + + for (int i = 0; i < _nodes.Count; i++) { _nodes[i].OnGUI(); } + for (int i = 0; i < _removeNodes.Count; i++) { _nodes.Remove(_removeNodes[i]); } + if (_removeNodes.Count > 0) + { + for (int i = 0; i < _nodes.Count; i++) { _nodes[i].ValidateInputs = true; } + _removeNodes.Clear(); + } + } + + private static void AddNode(object type) + { + if (_nodes == null) return; + Type _nodeType = (Type)type; + if (_nodeType == typeof(Node)) + { + _nodes.Add(new Node(new GUIContent("Node"), _mousePosition)); + } + else if (_nodeType == typeof(TestNode)) + { + _nodes.Add(new TestNode(new GUIContent("Test Node"), _mousePosition)); + } + else if (_nodeType == typeof(Constant_Node)) + { + _nodes.Add(new Constant_Node(_mousePosition)); + } + else if (_nodeType == typeof(Debug_Node)) + { + _nodes.Add(new Debug_Node(_mousePosition)); + } + else if (_nodeType == typeof(Remap_Node)) + { + _nodes.Add(new Remap_Node(_mousePosition)); + } + else if (_nodeType == typeof(Gate_Node)) + { + _nodes.Add(new Gate_Node(_mousePosition)); + } + else if (_nodeType == typeof(Clamp_Node)) + { + _nodes.Add(new Clamp_Node(_mousePosition)); + } + else if (_nodeType == typeof(Math_Node)) + { + _nodes.Add(new Math_Node(_mousePosition)); + } + else if (_nodeType == typeof(Random_Node)) + { + _nodes.Add(new Random_Node(_mousePosition)); + } + else if (_nodeType == typeof(Dampening_Node)) + { + _nodes.Add(new Dampening_Node(_mousePosition)); + } + else if (_nodeType == typeof(Object_Node)) + { + _nodes.Add(new Object_Node(_mousePosition)); + } + } + + public static void RemoveNode(Node node) { _removeNodes.Add(node); } + } + + #region Node + public class Node + { + public GUIContent content; + public Vector2 position; + private Vector2 _lastPosition; + + /// + /// The Size of the node (Automatically sets height if set to 0) + /// + public Vector2 NodeSize = new Vector2(100, 0); + + private Rect _dragArea = Rect.zero; + private Rect _removeNodeRect = Rect.zero; + private Rect _nodeBody = Rect.zero; + + private bool _isBeingDraged = false; + private Vector2 _mouseOffset = Vector2.zero; + + public struct InOutData + { + public Node node; + public string varName; + public Type type; + public string displayName; + public bool isEnabled; + } + + private InOutData[] _inputs = new InOutData[0]; + public InOutData[] Inputs + { + get { return _inputs; } + private set { _inputs = value; } + } + + private InOutData[] _outputs = new InOutData[0]; + + private Rect[] _inputPositions = new Rect[0]; + private Dictionary _outputPositions = new Dictionary(); + + private Texture _inputOutputTexture; + + private Rect? _inputRect = null; + private Rect? _bodyRect = null; + private Rect? _outputRect = null; + + private bool _validateInputs = false; + public bool ValidateInputs + { + get { return _validateInputs; } + set { _validateInputs = value; } + } + + public Node(GUIContent content, Vector2 position) + { + this.content = content; + this.position = position; + this._lastPosition = this.position; + + this._inputOutputTexture = Resources.Load("_UdonVR/Icons/NodeInputOutput"); + } + + private void Update() + { + _lastPosition = position; + if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && _dragArea.Contains(Event.current.mousePosition)) + { + if (Controller.SelectedNode == null) Controller.SelectedNode = this; + _isBeingDraged = true; + _mouseOffset = position - Event.current.mousePosition; + } + if (Event.current.isMouse && _isBeingDraged && Controller.SelectedNode == this) position = Event.current.mousePosition + _mouseOffset; + if (Event.current.type == EventType.MouseUp && _isBeingDraged) + { + if (Controller.SelectedNode == this) Controller.SelectedNode = null; + _isBeingDraged = false; + _mouseOffset = Vector2.zero; + } + + if (position != _lastPosition) EditorWindow.focusedWindow.Repaint(); // repaint if node was moved (makes movement smooth) also updates node link lines + + _dragArea = new Rect(position.x, position.y, NodeSize.x, _dragArea.height = GUI.skin.box.CalcSize(content).y); + _removeNodeRect = new Rect(_dragArea.x + _dragArea.width - _dragArea.height, _dragArea.y, _dragArea.height, _dragArea.height); + _dragArea.width -= _dragArea.height; + _nodeBody = new Rect(position.x, position.y + _dragArea.height, NodeSize.x, NodeSize.y); + + #region Get Height of different regions and save largest + if (NodeSize.y == 0) // only run if a custom height was not set + { + Rect _overrideRect = Rect.zero; + if (_inputRect.HasValue && _bodyRect.HasValue && _outputRect.HasValue) + { + if (_bodyRect?.height + 2 > _overrideRect.height) + { + _overrideRect.height = _bodyRect.Value.height + 2; + } + if (_inputRect?.height + 3 > _overrideRect.height) + { + _overrideRect.height = _inputRect.Value.height + 3; + } + if (_outputRect?.height + 3 > _overrideRect.height) + { + _overrideRect.height = _outputRect.Value.height + 3; + } + } + if (_overrideRect != Rect.zero) _nodeBody.height = _overrideRect.height; // apply generated override height + } + #endregion + } + + private void Validate_Inputs() + { + if (!_validateInputs) return; + for (int i = 0; i < _inputs.Length; i++) + { + if (_inputs[i].node != null) + { + if (Controller.Nodes.Contains(_inputs[i].node)) { continue; } + else + { + _inputs[i].node = null; + _inputs[i].varName = string.Empty; + } + } + } + } + + public void OnGUI() + { + Update(); + Validate_Inputs(); + + if (Event.current.type == EventType.Repaint) + { + _inputPositions = _inputPositions.Clear(); + _outputPositions.Clear(); + } + + GUILayout.BeginArea(_dragArea, EditorStyles.helpBox); + UdonVR_GUI.Header(content); + GUILayout.EndArea(); + if (GUI.Button(_removeNodeRect, new GUIContent("X", "Remove Node"))) { Controller.RemoveNode(this); } + + GUILayout.BeginArea(_nodeBody, EditorStyles.helpBox); + GUILayout.BeginHorizontal(); + #region Inputs + if (_inputs != null) + { + GUILayout.BeginVertical(); + if (_inputs.Length > 0) + { + for (int i = 0; i < _inputs.Length; i++) + { + GUILayout.BeginHorizontal(); + GUIContent _labelContent = new GUIContent(_inputs[i].displayName.ToString(), _inputs[i].type.ToString()); + Vector2 _labelSize = GUI.skin.label.CalcSize(_labelContent); + _labelSize.x += 16; + if (_labelSize.y < 8) _labelSize.y = 8; + Rect _rect = GUILayoutUtility.GetRect(_labelSize.x, _labelSize.y, GUI.skin.label); + Rect _labelRect = _rect; + _rect.width = 8; + _rect.height = 8; + //_rect.x += _labelRect.width + (_rect.width / 2); + _rect.y += (_labelRect.height / 2) - (_rect.height / 2); + GUI.enabled = _inputs[i].isEnabled; + _rect = UdonVR_GUI.DrawImage(_rect, _inputOutputTexture, _rect.width, _rect.height); + _labelRect.x += _rect.width + (_rect.width / 2); + _labelRect.width = GUI.skin.label.CalcSize(_labelContent).x; + GUI.Label(_labelRect, _labelContent); + GUI.enabled = true; + GUILayout.EndHorizontal(); + if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && _rect.Contains(Event.current.mousePosition) && _inputs[i].isEnabled) + { + if (Controller.ConnectionStart.HasValue) + { + if (_inputs[i].node == Controller.ConnectionStart.Value.node && _inputs[i].varName == Controller.ConnectionStart.Value.varName) + { + if (_inputs[i].type == Controller.ConnectionStart.Value.type || _inputs[i].type == typeof(object)) + { + _inputs[i].node = null; + _inputs[i].varName = string.Empty; + } + } + else + { + if (_inputs[i].type == Controller.ConnectionStart.Value.type || _inputs[i].type == typeof(object)) + { + _inputs[i].node = Controller.ConnectionStart.Value.node; + _inputs[i].varName = Controller.ConnectionStart.Value.varName; + } + } + } + } + _rect.x += _nodeBody.x + (_rect.width / 2); + _rect.y += _nodeBody.y + (_rect.height / 2) + 1; + if (_inputPositions.Length < _inputs.Length) _inputPositions = _inputPositions.Append(_rect); + } + } + GUILayout.EndVertical(); + if (Event.current.type == EventType.Repaint) _inputRect = GUILayoutUtility.GetLastRect(); + } + #endregion + + #region Body + GUILayout.BeginVertical(GUILayout.ExpandWidth(true)); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + NodeBody(); + GUILayout.EndVertical(); + if (Event.current.type == EventType.Repaint) _bodyRect = GUILayoutUtility.GetLastRect(); + #endregion + + #region Outputs + if (_outputs != null) + { + GUILayout.BeginVertical(); + if (_outputs.Length > 0) + { + for (int i = 0; i < _outputs.Length; i++) + { + GUILayout.BeginHorizontal(); + GUIContent _labelContent = new GUIContent(_outputs[i].displayName.ToString(), _outputs[i].type.ToString()); + Vector2 _labelSize = GUI.skin.label.CalcSize(_labelContent); + _labelSize.x += 16; + if (_labelSize.y < 8) _labelSize.y = 8; + Rect _rect = GUILayoutUtility.GetRect(_labelSize.x, _labelSize.y, GUI.skin.label); + Rect _labelRect = _rect; + _labelRect.width = GUI.skin.label.CalcSize(_labelContent).x; + GUI.enabled = _outputs[i].isEnabled; + GUI.Label(_labelRect, _labelContent); + _rect.width = 8; + _rect.height = 8; + _rect.x += _labelRect.width + (_rect.width / 2); + _rect.y += (_labelRect.height / 2) - (_rect.height / 2); + _rect = UdonVR_GUI.DrawImage(_rect, _inputOutputTexture, _rect.width, _rect.height); + GUI.enabled = true; + GUILayout.EndHorizontal(); + if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && _rect.Contains(Event.current.mousePosition) && _outputs[i].isEnabled) + { + InOutData _inOutData = new InOutData(); + _inOutData.node = this; + _inOutData.varName = _outputs[i].varName; + _inOutData.type = _outputs[i].type; + _inOutData.displayName = string.Empty; + _inOutData.isEnabled = _outputs[i].isEnabled; + + if (Controller.ConnectionStart.HasValue) + { + if (Controller.ConnectionStart.Value.Equals(_inOutData)) { Controller.ConnectionStart = null; } + } + else { Controller.ConnectionStart = _inOutData; } + } + _rect.x += _nodeBody.x + (_rect.width / 2); + _rect.y += _nodeBody.y + (_rect.height / 2) + 1; + if (_outputPositions.Count < _outputs.Length && !_outputPositions.ContainsKey(_outputs[i].varName)) _outputPositions.Add(_outputs[i].varName, _rect); + } + } + GUILayout.EndVertical(); + if (Event.current.type == EventType.Repaint) _outputRect = GUILayoutUtility.GetLastRect(); + } + #endregion + GUILayout.EndHorizontal(); + GUILayout.EndArea(); + + if (_inputs != null) + { + if (_inputs.Length > 0) + { + for (int i = 0; i < _inputs.Length; i++) + { + if (_inputs[i].node != null) + { + if (_inputs[i].node._outputPositions.TryGetValue(_inputs[i].varName, out Rect pos)) + { + Handles.BeginGUI(); + Handles.DrawLine( + new Vector3(_inputPositions[i].x, _inputPositions[i].y), + new Vector3(pos.x, pos.y)); + Handles.EndGUI(); + } + } + } + } + } + + if (_outputs != null) + { + if (_outputs.Length > 0) + { + if (Controller.ConnectionStart.HasValue) + { + InOutData connection = Controller.ConnectionStart.Value; + if (connection.node == this) + { + if (_outputPositions.TryGetValue(connection.varName, out Rect pos)) + { + Handles.BeginGUI(); + Handles.DrawLine( + new Vector3(pos.x, pos.y), + new Vector3(Event.current.mousePosition.x, Event.current.mousePosition.y)); + Handles.EndGUI(); + + EditorWindow.focusedWindow.Repaint(); + } + } + if (Event.current.type == EventType.MouseDown && Event.current.button == 1) Controller.ConnectionStart = null; + } + } + } + } + + public virtual void NodeBody() { } // used to set whats inside the nodes body + + /// + /// Converts an object to the passed type. + /// + /// Type to convert to. + /// Input. + /// + public T ConvertObject(object input) { return (T)Convert.ChangeType(input, typeof(T)); } + + #region GetPropertyValue + /// + /// Gets a property value from a node. + /// + /// Typeof property. + /// Node to get the object from. + /// Name of the property to get. + /// + public T GetPropertyValue(object node, string name) + { + if (node == null) return default(T); + + var _field = node.GetType().GetField(name); + if (_field != null) + { + return ConvertObject(_field.GetValue(node)); + } + var _prop = node.GetType().GetProperty(name); + if (_prop != null) + { + return ConvertObject(_prop.GetValue(node)); + } + + Debug.Log("No property / field: (" + name + ") found in: (" + node + ")"); + return default(T); + } + + /// + /// Gets a property value from a node. + /// + /// Typeof property. + /// Index of Inputs to get value from. + /// + public T GetPropertyValue(int inputIndex) + { + if (inputIndex > Inputs.Length - 1) + { + Debug.Log("No input at index: (" + inputIndex.ToString() + ")"); + return default(T); + } + return GetPropertyValue(Inputs[inputIndex].node, Inputs[inputIndex].varName); + } + + /// + /// Gets a property value from a node. (less efficent) + /// + /// Typeof property. + /// Display name of Inputs to get value from. + /// + public T GetPropertyValue(string displayName) + { + for (int i = 0; i < Inputs.Length; i++) + { + if (Inputs[i].displayName == displayName) + { + return GetPropertyValue(i); + } + } + Debug.Log("No input with displayName: (" + displayName + ") found."); + return default(T); + } + #endregion + + /// + /// Adds an Input to the Node. + /// + /// Typeof input. + /// Display name of the input. + public void AddInput(Type type, string displayName) + { + InOutData _data = new InOutData(); + _data.node = null; + _data.varName = string.Empty; + _data.type = type; + _data.displayName = displayName; + _data.isEnabled = true; + _inputs = _inputs.Append(_data); + } + + /// + /// Adds an Output to the Node. + /// + /// Node output variable is on. (this) + /// Name of the variable to output. + /// Typeof output. + /// Display name of the output. + public void AddOutput(Node node, string varName, Type type, string displayName) + { + InOutData _data = new InOutData(); + _data.node = node; + _data.varName = varName; + _data.type = type; + _data.displayName = displayName; + _data.isEnabled = true; + _outputs = _outputs.Append(_data); + } + + /// + /// Enables / Disables a Node Input. + /// + /// Index of the node to enable / disable. + /// Enabled / Disabled state. + public void EnableInput(int nodeIndex, bool state) + { + if (nodeIndex > _inputs.Length - 1) return; + InOutData _data = _inputs[nodeIndex]; + _data.isEnabled = state; + _inputs[nodeIndex] = _data; + } + + /// + /// Enables / Disables a Node Output. + /// + /// Index of the node to enable / disable. + /// Enabled / Disabled state. + public void EnableOutput(int nodeIndex, bool state) + { + if (nodeIndex > _outputs.Length - 1) return; + InOutData _data = _outputs[nodeIndex]; + _data.isEnabled = state; + _outputs[nodeIndex] = _data; + } + } + #endregion + + public class TestNode : Node + { + public bool A = true; + public int B = 25; + public float C = 0.5f; + + public TestNode(GUIContent content, Vector2 position) : base(content, position) + { + NodeSize = new Vector2(250, 0); + + AddInput(typeof(bool), "A Input"); + AddInput(typeof(int), "B Input"); + AddInput(typeof(float), "C Input"); + + AddOutput(this, "A", typeof(bool), "A Output"); + AddOutput(this, "B", typeof(int), "B Output"); + AddOutput(this, "C", typeof(float), "C Output"); + } + + public override void NodeBody() + { + GUILayout.Button("body1"); + GUILayout.Button("body2"); + GUILayout.Button("body3"); + GUILayout.Button("body4"); + } + } + + public class Constant_Node : Node + { + public float floatOutput = 0; + + public Constant_Node(Vector2 position) : base(new GUIContent("Constant"), position) + { + NodeSize = new Vector2(150, 0); + + AddOutput(this, "floatOutput", typeof(float), "Output"); + } + + public override void NodeBody() + { + base.NodeBody(); + floatOutput = EditorGUILayout.FloatField(floatOutput); + } + } + + public class Debug_Node : Node + { + private bool _printInput = false; + + public Debug_Node(Vector2 position) : base(new GUIContent("Debug"), position) + { + NodeSize = new Vector2(150, 0); + + AddInput(typeof(object), "Input"); + } + + public override void NodeBody() + { + base.NodeBody(); + + object _val = null; + _val = GetPropertyValue(0); + if (_val != null) { _val = _val.ToString(); } + else { _val = string.Empty; } + + GUI.enabled = false; + GUILayout.TextField(_val.ToString()); + GUI.enabled = true; + + _printInput = UdonVR_GUI.ToggleButton(new GUIContent("Print", "Prints output to console."), _printInput); + if (_printInput) Debug.Log(_val.ToString()); + } + } + + public class Remap_Node : Node + { + public float floatOutput = 0; + + private float _low1 = 0, _high1 = 1; + private float _low2 = 0, _high2 = 100; + + public Remap_Node(Vector2 position) : base(new GUIContent("Remap"), position) + { + NodeSize = new Vector2(225, 0); + + AddInput(typeof(float), "Input"); + AddOutput(this, "floatOutput", typeof(float), "Output"); + } + + // ( function:, input:<1|4|6|2|0> ,output:<1> ( function:, input:<1|4|6|2|0> ,output:<1> + + /* + + int index = 0; + + string[index] functions; + vector4[index] mode; + int[][] inputs; // array of array + int[][] outputs; // array of array + + vector4[] variables; + + update{ + + variables[0] = lowMid; + variables[1] = Mid; + variables[2] = highMid; + variables[3] = treble; + Remap(variables, index) + } + + */ + + + public override void NodeBody() + { + base.NodeBody(); + + float _val = GetPropertyValue(0); + _val = UdonVR_MathHelpers.Remap(_val, _low1, _high1, _low2, _high2); + floatOutput = _val; + + UdonVR_GUI.FieldArea( + new GUIContent[] + { + new GUIContent(), + new GUIContent() + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValue(_low1), + UdonVR_GUI.FieldAreaValues.SetValue(_high1) + }, + new Action[] + { + (areaValues) => + { + _low1 = areaValues.floatValue.Value; + }, + (areaValues) => + { + _high1 = areaValues.floatValue.Value; + } + } + ); + UdonVR_GUI.FieldArea( + new GUIContent[] + { + new GUIContent(), + new GUIContent() + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValue(_low2), + UdonVR_GUI.FieldAreaValues.SetValue(_high2) + }, + new Action[] + { + (areaValues) => + { + _low2 = areaValues.floatValue.Value; + }, + (areaValues) => + { + _high2 = areaValues.floatValue.Value; + } + } + ); + } + } + + public class Gate_Node : Node + { + public float floatOutput = 0; + + private float _threshold = 0.5f; + private bool _overThreshold = false; + + public enum Mode + { + GreaterThen, LessThen + } + private Mode _mode = Mode.GreaterThen; + + private bool _triggerOnce = true; + + public Gate_Node(Vector2 position) : base(new GUIContent("Gate"), position) + { + NodeSize = new Vector2(275, 0); + + AddInput(typeof(float), "Input"); + AddOutput(this, "floatOutput", typeof(float), "Output"); + } + + public override void NodeBody() + { + base.NodeBody(); + + float _labelWidth = EditorGUIUtility.labelWidth; // store current label width + + GUIContent _label = new GUIContent("Mode"); + EditorGUIUtility.labelWidth = GUI.skin.label.CalcSize(_label).x + 5; + var _enumPopup = UdonVR_GUI.EnumPopup(_label, _mode); + _mode = ConvertObject(_enumPopup.Item1); + + _label = new GUIContent("Threshold"); + EditorGUIUtility.labelWidth = GUI.skin.label.CalcSize(_label).x + 5; + _threshold = EditorGUILayout.FloatField(_label, _threshold); + + EditorGUIUtility.labelWidth = _labelWidth; // reset label width + + _triggerOnce = UdonVR_GUI.ToggleButton(new GUIContent("Trigger Once"), _triggerOnce); + + float _val = GetPropertyValue(0); + + if (_overThreshold) { floatOutput = 0; } + switch (_mode) + { + case Mode.GreaterThen: + if (_val >= _threshold && !_overThreshold) + { + floatOutput = _val; + _overThreshold = true; + } + if (!_triggerOnce) { _overThreshold = false; } + else + { + if (_val < _threshold) + { + floatOutput = 0; + _overThreshold = false; + } + } + break; + case Mode.LessThen: + if (_val <= _threshold && !_overThreshold) + { + floatOutput = _val; + _overThreshold = true; + } + if (!_triggerOnce) { _overThreshold = false; } + else + { + if (_val > _threshold) + { + floatOutput = 0; + _overThreshold = false; + } + } + break; + } + } + } + + public class Clamp_Node : Node + { + public float floatOutput = 0; + + private float _min = 0, _max = 1; + + public Clamp_Node(Vector2 position) : base(new GUIContent("Clamp"), position) + { + NodeSize = new Vector2(225, 0); + + AddInput(typeof(float), "Input"); + AddOutput(this, "floatOutput", typeof(float), "Output"); + } + + public override void NodeBody() + { + base.NodeBody(); + + UdonVR_GUI.FieldArea( + new GUIContent[] + { + new GUIContent(), + new GUIContent() + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValue(_min), + UdonVR_GUI.FieldAreaValues.SetValue(_max) + }, + new Action[] + { + (areaValues) => + { + _min = areaValues.floatValue.Value; + }, + (areaValues) => + { + _max = areaValues.floatValue.Value; + } + } + ); + + floatOutput = Mathf.Clamp(GetPropertyValue(0), _min, _max); + } + } + + public class Math_Node : Node + { + public float floatOutput = 0; + + public enum MathOperation + { + Add, Subtract, Multiply, Divide, Absolute + } + + private MathOperation _operation = MathOperation.Add; + + public Math_Node(Vector2 position) : base(new GUIContent("Math"), position) + { + NodeSize = new Vector2(275, 0); + + AddInput(typeof(float), "A Input"); + AddInput(typeof(float), "B Input"); + AddOutput(this, "floatOutput", typeof(float), "Output"); + } + + public override void NodeBody() + { + base.NodeBody(); + + float _labelWidth = EditorGUIUtility.labelWidth; + GUIContent _label = new GUIContent("Operation"); + EditorGUIUtility.labelWidth = GUI.skin.label.CalcSize(_label).x + 5; + var _enumPopup = UdonVR_GUI.EnumPopup(_label, _operation); + EditorGUIUtility.labelWidth = _labelWidth; + _operation = ConvertObject(_enumPopup.Item1); + + float _a = GetPropertyValue(0); + float _b = GetPropertyValue(1); + float _val = 0; + + switch (_operation) + { + case MathOperation.Add: + EnableInput(1, true); + _val = _a + _b; + break; + case MathOperation.Subtract: + EnableInput(1, true); + _val = _a - _b; + break; + case MathOperation.Multiply: + EnableInput(1, true); + _val = _a * _b; + break; + case MathOperation.Divide: + EnableInput(1, true); + if (_a == 0 || _b == 0) { _val = 0; } + else { _val = _a / _b; } + break; + case MathOperation.Absolute: + EnableInput(1, false); + _val = Math.Abs(_a); + break; + } + floatOutput = _val; + + GUI.enabled = false; + GUILayout.TextField(floatOutput.ToString()); + GUI.enabled = true; + } + } + + public class Random_Node : Node + { + public float floatOutput = 0; + + public float _min = 0, _max = 1; + + public Random_Node(Vector2 position) : base(new GUIContent("Random"), position) + { + NodeSize = new Vector2(225, 0); + + AddOutput(this, "floatOutput", typeof(float), "Output"); + } + + public override void NodeBody() + { + base.NodeBody(); + + UdonVR_GUI.FieldArea( + new GUIContent[] + { + new GUIContent(), + new GUIContent() + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValue(_min), + UdonVR_GUI.FieldAreaValues.SetValue(_max) + }, + new Action[] + { + (areaValues) => + { + _min = areaValues.floatValue.Value; + }, + (areaValues) => + { + _max = areaValues.floatValue.Value; + } + } + ); + + floatOutput = UnityEngine.Random.Range(_min, _max); + + GUI.enabled = false; + GUILayout.TextField(floatOutput.ToString()); + GUI.enabled = true; + } + } + + public class Dampening_Node : Node + { + public float floatOutput = 0; + + public Dampening_Node(Vector2 position) : base(new GUIContent("Dampening"), position) + { + NodeSize = new Vector2(130, 0); + + AddInput(typeof(float), "Target"); + AddOutput(this, "floatOutput", typeof(float), "Output"); + } + + public override void NodeBody() + { + base.NodeBody(); + + floatOutput = UdonVR_MathHelpers.Lerp(floatOutput, GetPropertyValue(0), Time.fixedDeltaTime); + } + } + + // future me make happen k thx + public class Object_Node : Node + { + public Object_Node(Vector2 position) : base(new GUIContent("Object"), position) + { + NodeSize = new Vector2(100, 0); + } + + public override void NodeBody() + { + base.NodeBody(); + } + } + } + + + /// + /// Used for adding additional options to some elements in UdonVR_GUI + /// + public class UdonVR_GUIOption + { + // created by Hamster9090901 + + #region Color + public Color? tintColorDefault; + public Color? tintColorActive; + public Color? textColor; + + /// + /// Sets Default Tint Color. + /// + /// + /// Generic Color, Vector4 | Color | UdonVR_Predefined.Color + /// + public static UdonVR_GUIOption TintColorDefault(GenericColor color) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.tintColorDefault = UdonVR_ColorHelpers.GetFromGenericColor(color); + return _option; + } + + /// + /// Sets Active Tint Color. + /// + /// + /// Generic Color, Vector4 | Color | UdonVR_Predefined.Color + /// + public static UdonVR_GUIOption TintColorActive(GenericColor color) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.tintColorActive = UdonVR_ColorHelpers.GetFromGenericColor(color); + return _option; + } + + /// + /// Sets text Color. + /// + /// + /// Generic Color, Vector4 | Color | UdonVR_Predefined.Color + /// + public static UdonVR_GUIOption TextColor(GenericColor color) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.textColor = UdonVR_ColorHelpers.GetFromGenericColor(color); + return _option; + } + #endregion + + #region Text + public int? fontSize; + public TextAnchor? textAnchor; + + /// + /// Sets Font Size. + /// + /// Size of the font. + /// + public static UdonVR_GUIOption FontSize(int fontSize) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.fontSize = fontSize; + return _option; + } + + /// + /// Sets Text Anchor + /// + /// Text anchor location. + /// + public static UdonVR_GUIOption TextAnchor(TextAnchor textAnchor) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.textAnchor = textAnchor; + return _option; + } + #endregion + + #region Positioning + public RectOffset padding = null; + public float? width; + public float? height; + + /// + /// Sets Padding. + /// + /// Padding. + /// + public static UdonVR_GUIOption Padding(RectOffset padding) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.padding = padding; + return _option; + } + + /// + /// Sets Width. + /// + /// Width. + /// + public static UdonVR_GUIOption Width(float width) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.width = width; + return _option; + } + /// + /// Sets Height. + /// + /// Height. + /// + public static UdonVR_GUIOption Height(float height) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.height = height; + return _option; + } + /// + /// Sets Width and Height. + /// + /// Width. + /// Height. + /// + public static UdonVR_GUIOption WidthHeight(float width, float height) + { + UdonVR_GUIOption _option = new UdonVR_GUIOption(); + _option.width = width; + _option.height = height; + return _option; + } + #endregion + + } + + public class UdonVR_GUI + { + // created by Hamster9090901 + + #region DrawImage + /// + /// Draws an image that follows the layout spacing and padding. + /// + /// Position of the texture. + /// Texture to draw. + /// Width of drawn image. + /// Height of drawn image. + /// Tooltip to show when hovered over. + /// + public static Rect DrawImage(Rect? position, Texture texture, float width = 0, float height = 0, string tooltip = "") + { + if (texture == null) return Rect.zero; // return an empty rect if there was no image + + GUIStyle _style = new GUIStyle(GUI.skin.box); + if (width != 0) _style.fixedWidth = width; + if (height != 0) _style.fixedHeight = height; + + GUIContent _content = new GUIContent(); + _content.image = texture; + _content.tooltip = tooltip; + + if (position == null) position = GUILayoutUtility.GetRect(_content, _style); + + #region Tooltip + if (tooltip != "") + { + GUIStyle _tooltipStyle = new GUIStyle(); + _tooltipStyle.fixedWidth = _style.fixedWidth; + _tooltipStyle.fixedHeight = _style.fixedHeight; + GUI.Box(position.Value, new GUIContent("", tooltip), _tooltipStyle); + } + #endregion + + GUI.DrawTexture(position.Value, texture, ScaleMode.ScaleToFit, true, (width / height)); // draw texture + return position.Value; + } + + /// + /// Draws an image that follows the layout spacing and padding. + /// + /// Texture to draw. + /// Width of drawn image. + /// Height of drawn image. + /// Tooltip to show when hovered over. + public static Rect DrawImage(Texture texture, float width = 0, float height = 0, string tooltip = "") + { + return DrawImage(null, texture, width, height, tooltip); + } + #endregion + + + + #region Href + /// + /// Displays an image that links to a website. + /// + /// Texture to draw. + /// Link to website. + /// Width of drawn image. + /// Height of drawn image. + /// Tooltip to show when hovered over. + public static void Href(Texture texture, string href, float width = 0, float height = 0, string tooltip = "") + { + Rect _rect = DrawImage(texture, width, height, tooltip); + if (Event.current.type == EventType.MouseUp && _rect.Contains(Event.current.mousePosition)) Application.OpenURL(href); + } + + /// + /// Displays a link. + /// + /// Content + /// Link to website. + public static void Href(GUIContent content, string href) + { + GUIStyle _style = new GUIStyle(GUI.skin.box); + Rect _rect = GUILayoutUtility.GetRect(content, _style); + GUI.Label(_rect, content); + if (Event.current.type == EventType.MouseUp && _rect.Contains(Event.current.mousePosition)) Application.OpenURL(href); + } + + /// + /// Displays a link. + /// + /// Link to website. + public static void Href(string href) + { + Href(new GUIContent(href), href); + } + #endregion + + + + #region UdonVR Links + /// + /// Draws the UdonVR links. (Centered) + /// + /// Path to the logos to display. + /// Width of drawn images. + /// Height of drawn images. + public static void ShowUdonVRLinks(int width = 32, int height = 32, bool forceBottom = false, GUIStyle style = null, bool showDebugContainers = false) + { + Texture _discord = (Texture)Resources.Load("_UdonVR/Logos/discord"); + Texture _github = (Texture)Resources.Load("_UdonVR/Logos/github"); + Texture _udonvr = (Texture)Resources.Load("_UdonVR/Logos/udonVR"); + Texture _kofi = (Texture)Resources.Load("_UdonVR/Logos/ko-fi"); + Texture _patreon = (Texture)Resources.Load("_UdonVR/Logos/patreon"); + + GUIStyle _style = new GUIStyle(EditorStyles.helpBox); + _style.padding = new RectOffset(10, 10, 10, 10); + _style = style != null ? style : _style; + + if (forceBottom) GUILayout.FlexibleSpace(); + + GUILayout.BeginHorizontal(showDebugContainers ? UdonVR_Style.Get(new Vector4(0.5f, 0.25f, 0.25f, 1)) : new GUIStyle()); + GUILayout.FlexibleSpace(); + GUILayout.BeginHorizontal(_style); + Href(_discord, "http://discord.Udonvr.com", width, height, "Discord"); + Href(_github, "http://github.UdonVr.com", width, height, "Github"); + Href(_udonvr, "http://discord.Udonvr.com", width, height, "Website"); + Href(_kofi, "http://kofi.UdonVR.com", width, height, "Ko-Fi"); + Href(_patreon, "http://patreon.Udonvr.com", width, height, "Patreon"); + GUILayout.EndHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + if (forceBottom) GUILayout.Space(16); + } + #endregion + + + + #region Header + /// + /// Creates a label with centered text to be used as a header. + /// + /// GUIContent of the header. + /// Additional options for the header. + public static void Header(GUIContent content, params UdonVR_GUIOption[] options) + { + GUIStyle _style = new GUIStyle(GUI.skin.label) + { + alignment = TextAnchor.MiddleCenter, // set text alignment + fontSize = 13 // set font size + }; + Color _tintColorDefault = Color.white; + + foreach (UdonVR_GUIOption option in options) + { + if (option.tintColorDefault.HasValue) _tintColorDefault = option.tintColorDefault.Value; + //if (option.tintColorActive.HasValue) _tintColorActive = option.tintColorActive.Value; + if (option.textColor.HasValue) _style.normal.textColor = option.textColor.Value; + + if (option.fontSize.HasValue) _style.fontSize = option.fontSize.Value; + if (option.textAnchor.HasValue) _style.alignment = option.textAnchor.Value; + + if (option.padding != null) _style.padding = option.padding; + if (option.width.HasValue) _style.fixedWidth = option.width.Value; + if (option.height.HasValue) _style.fixedHeight = option.height.Value; + } + + Color _backgroundColor = GUI.backgroundColor; + GUI.backgroundColor = _backgroundColor * Color.Lerp(Color.white, _tintColorDefault, 0.15f); + EditorGUILayout.LabelField(content, _style, GUILayout.ExpandWidth(true)); // create label field + GUI.backgroundColor = _backgroundColor; + } + #endregion + + + + #region Tinted Button + /// + /// Creates a button that can be tinted with a color. + /// + /// Button position. + /// Content of the button. + /// Additional options for the button. + /// + public static bool TintedButton(Rect position, GUIContent content, params UdonVR_GUIOption[] options) + { + GUIStyle _style = new GUIStyle(GUI.skin.button); + Color _tintColorDefault = Color.white; + + foreach (UdonVR_GUIOption option in options) + { + if (option.tintColorDefault.HasValue) _tintColorDefault = option.tintColorDefault.Value; + if (option.tintColorActive.HasValue) Debug.LogWarning("UdonVR_GUI.TintedButton does not support UdonVR_GUIOption.TintColorActive()"); + + if (option.textColor.HasValue) _style.normal.textColor = option.textColor.Value; + if (option.fontSize.HasValue) _style.fontSize = option.fontSize.Value; + if (option.textAnchor.HasValue) _style.alignment = option.textAnchor.Value; + + if (option.padding != null) _style.padding = option.padding; + if (option.width.HasValue) _style.fixedWidth = option.width.Value; + if (option.height.HasValue) _style.fixedHeight = option.height.Value; + } + + if (position == Rect.zero) position = GUILayoutUtility.GetRect(content, _style); + + //_tintColor = UdonVR_ColorHelpers.GetFromGenericColor(_tintColor); // get color from generic color + Color _backgroundColor = GUI.backgroundColor; + GUI.backgroundColor = _backgroundColor * Color.Lerp(Color.white, _tintColorDefault, 0.15f); + bool state = GUI.Button(position, content, _style); + GUI.backgroundColor = _backgroundColor; + + return state; + } + + /// + /// Creates a button that can be tinted with a color. + /// + /// Content of the button. + /// Additional options for the button. + /// + public static bool TintedButton(GUIContent content, params UdonVR_GUIOption[] options) + { + return TintedButton(Rect.zero, content, options); + } + #endregion + + + + #region Dynamic Scroll View + /// + /// Begin Dynamic Scroll View + /// + /// Width and Height of the Scroll View. + /// Position of the Scroll Content. + /// Size of the View Area. + /// + /// Vector2 scrollPosition. + public static Vector2 BeginDynamicScrollViewHeight( + Vector2 widthHeight, + Vector2 scrollPosition, + Rect viewArea, + bool showDebugContainers = false) + { + GUIStyle _debug = showDebugContainers ? UdonVR_Style.Get(new Vector4(0.5f, 0.25f, 0.5f, 1)) : new GUIStyle(); + float _height = viewArea.height >= widthHeight.y ? widthHeight.y : viewArea.height; + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(widthHeight.x, _height, _debug)); + //scrollPosition = GUI.BeginScrollView(new Rect(position.x, position.y, position.width, _height), scrollPosition, new Rect(0, 0, viewArea.width, viewArea.height)); + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar, GUILayout.Height(_height)); + return scrollPosition; + } + + /// + /// Begin Dynamic Scroll View + /// + /// Position of the Scroll View. + /// Position of the Scroll Content. + /// Size of the View Area. + /// + /// Vector2 scrollPosition. + public static Vector2 BeginDynamicScrollViewHeight( + Vector2 widthHeight, + ref Vector2 scrollPosition, + Rect viewArea, + bool showDebugContainers = false) + { + scrollPosition = BeginDynamicScrollViewHeight(widthHeight, scrollPosition, viewArea, showDebugContainers); + return scrollPosition; + } + + /// + /// Ends the Dynamic Scroll View. + /// + /// Content Rect of the Scroll View. + /// + public static Rect EndDynamicScrollViewHeight(Rect contentRect) + { + if (Event.current.type == EventType.Repaint) contentRect = GUILayoutUtility.GetLastRect(); + //GUI.EndScrollView(); + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + return contentRect; + } + + /// + /// Ends the Dynamic Scroll View. + /// + /// Content Rect of the Scroll View. + /// + public static Rect EndDynamicScrollViewHeight(ref Rect contentRect) + { + contentRect = EndDynamicScrollViewHeight(contentRect); + return contentRect; + } + #endregion + + + + #region Label + /// + /// Displays a label that only takes up enough space for its content. + /// + /// + /// Content of the Label. + /// Color, Vector4 | Color | UdonVR_Predefined.Color + /// RectOffset padding. + /// TextAnchor where does the text anchor. + public static void Label(GUIContent content, GenericColor color, RectOffset padding = null, TextAnchor textAnchor = TextAnchor.MiddleLeft, int fontSize = 13) + { + GUIStyle _style = new GUIStyle(GUI.skin.label) + { + alignment = textAnchor, // set text alignment + fontSize = fontSize // set font size + }; + Vector2 _contentSize = _style.CalcSize(content); + _style = UdonVR_Style.SetWidthHeight(_contentSize.x, _contentSize.y, _style); + if (padding != null) _style.padding = padding; + _style.normal.textColor = UdonVR_ColorHelpers.GetFromGenericColor(color); // get color from generic color + Rect _rect = GUILayoutUtility.GetRect(content, _style); + GUI.Label(_rect, content, _style); + } + #endregion + + + + #region Toggle Button + /// + /// Creates a toggleable button. + /// + /// Button position. + /// Content of the button. + /// State of the button. + /// Additional options for the button. + /// + public static bool ToggleButton(Rect position, GUIContent content, bool state, params UdonVR_GUIOption[] options) + { + GUIStyle _style = new GUIStyle(GUI.skin.button); + Color _tintColorDefault = Color.white; + Color _tintColorActive = Color.green; + + foreach (UdonVR_GUIOption option in options) + { + if (option.tintColorDefault.HasValue) _tintColorDefault = option.tintColorDefault.Value; + if (option.tintColorActive.HasValue) _tintColorActive = option.tintColorActive.Value; + + if (option.textColor.HasValue) _style.normal.textColor = option.textColor.Value; + if (option.fontSize.HasValue) _style.fontSize = option.fontSize.Value; + if (option.textAnchor.HasValue) _style.alignment = option.textAnchor.Value; + + if (option.padding != null) _style.padding = option.padding; + if (option.width.HasValue) _style.fixedWidth = option.width.Value; + if (option.height.HasValue) _style.fixedHeight = option.height.Value; + } + + if (position == Rect.zero) position = GUILayoutUtility.GetRect(content, _style); + + if (content == null) content = new GUIContent("I WAS LEFT NULL", "DO NOT LEAVE CONTENT FIELD NULL"); + + Color _backgroundColor = GUI.backgroundColor; // get background color + GUI.backgroundColor = state ? _backgroundColor * Color.Lerp(Color.white, _tintColorActive, 0.15f) : _backgroundColor * Color.Lerp(Color.white, _tintColorDefault, 0.15f); // pick color based on state + if (GUI.Button(position, content)) + { + state = !state; // invert state + } + GUI.backgroundColor = _backgroundColor; // reset background color + + return state; + } + + /// + /// Creates a toggleable button. + /// + /// Content of the button. + /// State of the button. + /// Additional options for the button. + /// + public static bool ToggleButton(GUIContent content, bool state, params UdonVR_GUIOption[] options) + { + return ToggleButton(Rect.zero, content, state, options); + } + + /// + /// Creates a toggleable button. + /// + /// Reference to the property to represent. + /// Content of the button. + /// Additional options for the button. + /// + public static void ToggleButton(SerializedProperty property, GUIContent content, params UdonVR_GUIOption[] options) + { + if (content == null) content = new GUIContent(property.displayName, property.tooltip); + property.boolValue = ToggleButton(Rect.zero, content, property.boolValue, options); + } + + /// + /// Creates a toggleable button. + /// + /// Reference to the property to represent. + /// Additional options for the button. + /// + public static void ToggleButton(SerializedProperty property, params UdonVR_GUIOption[] options) + { + GUIContent _content = new GUIContent(property.displayName, property.tooltip); + property.boolValue = ToggleButton(Rect.zero, _content, property.boolValue, options); + } + #endregion + + + + #region Enum Popup + /// + /// Displays an EnumPopup. + /// + /// Enum type. + /// Content to display. + /// Enum value. + /// (Enum, int) Selected enum, Selected index. + public static (Enum, int) EnumPopup(GUIContent content, Enum value) + { + value = EditorGUILayout.EnumPopup(content, value); + return (value, (int)(object)value); + } + + /// + /// Displays an EnumPopup. + /// + /// Reference to the property to represent. + /// Content to display. + /// Typeof enum to display. + /// + public static void EnumPopup(SerializedProperty property, GUIContent content, Type enumType) + { + if (content == null) content = new GUIContent(property.displayName, property.tooltip); + if (property.propertyType == SerializedPropertyType.Enum) + { + property.enumValueIndex = EnumPopup(content, UdonVR_EnumHelpers.EnumFromInt(property.enumValueIndex, enumType)).Item2; + } + if (property.propertyType == SerializedPropertyType.Integer) + { + property.intValue = EnumPopup(content, UdonVR_EnumHelpers.EnumFromInt(property.intValue, enumType)).Item2; + } + } + + /// + /// Displays an EnumPopup. + /// + /// Reference to the property to represent. + /// Typeof enum to display. + public static void EnumPopup(SerializedProperty property, Type enumType) { EnumPopup(property, null, enumType); } + #endregion + + + + #region Button Foldout + /// + /// Creates a ButtonFoldout area where content can be placed inbetween 'BeginButtonFoldout' and 'EndButtonFoldout' to create a Foldout area. + ///
--- Requires EndButtonFoldout after content ---
+ ///
+ /// Content of the Foldout. + /// State of the Foldout. + /// Name of the variable being used to store some data for the button foldout. (MUST BE ENTERED AGAIN IN EndButtonFoldout) + /// Style of the master BeginVertical. (Everything is inside of this.) + /// Style of the BeginHorizontal or BeginVertical this is inside of. (Only important if "parentStyle" parameter was not set.) + /// Indent Level of the Foldout (Matches indent of EditorGUI.indentLevel++) + /// Additional offset for the foldout arrow if it doesnt lineup. (Material Editors for example) + /// Inverts the foldout arrow icon. + /// + public static bool BeginButtonFoldout( + GUIContent content, + bool state, + string guid, + GUIStyle style = null, + GUIStyle parentStyle = null, + int indentLevel = 0, + int foldoutOffsetX = 0, + bool invertFoldoutArrow = false, + bool showDebugContainers = false, + [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "", + [System.Runtime.CompilerServices.CallerMemberName] string callerMemberName = "") + { + #region Get guid from script caller filepath to get correct variable group + string group_guid = UdonVR_Encryption.GuidFromStringMD5(callerFilePath).ToString(); + #endregion + + #region Get guid from script caller filepath and caller functio to create variable name guid + guid = UdonVR_Encryption.GuidFromStringMD5(callerFilePath + "_" + callerMemberName + "_" + guid).ToString(); + #endregion + + object _output = null; + try + { + _output = UdonVR_VariableStorage.Instance.GetVariableGroup(group_guid).GetVariable(guid); // load the variable + } + catch (System.Exception) { } + Rect _rect = _output != null ? (Rect)Convert.ChangeType(_output, typeof(Rect)) : Rect.zero; // get the rectangle from the variable object + + GUIStyle _style = style == null ? GUIStyle.none : style; + _style.padding = new RectOffset(); + GUILayout.BeginVertical(_style); + + GUILayout.BeginHorizontal(showDebugContainers == false ? GUIStyle.none : UdonVR_Style.Get(new Vector4(0.5f, 0.5f, 0.25f, 1f))); + GUIStyle _buttonStyle = new GUIStyle(GUI.skin.button); + Rect _buttonRect = GUILayoutUtility.GetRect(content, _buttonStyle); + state = ToggleButton(_buttonRect, content, state); + _buttonRect.x += 3 + foldoutOffsetX; + state = invertFoldoutArrow ? !state : state; + state = EditorGUI.Foldout(_buttonRect, state, (string)null, true); + state = invertFoldoutArrow ? !state : state; + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(showDebugContainers == false ? GUIStyle.none : UdonVR_Style.Get(new Vector4(0.25f, 0.5f, 0.5f, 1f))); + GUILayout.FlexibleSpace(); + GUILayout.BeginVertical(showDebugContainers == false ? GUIStyle.none : UdonVR_Style.Get(new Vector4(0.5f, 0.25f, 0.5f, 1f)), GUILayout.Width(_rect.width - ((indentLevel + 1) * 20) + (parentStyle != null ? parentStyle.padding.left : 0))); + return state; + } + + /// + /// Creates a ButtonFoldout area where content can be placed inbetween 'BeginButtonFoldout' and 'EndButtonFoldout' to create a Foldout area. + ///
--- Requires EndButtonFoldout after content ---
+ ///
+ /// Content of the Foldout. + /// ref state | State of the Foldout. Updates variable passed without using return. + /// Name of the variable being used to store some data for the button foldout. (MUST BE ENTERED AGAIN IN EndButtonFoldout) + /// Style of the master BeginVertical. (Everything is inside of this.) + /// Style of the BeginHorizontal or BeginVertical this is inside of. (Only important if "parentStyle" parameter was not set.) + /// Indent Level of the Foldout (Matches indent of EditorGUI.indentLevel++) + /// Additional offset for the foldout arrow if it doesnt lineup. (Material Editors for example) + /// Inverts the foldout arrow icon. + /// + public static bool BeginButtonFoldout( + GUIContent content, + ref bool state, + string guid, + GUIStyle style = null, + GUIStyle parentStyle = null, + int indentLevel = 0, + int foldoutOffsetX = 0, + bool invertFoldoutArrow = false, + bool showDebugContainers = false, + [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "", + [System.Runtime.CompilerServices.CallerMemberName] string callerMemberName = "") + { + state = BeginButtonFoldout( + content, + state, + guid, + style, + parentStyle, + indentLevel, + foldoutOffsetX, + invertFoldoutArrow, + showDebugContainers, + callerFilePath, + callerMemberName); + return state; + } + + /// + /// Creates a ButtonFoldout area where content can be placed inbetween 'BeginButtonFoldout' and 'EndButtonFoldout' to create a Foldout area. + ///
--- Requires EndButtonFoldout after content ---
+ ///
+ /// Reference to the property to represent. + /// Content of the Foldout. + /// Name of the variable being used to store some data for the button foldout. (MUST BE ENTERED AGAIN IN EndButtonFoldout) + /// Style of the master BeginVertical. (Everything is inside of this.) + /// Style of the BeginHorizontal or BeginVertical this is inside of. (Only important if "parentStyle" parameter was not set.) + /// Indent Level of the Foldout (Matches indent of EditorGUI.indentLevel++) + /// Additional offset for the foldout arrow if it doesnt lineup. (Material Editors for example) + /// Inverts the foldout arrow icon. + /// + public static void BeginButtonFoldout( + SerializedProperty property, + GUIContent content, + string guid, + GUIStyle style = null, + GUIStyle parentStyle = null, + int indentLevel = 0, + int foldoutOffsetX = 0, + bool invertFoldoutArrow = false, + bool showDebugContainers = false, + [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "", + [System.Runtime.CompilerServices.CallerMemberName] string callerMemberName = "") + { + if (content == null) content = new GUIContent(property.displayName, property.tooltip); + property.boolValue = BeginButtonFoldout( + content, + property.boolValue, + guid, + style, + parentStyle, + indentLevel, + foldoutOffsetX, + invertFoldoutArrow, + showDebugContainers, + callerFilePath, + callerMemberName); + } + + /// + /// Ends the ButtonFoldout. + ///
--- Requires BeginButtonFoldout before content ---
+ ///
+ /// MUST BE THE SAME GUID AS BeginButtonFoldout + public static void EndButtonFoldout( + string guid, + [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "", + [System.Runtime.CompilerServices.CallerMemberName] string callerMemberName = "" + ) + { + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + + GUILayout.EndVertical(); + + #region Get guid from script caller filepath to get correct variable group + string group_guid = UdonVR_Encryption.GuidFromStringMD5(callerFilePath).ToString(); + #endregion + + #region Get guid from script caller filepath and caller functio to create variable name guid + guid = UdonVR_Encryption.GuidFromStringMD5(callerFilePath + "_" + callerMemberName + "_" + guid).ToString(); + #endregion + + // if were repainting then save the size of the rect + if (Event.current.type == EventType.Repaint) + { + Rect _rect = GUILayoutUtility.GetLastRect(); + try + { + UdonVR_VariableStorage.Instance.GetVariableGroup(group_guid).SetVariable(guid, _rect); // save the rect + } + catch (System.Exception) { } + } + } + #endregion + + + + #region List + /// + /// Contains options for UdonVR_GUI.List + /// + public class ListOption + { + public bool? showSize; + public bool? allowSizeChange; + + /// + /// Does the list display the size. + /// + /// + /// + public static ListOption ShowSize(bool showSize) + { + ListOption _option = new ListOption(); + _option.showSize = showSize; + return _option; + } + + /// + /// Does the list allow the size to be modified. (Size of list is still displayed) + /// + /// + /// + public static ListOption AllowSizeChange(bool allowSizeChange) + { + ListOption _option = new ListOption(); + _option.allowSizeChange = allowSizeChange; + return _option; + } + } + + /// + /// Displays an array or a list. + /// + /// Property to display. + /// Content of the property. + public static void List(SerializedProperty property, GUIContent content, params ListOption[] options) + { + bool _showSize = true; + bool _allowSizeChange = true; + + if (content == null) content = new GUIContent(property.displayName, property.tooltip); + Rect _position = GUILayoutUtility.GetRect(content, GUI.skin.label); + + foreach (ListOption option in options) + { + if (option.showSize.HasValue) _showSize = option.showSize.Value; + if (option.allowSizeChange.HasValue) _allowSizeChange = option.allowSizeChange.Value; + } + + _position.x += 12; // account for foldout arrow + property.isExpanded = EditorGUI.Foldout(_position, property.isExpanded, content); + EditorGUI.indentLevel += 1; + if (property.isExpanded) + { + GUI.enabled = _allowSizeChange; + if (_showSize) EditorGUILayout.PropertyField(property.FindPropertyRelative("Array.size")); + GUI.enabled = true; + for (int i = 0; i < property.arraySize; i++) + { + EditorGUILayout.PropertyField(property.GetArrayElementAtIndex(i), true); + } + } + EditorGUI.indentLevel -= 1; + } + /// + /// Displays an array or list. + /// + /// Property to display. + public static void List(SerializedProperty property, params ListOption[] options) { List(property, null, options); } + #endregion + + + + #region Field Area + /// + /// Contains values to be fed into and recieved from UdonVR_GUI.FieldArea + /// + public class FieldAreaValues + { + public bool? boolValue; + public string stringValue = null; + public int? intValue; + public float? floatValue; + public double? doubleValue; + public Vector4? vectorValue; + public Color? colorValue; + public object objectValue = null; + + public Type type; // type of field to make + + public bool isEnabled = true; // is field enabled + + #region Slider + public bool isSlider = false; // is field a slider + public float sliderMin = 0.0f; // minimum slider value + public float sliderMax = 1.0f; // maximum slider value + #endregion + + #region Color + public bool colorShowEyeDropper = true; + public bool colorShowAlpha = true; + public bool colorIsHdr = false; + #endregion + + #region Options + #region Tint Color + public Color tintDefault = Color.white; // tint of field + public Color tintActive = Color.white; // tint of field (button active state) + #endregion + + #region Font + public Color? textColor; + //public int? fontSize; + //public TextAnchor? textAnchor; + #endregion + #endregion + + /// + /// Sets the field value and type. (Used as a base to make overloading easier.) + ///
Supported Types: bool, string, int, float, double, Vector2, Vector3, Vector4, Color, object
+ ///
+ /// + /// Current value of the field. (will make field the same type) + /// Is the field enabled. + /// Is the field a slider. (Numbers Only) + /// Minimum slider value (Numbers Only) + /// Maximum slider value (Numbers Only) + /// Does the color field show the eye dropper. (Color Only) + /// Does the color field allow alpha. (Color Only) + /// Does the color field support HDR (Color Only) + /// Additional options for the field. + /// All the data nessasary to create a input field inside the field area. + private static FieldAreaValues InternalSetValue( + T value, + bool isEnabled = true, + bool isSlider = false, + float sliderMin = 0.0f, + float sliderMax = 1.0f, + bool colorShowEyeDropper = true, + bool colorShowAlpha = true, + bool colorIsHdr = false, + params UdonVR_GUIOption[] options) + { + FieldAreaValues values = new FieldAreaValues(); + values.type = typeof(T); + values.isEnabled = isEnabled; + + values.isSlider = isSlider; + values.sliderMin = sliderMin; + values.sliderMax = sliderMax; + + values.colorShowEyeDropper = colorShowEyeDropper; + values.colorShowAlpha = colorShowAlpha; + values.colorIsHdr = colorIsHdr; + + foreach (UdonVR_GUIOption option in options) + { + if (option.tintColorDefault.HasValue) values.tintDefault = option.tintColorDefault.Value; + if (option.tintColorActive.HasValue) values.tintActive = option.tintColorActive.Value; + + if (option.textColor.HasValue) values.textColor = option.textColor.Value; + if (option.fontSize.HasValue) Debug.LogWarning("UdonVR_GUI.FieldArea does not support UdonVR_GUIOption.FontSize()"); + if (option.textAnchor.HasValue) Debug.LogWarning("UdonVR_GUI.FieldArea does not support UdonVR_GUIOption.TextAnchor()"); + //if (option.fontSize.HasValue) values.fontSize = option.fontSize.Value; + //if (option.textAnchor.HasValue) values.textAnchor = option.textAnchor.Value; + + if (option.padding != null) Debug.LogWarning("UdonVR_GUI.FieldArea does not support UdonVR_GUIOption.Padding()"); + if (option.width.HasValue) Debug.LogWarning("UdonVR_GUI.FieldArea does not support UdonVR_GUIOption.Width()"); + if (option.height.HasValue) Debug.LogWarning("UdonVR_GUI.FieldArea does not support UdonVR_GUIOption.Height()"); + } + + if (typeof(T).Equals(typeof(bool))) + { + values.boolValue = (bool)Convert.ChangeType(value, typeof(T)); + return values; + } + if (typeof(T).Equals(typeof(string))) + { + values.stringValue = (string)Convert.ChangeType(value, typeof(T)); + return values; + } + if (typeof(T).Equals(typeof(int))) + { + values.intValue = (int)Convert.ChangeType(value, typeof(T)); + return values; + } + if (typeof(T).Equals(typeof(float))) + { + values.floatValue = (float)Convert.ChangeType(value, typeof(T)); + return values; + } + if (typeof(T).Equals(typeof(double))) + { + values.doubleValue = (double)Convert.ChangeType(value, typeof(T)); + return values; + } + if (typeof(T).Equals(typeof(Vector2)) || typeof(T).Equals(typeof(Vector3)) || typeof(T).Equals(typeof(Vector4))) + { + if (typeof(T).Equals(typeof(Vector2))) + { + values.vectorValue = Convert.ChangeType(value, typeof(T)) as Vector2?; + } + if (typeof(T).Equals(typeof(Vector3))) + { + values.vectorValue = Convert.ChangeType(value, typeof(T)) as Vector3?; + } + if (typeof(T).Equals(typeof(Vector4))) + { + values.vectorValue = Convert.ChangeType(value, typeof(T)) as Vector4?; + } + return values; + } + if (typeof(T).Equals(typeof(Color))) + { + values.colorValue = (Color)Convert.ChangeType(value, typeof(Color)); + return values; + } + + values.objectValue = value; + return values; + } + + /// + /// Creates a button field that is always returning false unless pressed. + /// + /// Is button enabled. + /// Additional options for the button. + /// All the data nessasary to create a momentary button field inside the field area. + public static FieldAreaValues SetValueMomentary(bool isEnabled = true, params UdonVR_GUIOption[] options) + { + return InternalSetValue(false, isEnabled, false, 0.0f, 1.0f, true, true, false, options); + } + + /// + /// Creates a toggle button field that has its active tint color already set to Color.green + /// + /// Current value of the button. + /// Is button enabled. + /// Additional options for the button. + /// All the data nessasary to create a toggle button field inside the field area. + public static FieldAreaValues SetValueToggle(bool value, bool isEnabled = true, params UdonVR_GUIOption[] options) + { + options = options.Add(UdonVR_GUIOption.TintColorActive(Color.green)); + return InternalSetValue(value, isEnabled, false, 0.0f, 1.0f, true, true, false, options); + } + + /// + /// Creates a field that represents the input value. + /// + /// + /// Current value of the field. (Will make field of the same type.) + /// Is the field enabled. + /// Additional options for the field. + /// All the data nessasary to create a input field inside the field area. + public static FieldAreaValues SetValue(T value, bool isEnabled = true, params UdonVR_GUIOption[] options) + { + return InternalSetValue(value, isEnabled, false, 0.0f, 1.0f, true, true, false, options); + } + + /// + /// Creates a slider matching the input type. + /// + /// + /// Current value of the field. (Will make field of the same type.) + /// Is the slider field enabled. + /// Minimum slider value. + /// Maximum slider value. + /// Additional options for the slider. + /// All the data nessasary to create a slider field inside the field area. + public static FieldAreaValues SetValueSlider( + T value, + bool isEnabled = true, + float sliderMin = 0.0f, + float sliderMax = 1.0f, + params UdonVR_GUIOption[] options) + { + return InternalSetValue(value, isEnabled, true, sliderMin, sliderMax, true, true, false, options); + } + + /// + /// Creates a color field. + /// + /// Current Color value. + /// Is the color field enabled. + /// Does the color field show the eye dropper. + /// Does the color field allow alpha. + /// Does the color field support HDR. + /// Additional options for the slider. + /// All the data nessasary to create a color field inside the field area. + public static FieldAreaValues SetValueColor( + Color value, + bool isEnabled = true, + bool showEyeDropper = true, + bool showAlpha = true, + bool isHdr = false, + params UdonVR_GUIOption[] options) + { + return InternalSetValue(value, isEnabled, false, 0.0f, 1.0f, showEyeDropper, showAlpha, isHdr, options); + } + } + + /// + /// Create a horizontal area that supports different field types all evenly spaced to fill the area. + /// + /// Contents of the fields. + /// Current values of the fields. + /// Actions the fields call when modified. + /// Are the fields enabled / disabled. + /// Style of the area. + /// Custom GUID (Generated if left empty) + /// Show areas inside for debug purposes. + public static void FieldArea( + GUIContent[] fieldContents, + FieldAreaValues[] currentValues, + Action[] actions, + GUIStyle areaStyle = null, + string guid = null, + bool showDebugContainers = false, + [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "", + [System.Runtime.CompilerServices.CallerMemberName] string callerMemberName = "", + [System.Runtime.CompilerServices.CallerLineNumber] int callerLineNumber = 0) + { + #region Get guid from script caller filepath to get correct variable group + string group_guid = UdonVR_Encryption.GuidFromStringMD5(callerFilePath).ToString(); + #endregion + + #region Get guid from script caller filepath, caller function and caller line number to create variable name guid + if (string.IsNullOrEmpty(guid?.Trim())) + { + guid = UdonVR_Encryption.GuidFromStringMD5(callerFilePath + "_" + callerMemberName + "_" + callerLineNumber.ToString() + "nullable_fieldArea").ToString(); + } + #endregion + + object _output = null; + try + { + _output = UdonVR_VariableStorage.Instance.GetVariableGroup(group_guid).GetVariable(guid); // load the variable + } + catch (System.Exception) { } + Rect _rect = _output != null ? (Rect)Convert.ChangeType(_output, typeof(Rect)) : Rect.zero; // get the rectangle from the variable object + if (_rect.width > EditorGUIUtility.currentViewWidth) _rect.width = EditorGUIUtility.currentViewWidth; // restrict width to the maximum view width + float _width = _rect != Rect.zero ? _rect.width / fieldContents.Length : -1; // get the width / number of fields + _width -= (GUI.skin.button.padding.left + GUI.skin.button.padding.right) / 2; + + bool isEven = fieldContents.Length % 2 == 0 ? true : false; // check if we have an even number of fields + int middle = fieldContents.Length / 2; // get the middle slider index + bool evenSplit = _rect.width % fieldContents.Length == 0 ? true : false; // can the current rect width be split evenly between the fields + + // begin area and set the style to use for the area + GUILayout.BeginHorizontal(showDebugContainers == false ? areaStyle != null ? areaStyle : GUIStyle.none : UdonVR_Style.Get(new Vector4(0.25f, 0.25f, 0.5f, 1f))); + for (int i = 0; i < fieldContents.Length; i++) + { + if (showDebugContainers) GUILayout.BeginHorizontal(i % 2 == 0 ? UdonVR_Style.Get(Color.red * Color.grey) : UdonVR_Style.Get(Color.blue * Color.grey)); // used to dislay different field areas in alternating colors during debug + Type type = currentValues[i].type; // type of field to make + + Color _backgroundColor = GUI.backgroundColor; // save background color before modifing + Color _textColor = GUI.contentColor; // save text color before modifing + + GUI.enabled = currentValues[i].isEnabled; // set if the button is enabled or not + EditorGUIUtility.labelWidth = GUIStyle.none.CalcSize(fieldContents[i]).x + 5; + GUI.backgroundColor = _backgroundColor * Color.Lerp(Color.white, currentValues[i].tintDefault, 0.15f); + GUI.contentColor = currentValues[i].textColor.HasValue ? currentValues[i].textColor.Value : _textColor; + if (type.Equals(typeof(bool))) + { + bool boolVal = currentValues[i].boolValue.Value; + GUI.backgroundColor = boolVal ? _backgroundColor * Color.Lerp(Color.white, currentValues[i].tintActive, 0.15f) : _backgroundColor * Color.Lerp(Color.white, currentValues[i].tintDefault, 0.15f); + if (GUILayout.Button( + fieldContents[i], + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width))) + { + boolVal = !boolVal; + } + GUI.backgroundColor = _backgroundColor; + currentValues[i].boolValue = boolVal; + } + else if (type.Equals(typeof(string))) + { + string strVal = currentValues[i].stringValue; + strVal = EditorGUILayout.TextField( + fieldContents[i], + strVal, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + currentValues[i].stringValue = strVal; + } + else if (type.Equals(typeof(int))) + { + int intVal = currentValues[i].intValue.Value; + if (!currentValues[i].isSlider) + { + intVal = EditorGUILayout.IntField( + fieldContents[i], + intVal, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + else + { + intVal = EditorGUILayout.IntSlider( + fieldContents[i], + intVal, + (int)currentValues[i].sliderMin, + (int)currentValues[i].sliderMax, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + currentValues[i].intValue = intVal; + } + else if (type.Equals(typeof(float))) + { + float floatVal = currentValues[i].floatValue.Value; + if (!currentValues[i].isSlider) + { + floatVal = EditorGUILayout.FloatField( + fieldContents[i], + floatVal, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + else + { + floatVal = EditorGUILayout.Slider( + fieldContents[i], + floatVal, + currentValues[i].sliderMin, + currentValues[i].sliderMax, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + currentValues[i].floatValue = floatVal; + } + else if (type.Equals(typeof(double))) + { + double doubleVal = currentValues[i].doubleValue.Value; + if (!currentValues[i].isSlider) + { + doubleVal = EditorGUILayout.DoubleField( + fieldContents[i], + doubleVal, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + else + { + doubleVal = (double)EditorGUILayout.Slider( + fieldContents[i], + (float)doubleVal, + (float)currentValues[i].sliderMin, + (float)currentValues[i].sliderMax, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + currentValues[i].doubleValue = doubleVal; + } + else if (type.Equals(typeof(Vector2)) || type.Equals(typeof(Vector3)) || type.Equals(typeof(Vector4))) + { + if (currentValues[i].vectorValue.HasValue) + { + Vector4 vectorVal = Vector4.zero; + if (type.Equals(typeof(Vector2))) + { + vectorVal = EditorGUILayout.Vector2Field( + fieldContents[i], + currentValues[i].vectorValue.Value, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + if (type.Equals(typeof(Vector3))) + { + vectorVal = EditorGUILayout.Vector3Field( + fieldContents[i], + currentValues[i].vectorValue.Value, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + if (type.Equals(typeof(Vector4))) + { + vectorVal = EditorGUILayout.Vector4Field( + fieldContents[i], + currentValues[i].vectorValue.Value, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + } + currentValues[i].vectorValue = vectorVal; + } + } + else if (type.Equals(typeof(Color))) + { + Color colorVal = currentValues[i].colorValue.Value; + colorVal = EditorGUILayout.ColorField( + fieldContents[i], + colorVal, + currentValues[i].colorShowEyeDropper, + currentValues[i].colorShowAlpha, + currentValues[i].colorIsHdr, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + currentValues[i].colorValue = colorVal; + } + else + { + UnityEngine.Object objVal = (UnityEngine.Object)Convert.ChangeType(currentValues[i].objectValue, typeof(UnityEngine.Object)); + objVal = EditorGUILayout.ObjectField( + fieldContents[i], + objVal, + type, + true, + GUILayout.Width(!isEven && i == middle && !evenSplit ? _width + 1 : _width)); + currentValues[i].objectValue = objVal; + } + actions[i](currentValues[i]); // output the current values to the actions + GUI.contentColor = _textColor; // reset text color + GUI.backgroundColor = _backgroundColor; // reset background color + EditorGUIUtility.labelWidth = 0; // reset width + GUI.enabled = true; // re enable GUI elements + + if (showDebugContainers) GUILayout.EndHorizontal(); // used to dislay different field areas in alternating colors during debug + if (i < fieldContents.Length - 1) GUILayout.FlexibleSpace(); // add flexable space between the buttons + } + GUILayout.EndHorizontal(); + + + // if were repainting then save the size of the rect + if (Event.current.type == EventType.Repaint) + { + _rect = GUILayoutUtility.GetLastRect(); + try + { + UdonVR_VariableStorage.Instance.GetVariableGroup(group_guid).SetVariable(guid, _rect); // save the rect + } + catch (System.Exception) { } + } + } + #endregion + } + + public static class UdonVR_Predefined + { + // created by Hamster9090901 + + public enum Color + { + // general + General_Clear, + + // background + Background_UnityDefault, + Background_Default, + Background_Light, + + // style colors + Style_DefaultTextColor, + } + + private static Vector4[] PrefefinedColors = { + // general + new Vector4(0f, 0f, 0f, 0f), // clear + + // background + UdonVR_ColorHelpers.FromRGB(56f, 56f, 56f, 255f), // unity default + new Vector4(0.25f, 0.25f, 0.25f, 1f), // default + new Vector4(0.3f, 0.3f, 0.3f, 1f), // light + + // style + GUI.skin.button.normal.textColor, // default text color + }; + + /// + /// Returns a predefined color. + /// + /// Predefined Color enum. + /// + public static Vector4 GetColor(Color color) + { + return PrefefinedColors[(int)color]; + } + } + + public static class UdonVR_Style + { + // created by Hamster9090901 + + /// + /// Gets a blank GUI style with a background color. + /// + /// + /// Vector4 | Color | UdonVR_Predefined.Color + /// + public static GUIStyle Get(GenericColor color) + { + Vector4 _color = UdonVR_ColorHelpers.GetFromGenericColor(color); // get color from generic color + GUIStyle _style = new GUIStyle(); + Texture2D _texture = new Texture2D(1, 1); + _texture.SetPixel(0, 0, _color); + _texture.Apply(); + _style.normal.background = _texture; + return _style; + } + + /// + /// Gets a GUI style with a fixed Width and Height + /// + /// Width of GUI Style. + /// Height of GUI Style. + /// Style to copy. + /// + public static GUIStyle SetWidthHeight(float width, float height, GUIStyle style = null) + { + // get new style or new style based off input style + GUIStyle _style = style != null ? new GUIStyle(style) : new GUIStyle(); + if (width != -1) _style.fixedWidth = width; // set width + if (height != -1) _style.fixedHeight = height; // set height + + return _style; // return new style + } + + /// + /// Gets a GUI style with a fixed Width and Height, And padding offset + /// + /// Width of GUI Style + /// Height of GUI Style. + /// Style to copy. + /// Padding to apply to style. + /// + public static GUIStyle SetWidthHeight(float width, float height, GUIStyle style, RectOffset padding = null) + { + GUIStyle _style = SetWidthHeight(width, height, style); + if (padding != null) _style.padding = padding; + + return _style; + } + + public static GUIStyle SetPadding(GUIStyle style, RectOffset padding) + { + GUIStyle _style = style != null ? new GUIStyle(style) : new GUIStyle(); + _style.padding = padding; + + return _style; + } + + public static GUIStyle SetTextSettings(GUIStyle style, GenericColor genericColor, TextAnchor alignment = TextAnchor.MiddleLeft) + { + GUIStyle _style = style != null ? new GUIStyle(style) : new GUIStyle(); + _style.normal.textColor = UdonVR_ColorHelpers.GetFromGenericColor(genericColor); + _style.alignment = alignment; + + return _style; + } + + public static GUIStyle SetFontSettings(GUIStyle style, Font font = null, int fontSize = -1) + { + GUIStyle _style = style != null ? new GUIStyle(style) : new GUIStyle(); + if (font != null) _style.font = font; + if (fontSize != -1) _style.fontSize = fontSize; + + return _style; + } + + public static GUIStyle SetFontSettings(GUIStyle style, Font font = null, int fontSize = -1, FontStyle fontStyle = FontStyle.Normal) + { + GUIStyle _style = SetFontSettings(style, font, fontSize); + _style.fontStyle = fontStyle; + + return _style; + } + } +} diff --git a/Tools/Editor/Functions/UdonVR_Handles.cs b/Tools/Editor/Functions/UdonVR_Handles.cs new file mode 100644 index 0000000..266d253 --- /dev/null +++ b/Tools/Editor/Functions/UdonVR_Handles.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +// created by Hamster9090901 + +namespace UdonVR.EditorUtility +{ + public static class UdonVR_Handles + { + // created by Hamster9090901 + + /// + /// Draws a sphere using handles. Must be in OnSceneGUI() + /// + /// + /// + /// + public static void DrawWireSphere(Vector3 position, float radius, Color color) + { + Handles.color = color; + Handles.DrawWireDisc(position, new Vector3(1, 0, 0), radius); // x + Handles.DrawWireDisc(position, new Vector3(0, 1, 0), radius); // y + Handles.DrawWireDisc(position, new Vector3(0, 0, 1), radius); // z + } + /// + /// Draws a sphere using handles. Must be in OnSceneGUI() + /// + /// + /// + public static void DrawWireSphere(Vector3 position, float radius) + { + DrawWireSphere(position, radius, Color.white); + } + } +} \ No newline at end of file diff --git a/Tools/Editor/Functions/UdonVR_Helpers.cs b/Tools/Editor/Functions/UdonVR_Helpers.cs new file mode 100644 index 0000000..dd6d17b --- /dev/null +++ b/Tools/Editor/Functions/UdonVR_Helpers.cs @@ -0,0 +1,803 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +using System; + +// created by Hamster9090901 + +namespace UdonVR.EditorUtility +{ + public static class UdonVR_ColorHelpers + { + // created by Hamster9090901 + + /// + /// Get Color from RGB input. + /// + /// Red value. + /// Green value. + /// Blue value. + /// Alpha value. + /// + public static Color FromRGB(float red, float green, float blue, float alpha = -1f) + { + bool _isRGB = red > 1.0f || green > 1.0f || blue > 1.0f || alpha > 1.0f ? true : false; + + return new Color + { + r = _isRGB ? red / 255.0f : red, + g = _isRGB ? green / 255.0f : green, + b = _isRGB ? blue / 255.0f : blue, + a = alpha == -1 ? 1.0f : _isRGB ? alpha / 255.0f : 1.0f, + }; + } + + /// + /// Get Color from RGB input. Vector3 for RGB input. + /// + /// Red Green Blue values. + /// + public static Color FromRGB(Vector3 rgb) + { + return FromRGB(rgb.x, rgb.y, rgb.z); + } + + /// + /// Get Color from RGB input. Vector4 for RGBA input. + /// + /// Red Green Blue Alpha values. + /// + public static Color FromRGB(Vector4 rgba) + { + return FromRGB(rgba.x, rgba.y, rgba.z, rgba.w); + } + + /// + /// Returns the color in a RGB format with a range of 0 to 255 + /// + /// + /// + public static Color ToRGB(this Color color) + { + return new Color + { + r = color.r * 255.0f, + g = color.g * 255.0f, + b = color.b * 255.0f, + a = color.a * 255.0f + }; + } + + /// + /// Takes an input color and returns it in a Vector4 format. + /// + /// + /// Generic Color, Vector4 | Color | UdonVR_Predefined.Color + /// + public static Vector4 GetFromGenericColor(GenericColor color) + { + Vector4 _color = Vector4.one; + if (typeof(GenericColor) == typeof(Vector4)) + { + _color = (Vector4)Convert.ChangeType(color, typeof(Vector4)); + } + else if (typeof(GenericColor) == typeof(Color)) + { + _color = (Vector4)(Color)Convert.ChangeType(color, typeof(Color)); + } + else if (typeof(GenericColor) == typeof(UdonVR_Predefined.Color)) + { + _color = UdonVR_Predefined.GetColor((UdonVR_Predefined.Color)Convert.ChangeType(color, typeof(UdonVR_Predefined.Color))); + } + return _color; + } + } + + public static class UdonVR_EnumHelpers + { + /// + /// Gets an Integer from Enum. + /// + /// + /// Enum to get integer from. + /// + public static int IntFromEnum(T value) + { + Enum _enum = (Enum)Convert.ChangeType(value, typeof(T)); + return (int)(object)_enum; + } + + /// + /// Gets an Enum from an Integer. + /// + /// + /// Integer to get enum for. + /// Typeof enum to return. + /// + public static Enum EnumFromInt(int integer, Type type) + { + return (Enum)Enum.ToObject(type, integer); + } + } + + public static class ArrayHelpers + { + /// + /// Appends an item to the passed array lengthing it in the process. + /// + /// + /// Array to append item to. + /// Item to append. + /// + public static T[] ArrayAppendItem(T[] array, T item) + { + array = ArrayIncreaseLength(array); + array[array.Length - 1] = item; + return array; + } + + /// + /// Appends items to the passed array lengthing it in the process. + /// + /// + /// Array to append items to. + /// Optional items to append to the end of the array. + /// + public static T[] ArrayAppendItem(T[] array, params T[] items) + { + int _itemCount = items.Length; + int _previousLength = array.Length; + array = ArrayIncreaseLength(array, _itemCount); + + for (int _i = 0; _i < _itemCount; _i++) + { + array[_previousLength + _i] = items[_i]; + } + + return array; + } + + /// + /// Removes an item from the passed array shortening it in the process. + /// + /// + /// Array to remove item from. + /// Item to remove. (Will remove first found) + /// + public static T[] ArrayRemoveItem(T[] array, T item) + { + array = ArrayDecreaseLength(array, array.FindIndex(item)); + return array; + } + + /// + /// Removes an index from the passed array shortening it in the process. + /// + /// + /// Array to remove item from. + /// Index to remove. + /// + public static T[] ArrayRemoveItem(T[] array, int index) + { + array = ArrayDecreaseLength(array, index); + return array; + } + + /// + /// Removes an index from the passed array shortening it in the process. + /// + /// + /// Array to remove item from. + /// Index to remove. + /// Number of additional indexes / indices to remove. + /// + public static T[] ArrayRemoveItem(T[] array, int index, int count) + { + array = ArrayDecreaseLength(array, index, count); + return array; + } + + /// + /// Increases the length of the array. + /// + /// + /// Array to increase the length of. + /// Amount of slots to add to the end of the array. + /// + public static T[] ArrayIncreaseLength(T[] array, int amount = 1) + { + T[] _extendedArray = new T[array.Length + amount]; + for (int _i = 0; _i < array.Length; _i++) + { + _extendedArray[_i] = array[_i]; + } + for (int _i = array.Length + 1; _i < _extendedArray.Length; _i++) + { + _extendedArray[_i] = default(T); + } + return _extendedArray; + } + + /// + /// Decreases the length of the array. + /// + /// + /// Array to decrease the length of. + /// Index to remove. (-1 is last index in the array) + /// + public static T[] ArrayDecreaseLength(T[] array, int index = -1) + { + T[] _shortenedArray = new T[array.Length - 1]; + int _index = 0; + for (int _i = 0; _i < array.Length; _i++) + { + if (_i != index) + { + _shortenedArray[_index] = array[_i]; + _index++; + } + } + return _shortenedArray; + } + + /// + /// Decreases the length of the array. + /// + /// + /// Array to decrease the length of. + /// Index to remove. + /// Number of additional indexes / indices to remove. + /// + public static T[] ArrayDecreaseLength(T[] array, int index, int count = 0) + { + T[] _shortenedArray = new T[(array.Length - 1) - count]; + int _index = 0; + for (int _i = 0; _i < array.Length; _i++) + { + if (!(_i >= index && _i <= index + count)) + { + _shortenedArray[_index] = array[_i]; + _index++; + } + } + return _shortenedArray; + } + } + + public static class UdonVR_MathHelpers + { + // created by Hamster9090901 + + #region Remap + /// + /// Changes value range from low1 -> high1 (Current range) to low2 -> high2 (New range) + /// + /// Value to change range of. + /// Current minimum. + /// Current maximum. + /// New minimum. + /// New maximum. + /// + public static float Remap(float value, float low1, float high1, float low2, float high2) + { + return low2 + (value - low1) * (high2 - low2) / (high1 - low1); + } + + /// + /// Changes value range from low1 -> high1 (Current range) to low2 -> high2 (New range) + /// + /// Value to change range of. + /// Current minimum. + /// Current maximum. + /// New minimum. + /// New maximum. + /// + public static Vector2 Remap(Vector2 value, float low1, float high1, float low2, float high2) + { + return new Vector2 + { + x = Remap(value.x, low1, high1, low2, high2), + y = Remap(value.y, low1, high1, low2, high2) + }; + } + + /// + /// Changes value range from low1 -> high1 (Current range) to low2 -> high2 (New range) + /// + /// Value to change range of. + /// Current minimum. + /// Current maximum. + /// New minimum. + /// New maximum. + /// + public static Vector2 Remap(Vector2 value, Vector2 low1, Vector2 high1, Vector2 low2, Vector2 high2) + { + return new Vector2 + { + x = Remap(value.x, low1.x, high1.x, low2.x, high2.x), + y = Remap(value.y, low1.y, high1.y, low2.y, high2.y) + }; + } + + /// + /// Changes value range from low1 -> high1 (Current range) to low2 -> high2 (New range) + /// + /// Value to change range of. + /// Current minimum. + /// Current maximum. + /// New minimum. + /// New maximum. + /// + public static Vector3 Remap(Vector3 value, float low1, float high1, float low2, float high2) + { + return new Vector3 + { + x = Remap(value.x, low1, high1, low2, high2), + y = Remap(value.y, low1, high1, low2, high2), + z = Remap(value.z, low1, high1, low2, high2) + }; + } + + /// + /// Changes value range from low1 -> high1 (Current range) to low2 -> high2 (New range) + /// + /// Value to change range of. + /// Current minimum. + /// Current maximum. + /// New minimum. + /// New maximum. + /// + public static Vector3 Remap(Vector3 value, Vector3 low1, Vector3 high1, Vector3 low2, Vector3 high2) + { + return new Vector3 + { + x = Remap(value.x, low1.x, high1.x, low2.x, high2.x), + y = Remap(value.y, low1.y, high1.y, low2.y, high2.y), + z = Remap(value.z, low1.z, high1.z, low2.z, high2.z) + }; + } + + /// + /// Changes value range from low1 -> high1 (Current range) to low2 -> high2 (New range) + /// + /// Value to change range of. + /// Current minimum. + /// Current maximum. + /// New minimum. + /// New maximum. + /// + public static Vector4 Remap(Vector4 value, float low1, float high1, float low2, float high2) + { + return new Vector4 + { + x = Remap(value.x, low1, high1, low2, high2), + y = Remap(value.y, low1, high1, low2, high2), + z = Remap(value.z, low1, high1, low2, high2), + w = Remap(value.w, low1, high1, low2, high2) + }; + } + + /// + /// Changes value range from low1 -> high1 (Current range) to low2 -> high2 (New range) + /// + /// Value to change range of. + /// Current minimum. + /// Current maximum. + /// New minimum. + /// New maximum. + /// + public static Vector4 Remap(Vector4 value, Vector4 low1, Vector4 high1, Vector4 low2, Vector4 high2) + { + return new Vector4 + { + x = Remap(value.x, low1.x, high1.x, low2.x, high2.x), + y = Remap(value.y, low1.y, high1.y, low2.y, high2.y), + z = Remap(value.z, low1.z, high1.z, low2.z, high2.z), + w = Remap(value.w, low1.w, high1.w, low2.w, high2.w) + }; + } + #endregion + + #region Lerp + /// + /// Lerp between values. (Linear Interpolation) + /// + /// Start value. + /// End value. + /// Time. + /// + public static float Lerp(float a, float b, float t) + { + return a + t * (b - a); + } + + /// + /// Lerp between values. (Linear Interpolation) + /// + /// Start value. + /// End value. + /// Time. + /// + public static Vector2 Lerp(Vector2 a, Vector2 b, float t) + { + return new Vector2 + { + x = Lerp(a.x, b.x, t), + y = Lerp(a.y, b.y, t) + }; + } + + /// + /// Lerp between values. (Linear Interpolation) + /// + /// Start value. + /// End value. + /// Time. + /// + public static Vector3 Lerp(Vector3 a, Vector3 b, float t) + { + return new Vector3 + { + x = Lerp(a.x, b.x, t), + y = Lerp(a.y, b.y, t), + z = Lerp(a.z, b.z, t) + }; + } + + /// + /// Lerp between values. (Linear Interpolation) + /// + /// Start value. + /// End value. + /// Time. + /// + public static Vector4 Lerp(Vector4 a, Vector4 b, float t) + { + return new Vector4 + { + x = Lerp(a.x, b.x, t), + y = Lerp(a.y, b.y, t), + z = Lerp(a.z, b.z, t), + w = Lerp(a.w, b.w, t) + }; + } + #endregion + + #region ClampMin + /// + /// Clamps the value to be above or equal to the minimum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// + public static float ClampMin(float value, float minimum) + { + return value < minimum ? minimum : value; + } + + /// + /// Clamps the value to be above or equal to the minimum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// + public static Vector2 ClampMin(Vector2 value, float minimum) + { + return new Vector2 + { + x = value.x < minimum ? minimum : value.x, + y = value.y < minimum ? minimum : value.y + }; + } + + /// + /// Clamps the value to be above or equal to the minimum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// + public static Vector2 ClampMin(Vector2 value, Vector2 minimum) + { + return new Vector2 + { + x = value.x < minimum.x ? minimum.x : value.x, + y = value.y < minimum.y ? minimum.y : value.y + }; + } + + /// + /// Clamps the value to be above or equal to the minimum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// + public static Vector3 ClampMin(Vector3 value, float minimum) + { + return new Vector3 + { + x = value.x < minimum ? minimum : value.x, + y = value.y < minimum ? minimum : value.y, + z = value.z < minimum ? minimum : value.z + }; + } + + /// + /// Clamps the value to be above or equal to the minimum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// + public static Vector3 ClampMin(Vector3 value, Vector3 minimum) + { + return new Vector3 + { + x = value.x < minimum.x ? minimum.x : value.x, + y = value.y < minimum.y ? minimum.y : value.y, + z = value.z < minimum.z ? minimum.z : value.z + }; + } + + /// + /// Clamps the value to be above or equal to the minimum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// + public static Vector4 ClampMin(Vector4 value, float minimum) + { + return new Vector4 + { + x = value.x < minimum ? minimum : value.x, + y = value.y < minimum ? minimum : value.y, + z = value.z < minimum ? minimum : value.z, + w = value.w < minimum ? minimum : value.w + }; + } + + /// + /// Clamps the value to be above or equal to the minimum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// + public static Vector4 ClampMin(Vector4 value, Vector4 minimum) + { + return new Vector4 + { + x = value.x < minimum.x ? minimum.x : value.x, + y = value.y < minimum.y ? minimum.y : value.y, + z = value.z < minimum.z ? minimum.z : value.z, + w = value.w < minimum.w ? minimum.w : value.w + }; + } + #endregion + + #region ClampMax + /// + /// Clamps the value to be below or equal to the maximum. + /// + /// Value to clamp. + /// Maximum the value can be. + /// + public static float ClampMax(float value, float maximum) + { + return value > maximum ? maximum : value; + } + + /// + /// Clamps the value to be below or equal to the maximum. + /// + /// Value to clamp. + /// Maximum the value can be. + /// + public static Vector2 ClampMax(Vector2 value, float maximum) + { + return new Vector2 + { + x = value.x > maximum ? maximum : value.x, + y = value.y > maximum ? maximum : value.y + }; + } + + /// + /// Clamps the value to be below or equal to the maximum. + /// + /// Value to clamp. + /// Maximum the value can be. + /// + public static Vector2 ClampMax(Vector2 value, Vector2 maximum) + { + return new Vector2 + { + x = value.x > maximum.x ? maximum.x : value.x, + y = value.y > maximum.y ? maximum.y : value.y + }; + } + + /// + /// Clamps the value to be below or equal to the maximum. + /// + /// Value to clamp. + /// Maximum the value can be. + /// + public static Vector3 ClampMax(Vector3 value, float maximum) + { + return new Vector3 + { + x = value.x > maximum ? maximum : value.x, + y = value.y > maximum ? maximum : value.y, + z = value.z > maximum ? maximum : value.z + }; + } + + /// + /// Clamps the value to be below or equal to the maximum. + /// + /// Value to clamp. + /// Maximum the value can be. + /// + public static Vector3 ClampMax(Vector3 value, Vector3 maximum) + { + return new Vector3 + { + x = value.x > maximum.x ? maximum.x : value.x, + y = value.y > maximum.y ? maximum.y : value.y, + z = value.z > maximum.z ? maximum.z : value.z + }; + } + + /// + /// Clamps the value to be below or equal to the maximum. + /// + /// Value to clamp. + /// Maximum the value can be. + /// + public static Vector4 ClampMax(Vector4 value, float maximum) + { + return new Vector4 + { + x = value.x > maximum ? maximum : value.x, + y = value.y > maximum ? maximum : value.y, + z = value.z > maximum ? maximum : value.z, + w = value.w > maximum ? maximum : value.w + }; + } + + /// + /// Clamps the value to be below or equal to the maximum. + /// + /// Value to clamp. + /// Maximum the value can be. + /// + public static Vector4 ClampMax(Vector4 value, Vector4 maximum) + { + return new Vector4 + { + x = value.x > maximum.x ? maximum.x : value.x, + y = value.y > maximum.y ? maximum.y : value.y, + z = value.z > maximum.z ? maximum.z : value.z, + w = value.w > maximum.w ? maximum.w : value.w + }; + } + #endregion + + #region Clamp + /// + /// Clamps the value to be within the minimum and maximum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// Maximum the value can be. + /// + public static float Clamp(float value, float minimum, float maximum) + { + value = ClampMin(value, minimum); + value = ClampMax(value, maximum); + return value; + } + + /// + /// Clamps the value to be within the minimum and maximum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// Maximum the value can be. + /// + public static Vector2 Clamp(Vector2 value, float minimum, float maximum) + { + return new Vector2 + { + x = Clamp(value.x, minimum, maximum), + y = Clamp(value.y, minimum, maximum) + }; + } + + /// + /// Clamps the value to be within the minimum and maximum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// Maximum the value can be. + /// + public static Vector2 Clamp(Vector2 value, Vector2 minimum, Vector2 maximum) + { + return new Vector2 + { + x = Clamp(value.x, minimum.x, maximum.x), + y = Clamp(value.y, minimum.y, maximum.y) + }; + } + + /// + /// Clamps the value to be within the minimum and maximum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// Maximum the value can be. + /// + public static Vector3 Clamp(Vector3 value, float minimum, float maximum) + { + return new Vector3 + { + x = Clamp(value.x, minimum, maximum), + y = Clamp(value.y, minimum, maximum), + z = Clamp(value.z, minimum, maximum) + }; + } + + /// + /// Clamps the value to be within the minimum and maximum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// Maximum the value can be. + /// + public static Vector3 Clamp(Vector3 value, Vector3 minimum, Vector3 maximum) + { + return new Vector3 + { + x = Clamp(value.x, minimum.x, maximum.x), + y = Clamp(value.y, minimum.y, maximum.y), + z = Clamp(value.z, minimum.z, maximum.z) + }; + } + + /// + /// Clamps the value to be within the minimum and maximum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// Maximum the value can be. + /// + public static Vector4 Clamp(Vector4 value, float minimum, float maximum) + { + return new Vector4 + { + x = Clamp(value.x, minimum, maximum), + y = Clamp(value.y, minimum, maximum), + z = Clamp(value.z, minimum, maximum), + w = Clamp(value.w, minimum, maximum) + }; + } + + /// + /// Clamps the value to be within the minimum and maximum. + /// + /// Value to clamp. + /// Minimum the value can be. + /// Maximum the value can be. + /// + public static Vector4 Clamp(Vector4 value, Vector4 minimum, Vector4 maximum) + { + return new Vector4 + { + x = Clamp(value.x, minimum.x, maximum.x), + y = Clamp(value.y, minimum.y, maximum.y), + z = Clamp(value.z, minimum.z, maximum.z), + w = Clamp(value.w, minimum.w, maximum.w) + }; + } + #endregion + } +} diff --git a/Tools/Editor/Functions/UdonVR_ShaderGUI.cs b/Tools/Editor/Functions/UdonVR_ShaderGUI.cs new file mode 100644 index 0000000..a4aa6e1 --- /dev/null +++ b/Tools/Editor/Functions/UdonVR_ShaderGUI.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEngine.Rendering; + +using UdonVR.EditorUtility; + +namespace UdonVR.EditorUtility.ShaderGUI +{ + public static class UdonVR_ShaderGUI + { + #region Cull Blend Specular + public enum CullMode + { + Off, Front, Back + }; + + public enum BlendMode + { + Opaque, Cutout, Fade + }; + + public struct CullBlendSpecularData + { + public UdonVR_ShaderGUI.BlendMode? blendMode; + public UdonVR_ShaderGUI.CullMode? cullMode; + public bool? specularHighlights; + }; + + /// + /// Displays the necessary fields in the inspector. + /// + /// Reference to the Material currently selected. + /// CullBlendSpecularData + public static CullBlendSpecularData ShowCullBlendSpecular(Material targetMat) + { + CullBlendSpecularData data = new CullBlendSpecularData(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + if (targetMat.HasProperty("_BlendMode")) + { + data.blendMode = (UdonVR_ShaderGUI.BlendMode)EditorGUILayout.EnumPopup(new GUIContent("Blend Mode"), (UdonVR_ShaderGUI.BlendMode)targetMat.GetInt("_BlendMode")); + } + if (targetMat.HasProperty("_Cull")) + { + data.cullMode = (UdonVR_ShaderGUI.CullMode)EditorGUILayout.EnumPopup(new GUIContent("Cull"), (UdonVR_ShaderGUI.CullMode)targetMat.GetInt("_Cull")); + } + if (targetMat.HasProperty("_SpecularHighlights")) + { + data.specularHighlights = UdonVR_GUI.ToggleButton(new GUIContent("Specular Highlights"), System.Convert.ToBoolean(targetMat.GetInt("_SpecularHighlights"))); + } + GUILayout.EndVertical(); + + return data; + } + + /// + /// Applies the value changes to the Material. + /// + /// Reference to the Material currently selected. + /// Reference to the struct containing the values. + public static void SaveCullBlendSpecular(Material targetMat, CullBlendSpecularData data) + { + if (data.blendMode.HasValue) + { + targetMat.SetInt("_BlendMode", (int)data.blendMode.Value); + #region Blend Mode Keywords & Tags + switch (data.blendMode.Value) + { + case UdonVR_ShaderGUI.BlendMode.Opaque: + targetMat.SetOverrideTag("RenderType", ""); + targetMat.SetOverrideTag("Queue", "Geometry"); + targetMat.renderQueue = (int)RenderQueue.Geometry; + targetMat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); + targetMat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); + targetMat.SetInt("_ZWrite", 1); + targetMat.DisableKeyword("_ALPHACUTOUT"); + targetMat.DisableKeyword("_ALPHAFADE"); + + targetMat.DisableKeyword("_ALPHATEST_ON"); + targetMat.DisableKeyword("_ALPHABLEND_ON"); + targetMat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + break; + + case UdonVR_ShaderGUI.BlendMode.Cutout: + targetMat.SetOverrideTag("RenderType", "TransparentCutout"); + targetMat.SetOverrideTag("Queue", "AlphaTest"); + targetMat.renderQueue = (int)RenderQueue.Transparent; // should be AlphaTest but that would require lighting passes to be added to the shader + targetMat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); + targetMat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); + targetMat.SetInt("_ZWrite", 1); + targetMat.EnableKeyword("_ALPHACUTOUT"); + targetMat.DisableKeyword("_ALPHAFADE"); + + targetMat.EnableKeyword("_ALPHATEST_ON"); + targetMat.DisableKeyword("_ALPHABLEND_ON"); + targetMat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + break; + + case UdonVR_ShaderGUI.BlendMode.Fade: + targetMat.SetOverrideTag("RenderType", "Transparent"); + targetMat.SetOverrideTag("Queue", "Transparent"); + targetMat.renderQueue = (int)RenderQueue.Transparent; + targetMat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); + targetMat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); + targetMat.SetInt("_ZWrite", 0); + targetMat.DisableKeyword("_ALPHACUTOUT"); + targetMat.EnableKeyword("_ALPHAFADE"); + + targetMat.DisableKeyword("_ALPHATEST_ON"); + targetMat.EnableKeyword("_ALPHABLEND_ON"); + targetMat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); + break; + } + #endregion + } + if (data.cullMode.HasValue) + { + targetMat.SetInt("_Cull", (int)data.cullMode.Value); + } + if (data.specularHighlights.HasValue) + { + targetMat.SetInt("_SpecularHighlights", (int)System.Convert.ToSingle(data.specularHighlights.Value)); + #region Specular Highlights Keywords + if (data.specularHighlights.Value) + { + targetMat.DisableKeyword("_SPECULARHIGHLIGHTS_OFF"); + } + else + { + targetMat.EnableKeyword("_SPECULARHIGHLIGHTS_OFF"); + } + #endregion + } + } + #endregion + + #region Glossiness Metallic AlphaCut AlphaFade + public static float AlphaCutout(Material targetMat) + { + float _alphaCutout = targetMat.GetFloat("_AlphaCutout"); + + if (targetMat.HasProperty("_BlendMode")) + { + if (targetMat.IsKeywordEnabled("_ALPHACUTOUT")) + { + _alphaCutout = EditorGUILayout.Slider(new GUIContent("Alpha Cutout"), _alphaCutout, 0, 1.01f); + } + } + else + { + _alphaCutout = EditorGUILayout.Slider(new GUIContent("Alpha Cutout"), _alphaCutout, 0, 1.01f); + } + + return _alphaCutout; + } + + public static float AlphaFade(Material targetMat) + { + float _alphaFade = targetMat.GetFloat("_AlphaFade"); + + if (targetMat.HasProperty("_BlendMode")) + { + if (targetMat.IsKeywordEnabled("_ALPHAFADE")) + { + _alphaFade = EditorGUILayout.Slider(new GUIContent("Alpha Fade"), _alphaFade, 0, 1.0f); + } + } + else + { + _alphaFade = EditorGUILayout.Slider(new GUIContent("Alpha Fade"), _alphaFade, 0, 1.0f); + } + + return _alphaFade; + } + + public struct GlossMetalAlphaData + { + public float? glossiness; + public float? metallic; + public float? alphaCutout; + public float? alphaFade; + }; + + /// + /// Shows the necessary fields in the inspector. + /// + /// Reference to the Material currently selected. + /// GlossMetalAlphaData + public static GlossMetalAlphaData ShowGlossMetalAlpha(Material targetMat) + { + GlossMetalAlphaData data = new GlossMetalAlphaData(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + if (targetMat.HasProperty("_Glossiness")) data.glossiness = EditorGUILayout.Slider(new GUIContent("Smoothness"), targetMat.GetFloat("_Glossiness"), 0, 1); + if (targetMat.HasProperty("_Metallic")) data.metallic = EditorGUILayout.Slider(new GUIContent("Metallic"), targetMat.GetFloat("_Metallic"), 0, 1); + if (targetMat.HasProperty("_AlphaCutout")) data.alphaCutout = UdonVR_ShaderGUI.AlphaCutout(targetMat); + if (targetMat.HasProperty("_AlphaFade")) data.alphaFade = UdonVR_ShaderGUI.AlphaFade(targetMat); + GUILayout.EndVertical(); + + return data; + } + + /// + /// Saves the value changes to the Material. + /// + /// Reference to the Material currently selected. + /// Reference to the struct containing the values. + public static void SaveGlossMetalAlpha(Material targetMat, GlossMetalAlphaData data) + { + if (data.glossiness.HasValue) targetMat.SetFloat("_Glossiness", data.glossiness.Value); + if (data.glossiness.HasValue) targetMat.SetFloat("_Metallic", data.metallic.Value); + if (data.alphaCutout.HasValue) targetMat.SetFloat("_AlphaCutout", data.alphaCutout.Value); + if (data.alphaFade.HasValue) targetMat.SetFloat("_AlphaFade", data.alphaFade.Value); + } + #endregion + + #region Emission + public struct EmissionData + { + public bool? lightOnly; + public float? intensity; + public Color? color; + public Texture emissionMap; + }; + + /// + /// Shows the necessary fields in the inspector. + /// + /// Reference to the Material currently selected. + /// Reference to the MaterialEditor. + /// RandomAxisFlipData + public static EmissionData ShowEmission(Material targetMat, MaterialEditor materialEditor) + { + EmissionData _data = new EmissionData(); + if (targetMat.HasProperty("_EmissionLightOnly")) _data.lightOnly = System.Convert.ToBoolean(targetMat.GetFloat("_EmissionLightOnly")); + if (targetMat.HasProperty("_EmissionIntensity")) _data.intensity = targetMat.GetFloat("_EmissionIntensity"); + if (targetMat.HasProperty("_EmissionColor")) _data.color = targetMat.GetColor("_EmissionColor"); + if (targetMat.HasProperty("_EmissionMap")) _data.emissionMap = targetMat.GetTexture("_EmissionMap"); + + GUILayout.BeginVertical(EditorStyles.helpBox); + UdonVR_GUI.Header(new GUIContent("Realtime GI does not work unless you use a script update it when there are changes."), UdonVR_GUIOption.FontSize(12), UdonVR_GUIOption.TextAnchor(TextAnchor.MiddleCenter)); + materialEditor.LightmapEmissionFlagsProperty(0, true); + if (_data.lightOnly.HasValue) _data.lightOnly = UdonVR_GUI.ToggleButton(new GUIContent("Emission Light Only", "Only emits GI light."), _data.lightOnly.Value); + if (_data.intensity.HasValue) _data.intensity = EditorGUILayout.Slider(new GUIContent("Emission Intensity", "Unused if 'Global Illumination' is set to 'None'."), _data.intensity.Value, 0, 4096); + if (_data.color.HasValue) _data.color = EditorGUILayout.ColorField(new GUIContent("Emission Color"), _data.color.Value, true, true, true); + if (targetMat.HasProperty("_EmissionMap")) _data.emissionMap = (Texture)EditorGUILayout.ObjectField(new GUIContent("Emission Map"), _data.emissionMap, typeof(Texture), false); + GUILayout.EndVertical(); + + return _data; + } + + /// + /// Saves the value changes to the Material. + /// + /// Reference to the Material currently selected. + /// Reference to the struct containing the values. + public static void SaveEmission(Material targetMat, EmissionData data) + { + if (data.lightOnly.HasValue) targetMat.SetFloat("_EmissionLightOnly", System.Convert.ToSingle(data.lightOnly)); + if (data.intensity.HasValue) targetMat.SetFloat("_EmissionIntensity", data.intensity.Value); + if (data.color.HasValue) targetMat.SetColor("_EmissionColor", data.color.Value); + if (targetMat.HasProperty("_EmissionMap")) targetMat.SetTexture("_EmissionMap", data.emissionMap); + targetMat.SetShaderPassEnabled("META", true); + } + #endregion + } +} diff --git a/Tools/Editor/Functions/UdonVR_VariableStorage.cs b/Tools/Editor/Functions/UdonVR_VariableStorage.cs new file mode 100644 index 0000000..b4e0ce9 --- /dev/null +++ b/Tools/Editor/Functions/UdonVR_VariableStorage.cs @@ -0,0 +1,332 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using System; + +// created by Hamster9090901 + +namespace UdonVR.EditorUtility +{ + public class UdonVR_VariableStorage + { + // created by Hamster9090901 + + #region Get and Create Instance + private static UdonVR_VariableStorage instance; + + /// + /// Get instance / Create one if there wasn't one. + /// + public static UdonVR_VariableStorage Instance + { + get + { + if (instance == null) + { + instance = new UdonVR_VariableStorage(); + instance.groups = new Hamster_GenericDictionary>(); + instance.groups.Initalize(); + } + return instance; + } + } + #endregion + + private Hamster_GenericDictionary> groups; + + /// + /// Get variable group if it exists and create a new group if it dosen't. + /// + /// Name of the group. If left empty generates a group. + /// + public UdonVR_Variables GetVariableGroup( + string name = null, + [System.Runtime.CompilerServices.CallerFilePath] string callerFilePath = "") + { + #region Get guid from script caller filepath to get correct variable group + if (string.IsNullOrEmpty(name?.Trim())) name = UdonVR_Encryption.GuidFromStringMD5(callerFilePath).ToString(); + #endregion + + groups.TryGetValue(name, out UdonVR_Variables _value, out _); // get the group + + // create new group and get the created group + if (_value == null) + { + groups.Add(name, new UdonVR_Variables()); + groups.TryGetValue(name, out _value, out _); + } + + return _value; + } + + /// + /// List all the variable groups and their contents + /// + public void ListContainers() + { + for (int i = 0; i < groups.Count; i++) + { + Debug.Log("Container: " + groups.Keys[i]); + groups.TryGetValue(groups.Keys[i], out UdonVR_Variables _value, out _); + _value.ListVariables(4); + } + } + + public class UdonVR_Variables + { + /// + /// Dictionary containing variable names and values + /// + private Hamster_GenericDictionary variables; + + /// + /// Initalize variable dictinary if it hasn't been initalized. + /// + public void Initalize() + { + if (variables == null) + { + variables = new Hamster_GenericDictionary(); + variables.Initalize(); + } + } + + /// + /// Get the value of the variable with the same name. + /// + /// Name of the variable. + /// + public object GetVariable(object name) + { + if (variables == null) Initalize(); + variables.TryGetValue(name, out object _value, out _); + return _value; + } + + /// + /// Save a value to a variable with the same name. + /// + /// Variable name to save the value to. + /// Value to set the variable to. + public void SetVariable(object name, object value) + { + if (variables == null) variables.Initalize(); + variables.SetValue(name, value); + } + + /// + /// List variables. + /// + public void ListVariables(int indentLevel = 0) + { + if (variables == null) Initalize(); + variables.ListContents(indentLevel); + } + + /// + /// Clear variables. + /// + public void ClearVariables() + { + if (variables == null) + { + Initalize(); + } + else + { + variables.Clear(); + } + } + } + } + + public class Hamster_GenericDictionary + { + private List keys = null; + private List values = null; + + /// + /// Number of key value pairs in the dictionary. + /// + public int Count + { + get + { + return keys.Count; + } + } + + /// + /// Keys in the dictionary. + /// + public List Keys + { + get + { + return keys; + } + } + + /// + /// Values in the dictionary. + /// + public List Values + { + get + { + return values; + } + } + + /// + /// Initalize the dictionary + /// + public void Initalize() + { + keys = new List(); + values = new List(); + } + + /// + /// Checks to see if the dictionary was initalized or not. + /// + /// + public bool IsInitalized() + { + return keys != null && values != null ? true : false; + } + + /// + /// Clear the dictionary. + /// + public void Clear() + { + keys.Clear(); + values.Clear(); + } + + /// + /// Add a key value pair to the dictionary. + /// + /// Key to add. + /// Value to add. + public void Add(TKey key, TValue value) + { + if (!IsInitalized()) return; + keys.Add(key); + values.Add(value); + } + + /// + /// Try to get a Value in the dictionary. + /// + /// Key to find the value of. + /// Found value. ("out _" to ignore output.) + /// Index of the found value. ("out _" to ignore output.) + /// True if Value was found. + public bool TryGetValue(TKey key, out TValue value, out int index) + { + value = default(TValue); // set output value to null + index = -1; // set output index to -1 (null) + + if (!IsInitalized() || !ContainsKey(key) || Count == 0) return false; + + int _index = keys.BinarySearch(key); // binary search for key (-1 if not found) + if (_index < 0) return false; // exit if no key was found + + value = values[_index]; // output value + index = _index; // output index + return true; + } + + /// + /// Remove a key value pair from the dictionary. + /// + /// Key to remove. + public void Remove(TKey key) + { + if (!IsInitalized()) return; + + if (!TryGetValue(key, out TValue _value, out _)) return; // exit no value was found + + keys.Remove(key); // remove key + values.Remove(_value); // remove value + } + + /// + /// Update a key value pair in the dictionary. + /// + /// Key of value to update. + /// New Value. + public void Update(TKey key, TValue value) + { + if (!IsInitalized()) return; + + if (!TryGetValue(key, out _, out int _valueIndex)) return; // exit if no value was found + + values[_valueIndex] = value; // upade the value at the index to the new value + } + + /// + /// Set a value in the dictionary. + /// + /// Key in the dictionary. + /// Value to save. + public void SetValue(TKey key, TValue value) + { + if (!IsInitalized()) return; + + // try to get the value and if we found it upade it with the new value + if (TryGetValue(key, out _, out _)) + { + Update(key, value); // update the key value pair + return; // exit after update so we dont add a new key value pair + } + + Add(key, value); // add new key value pair + } + + /// + /// Does the dictionary contain this key. + /// + /// Key to check for. + /// + public bool ContainsKey(TKey key) + { + return keys.Contains(key); + } + + /// + /// Does the dictionary contain this value. + /// + /// Value to check for. + /// + public bool ContainsValue(TValue value) + { + return values.Contains(value); + } + + /// + /// Lists the content of the dictionary in the Console. + /// + public void ListContents(int indentLevel = 0) + { + if (!IsInitalized()) return; + + string _indent = ""; + for (int i = 0; i < indentLevel; i++) + { + _indent += " "; + } + + for (int i = 0; i < Count; i++) + { + TKey _key = keys[i]; + TValue _value = values[i]; + Debug.Log(_indent + "Key: " + _key + " | Value: " + _value); + } + } + } +} \ No newline at end of file diff --git a/Tools/Editor/Hamster9090901_TestingWindow.cs b/Tools/Editor/Hamster9090901_TestingWindow.cs new file mode 100644 index 0000000..f5e2cc6 --- /dev/null +++ b/Tools/Editor/Hamster9090901_TestingWindow.cs @@ -0,0 +1,136 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using System; + +using UdonVR.EditorUtility; + +public class Hamster9090901_TestingWindow : EditorWindow +{ + private static Hamster9090901_TestingWindow window; + + private bool show_rgbToColor = false; + private Color rgbToColor = Color.white; + + private bool testingButton = false; + private bool foldoutState = false; + + private Vector2 scrollview = Vector2.zero; + + [MenuItem("UdonVR/Dev/Hamster9090901_TestingWindow")] + private static void Init() + { + window = EditorWindow.GetWindow(); + //window.maxSize = new Vector2(700, 850); + window.minSize = new Vector2(350, 300); + //window.titleContent = windowTitleContent; + window.Show(); + } + + private void OnGUI() + { + if (window == null) window = EditorWindow.GetWindow(); // get window if it was null + + #region Color Convert + if (UdonVR_GUI.BeginButtonFoldout(new GUIContent("Color Convert"), ref show_rgbToColor, "show_rgbToColor", EditorStyles.helpBox)) + { + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + UdonVR_GUI.Header(new GUIContent("Show Input Color with a (0 - 1) range.")); + rgbToColor = EditorGUILayout.ColorField(GUIContent.none, rgbToColor); + EditorGUILayout.SelectableLabel(String.Format("({0},{1},{2},{3})", rgbToColor.r, rgbToColor.g, rgbToColor.b, rgbToColor.a), UdonVR_Style.SetTextSettings(GUIStyle.none, UdonVR_Predefined.Color.Style_DefaultTextColor, TextAnchor.MiddleCenter)); + EditorGUILayout.EndVertical(); + } + UdonVR_GUI.EndButtonFoldout("show_rgbToColor"); + #endregion + + #region FieldArea + MeshRenderer testing = null; + UdonVR_GUI.FieldArea( + new GUIContent[] + { + new GUIContent("bool"), + new GUIContent("string"), + new GUIContent("int"), + new GUIContent("float"), + new GUIContent("double"), + new GUIContent("vector2"), + new GUIContent("vector3"), + new GUIContent("vector4"), + new GUIContent("color"), + new GUIContent("object"), + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValueToggle(testingButton, true), + UdonVR_GUI.FieldAreaValues.SetValue("hi"), + UdonVR_GUI.FieldAreaValues.SetValueSlider(5, true, 0, 100), + UdonVR_GUI.FieldAreaValues.SetValue(7.5f, true), + UdonVR_GUI.FieldAreaValues.SetValueSlider(0.6, true, 0, 1), + UdonVR_GUI.FieldAreaValues.SetValue(new Vector2(8, 3)), + UdonVR_GUI.FieldAreaValues.SetValue(new Vector3(8, 3, 7)), + UdonVR_GUI.FieldAreaValues.SetValue(new Vector4(8, 3, 7, 2)), + UdonVR_GUI.FieldAreaValues.SetValueColor(Color.cyan), + UdonVR_GUI.FieldAreaValues.SetValue(testing) + }, + new Action[] + { + (areaValues) => + { + testingButton = areaValues.boolValue.Value; + }, + (areaValues) => + { + + }, + (areaValues) => + { + + }, + (areaValues) => + { + + }, + (areaValues) => + { + + }, + (areaValues) => + { + + }, + (areaValues) => + { + + }, + (areaValues) => + { + + }, + (areaValues) => + { + + }, + (areaValues) => + { + + } + } + ); + #endregion + + #region Foldout + foldoutState = UdonVR_GUI.BeginButtonFoldout(new GUIContent("Foldout"), foldoutState, "foldout"); + if (foldoutState) + { + GUILayout.Button("HI"); + } + UdonVR_GUI.EndButtonFoldout("foldout"); + #endregion + + scrollview = GUI.BeginScrollView(new Rect(100, 100, 800, 400), scrollview, new Rect(0, 0, 5000, 5000)); + UdonVR_GUI_DragAndDrop.Controller.Update(); + GUI.EndScrollView(); + } +} \ No newline at end of file diff --git a/Tools/Editor/SortChild.cs b/Tools/Editor/SortChild.cs index 17505a6..14972bf 100644 --- a/Tools/Editor/SortChild.cs +++ b/Tools/Editor/SortChild.cs @@ -1,93 +1,97 @@ -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using System.Linq; -using UnityEditor; -using UnityEngine.UI; -using static UdonVR.EditorUtility.EditorHelper; -using System.Text; -using System; - -public class SortChild : EditorWindow -{ - - public string newPath = " "; - private GUILayoutOption[] space = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16) }; - private GUILayoutOption[] noExpandWidth = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16), GUILayout.ExpandWidth(false) }; - private GUIStyle style; - public GUIStyle logoStyle; - - Transform parentTransform; - - - [MenuItem("UdonVR/Sort Children")] - public static void ShowWindow() - { - SortChild window = GetWindow("Sort Children"); - window.minSize = new Vector2(385, 250); - } - private void GUIWarning(string text) - { - - EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.warnicon")), style); - - } - private void GUIError(string text) - { - EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.erroricon")), style); - - } - private void InitGuiStyles() - { - style = new GUIStyle(EditorStyles.helpBox); - style.richText = true; - style.fontSize = 15; - - logoStyle = new GUIStyle("flow node hex 0") - { - fontSize = 15, - richText = true, - alignment = TextAnchor.MiddleCenter, - hover = ((GUIStyle)"flow node hex 1").normal, - active = ((GUIStyle)"flow node hex 1 on").normal - }; - - logoStyle.padding.top = 17; - logoStyle.normal.textColor = Color.cyan; - } - public static string PadNumbers(string input) - { - return System.Text.RegularExpressions.Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0')); - } - private void OnGUI() - { - if (style == null) - InitGuiStyles(); - - EditorGUILayout.LabelField("Sort", style); - EditorGUILayout.Space(); - - parentTransform = (Transform)EditorGUILayout.ObjectField("Parent", parentTransform, typeof(Transform), true); - - if (GUILayout.Button("Sort")) - { - if (parentTransform != null && !UnityEditor.EditorUtility.IsPersistent(parentTransform)) - { - - List children = new List(); - foreach (Transform child in parentTransform) - { - children.Add(child); - } - - var sorted = children.OrderBy(child => PadNumbers(child.name)).ToList(); - - for (int i = 0; i < sorted.Count; i++) - { - sorted[i].SetSiblingIndex(i); - } - } - } - } - -} +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System.Linq; +using UnityEditor; +using UnityEngine.UI; +using static UdonVR.EditorUtility.EditorHelper; +using System.Text; +using System; + +using UdonVR.EditorUtility; + +public class SortChild : EditorWindow +{ + + public string newPath = " "; + private GUILayoutOption[] space = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16) }; + private GUILayoutOption[] noExpandWidth = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16), GUILayout.ExpandWidth(false) }; + private GUIStyle style; + public GUIStyle logoStyle; + + Transform parentTransform; + + + [MenuItem("UdonVR/Sort Children")] + public static void ShowWindow() + { + SortChild window = GetWindow("Sort Children"); + window.minSize = new Vector2(385, 250); + } + private void GUIWarning(string text) + { + + EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.warnicon")), style); + + } + private void GUIError(string text) + { + EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.erroricon")), style); + + } + private void InitGuiStyles() + { + style = new GUIStyle(EditorStyles.helpBox); + style.richText = true; + style.fontSize = 15; + + logoStyle = new GUIStyle("flow node hex 0") + { + fontSize = 15, + richText = true, + alignment = TextAnchor.MiddleCenter, + hover = ((GUIStyle)"flow node hex 1").normal, + active = ((GUIStyle)"flow node hex 1 on").normal + }; + + logoStyle.padding.top = 17; + logoStyle.normal.textColor = Color.cyan; + } + public static string PadNumbers(string input) + { + return System.Text.RegularExpressions.Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0')); + } + private void OnGUI() + { + if (style == null) + InitGuiStyles(); + + EditorGUILayout.LabelField("Sort", style); + EditorGUILayout.Space(); + + parentTransform = (Transform)EditorGUILayout.ObjectField("Parent", parentTransform, typeof(Transform), true); + + if (GUILayout.Button("Sort")) + { + if (parentTransform != null && !UnityEditor.EditorUtility.IsPersistent(parentTransform)) + { + + List children = new List(); + foreach (Transform child in parentTransform) + { + children.Add(child); + } + + var sorted = children.OrderBy(child => PadNumbers(child.name)).ToList(); + + for (int i = 0; i < sorted.Count; i++) + { + sorted[i].SetSiblingIndex(i); + } + } + } + + UdonVR_GUI.ShowUdonVRLinks(32, 32, true); + } + +} diff --git a/Tools/Editor/UdonVRProjectMenu.cs b/Tools/Editor/UdonVRProjectMenu.cs new file mode 100644 index 0000000..71b7918 --- /dev/null +++ b/Tools/Editor/UdonVRProjectMenu.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UdonSharp; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using VRC.Udon; +using VRC.Udon.Editor; + +namespace UdonVR.Tools.Utility +{ + public class UdonVRProjectMenu + { + // Start is called before the first frame update + [MenuItem("Assets/Create/VRChat Scene", false, 201)] + public static void CreateVRCScene() + { + Debug.Log("[UdonVR] Creating New VRChat Scene"); + string folderPath = "Assets/"; + if (Selection.activeObject != null) + { + folderPath = AssetDatabase.GetAssetPath(Selection.activeObject); + if (Selection.activeObject.GetType() != typeof(UnityEditor.DefaultAsset)) + { + folderPath = Path.GetDirectoryName(folderPath); + } + } + else if (Selection.assetGUIDs.Length > 0) + { + folderPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); + } + folderPath = folderPath.Replace('\\', '/'); + + + string filePath = AssetDatabase.GenerateUniqueAssetPath(folderPath + "/VRChatScene.unity"); + Debug.Log("[UdonVR] Creating: " + folderPath); + string chosenFilePath = UnityEditor.EditorUtility.SaveFilePanelInProject("Save New Scene", Path.GetFileName(filePath), "unity", "Save New Scene", folderPath); + AssetDatabase.CopyAsset("Assets/_UdonVR/Tools/Utility/Scenes/VRChatScene.unity", chosenFilePath); + AssetDatabase.Refresh(); + } + } +} \ No newline at end of file diff --git a/Tools/Editor/UdonVR_WorldSettings.cs b/Tools/Editor/UdonVR_WorldSettings.cs new file mode 100644 index 0000000..f97c81c --- /dev/null +++ b/Tools/Editor/UdonVR_WorldSettings.cs @@ -0,0 +1,780 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +using VRC.SDK3.Components; +using VRC.Core; + +using UdonVR.Tools.ApiUpdate; +using UdonVR.EditorUtility; + +// created by Hamster9090901 + +public class UdonVR_WorldSettings : EditorWindow +{ + private static GUIContent windowTitleContent = new GUIContent("Udon VR - World Settings"); + public static UdonVR_WorldSettings window; + + #region GUI Images + private Texture2D gui_icon_plus; + private Texture2D gui_icon_minus; + + private Texture2D gui_icon_unlocked; + private Texture2D gui_icon_locked; + private Texture2D gui_icon_communityLabs; + #endregion + + #region Save Load Variables + private UVRImageUpload uVRImageUpload; + + private bool showBlueprintIDFoldoutMenu = true; + private bool lastShowBlueprintIDFoldoutMenu = false; + private string userEnteredBlueprintID; + private string blueprintID; + private ApiWorld apiWorld = null; + private APIUser apiUser = null; + private bool isInitalized = false; + private bool isUploading = false; + + private const int MAX_USER_TAGS_FOR_WORLD = 5; // maximum user tags + private const int MAX_CHARACTERS_ALLOWED_IN_USER_TAG = 20; // maximum characters allowed in user tags + #endregion + + #region Image Variables + private Texture2D udonVRLogo = null; + private Texture2D worldTexture = null; // loaded world texture + private Texture2D displayedTexture = null; // displayed texture + private string displayedTexturePath = ""; // path gotten from picking a file from disc or taking a screenshot using the camera + private int currentPickerWindow = 0; + #endregion + + #region World Variables + private float worldFileSize = -1; + private string worldName = "Sample Name"; + private string worldDescription = "Sample Description"; + private int worldCapacity = 16; + private string[] worldTags = { }; + #endregion + + #region Debug + private bool showContainers = false; // used for debug + #endregion + + #region Scroll View + private Rect preScrollViewArea = Rect.zero; // contains the size of the area before the scroll view (used for always showing logos at the bottom) + private Vector2 scrollPosition = Vector2.zero; + private Rect scrollContentRect = Rect.zero; // contains the size of the scroll view content + #endregion + + [MenuItem("UdonVR/World Settings")] + private static void Init() + { + window = EditorWindow.GetWindow(); + window.maxSize = new Vector2(700, 850); + window.minSize = new Vector2(350, 300); + window.titleContent = windowTitleContent; + window.Show(); + } + + private void OnGUI() + { + if (uVRImageUpload == null) + { + uVRImageUpload = UVRImageUpload.Instance; + Debug.Log("Got Instance of UVRImageUpload"); + } + + if (window == null) window = EditorWindow.GetWindow(); // get windopw if it was null + + int inspectorWidth = (int)window.position.width; // get window width + int inspectorHeight = (int)window.position.height; // get window height + + PipelineManager pipelineManager = UnityEngine.Object.FindObjectOfType(); + if (pipelineManager != null) + { + //blueprintID = pipelineManager.blueprintId; + //if (!isInitalized) Initalize(); + } + + #region Show Options to allow user to add a world ID with out requiring a VRC World Scene Descriptor + EditorGUILayout.BeginVertical(showContainers ? UdonVR_Style.Get(new Vector4(0.5f, 0.25f, 0.25f, 1f)) : new GUIStyle()); + + if (lastShowBlueprintIDFoldoutMenu != showBlueprintIDFoldoutMenu) + { + lastShowBlueprintIDFoldoutMenu = showBlueprintIDFoldoutMenu; + Repaint(); + } + + if (UdonVR_GUI.BeginButtonFoldout(new GUIContent("Show World ID Options"), ref showBlueprintIDFoldoutMenu, "showWorldIDOptions", EditorStyles.helpBox, null, 0, 0, false, showContainers)) + { + #region Show Input field and buttons for letting users input a World ID or use the Scene Descriptor to get the ID + EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + EditorGUILayout.BeginHorizontal(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Label(new GUIContent("Enter World ID: ", "Enter a World ID to View / Edit."), UdonVR_Predefined.Color.Style_DefaultTextColor); + userEnteredBlueprintID = EditorGUILayout.TextField(userEnteredBlueprintID, UdonVR_Style.SetWidthHeight(-1, 18 * 1, GUI.skin.textField)); + EditorGUILayout.EndHorizontal(); + + bool isPipelineNull = pipelineManager == null; + bool isPipelineComplete = pipelineManager?.blueprintId != null; + string vrcWorldToolTip = isPipelineNull ? "Could not find Scene Descriptor." : "Use the Scene Descriptor to get the world information to edit."; + if (!isPipelineNull) vrcWorldToolTip = !isPipelineComplete ? "Pipeline Manager does not have a World ID." : vrcWorldToolTip; + + UdonVR_GUI.FieldArea( + new GUIContent[] { + new GUIContent("Use Entered ID", "Use the entered World ID to get the world information to edit."), + new GUIContent("Use Scene Descriptor", vrcWorldToolTip), + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValueMomentary(!string.IsNullOrWhiteSpace(userEnteredBlueprintID)), + UdonVR_GUI.FieldAreaValues.SetValueMomentary(isPipelineComplete, UdonVR_GUIOption.TintColorDefault(isPipelineComplete ? Color.white : Color.red)) + }, + new Action[] + { + (areaValues) => + { + // use entered id + if(!areaValues.boolValue.Value) return; + blueprintID = userEnteredBlueprintID; + Initalize(); + }, + (areaValues) => + { + // use scene descriptor id + if(!areaValues.boolValue.Value) return; + blueprintID = pipelineManager.blueprintId; + Initalize(); + } + }, + EditorStyles.helpBox, + null, + showContainers + ); + + if (isPipelineNull || !isPipelineComplete) + { + EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + GUIStyle warningStyle = new GUIStyle(GUI.skin.textField); + warningStyle.normal.textColor = Color.red; + warningStyle.wordWrap = true; + warningStyle.alignment = TextAnchor.MiddleCenter; + EditorGUILayout.LabelField(vrcWorldToolTip, UdonVR_Style.SetWidthHeight(-1, -1, warningStyle)); + EditorGUILayout.EndVertical(); + } + EditorGUILayout.EndVertical(); + #endregion + + #region Display the current ID of the world were editing. + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(-1, -1, showContainers ? UdonVR_Style.Get(new Vector4(0.25f, 0.5f, 0.25f, 1f)) : new GUIStyle())); + EditorGUILayout.BeginHorizontal(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Label(new GUIContent("Current World ID: "), UdonVR_Predefined.Color.Style_DefaultTextColor); + GUI.enabled = false; + EditorGUILayout.TextField(blueprintID, new GUIStyle(GUI.skin.textField)); + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + #endregion + } + UdonVR_GUI.EndButtonFoldout("showWorldIDOptions"); + + EditorGUILayout.EndVertical(); + if (Event.current.type == EventType.Repaint) preScrollViewArea = GUILayoutUtility.GetLastRect(); // get the size of the rectangle + #endregion + + + + if (apiWorld == null && blueprintID != null) + { + if (!isInitalized) Initalize(); + } + + if (apiUser == null || apiWorld == null) + { + showBlueprintIDFoldoutMenu = true; // show the ID foldout menu for users to enter or select an ID + + EditorGUILayout.BeginVertical(showContainers ? UdonVR_Style.Get(new Vector4(0.5f, 0.25f, 0.25f, 1)) : new GUIStyle(EditorStyles.helpBox)); + GUIStyle warningStyle = new GUIStyle(GUI.skin.textField); + warningStyle.normal.textColor = Color.red; + warningStyle.wordWrap = true; + warningStyle.alignment = TextAnchor.MiddleCenter; + EditorGUILayout.LabelField( + "Potentially not logged in please log in to your SDK if you have not." + "\n" + + "No world information found for this ID, Please select a World ID to start.", + UdonVR_Style.SetWidthHeight(-1, -1, warningStyle)); + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(); + + UdonVR_GUI.ShowUdonVRLinks(32, 32, true, null, showContainers); + return; + } + + // check if the logged in user is the author of the world + bool isUserWorldAuthor = (apiUser.id == apiWorld.authorId) ? true : false; + + #region Scroll Area + // add a line to show where the end of the scroll view ends and where the area for the links starts + bool isDividerActive = (inspectorHeight - (96 + preScrollViewArea.height) <= scrollContentRect.height) ? true : false; + GUIStyle dividerStyle = isDividerActive ? UdonVR_Style.Get(UdonVR_Predefined.Color.Background_Light) : UdonVR_Style.Get(UdonVR_Predefined.Color.General_Clear); + dividerStyle = showContainers ? UdonVR_Style.Get(Color.white) : dividerStyle; + GUILayout.Box(GUIContent.none, UdonVR_Style.SetWidthHeight(-1, 1, dividerStyle, new RectOffset(0, 0, 0, 0))); + + UdonVR_GUI.BeginDynamicScrollViewHeight(new Vector2(inspectorWidth, inspectorHeight - (96 + preScrollViewArea.height)), ref scrollPosition, new Rect(0, 0, inspectorWidth, scrollContentRect.height), showContainers); + /* + // should be used but update needs to be pushed so disabled until bugs can be fixed and it resizes proporly + string guid = "testing"; + var output = UdonVR_VariableStorage.Instance.GetVariableGroup().GetVariable(guid); // save the rect + Rect areaRect = output != null ? (Rect)Convert.ChangeType(output, typeof(Rect)) : Rect.zero; + areaRect.width = inspectorWidth; + */ + + #region Begin inspector width modes + float areaWidth = -1; // width of the area to center areas 1 & 2 in + if (inspectorWidth >= 350 && !(inspectorWidth <= 550)) + { + EditorGUILayout.BeginHorizontal(); + + areaWidth = inspectorWidth; // areaRect.width + areaWidth -= isDividerActive ? 13 : 0; // subtract the scrollbar width if scrollbar is active + } + else + { + EditorGUILayout.BeginHorizontal(showContainers ? UdonVR_Style.Get(new Vector4(0.25f, 0.25f, 0.5f, 1)) : new GUIStyle()); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginVertical(showContainers ? UdonVR_Style.Get(new Vector4(0.25f, 0.5f, 0.5f, 1)) : new GUIStyle()); + } + #endregion + + //---------------- + + #region Area 1 + EditorGUILayout.BeginVertical(); // group one start + // width was 350 + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(-1, -1, showContainers ? UdonVR_Style.Get(new Vector4(0.5f, 0.25f, 0.25f, 1)) : new GUIStyle())); + + EditorGUILayout.BeginHorizontal(UdonVR_Style.SetWidthHeight(areaWidth != -1 ? areaWidth / 2 : -1, -1)); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(256 + 16, -1, EditorStyles.helpBox)); + + #region Image Display + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(256 + 8, -1, EditorStyles.helpBox)); + UdonVR_GUI.DrawImage(displayedTexture, 256, 196); + EditorGUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + #endregion + + #region Image Options + EditorGUILayout.BeginHorizontal(showContainers ? UdonVR_Style.Get(new Vector4(0.5f, 0.5f, 0.25f, 1)) : new GUIStyle()); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(256, -1, EditorStyles.helpBox)); + + UdonVR_GUI.FieldArea( + new GUIContent[] { + new GUIContent("Image From File", ".png ONLY"), + new GUIContent("Image From Assets", ".png ONLY"), + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValueMomentary(isUserWorldAuthor), + UdonVR_GUI.FieldAreaValues.SetValueMomentary(isUserWorldAuthor) + }, + new Action[] + { + (areaValues) => + { + // image from file + if(!areaValues.boolValue.Value) return; + string path = EditorUtility.OpenFilePanel("Select a .png", "", "png"); + if (path.Length != 0) + { + var fileContent = File.ReadAllBytes(path); + displayedTexture = new Texture2D(1, 1); + displayedTexture.LoadImage(fileContent); + } + displayedTexturePath = path; + }, + (areaValues) => + { + // image from assets + if(!areaValues.boolValue.Value) return; + currentPickerWindow = EditorGUIUtility.GetControlID(FocusType.Passive) + 100; + EditorGUIUtility.ShowObjectPicker(displayedTexture, false, "", currentPickerWindow); + } + }, + EditorStyles.helpBox, + null, + showContainers + ); + + // used for getting the image from the object picker + if (Event.current.commandName == "ObjectSelectorUpdated" && EditorGUIUtility.GetObjectPickerControlID() == currentPickerWindow) + { + Texture2D pickedTexture = (Texture2D)EditorGUIUtility.GetObjectPickerObject(); + + string path = AssetDatabase.GetAssetPath(pickedTexture); + string extension = Path.GetExtension(path); + if (extension.ToLower() == ".png") + { + displayedTexture = pickedTexture; + displayedTexturePath = path; + } + else + { + EditorUtility.DisplayDialog( + windowTitleContent.text, + "You Selected a file with the " + extension + " extension and that extension cannot be used.", + "Ok"); + currentPickerWindow = -1; + } + + Repaint(); + } + // object picker cleanup + if (Event.current.commandName == "ObjectSelectorClosed") + { + currentPickerWindow = -1; + } + + GUI.enabled = isUserWorldAuthor; // enable / disable editing if user is not world owner + if (GUILayout.Button(new GUIContent("From Camera"))) + { + UdonVR_CameraImage capture = Camera.main.gameObject.GetComponent(); + if (capture == null) capture = Camera.main.gameObject.AddComponent(); + string path = "Assets\\_UdonVR\\Tools\\Settings\\World\\SavedImages\\"; + capture.TakeImage(worldName, ref path, -1, -1, true); + var fileContent = File.ReadAllBytes(path); + displayedTexture = new Texture2D(1, 1); + displayedTexture.LoadImage(fileContent); + displayedTexturePath = path; + } + GUI.enabled = true; + EditorGUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + #endregion + + EditorGUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.EndVertical(); + EditorGUILayout.EndVertical(); // group one end + #endregion + + //---------------- + + if ((inspectorWidth >= 350) && (inspectorWidth <= 550)) EditorGUILayout.Space(); + + //---------------- + + #region Area 2 + EditorGUILayout.BeginVertical(); // group two start + // width was 350 + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(-1, -1, showContainers ? UdonVR_Style.Get(new Vector4(0.25f, 0.5f, 0.25f, 1)) : new GUIStyle())); + + EditorGUILayout.BeginHorizontal(UdonVR_Style.SetWidthHeight(areaWidth != -1 ? areaWidth / 2 : -1, -1)); + GUILayout.FlexibleSpace(); + EditorGUILayout.BeginVertical(UdonVR_Style.SetWidthHeight(256 + 16, -1, EditorStyles.helpBox)); + + #region World Creater, Version & File Size + EditorGUILayout.BeginHorizontal(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Label(new GUIContent("Author: "), UdonVR_Predefined.Color.Style_DefaultTextColor); + UdonVR_GUI.Label(new GUIContent(apiWorld.authorName), (apiUser.displayName == apiWorld.authorName) ? Color.green : Color.red); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Label(new GUIContent("Version:"), UdonVR_Predefined.Color.Style_DefaultTextColor); + UdonVR_GUI.Label(new GUIContent(apiWorld.version.ToString()), UdonVR_Predefined.Color.Style_DefaultTextColor); + EditorGUILayout.EndHorizontal(); + EditorGUILayout.BeginHorizontal(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Label(new GUIContent("File Size:"), UdonVR_Predefined.Color.Style_DefaultTextColor); + UdonVR_GUI.Label(new GUIContent(UdonVR_Functions.FormatFileSize(worldFileSize)), UdonVR_Predefined.Color.Style_DefaultTextColor); + EditorGUILayout.EndHorizontal(); + #endregion + + #region World Name + EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Header(new GUIContent("Name:")); + GUI.enabled = isUserWorldAuthor; // enable / disable editing if user is not world owner + worldName = EditorGUILayout.TextArea(worldName, UdonVR_Style.SetWidthHeight(-1, 18 * 1, GUI.skin.textField)); + GUI.enabled = true; + EditorGUILayout.EndVertical(); + #endregion + + #region World Description + EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Header(new GUIContent("Description:")); + GUIStyle worldDescriptionStyle = UdonVR_Style.SetWidthHeight(-1, -1, GUI.skin.textField); + worldDescriptionStyle.wordWrap = true; + GUI.enabled = isUserWorldAuthor; // enable / disable editing if user is not world owner + worldDescription = EditorGUILayout.TextArea(worldDescription, worldDescriptionStyle); + GUI.enabled = true; + EditorGUILayout.EndVertical(); + #endregion + + #region World Capacity + EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Header(new GUIContent("Capacity:")); + EditorGUILayout.BeginHorizontal(); + GUI.enabled = isUserWorldAuthor; // enable / disable editing if user is not world owner + if (UdonVR_GUI.TintedButton(new GUIContent(gui_icon_plus), UdonVR_GUIOption.WidthHeight(18, 18), UdonVR_GUIOption.Padding(new RectOffset()), UdonVR_GUIOption.TintColorDefault(Color.green))) worldCapacity++; + worldCapacity = EditorGUILayout.IntField(worldCapacity, UdonVR_Style.SetWidthHeight(-1, 18 * 1, GUI.skin.textField)); + if (UdonVR_GUI.TintedButton(new GUIContent(gui_icon_minus), UdonVR_GUIOption.WidthHeight(18, 18), UdonVR_GUIOption.Padding(new RectOffset()), UdonVR_GUIOption.TintColorDefault(Color.red))) worldCapacity--; + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + worldCapacity = Mathf.Clamp(worldCapacity, 1, 40); // clamp world capacity to be within 1 - 40 + EditorGUILayout.EndVertical(); + #endregion + + #region World Visibility + EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + EditorGUILayout.BeginHorizontal(); + UdonVR_GUI.Label(new GUIContent("Visibility:"), UdonVR_Predefined.Color.Style_DefaultTextColor, new RectOffset(0, 0, 1, 0)); + GUIContent content = new GUIContent(apiWorld.releaseStatus.ToUpperFirst()); + if (apiWorld.releaseStatus == "public") content.image = gui_icon_unlocked; // show unlocked ucon if world is public + if (apiWorld.releaseStatus == "private") content.image = gui_icon_locked; // show locked icon if world is private + UdonVR_GUI.Label(content, UdonVR_Predefined.Color.Style_DefaultTextColor); + GUILayout.FlexibleSpace(); + if (apiWorld.IsCommunityLabsWorld) UdonVR_GUI.DrawImage(gui_icon_communityLabs, 16, 16); // draw community labs logo if world is community labs + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + #endregion + + #region World Tags + EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + UdonVR_GUI.Header(new GUIContent("Tags: ")); + + // display tags in array + for (int i = 0; i < worldTags.Length; i++) + { + EditorGUILayout.BeginHorizontal(); + GUI.enabled = isUserWorldAuthor; // enable / disable editing if user is not world owner + worldTags[i] = EditorGUILayout.TextArea(worldTags[i], UdonVR_Style.SetWidthHeight(-1, 18 * 1, GUI.skin.textField)); // show input field for this index in the array + if (UdonVR_GUI.TintedButton(new GUIContent("X"), UdonVR_GUIOption.WidthHeight(18, 18), UdonVR_GUIOption.TintColorDefault(Color.red))) + { + string removeKeyword = "__REMOVE_ME_THX_U__"; + worldTags[i] = removeKeyword; + worldTags = worldTags.Remove(removeKeyword); + //worldTags = UdonVR_Functions.ArrayRemoveItem(worldTags, removeKeyword); + } + GUI.enabled = true; + EditorGUILayout.EndHorizontal(); + } + + GUI.enabled = (worldTags.Length < MAX_USER_TAGS_FOR_WORLD && isUserWorldAuthor) ? true : false; + if (GUILayout.Button(new GUIContent("Add"), GUI.skin.button)) + { + worldTags = worldTags.Add("New Tag"); + //worldTags = (string[])UdonVR_Functions.ArrayAddItem(worldTags, "New Tag"); + } + GUI.enabled = true; + EditorGUILayout.EndVertical(); + #endregion + + #region Upload Changes + GUI.enabled = (isUserWorldAuthor && !isUploading) ? true : false; // enable / disable editing if user is not world owner + GUIContent saveChangesButtonContent = GUIContent.none; + saveChangesButtonContent = new GUIContent("Save Changes", isUserWorldAuthor ? "Upload changes to VRChat Servers." : "Not World Author cannot Upload changes."); + saveChangesButtonContent = isUploading ? new GUIContent("Uploading Changes...", "Uploading changes to VRChat Servers.") : saveChangesButtonContent; + if (UdonVR_GUI.TintedButton(saveChangesButtonContent, UdonVR_GUIOption.Height(18 * 2), UdonVR_GUIOption.TintColorDefault(isUserWorldAuthor ? Color.green : Color.red))) + { + switch (EditorUtility.DisplayDialogComplex( + windowTitleContent.text, + "Are you sure you want to save your changes?", + "Im Sure.", + "Not Yet.", + "Don't Save Image.")) + { + case 0: + Debug.Log("Uploading with image."); + SaveWorldChanges(true); + break; + case 1: + //Debug.Log("Not Uploading Yet."); + break; + case 2: + Debug.Log("Uploading with out Image."); + SaveWorldChanges(false); + break; + } + } + GUI.enabled = true; + #endregion + + EditorGUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.EndVertical(); + EditorGUILayout.EndVertical(); // group two end + #endregion + + //---------------- + + #region End inspector width modes + if (inspectorWidth >= 350 && !(inspectorWidth <= 550)) + { + EditorGUILayout.EndHorizontal(); + /* + // should be used but update needs to be pushed so disabled until bugs can be fixed and it resizes proporly + if (Event.current.type == EventType.Repaint) + { + areaRect = GUILayoutUtility.GetLastRect(); + UdonVR_VariableStorage.Instance.GetVariableGroup().SetVariable(guid, areaRect); + } + */ + } + else + { + EditorGUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + } + #endregion + + UdonVR_GUI.EndDynamicScrollViewHeight(ref scrollContentRect); // end scroll view + + // add a line to show where the end of the scroll view ends and where the area for the links starts + GUILayout.Box(GUIContent.none, UdonVR_Style.SetWidthHeight(-1, 1, dividerStyle, new RectOffset(0, 0, 0, 0))); + #endregion + + #region UdonVR Links + bool forceBottom = false; + if (!forceBottom) EditorGUILayout.BeginVertical(showContainers ? UdonVR_Style.Get(new Vector4(0.5f, 0.25f, 0.125f, 1)) : new GUIStyle()); + if (!forceBottom) GUILayout.FlexibleSpace(); + UdonVR_GUI.ShowUdonVRLinks(32, 32, forceBottom, null, showContainers); + if (!forceBottom) GUILayout.FlexibleSpace(); + if (!forceBottom) EditorGUILayout.EndVertical(); + #endregion + } + + #region Functions + private void Initalize() + { + udonVRLogo = (Texture2D)Resources.Load("_UdonVR/Logos/udonVR_256_196"); + displayedTexture = udonVRLogo; // set the default displayed texture to the udonVR logo + + gui_icon_plus = (Texture2D)Resources.Load("_UdonVR/Icons/gui_icon_plus"); + gui_icon_minus = (Texture2D)Resources.Load("_UdonVR/Icons/gui_icon_minus"); + + gui_icon_unlocked = (Texture2D)Resources.Load("_UdonVR/Icons/gui_icon_unlocked"); + gui_icon_locked = (Texture2D)Resources.Load("_UdonVR/Icons/gui_icon_locked"); + gui_icon_communityLabs = (Texture2D)Resources.Load("CL_Icons/CL_Lab_Icon_256"); + + API.SetOnlineMode(true); // force online mode + if (!APIUser.IsLoggedIn) + { + if (ApiCredentials.Load()) + APIUser.InitialFetchCurrentUser((c) => UserLoggedInCallback(c.Model as APIUser), (c) => ThrowError(c.Error)); + else + ThrowError("No Credentials."); + } + else + { + UserLoggedInCallback(APIUser.CurrentUser); + } + + isInitalized = true; + } + + private void ThrowError(string error) + { + Debug.LogError("Error: " + error); + } + + private void UserLoggedInCallback(APIUser user) + { + apiUser = user; + Debug.Log("UserLoggedInCallback"); + ApiWorld model = new ApiWorld + { + id = blueprintID + }; + model.Fetch(null, + (c) => + { + //VRC.Core.Logger.Log("Updating an existing world.", DebugLevel.All); + apiWorld = c.Model as ApiWorld; + ImageDownloader.DownloadImage(apiWorld.imageUrl, 0, + obj => + { + worldTexture = obj; + displayedTexture = worldTexture; + displayedTexturePath = AssetDatabase.GetAssetPath(displayedTexture); + Repaint(); + }, + () => + { + worldTexture = null; + displayedTexture = udonVRLogo; + Repaint(); + }); + + GetWorldFileSize(); // get the worlds filesize in bytes + + worldName = apiWorld.name; + worldDescription = apiWorld.description; + worldCapacity = apiWorld.capacity; + worldTags = apiWorld.tags.Where(s => s.StartsWith("author_tag_")).ToArray(); + + for (int i = 0; i < worldTags.Length; i++) + { + apiWorld.tags.Remove(worldTags[i]); // required to remove the tags so if we remove them on our side they actually get removed + worldTags[i] = worldTags[i].Replace("author_tag_", ""); + } + showBlueprintIDFoldoutMenu = false; // hide the ID foldout menu because we have a world to edit + //Debug.Log("World record fetched."); + }, + (c) => + { + showBlueprintIDFoldoutMenu = true; // show the ID foldout menu because the ID we tried failed + Debug.Log("World record not found."); + }); + } + + private void SaveWorldChanges(bool saveImage) + { + isUploading = true; + + string[] delimiterStrings = { " ", ",", ".", ":", "\t", "\n", "\"", "#" }; + + apiWorld.name = worldName; + if (string.IsNullOrEmpty(worldDescription.Trim())) worldDescription = worldName; + apiWorld.description = worldDescription; + apiWorld.capacity = worldCapacity; + for (int i = 0; i < worldTags.Length; i++) + { + string tag = worldTags[i]; + + // stop if tag contains more characters than the max number + if (tag.Length > MAX_CHARACTERS_ALLOWED_IN_USER_TAG) + { + EditorUtility.DisplayDialog( + windowTitleContent.text, + "One or more of your tags exceeds the maximum " + MAX_CHARACTERS_ALLOWED_IN_USER_TAG + " character limit.\n\n" + "Please shorten tags before uploading.", + "OK"); + isUploading = false; + return; + } + + // stop if tag contains a character that is not a letter or digit + if (!tag.All(char.IsLetterOrDigit)) + { + EditorUtility.DisplayDialog(windowTitleContent.text, + "Please remove any non-alphanumeric characters from tags before uploading.", + "OK"); + isUploading = false; + return; + } + + // replace seperator characters in tags + foreach (string s in delimiterStrings) + { + tag = tag.Replace(s, ""); + } + + Debug.Log("Tag: " + tag); + + // add tag + apiWorld.tags.Add("author_tag_" + tag); + } + + if (!saveImage) + { + SaveAPIWorld(); // save world information + } + else + { + string filePath = AssetDatabase.GetAssetPath(displayedTexture); + if (string.IsNullOrEmpty(filePath.Trim())) filePath = displayedTexturePath; + if (!string.IsNullOrEmpty(filePath.Trim())) + { + if (!Path.IsPathRooted(filePath)) filePath = Path.Combine(Directory.GetCurrentDirectory(), filePath); + filePath = filePath.Replace("/", "\\"); + } + + // check to see if the filepath is null and if it is abort + if (string.IsNullOrEmpty(filePath.Trim())) + { + isUploading = false; + Debug.LogWarning("Image file path was null. Skipping Image Upload. \n" + + "This error can be caused if we tried to upload the displayed image but the displayed image was never changed from the downloaded world image."); + SaveAPIWorld(); + return; + } + + EditorCoroutine.Start(uVRImageUpload.DoUpdateImage(apiWorld.imageUrl, GetFriendlyWorldFileName(), filePath, delegate (string fileUrl) + { + if (!string.IsNullOrEmpty(fileUrl.Trim())) + { + apiWorld.imageUrl = fileUrl; + Debug.Log("Image Uploaded"); + } + else + { + Debug.Log("Image URL is NULL"); + } + + SaveAPIWorld(); // save world information + })); + } + } + + private void SaveAPIWorld() + { + ApiWorld saveWorld = new ApiWorld + { + id = apiWorld.id, + authorId = apiWorld.authorId, + name = apiWorld.name, + description = apiWorld.description, + capacity = apiWorld.capacity, + tags = apiWorld.tags, + imageUrl = apiWorld.imageUrl, + }; + saveWorld.Save( + (c) => + { + ApiWorld savedBP = (ApiWorld)c.Model; + if (EditorUtility.DisplayDialog( + windowTitleContent.text, + "Changes have been saved.", + "OK")) + { + Initalize(); // reinitalize to refresh and load the changes + } + }, + (c) => + { + Debug.LogError(c.Error); + Initalize(); // reinitalize to refresh and load the changes + }); + isUploading = false; + } + + private string GetFriendlyWorldFileName(string type = "Image") + { + return "World - " + apiWorld.name + " - " + type + " - " + Application.unityVersion + "_" + ApiWorld.VERSION.ApiVersion + + "_" + VRC.Tools.Platform + "_" + API.GetServerEnvironmentForApiUrl(); + } + + private void GetWorldFileSize() + { + ApiFile _file = new ApiFile(ApiFile.ParseFileIdFromFileAPIUrl(apiWorld.assetUrl)); + worldFileSize = -1; // reset filesize before grabbing filesize + _file.Fetch( + (c) => + { + ApiFile fetchedFile = c.Model as ApiFile; + worldFileSize = fetchedFile.GetLatestVersion().file.sizeInBytes; + }); + } + #endregion +} diff --git a/Tools/Editor/UdonVRinfo.cs b/Tools/Editor/UdonVRinfo.cs new file mode 100644 index 0000000..f722171 --- /dev/null +++ b/Tools/Editor/UdonVRinfo.cs @@ -0,0 +1,82 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor.Animations; +using System.Linq; +using UnityEditor; +using System.IO; +using UnityEngine.Networking; +using UnityEngine.UI; +using static UdonVR.EditorUtility.EditorHelper; +using System.Text; +using System; + +using UdonVR.EditorUtility; + +public class UdonVRInfo : EditorWindow +{ + private StreamReader VersionFile; + + public string newPath = " "; + private GUILayoutOption[] space = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16) }; + private GUILayoutOption[] noExpandWidth = new GUILayoutOption[] { GUILayout.Height(16), GUILayout.MinHeight(16), GUILayout.MaxHeight(16), GUILayout.ExpandWidth(false) }; + private GUIStyle style; + public GUIStyle logoStyle; + private string _Version = "null"; + + [MenuItem("UdonVR/About UdonVR", false, 5000)] + public static void ShowWindow() + { + UdonVRInfo window = GetWindow("About UdonVR"); + window.minSize = new Vector2(385, 250); + } + private void GUIWarning(string text) + { + + EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.warnicon")), style); + + } + private void GUIError(string text) + { + EditorGUILayout.LabelField(new GUIContent(text, EditorGUIUtility.FindTexture("d_console.erroricon")), style); + + } + private void InitGuiStyles() + { + style = new GUIStyle(EditorStyles.helpBox); + style.richText = true; + style.fontSize = 15; + + logoStyle = new GUIStyle("flow node hex 0") + { + fontSize = 15, + richText = true, + alignment = TextAnchor.MiddleCenter, + hover = ((GUIStyle)"flow node hex 1").normal, + active = ((GUIStyle)"flow node hex 1 on").normal + }; + + logoStyle.padding.top = 17; + logoStyle.normal.textColor = Color.cyan; + } + private void OnGUI() + { + if (style == null) + InitGuiStyles(); + if (_Version == "null") + CheckUpdate(); + EditorGUILayout.LabelField("UdonVR Patreon Tools.\nThese are a collection of tools that are trying to streamline the creation of VRChat worlds.", style); + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Version: " + _Version, style); + EditorGUILayout.Space(); + EditorGUILayout.LabelField("If you didnt get these tools through our Patreon please consider supporting us.", style); + + UdonVR_GUI.ShowUdonVRLinks(32, 32, true); + } + private void CheckUpdate() + { + VersionFile = new StreamReader(GetfileDirectory() + "/Version.txt", Encoding.Default); + _Version = VersionFile.ReadToEnd(); + VersionFile.Close(); + } +} diff --git a/Tools/Editor/Version.txt b/Tools/Editor/Version.txt new file mode 100644 index 0000000..be4cea5 --- /dev/null +++ b/Tools/Editor/Version.txt @@ -0,0 +1 @@ +7.2 \ No newline at end of file diff --git a/Tools/Editor/api/SDK.cs b/Tools/Editor/api/SDK.cs new file mode 100644 index 0000000..578e119 --- /dev/null +++ b/Tools/Editor/api/SDK.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class SDKapi +{ + // Start is called before the first frame update + public static void UploadImage(Texture2D _img) + { + + } + public static void UploadImage(Texture _img) + { + Texture2D _temp = Texture2D.CreateExternalTexture(_img.width,_img.height,TextureFormat.RGB24,false, false,_img.GetNativeTexturePtr()); + UploadImage(_temp); + } + public static void UploadTags(string _tags) + { + + } + public static void UploadTitle(string _title) + { + + } +} diff --git a/Tools/Editor/api/UVRImageUpload.cs b/Tools/Editor/api/UVRImageUpload.cs new file mode 100644 index 0000000..eb98587 --- /dev/null +++ b/Tools/Editor/api/UVRImageUpload.cs @@ -0,0 +1,2072 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using UnityEngine; +using UnityEngine.SceneManagement; +using VRC.Core; + +namespace UdonVR.Tools.ApiUpdate +{ + public class UVRImageUpload + { + #region RuntimeAPI + private Dictionary mRetryState = new Dictionary(); + //private string uploadTitle; + //private string uploadMessage; + //private float uploadProgress; + private bool cancelRequested; + //private bool isUploading; + + #endregion + #region ApiFileHelper + private readonly int kMultipartUploadChunkSize = 100 * 1024 * 1024; // 100 MB + private readonly int SERVER_PROCESSING_WAIT_TIMEOUT_CHUNK_SIZE = 50 * 1024 * 1024; + private readonly float SERVER_PROCESSING_WAIT_TIMEOUT_PER_CHUNK_SIZE = 120.0f; + private readonly float SERVER_PROCESSING_MAX_WAIT_TIMEOUT = 600.0f; + private readonly float SERVER_PROCESSING_INITIAL_RETRY_TIME = 2.0f; + private readonly float SERVER_PROCESSING_MAX_RETRY_TIME = 10.0f; + public delegate void OnFileOpSuccess(ApiFile apiFile, string message); + public delegate void OnFileOpError(ApiFile apiFile, string error); + public delegate void OnFileOpProgress(ApiFile apiFile, string status, string subStatus, float pct); + public delegate bool FileOpCancelQuery(ApiFile apiFile); + private bool EnableDeltaCompression; + private const float kPostWriteDelay = 0.75f; + #endregion + + public static UVRImageUpload mInstance = null; + public static UVRImageUpload Instance + { + get + { + CheckInstance(); + return mInstance; + } + } + + private static void CheckInstance() + { + if (mInstance == null) + mInstance = new UVRImageUpload(); + } + + public IEnumerator DoUpdateImage(string existingFileUrl, string friendlyFileName, string imagePath, Action onSuccess) + { + if (!string.IsNullOrEmpty(imagePath)) + { + yield return EditorCoroutine.Start(UploadImage(existingFileUrl, friendlyFileName, imagePath, onSuccess)); + } + else + { + Debug.Log("Image path was null"); + yield return null; + } + } + #region ApiFileHelper + public IEnumerator UploadFile(string filename, string existingFileId, string friendlyName, + OnFileOpSuccess onSuccess, OnFileOpError onError, OnFileOpProgress onProgress, FileOpCancelQuery cancelQuery) + { + VRC.Core.Logger.Log("UploadFile: filename: " + filename + ", file id: " + + (!string.IsNullOrEmpty(existingFileId) ? existingFileId : "") + ", name: " + friendlyName, DebugLevel.All); + + // init remote config + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + bool done = false; + ConfigManager.RemoteConfig.Init( + delegate () { done = true; }, + delegate () { done = true; } + ); + + while (!done) + yield return null; + + if (!ConfigManager.RemoteConfig.IsInitialized()) + { + Error(onError, null, "Failed to fetch configuration."); + yield break; + } + } + + // configure delta compression + { + EnableDeltaCompression = ConfigManager.RemoteConfig.GetBool("sdkEnableDeltaCompression", false); + } + + // validate input file + Progress(onProgress, null, "Checking file..."); + + if (string.IsNullOrEmpty(filename)) + { + Error(onError, null, "Upload filename is empty!"); + yield break; + } + + if (!Path.HasExtension(filename)) + { + Error(onError, null, "Upload filename must have an extension: " + filename); + yield break; + } + + string whyNot; + if (!VRC.Tools.FileCanRead(filename, out whyNot)) + { + Error(onError, null, "Could not read file to upload!", filename + "\n" + whyNot); + yield break; + } + + // get or create ApiFile + Progress(onProgress, null, string.IsNullOrEmpty(existingFileId) ? "Creating file record..." : "Getting file record..."); + + bool wait = true; + bool wasError = false; + bool worthRetry = false; + string errorStr = ""; + + if (string.IsNullOrEmpty(friendlyName)) + friendlyName = filename; + + string extension = Path.GetExtension(filename); + string mimeType = ApiFileHelper.GetMimeTypeFromExtension(extension.ToLower()); + + ApiFile apiFile = null; + + Action fileSuccess = (c) => + { + apiFile = c.Model as ApiFile; + wait = false; + }; + + Action fileFailure = (c) => + { + errorStr = c.Error; + wait = false; + + if (c.Code == 400) + worthRetry = true; + }; + + while (true) + { + apiFile = null; + wait = true; + worthRetry = false; + errorStr = ""; + + if (string.IsNullOrEmpty(existingFileId)) + ApiFile.Create(friendlyName, mimeType, extension, fileSuccess, fileFailure); + else + API.Fetch(existingFileId, fileSuccess, fileFailure); + + while (wait) + { + if (apiFile != null && CheckCancelled(cancelQuery, onError, apiFile)) + yield break; + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (errorStr.Contains("File not found")) + { + Debug.LogError("Couldn't find file record: " + existingFileId + ", creating new file record"); + + existingFileId = ""; + continue; + } + + string msg = string.IsNullOrEmpty(existingFileId) ? "Failed to create file record." : "Failed to get file record."; + Error(onError, null, msg, errorStr); + + if (!worthRetry) + yield break; + } + + if (!worthRetry) + break; + else + yield return new WaitForSecondsRealtime(kPostWriteDelay); + } + + if (apiFile == null) + yield break; + + LogApiFileStatus(apiFile, false, true); + + while (apiFile.HasQueuedOperation(EnableDeltaCompression)) + { + wait = true; + + apiFile.DeleteLatestVersion((c) => wait = false, (c) => wait = false); + + while (wait) + { + if (apiFile != null && CheckCancelled(cancelQuery, onError, apiFile)) + yield break; + + yield return null; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + LogApiFileStatus(apiFile, false); + + // check for server side errors from last upload + if (apiFile.IsInErrorState()) + { + Debug.LogWarning("ApiFile: " + apiFile.id + ": server failed to process last uploaded, deleting failed version"); + + while (true) + { + // delete previous failed version + Progress(onProgress, apiFile, "Preparing file for upload...", "Cleaning up previous version"); + + wait = true; + errorStr = ""; + worthRetry = false; + + apiFile.DeleteLatestVersion(fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, null)) + { + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to delete previous failed version!", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + if (worthRetry) + yield return new WaitForSecondsRealtime(kPostWriteDelay); + else + break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + LogApiFileStatus(apiFile, false); + + // verify previous file op is complete + if (apiFile.HasQueuedOperation(EnableDeltaCompression)) + { + Error(onError, apiFile, "A previous upload is still being processed. Please try again later."); + yield break; + } + + if (wasError) + yield break; + + LogApiFileStatus(apiFile, false); + + // generate md5 and check if file has changed + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating file hash"); + + string fileMD5Base64 = ""; + wait = true; + errorStr = ""; + VRC.Tools.FileMD5(filename, + delegate (byte[] md5Bytes) + { + fileMD5Base64 = Convert.ToBase64String(md5Bytes); + wait = false; + }, + delegate (string error) + { + errorStr = filename + "\n" + error; + wait = false; + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to generate MD5 hash for upload file.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + LogApiFileStatus(apiFile, false); + + // check if file has been changed + Progress(onProgress, apiFile, "Preparing file for upload...", "Checking for changes"); + + bool isPreviousUploadRetry = false; + if (apiFile.HasExistingOrPendingVersion()) + { + // uploading the same file? + if (string.Compare(fileMD5Base64, apiFile.GetFileMD5(apiFile.GetLatestVersionNumber())) == 0) + { + // the previous operation completed successfully? + if (!apiFile.IsWaitingForUpload()) + { + Success(onSuccess, apiFile, "The file to upload is unchanged."); + CleanupTempFiles(apiFile.id); + yield break; + } + else + { + isPreviousUploadRetry = true; + + Debug.Log("Retrying previous upload"); + } + } + else + { + // the file has been modified + if (apiFile.IsWaitingForUpload()) + { + // previous upload failed, and the file is changed + while (true) + { + // delete previous failed version + Progress(onProgress, apiFile, "Preparing file for upload...", "Cleaning up previous version"); + + wait = true; + worthRetry = false; + errorStr = ""; + + apiFile.DeleteLatestVersion(fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to delete previous incomplete version!", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + } + } + } + + LogApiFileStatus(apiFile, false); + + // generate signature for new file + + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating signature"); + + string signatureFilename = VRC.Tools.GetTempFileName(".sig", out errorStr, apiFile.id); + if (string.IsNullOrEmpty(signatureFilename)) + { + Error(onError, apiFile, "Failed to generate file signature!", "Failed to create temp file: \n" + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + wasError = false; + //Debug.Log("") + bool waitSigFile = true; + yield return EditorCoroutine.Start(CreateFileSignatureInternal(filename, signatureFilename, + delegate () + { + waitSigFile = false; + // success! + //if (File.Exists(signatureFilename)) + //{ + // Debug.Log("[0A]Found " + signatureFilename); + //} + //else + // Debug.Log("[0A]Lost " + signatureFilename); + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to generate file signature!", error); + CleanupTempFiles(apiFile.id); + wasError = true; + waitSigFile = false; + }) + ); + //Debug.Log("[0B]WaitSigFile " + signatureFilename); + while (waitSigFile) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (wasError) + yield break; + + LogApiFileStatus(apiFile, false); + + // generate signature md5 and file size + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating signature hash"); + Debug.Log("Generating signature hash"); + //if (File.Exists(signatureFilename)) + //{ + // Debug.Log("[1]Found " + signatureFilename); + //} + //else + // Debug.Log("[1]Lost " + signatureFilename); + string sigMD5Base64 = ""; + wait = true; + errorStr = ""; + FileMD5(signatureFilename, + delegate (byte[] md5Bytes) + { + sigMD5Base64 = Convert.ToBase64String(md5Bytes); + wait = false; + //Debug.Log("[2] Hash Success"); + }, + delegate (string error) + { + errorStr = signatureFilename + "\n" + error; + wait = false; + //Debug.Log("[2] Hash Error"); + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + //Debug.Log("[3] ErrorCheck ERROR"); + Error(onError, apiFile, "Failed to generate MD5 hash for signature file.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + //else { Debug.Log("[3] ErrorCheck GOOD"); } + + long sigFileSize = 0; + if (!VRC.Tools.GetFileSize(signatureFilename, out sigFileSize, out errorStr)) + { + Error(onError, apiFile, "Failed to generate file signature!", "Couldn't get file size:\n" + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + LogApiFileStatus(apiFile, false); + + // download previous version signature (if exists) + string existingFileSignaturePath = null; + if (EnableDeltaCompression && apiFile.HasExistingVersion()) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Downloading previous version signature"); + + wait = true; + errorStr = ""; + apiFile.DownloadSignature( + delegate (byte[] data) + { + // save to temp file + existingFileSignaturePath = VRC.Tools.GetTempFileName(".sig", out errorStr, apiFile.id); + if (string.IsNullOrEmpty(existingFileSignaturePath)) + { + errorStr = "Failed to create temp file: \n" + errorStr; + wait = false; + } + else + { + try + { + File.WriteAllBytes(existingFileSignaturePath, data); + } + catch (Exception e) + { + existingFileSignaturePath = null; + errorStr = "Failed to write signature temp file:\n" + e.Message; + } + wait = false; + } + }, + delegate (string error) + { + errorStr = error; + wait = false; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Downloading previous version signature", VRC.Tools.DivideSafe(downloaded, length)); + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to download previous file version signature.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + } + + LogApiFileStatus(apiFile, false); + + // create delta if needed + string deltaFilename = null; + + if (EnableDeltaCompression && !string.IsNullOrEmpty(existingFileSignaturePath)) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Creating file delta"); + + deltaFilename = VRC.Tools.GetTempFileName(".delta", out errorStr, apiFile.id); + if (string.IsNullOrEmpty(deltaFilename)) + { + Error(onError, apiFile, "Failed to create file delta for upload.", "Failed to create temp file: \n" + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + wasError = false; + yield return EditorCoroutine.Start(CreateFileDeltaInternal(filename, existingFileSignaturePath, deltaFilename, + delegate () + { + // success! + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to create file delta for upload.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + }) + ); + + if (wasError) + yield break; + } + + // upload smaller of delta and new file + long fullFileSize = 0; + long deltaFileSize = 0; + if (!VRC.Tools.GetFileSize(filename, out fullFileSize, out errorStr) || + !string.IsNullOrEmpty(deltaFilename) && !VRC.Tools.GetFileSize(deltaFilename, out deltaFileSize, out errorStr)) + { + Error(onError, apiFile, "Failed to create file delta for upload.", "Couldn't get file size: " + errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + + bool uploadDeltaFile = EnableDeltaCompression && deltaFileSize > 0 && deltaFileSize < fullFileSize; + if (EnableDeltaCompression) + VRC.Core.Logger.Log("Delta size " + deltaFileSize + " (" + deltaFileSize / (float)fullFileSize + " %), full file size " + fullFileSize + ", uploading " + (uploadDeltaFile ? " DELTA" : " FULL FILE"), DebugLevel.All); + else + VRC.Core.Logger.Log("Delta compression disabled, uploading FULL FILE, size " + fullFileSize, DebugLevel.All); + + LogApiFileStatus(apiFile, uploadDeltaFile); + + string deltaMD5Base64 = ""; + if (uploadDeltaFile) + { + Progress(onProgress, apiFile, "Preparing file for upload...", "Generating file delta hash"); + + wait = true; + errorStr = ""; + VRC.Tools.FileMD5(deltaFilename, + delegate (byte[] md5Bytes) + { + deltaMD5Base64 = Convert.ToBase64String(md5Bytes); + wait = false; + }, + delegate (string error) + { + errorStr = error; + wait = false; + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to generate file delta hash.", errorStr); + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // validate existing pending version info, if this is a retry + bool versionAlreadyExists = false; + + LogApiFileStatus(apiFile, uploadDeltaFile); + + if (isPreviousUploadRetry) + { + bool isValid = true; + + ApiFile.Version v = apiFile.GetVersion(apiFile.GetLatestVersionNumber()); + if (v != null) + { + if (uploadDeltaFile) + { + isValid = deltaFileSize == v.delta.sizeInBytes && + deltaMD5Base64.CompareTo(v.delta.md5) == 0 && + sigFileSize == v.signature.sizeInBytes && + sigMD5Base64.CompareTo(v.signature.md5) == 0; + } + else + { + isValid = fullFileSize == v.file.sizeInBytes && + fileMD5Base64.CompareTo(v.file.md5) == 0 && + sigFileSize == v.signature.sizeInBytes && + sigMD5Base64.CompareTo(v.signature.md5) == 0; + } + } + else + { + isValid = false; + } + + if (isValid) + { + versionAlreadyExists = true; + + Debug.Log("Using existing version record"); + } + else + { + // delete previous invalid version + Progress(onProgress, apiFile, "Preparing file for upload...", "Cleaning up previous version"); + + while (true) + { + wait = true; + errorStr = ""; + worthRetry = false; + + apiFile.DeleteLatestVersion(fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, null)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to delete previous incomplete version!", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + } + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // create new version of file + if (!versionAlreadyExists) + { + while (true) + { + Progress(onProgress, apiFile, "Creating file version record..."); + + wait = true; + errorStr = ""; + worthRetry = false; + + if (uploadDeltaFile) + // delta file + apiFile.CreateNewVersion(ApiFile.Version.FileType.Delta, deltaMD5Base64, deltaFileSize, sigMD5Base64, sigFileSize, fileSuccess, fileFailure); + else + // full file + apiFile.CreateNewVersion(ApiFile.Version.FileType.Full, fileMD5Base64, fullFileSize, sigMD5Base64, sigFileSize, fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Failed to create file version record.", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + } + + // upload components + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // upload delta + if (uploadDeltaFile) + { + //Debug.Log("uploadDeltaFile True"); + if (apiFile.GetLatestVersion().delta.status == ApiFile.Status.Waiting) + { + Progress(onProgress, apiFile, "Uploading file delta..."); + + wasError = false; + yield return EditorCoroutine.Start(UploadFileComponentInternal(apiFile, + ApiFile.Version.FileDescriptor.Type.delta, deltaFilename, deltaMD5Base64, deltaFileSize, + delegate (ApiFile file) + { + //Debug.Log("Successfully uploaded file delta."); + apiFile = file; + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to upload file delta.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Uploading file delta...", "", VRC.Tools.DivideSafe(downloaded, length)); + }, + cancelQuery) + ); + + if (wasError) + yield break; + } + } + // upload file + else + { + //Debug.Log("uploadDeltaFile Else"); + Debug.Log("File Status " + apiFile.GetLatestVersion().file.status.ToString()); + + if (apiFile.GetLatestVersion().file.status == ApiFile.Status.Waiting) + { + Progress(onProgress, apiFile, "Uploading file..."); + bool waitFileUp = true; + wasError = false; + + yield return EditorCoroutine.Start(UploadFileComponentInternal(apiFile, + ApiFile.Version.FileDescriptor.Type.file, filename, fileMD5Base64, fullFileSize, + delegate (ApiFile file) + { + VRC.Core.Logger.Log("Successfully uploaded file.", DebugLevel.All); + apiFile = file; + waitFileUp = false; + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to upload file.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + waitFileUp = false; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Uploading file...", "", VRC.Tools.DivideSafe(downloaded, length)); + }, + cancelQuery) + ); + + while (waitFileUp) + { + // omitted to debug log filespam + //Debug.Log("File Status " + apiFile.GetLatestVersion().file.status.ToString()); + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (wasError) + yield break; + } + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // upload signature + Debug.Log("signature Status " + apiFile.GetLatestVersion().file.status.ToString()); + if (apiFile.GetLatestVersion().signature.status == ApiFile.Status.Waiting) + { + Progress(onProgress, apiFile, "Uploading file signature..."); + + wasError = false; + bool waitSigUp = true; + yield return EditorCoroutine.Start(UploadFileComponentInternal(apiFile, + ApiFile.Version.FileDescriptor.Type.signature, signatureFilename, sigMD5Base64, sigFileSize, + delegate (ApiFile file) + { + Debug.Log("Successfully uploaded file signature."); + apiFile = file; + waitSigUp = false; + }, + delegate (string error) + { + Error(onError, apiFile, "Failed to upload file signature.", error); + CleanupTempFiles(apiFile.id); + wasError = true; + waitSigUp = false; + }, + delegate (long downloaded, long length) + { + Progress(onProgress, apiFile, "Uploading file signature...", "", VRC.Tools.DivideSafe(downloaded, length)); + }, + cancelQuery) + ); + + while (waitSigUp) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + yield return null; + } + + if (wasError) + yield break; + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // Validate file records queued or complete + Progress(onProgress, apiFile, "Validating upload..."); + + bool isUploadComplete = uploadDeltaFile + ? apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.delta).status == ApiFile.Status.Complete + : apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.file).status == ApiFile.Status.Complete; + isUploadComplete = isUploadComplete && + apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.signature).status == ApiFile.Status.Complete; + + if (!isUploadComplete) + { + Error(onError, apiFile, "Failed to upload file.", "Record status is not 'complete'"); + CleanupTempFiles(apiFile.id); + yield break; + } + + bool isServerOpQueuedOrComplete = uploadDeltaFile + ? apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.file).status != ApiFile.Status.Waiting + : apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), ApiFile.Version.FileDescriptor.Type.delta).status != ApiFile.Status.Waiting; + + if (!isServerOpQueuedOrComplete) + { + Error(onError, apiFile, "Failed to upload file.", "Record is still in 'waiting' status"); + CleanupTempFiles(apiFile.id); + yield break; + } + + LogApiFileStatus(apiFile, uploadDeltaFile); + + // wait for server processing to complete + Progress(onProgress, apiFile, "Processing upload..."); + float checkDelay = SERVER_PROCESSING_INITIAL_RETRY_TIME; + float maxDelay = SERVER_PROCESSING_MAX_RETRY_TIME; + float timeout = GetServerProcessingWaitTimeoutForDataSize(apiFile.GetLatestVersion().file.sizeInBytes); + double initialStartTime = Time.realtimeSinceStartup; + double startTime = initialStartTime; + while (apiFile.HasQueuedOperation(uploadDeltaFile)) + { + // wait before polling again + Progress(onProgress, apiFile, "Processing upload...", "Checking status in " + Mathf.CeilToInt(checkDelay) + " seconds"); + + while (Time.realtimeSinceStartup - startTime < checkDelay) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + + if (Time.realtimeSinceStartup - initialStartTime > timeout) + { + LogApiFileStatus(apiFile, uploadDeltaFile); + + Error(onError, apiFile, "Timed out waiting for upload processing to complete."); + CleanupTempFiles(apiFile.id); + yield break; + } + + yield return null; + } + + while (true) + { + // check status + Progress(onProgress, apiFile, "Processing upload...", "Checking status..."); + + wait = true; + worthRetry = false; + errorStr = ""; + API.Fetch(apiFile.id, fileSuccess, fileFailure); + + while (wait) + { + if (CheckCancelled(cancelQuery, onError, apiFile)) + { + CleanupTempFiles(apiFile.id); + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + Error(onError, apiFile, "Checking upload status failed.", errorStr); + if (!worthRetry) + { + CleanupTempFiles(apiFile.id); + yield break; + } + } + + if (!worthRetry) + break; + } + + checkDelay = Mathf.Min(checkDelay * 2, maxDelay); + startTime = Time.realtimeSinceStartup; + } + + // cleanup and wait for it to finish + yield return EditorCoroutine.Start(CleanupTempFilesInternal(apiFile.id)); + + Success(onSuccess, apiFile, "Upload complete!"); + } + + + protected static void Error(OnFileOpError onError, ApiFile apiFile, string error, string moreInfo = "") + { + if (apiFile == null) + apiFile = new ApiFile(); + + Debug.LogError("ApiFile " + apiFile.ToStringBrief() + ": Error: " + error + "\n" + moreInfo); + if (onError != null) + onError(apiFile, error); + } + protected static void Progress(OnFileOpProgress onProgress, ApiFile apiFile, string status, string subStatus = "", float pct = 0.0f) + { + if (apiFile == null) + apiFile = new ApiFile(); + + if (onProgress != null) + onProgress(apiFile, status, subStatus, pct); + } + + protected static bool CheckCancelled(FileOpCancelQuery cancelQuery, OnFileOpError onError, ApiFile apiFile) + { + if (apiFile == null) + { + Debug.LogError("apiFile was null"); + return true; + } + + if (cancelQuery != null && cancelQuery(apiFile)) + { + Debug.Log("ApiFile " + apiFile.ToStringBrief() + ": Operation cancelled"); + if (onError != null) + onError(apiFile, "Cancelled by user."); + return true; + } + + return false; + } + private static void LogApiFileStatus(ApiFile apiFile, bool checkDelta, bool logSuccess = false) + { + if (apiFile == null || !apiFile.IsInitialized) + { + Debug.LogFormat("apiFile not initialized"); + } + else if (apiFile.IsInErrorState()) + { + Debug.LogFormat("ApiFile {0} is in an error state.", apiFile.name); + } + else if (logSuccess) + VRC.Core.Logger.Log("< color = yellow > Processing { 3}: { 0}, { 1}, { 2} " + + (apiFile.IsWaitingForUpload() ? "waiting for upload" : "upload complete") + + (apiFile.HasExistingOrPendingVersion() ? "has existing or pending version" : "no previous version") + + (apiFile.IsLatestVersionQueued(checkDelta) ? "latest version queued" : "latest version not queued") + + apiFile.name, DebugLevel.All); + + if (apiFile != null && apiFile.IsInitialized && logSuccess) + { + var apiFields = apiFile.ExtractApiFields(); + if (apiFields != null) + VRC.Core.Logger.Log("{0}" + VRC.Tools.JsonEncode(apiFields), DebugLevel.All); + } + } + void CleanupTempFiles(string subFolderName) + { + EditorCoroutine.Start(CleanupTempFilesInternal(subFolderName)); + } + + protected IEnumerator CleanupTempFilesInternal(string subFolderName) + { + if (!string.IsNullOrEmpty(subFolderName)) + { + string folder = VRC.Tools.GetTempFolderPath(subFolderName); + + while (Directory.Exists(folder)) + { + try + { + if (Directory.Exists(folder)) + Directory.Delete(folder, true); + } + catch (Exception) + { + } + + yield return null; + } + } + } + protected static void Success(OnFileOpSuccess onSuccess, ApiFile apiFile, string message) + { + if (apiFile == null) + apiFile = new ApiFile(); + + VRC.Core.Logger.Log("ApiFile " + apiFile.ToStringBrief() + ": Operation Succeeded!", DebugLevel.All); + if (onSuccess != null) + onSuccess(apiFile, message); + } + public IEnumerator CreateFileSignatureInternal(string filename, string outputSignatureFilename, Action onSuccess, Action onError) + { + VRC.Core.Logger.Log("CreateFileSignature: " + filename + " => " + outputSignatureFilename, DebugLevel.Always); + + yield return null; + + Stream inStream = null; + FileStream outStream = null; + byte[] buf = new byte[64 * 1024]; + IAsyncResult asyncRead = null; + IAsyncResult asyncWrite = null; + + try + { + inStream = librsync.net.Librsync.ComputeSignature(File.OpenRead(filename)); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't open input file: " + e.Message); + yield break; + } + + try + { + outStream = File.Open(outputSignatureFilename, FileMode.Create, FileAccess.Write); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't create output file: " + e.Message); + yield break; + } + + while (true) + { + try + { + asyncRead = inStream.BeginRead(buf, 0, buf.Length, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + while (!asyncRead.IsCompleted) + yield return null; + + int read = 0; + try + { + read = inStream.EndRead(asyncRead); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + if (read <= 0) + break; + + try + { + asyncWrite = outStream.BeginWrite(buf, 0, read, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + + while (!asyncWrite.IsCompleted) + yield return null; + + try + { + outStream.EndWrite(asyncWrite); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + } + + inStream.Close(); + outStream.Close(); + + yield return null; + + if (onSuccess != null) + onSuccess(); + } + public IEnumerator CreateFileDeltaInternal(string newFilename, string existingFileSignaturePath, string outputDeltaFilename, Action onSuccess, Action onError) + { + Debug.Log("CreateFileDelta: " + newFilename + " (delta) " + existingFileSignaturePath + " => " + outputDeltaFilename); + + yield return null; + + Stream inStream = null; + FileStream outStream = null; + byte[] buf = new byte[64 * 1024]; + IAsyncResult asyncRead = null; + IAsyncResult asyncWrite = null; + + try + { + inStream = librsync.net.Librsync.ComputeDelta(File.OpenRead(existingFileSignaturePath), File.OpenRead(newFilename)); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't open input file: " + e.Message); + yield break; + } + + try + { + outStream = File.Open(outputDeltaFilename, FileMode.Create, FileAccess.Write); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't create output file: " + e.Message); + yield break; + } + + while (true) + { + try + { + asyncRead = inStream.BeginRead(buf, 0, buf.Length, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + while (!asyncRead.IsCompleted) + yield return null; + + int read = 0; + try + { + read = inStream.EndRead(asyncRead); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + if (read <= 0) + break; + + try + { + asyncWrite = outStream.BeginWrite(buf, 0, read, null, null); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + + while (!asyncWrite.IsCompleted) + yield return null; + + try + { + outStream.EndWrite(asyncWrite); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't write file: " + e.Message); + yield break; + } + } + + inStream.Close(); + outStream.Close(); + + yield return null; + + if (onSuccess != null) + onSuccess(); + } + private IEnumerator UploadFileComponentInternal(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + Action onSuccess, + Action onError, + Action onProgress, + FileOpCancelQuery cancelQuery) + { + VRC.Core.Logger.Log("UploadFileComponent: " + fileDescriptorType + " (" + apiFile.id + "): " + filename, DebugLevel.All); + ApiFile.Version.FileDescriptor fileDesc = apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), fileDescriptorType); + + if (!UploadFileComponentValidateFileDesc(apiFile, filename, md5Base64, fileSize, fileDesc, onSuccess, onError)) + yield break; + + switch (fileDesc.category) + { + case ApiFile.Category.Simple: + yield return EditorCoroutine.Start(UploadFileComponentDoSimpleUpload(apiFile, fileDescriptorType, filename, md5Base64, fileSize, onSuccess, onError, onProgress, cancelQuery)); + break; + case ApiFile.Category.Multipart: + yield return UploadFileComponentDoMultipartUpload(apiFile, fileDescriptorType, filename, md5Base64, fileSize, onSuccess, onError, onProgress, cancelQuery); + break; + default: + if (onError != null) + onError("Unknown file category type: " + fileDesc.category); + yield break; + } + + yield return EditorCoroutine.Start(UploadFileComponentVerifyRecord(apiFile, fileDescriptorType, filename, md5Base64, fileSize, fileDesc, onSuccess, onError, onProgress, cancelQuery)); + } + private IEnumerator UploadFileComponentDoSimpleUpload(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + Action onSuccess, + Action onError, + Action onProgress, + FileOpCancelQuery cancelQuery) + { + OnFileOpError onCancelFunc = delegate (ApiFile file, string s) + { + if (onError != null) + onError(s); + }; + + string uploadUrl = ""; + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.StartSimpleUpload(fileDescriptorType, + (c) => + { + uploadUrl = (string)(c as ApiDictContainer).ResponseDictionary["url"]; + wait = false; + }, + (c) => + { + errorStr = "Failed to start upload: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + // PUT file + { + bool wait = true; + string errorStr = ""; + + VRC.HttpRequest req = ApiFile.PutSimpleFileToURL(uploadUrl, filename, ApiFileHelper.GetMimeTypeFromExtension(Path.GetExtension(filename).ToLower()), md5Base64, true, + delegate () + { + wait = false; + }, + delegate (string error) + { + errorStr = "Failed to upload file: " + error; + wait = false; + }, + delegate (long uploaded, long length) + { + if (onProgress != null) + onProgress(uploaded, length); + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + if (req != null) + req.Abort(); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + yield break; + } + } + + // finish upload + while (true) + { + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.FinishUpload(fileDescriptorType, null, + (c) => + { + apiFile = c.Model as ApiFile; + wait = false; + }, + (c) => + { + errorStr = "Failed to finish upload: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = false; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + } + + private IEnumerator UploadFileComponentDoMultipartUpload(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + Action onSuccess, + Action onError, + Action onProgress, + FileOpCancelQuery cancelQuery) + { + FileStream fs = null; + OnFileOpError onCancelFunc = delegate (ApiFile file, string s) + { + if (fs != null) + fs.Close(); + if (onError != null) + onError(s); + }; + + // query multipart upload status. + // we might be resuming a previous upload + ApiFile.UploadStatus uploadStatus = null; + { + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.GetUploadStatus(apiFile.GetLatestVersionNumber(), fileDescriptorType, + (c) => + { + uploadStatus = c.Model as ApiFile.UploadStatus; + wait = false; + + VRC.Core.Logger.Log("Found existing multipart upload status (next part = " + uploadStatus.nextPartNumber + ")", DebugLevel.All); + }, + (c) => + { + errorStr = "Failed to query multipart upload status: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + if (!worthRetry) + break; + } + } + + // split file into chunks + try + { + fs = File.OpenRead(filename); + } + catch (Exception e) + { + if (onError != null) + onError("Couldn't open file: " + e.Message); + yield break; + } + + byte[] buffer = new byte[kMultipartUploadChunkSize * 2]; + + long totalBytesUploaded = 0; + List etags = new List(); + if (uploadStatus != null) + etags = uploadStatus.etags.ToList(); + + int numParts = Mathf.Max(1, Mathf.FloorToInt(fs.Length / (float)kMultipartUploadChunkSize)); + for (int partNumber = 1; partNumber <= numParts; partNumber++) + { + // read chunk + int bytesToRead = partNumber < numParts ? kMultipartUploadChunkSize : (int)(fs.Length - fs.Position); + int bytesRead = 0; + try + { + bytesRead = fs.Read(buffer, 0, bytesToRead); + } + catch (Exception e) + { + fs.Close(); + if (onError != null) + onError("Couldn't read file: " + e.Message); + yield break; + } + + if (bytesRead != bytesToRead) + { + fs.Close(); + if (onError != null) + onError("Couldn't read file: read incorrect number of bytes from stream"); + yield break; + } + + // check if this part has been upload already + // NOTE: uploadStatus.nextPartNumber == number of parts already uploaded + if (uploadStatus != null && partNumber <= uploadStatus.nextPartNumber) + { + totalBytesUploaded += bytesRead; + continue; + } + + // start upload + string uploadUrl = ""; + + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.StartMultipartUpload(fileDescriptorType, partNumber, + (c) => + { + uploadUrl = (string)(c as ApiDictContainer).ResponseDictionary["url"]; + wait = false; + }, + (c) => + { + errorStr = "Failed to start part upload: " + c.Error; + wait = false; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + fs.Close(); + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + // PUT file part + { + bool wait = true; + string errorStr = ""; + + VRC.HttpRequest req = ApiFile.PutMultipartDataToURL(uploadUrl, buffer, bytesRead, ApiFileHelper.GetMimeTypeFromExtension(Path.GetExtension(filename)), true, + delegate (string etag) + { + if (!string.IsNullOrEmpty(etag)) + etags.Add(etag); + totalBytesUploaded += bytesRead; + wait = false; + }, + delegate (string error) + { + errorStr = "Failed to upload data: " + error; + wait = false; + }, + delegate (long uploaded, long length) + { + if (onProgress != null) + onProgress(totalBytesUploaded + uploaded, fileSize); + } + ); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + if (req != null) + req.Abort(); + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + fs.Close(); + if (onError != null) + onError(errorStr); + yield break; + } + } + } + + // finish upload + while (true) + { + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.FinishUpload(fileDescriptorType, etags, + (c) => + { + apiFile = c.Model as ApiFile; + wait = false; + }, + (c) => + { + errorStr = "Failed to finish upload: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + fs.Close(); + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + // delay to let write get through servers + yield return new WaitForSecondsRealtime(kPostWriteDelay); + + if (!worthRetry) + break; + } + + fs.Close(); + } + + private IEnumerator UploadFileComponentVerifyRecord(ApiFile apiFile, + ApiFile.Version.FileDescriptor.Type fileDescriptorType, + string filename, + string md5Base64, + long fileSize, + ApiFile.Version.FileDescriptor fileDesc, + Action onSuccess, + Action onError, + Action onProgress, + FileOpCancelQuery cancelQuery) + { + OnFileOpError onCancelFunc = delegate (ApiFile file, string s) + { + if (onError != null) + onError(s); + }; + + float initialStartTime = Time.realtimeSinceStartup; + float startTime = initialStartTime; + float timeout = GetServerProcessingWaitTimeoutForDataSize(fileDesc.sizeInBytes); + float waitDelay = SERVER_PROCESSING_INITIAL_RETRY_TIME; + float maxDelay = SERVER_PROCESSING_MAX_RETRY_TIME; + + while (true) + { + if (apiFile == null) + { + if (onError != null) + onError("ApiFile is null"); + yield break; + } + + var desc = apiFile.GetFileDescriptor(apiFile.GetLatestVersionNumber(), fileDescriptorType); + if (desc == null) + { + if (onError != null) + onError("File descriptor is null ('" + fileDescriptorType + "')"); + yield break; + } + + if (desc.status != ApiFile.Status.Waiting) + { + // upload completed or is processing + break; + } + + // wait for next poll + while (Time.realtimeSinceStartup - startTime < waitDelay) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + + if (Time.realtimeSinceStartup - initialStartTime > timeout) + { + if (onError != null) + onError("Couldn't verify upload status: Timed out wait for server processing"); + yield break; + } + + yield return null; + } + + + while (true) + { + bool wait = true; + string errorStr = ""; + bool worthRetry = false; + + apiFile.Refresh( + (c) => + { + wait = false; + }, + (c) => + { + errorStr = "Couldn't verify upload status: " + c.Error; + wait = false; + if (c.Code == 400) + worthRetry = true; + }); + + while (wait) + { + if (CheckCancelled(cancelQuery, onCancelFunc, apiFile)) + { + yield break; + } + + yield return null; + } + + if (!string.IsNullOrEmpty(errorStr)) + { + if (onError != null) + onError(errorStr); + if (!worthRetry) + yield break; + } + + if (!worthRetry) + break; + } + + waitDelay = Mathf.Min(waitDelay * 2, maxDelay); + startTime = Time.realtimeSinceStartup; + } + + if (onSuccess != null) + onSuccess(apiFile); + } + private bool UploadFileComponentValidateFileDesc(ApiFile apiFile, string filename, string md5Base64, long fileSize, ApiFile.Version.FileDescriptor fileDesc, Action onSuccess, Action onError) + { + if (fileDesc.status != ApiFile.Status.Waiting) + { + // nothing to do (might be a retry) + Debug.Log("UploadFileComponent: (file record not in waiting status, done)"); + if (onSuccess != null) + onSuccess(apiFile); + return false; + } + + if (fileSize != fileDesc.sizeInBytes) + { + if (onError != null) + onError("File size does not match version descriptor"); + return false; + } + if (string.Compare(md5Base64, fileDesc.md5) != 0) + { + if (onError != null) + onError("File MD5 does not match version descriptor"); + return false; + } + + // make sure file is right size + long tempSize = 0; + string errorStr = ""; + if (!VRC.Tools.GetFileSize(filename, out tempSize, out errorStr)) + { + if (onError != null) + onError("Couldn't get file size"); + return false; + } + if (tempSize != fileSize) + { + if (onError != null) + onError("File size does not match input size"); + return false; + } + + return true; + } + private float GetServerProcessingWaitTimeoutForDataSize(int size) + { + float timeoutMultiplier = Mathf.Ceil(size / (float)SERVER_PROCESSING_WAIT_TIMEOUT_CHUNK_SIZE); + return Mathf.Clamp(timeoutMultiplier * SERVER_PROCESSING_WAIT_TIMEOUT_PER_CHUNK_SIZE, SERVER_PROCESSING_WAIT_TIMEOUT_PER_CHUNK_SIZE, SERVER_PROCESSING_MAX_WAIT_TIMEOUT); + } + #endregion + + #region VRC.Tools + public static void FileMD5(string filename, Action onSuccess, Action onError) + { + string text = ""; + byte[] array = null; + try + { + array = MD5.Create().ComputeHash(File.OpenRead(filename)); + } + catch (Exception ex) + { + array = null; + text = ex.Message; + } + + if (string.IsNullOrEmpty(text)) + { + onSuccess?.Invoke(array); + } + else + { + onError?.Invoke(text); + } + } + #endregion + #region RuntimeAPI + public IEnumerator UploadImage(string existingFileUrl, string friendlyFilename, string filename, Action onSuccess) + { + + const string fileType = "Image"; + bool isDone = false; + cancelRequested = false; + if (string.IsNullOrEmpty(filename.Trim())) yield return null; + VRC.Core.Logger.Log("Uploading " + fileType + "(" + filename + ") ...", DebugLevel.All); + SetUploadProgress("Uploading " + fileType + "...", "", 0.0f); + + string fileId = GetUploadRetryStateValue(filename); + if (string.IsNullOrEmpty(fileId)) + fileId = ApiFile.ParseFileIdFromFileAPIUrl(existingFileUrl); + string errorStr = ""; + string newFileUrl = ""; + + + yield return EditorCoroutine.Start(UploadFile(filename, fileId, friendlyFilename, + delegate (ApiFile apiFile, string message) + { + newFileUrl = apiFile.GetFileURL(); + Debug.Log($"{fileType} upload succeeded: {message} ({filename})"); + isDone = true; + }, + delegate (ApiFile apiFile, string error) + { + SaveUploadRetryState(filename, apiFile.id); + + errorStr = error; + Debug.LogError(fileType + " upload failed: " + error + " (" + filename + + ") => " + apiFile.ToString()); + isDone = true; + }, + delegate (ApiFile apiFile, string status, string subStatus, float pct) + { + SetUploadProgress($"Uploading {fileType}...", $"{status} {(!string.IsNullOrEmpty(subStatus) ? $" ({subStatus})" : string.Empty)}", pct); + }, + WasCancelRequested + )); + + if (!string.IsNullOrEmpty(errorStr)) + { + OnSDKPipelineError(fileType + " upload failed.", errorStr); + yield break; + } + + while (!isDone) + { + yield return null; + } + + if (onSuccess != null) + onSuccess(newFileUrl); + UnityEditor.EditorUtility.ClearProgressBar(); + cancelRequested = true; + } + protected void OnSDKPipelineError(string error, string details) + { + //VRC.Core.Logger.Log("OnSDKPipelineError: " + error + " - " + details, DebugLevel.All); + //isUploading = false; + //pipelineManager.completedSDKPipeline = true; + //UnityEditor.EditorApplication.isPaused = false; + //UnityEditor.EditorApplication.isPlaying = false; + UnityEditor.EditorUtility.ClearProgressBar(); + if (cancelRequested) + UnityEditor.EditorUtility.DisplayDialog("Udon VR - World Settings", "The update was cancelled.", "Okay"); + else + UnityEditor.EditorUtility.DisplayDialog("Udon VR - World Settings", "Error updating content. " + error + "\n" + details, "Okay"); + } + protected void SetUploadProgress(string title, string message, float progress) + { + bool cancelled = UnityEditor.EditorUtility.DisplayCancelableProgressBar(title, message, progress); + if (cancelled) + { + cancelRequested = true; + } + //uploadTitle = title; + //uploadMessage = message; + //uploadProgress = progress; + } + + protected bool WasCancelRequested(ApiFile _) + { + return cancelRequested; + } + + #region RetryFileState Methods + string GetUploadRetryStateValue(string key) + { + return mRetryState.ContainsKey(key) ? mRetryState[key] : ""; + } + protected void SaveUploadRetryState(string key, string val) + { + if (string.IsNullOrEmpty(val)) + return; + mRetryState[key] = val; + SaveUploadRetryState(); + } + protected void SaveUploadRetryState() + { + try + { + Directory.CreateDirectory(Path.GetDirectoryName(GetUploadRetryStateFilePath())); + string json = VRC.Tools.JsonEncode(mRetryState); + File.WriteAllText(GetUploadRetryStateFilePath(), json); + + Debug.LogFormat(" wrote retry state: {0}", json); + } + catch (Exception e) + { + Debug.LogError("Couldn't save upload retry state: " + GetUploadRetryStateFilePath() + "\n" + e.Message); + return; + } + + Debug.Log("Saved upload retry state to: " + GetUploadRetryStateFilePath()); + } + protected string GetUploadRetryStateFilePath() + { + string id = UnityEditor.AssetDatabase.AssetPathToGUID(SceneManager.GetActiveScene().path); + return Path.Combine(VRC.Tools.GetTempFolderPath(id), "upload_retry.dat"); + } + #endregion + #endregion + } +} \ No newline at end of file diff --git a/Tools/Player Chimes/scripts/Editor/PlayerChimes_Editor.cs b/Tools/Player Chimes/scripts/Editor/PlayerChimes_Editor.cs new file mode 100644 index 0000000..bc127fb --- /dev/null +++ b/Tools/Player Chimes/scripts/Editor/PlayerChimes_Editor.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using UdonVR.EditorUtility; + +[CustomEditor(typeof(UdonVR.PlayerLogger))] +public class ExampleEditor : Editor +{ + // code here + private SerializedProperty enableSounds; + private SerializedProperty bell; + private SerializedProperty join; + private SerializedProperty leave; + private SerializedProperty joinEnable; + private SerializedProperty leaveEnable; + private SerializedProperty joinToggle; + private SerializedProperty leaveToggle; + private SerializedProperty logger; + private SerializedProperty playerLogger; + private SerializedProperty timeStamps; + private SerializedProperty joinPrefix; + private SerializedProperty leavePrefix; + private SerializedProperty header; + + protected virtual void OnEnable() + { + // code here + enableSounds = serializedObject.FindProperty("enableSounds"); + bell = serializedObject.FindProperty("bell"); + join = serializedObject.FindProperty("join"); + leave = serializedObject.FindProperty("leave"); + joinEnable = serializedObject.FindProperty("joinEnable"); + leaveEnable = serializedObject.FindProperty("leaveEnable"); + joinToggle = serializedObject.FindProperty("joinToggle"); + leaveToggle = serializedObject.FindProperty("leaveToggle"); + logger = serializedObject.FindProperty("logger"); + playerLogger = serializedObject.FindProperty("playerLogger"); + timeStamps = serializedObject.FindProperty("timeStamps"); + joinPrefix = serializedObject.FindProperty("joinPrefix"); + leavePrefix = serializedObject.FindProperty("leavePrefix"); + header = serializedObject.FindProperty("header"); + } + + public override void OnInspectorGUI() + { + //base.OnInspectorGUI(); + //EditorGUILayout.Space(); + serializedObject.Update(); + DrawGUI(); + serializedObject.ApplyModifiedProperties(); + } + + protected virtual void DrawGUI() + { + // code here + if (UdonSharpEditor.UdonSharpGUI.DrawDefaultUdonSharpBehaviourHeader(target)) return; + UdonVR_GUI.BeginButtonFoldout(enableSounds, null, "foldout_enableSounds", EditorStyles.helpBox, null, 0, 13); + if (enableSounds.boolValue) + { + EditorGUILayout.PropertyField(bell); + EditorGUILayout.PropertyField(join); + EditorGUILayout.PropertyField(leave); + EditorGUILayout.PropertyField(joinEnable); + EditorGUILayout.PropertyField(leaveEnable); + EditorGUILayout.PropertyField(joinToggle); + EditorGUILayout.PropertyField(leaveToggle); + } + UdonVR_GUI.EndButtonFoldout("foldout_enableSounds"); + + UdonVR_GUI.BeginButtonFoldout(logger, null, "foldout_logger", EditorStyles.helpBox, null, 0, 13); + if (logger.boolValue) + { + EditorGUILayout.PropertyField(playerLogger); + EditorGUILayout.PropertyField(timeStamps); + EditorGUILayout.PropertyField(joinPrefix); + EditorGUILayout.PropertyField(leavePrefix); + EditorGUILayout.PropertyField(header); + } + UdonVR_GUI.EndButtonFoldout("foldout_logger"); + + serializedObject.ApplyModifiedProperties(); + } +} \ No newline at end of file diff --git a/Tools/Player Chimes/scripts/PlayerLogger.cs b/Tools/Player Chimes/scripts/PlayerLogger.cs new file mode 100644 index 0000000..2b5437d --- /dev/null +++ b/Tools/Player Chimes/scripts/PlayerLogger.cs @@ -0,0 +1,119 @@ +using System; +using UdonSharp; +using UnityEngine; +using VRC.SDKBase; +using TMPro; + +namespace UdonVR +{ + [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] + public class PlayerLogger : UdonSharpBehaviour + { + [Header("Join/Leave Sounds")] + public bool enableSounds = true; + + public AudioSource bell; + public AudioClip join; + public AudioClip leave; + public bool joinEnable = true; + public bool leaveEnable = true; + public GameObject joinToggle; + public GameObject leaveToggle; + private int _numPlayers; + private int _numJoins; + + [Header("Player Logger")] + public bool logger = true; + + public TextMeshProUGUI playerLogger; + public bool timeStamps = true; + public string joinPrefix = "[Joined]"; + public string leavePrefix = "[Left]"; + private string _log = ""; + + private bool hasHeader = false; + public TextMeshProUGUI header; + + private void Start() + { + _log = "PlayerChimes made by UdonVR\n=========================================\n"; + _numPlayers = VRCPlayerApi.GetPlayerCount(); + if (enableSounds) + { + if (joinToggle != null) joinToggle.SetActive(joinEnable); + if (leaveToggle != null) leaveToggle.SetActive(leaveEnable); + } + + if (header != null) hasHeader = true; + } + + private void Update() + { + if (!hasHeader) return; + header.text = "----------------------------------------------------------------------------------------------------------\n[" + DateTime.Now.ToString("hh:mm") + "] Players: " + VRCPlayerApi.GetPlayerCount(); + } + + public override void OnPlayerJoined(VRCPlayerApi player) + { + if (logger) LogPlayer(true, player); + if (enableSounds) + { + _numJoins = _numJoins + 1; + if (join != null && joinEnable && _numJoins > _numPlayers) + { + bell.clip = join; + bell.Play(); + } + } + } + + public override void OnPlayerLeft(VRCPlayerApi player) + { + if (playerLogger != null) LogPlayer(false, player); + if (enableSounds) + { + if (leave != null && leaveEnable) + { + bell.clip = leave; + bell.Play(); + } + } + } + + public void JoinToggle() + { + joinEnable = !joinEnable; + if (joinToggle != null) joinToggle.SetActive(joinEnable); + } + + public void LeaveToggle() + { + leaveEnable = !leaveEnable; + if (leaveToggle != null) leaveToggle.SetActive(leaveEnable); + } + + private void LogPlayer(bool _isJoin, VRCPlayerApi _player) + { + if (playerLogger == null) + { + Debug.LogError("[UdonVR] Player Logger has no logger attached"); + return; + } + if (timeStamps) + { + _log = _log + DateTime.Now.ToString("hh:mm"); + } + if (_isJoin) + { + _log = _log + joinPrefix + _player.displayName; + } + else + { + _log = _log + leavePrefix + _player.displayName; + } + if (_log.Length > 2000) _log = _log.Remove(0, (_log.Length - 2000)); + _log = _log + "\n"; + playerLogger.text = _log; + } + } +} \ No newline at end of file diff --git a/Tools/Shaders/BowlingCarpet.shader b/Tools/Shaders/BowlingCarpet.shader new file mode 100644 index 0000000..8494ef0 --- /dev/null +++ b/Tools/Shaders/BowlingCarpet.shader @@ -0,0 +1,458 @@ +/* + +Writen by Hamster9090901 after jokingly talking about how it would be easy to make. + +*/ + +Shader "_UdonVR/Toolkit/BowlingCarpet" +{ + Properties + { + [HideInInspector][ToggleOff]_SpecularHighlights("Specular Highlights", Int) = 0 // allow toggling of specular highlights + + [HideInInspector]_Glossiness("Smoothness", Range(0,1)) = 0 + [HideInInspector]_Metallic("Metallic", Range(0,1)) = 0 + + [HideInInspector]_MainTex("Main Tex", 2D) = "white" {} + [HideInInspector][MaterialToggle]_GreyscaleMainTex("Greyscale Main Tex", Float) = 1 + [HideInInspector]_Color("Color", Color) = (0.1255, 0.008, 0.33725, 1) + [HideInInspector]_EmissionBrightness("Emission Brightness", Range(0, 1)) = 0.5 + + [HideInInspector]_ShapeBrightnessMultiplier("Shape Brightness Multiplier", Float) = 1 + + [HideInInspector][MaterialToggle]_IsFrame("Frame", Float) = 1 + [HideInInspector][MaterialToggle]_IsFrameRandom("Frame Random", Float) = 1 + + [HideInInspector][MaterialToggle]_RemoveRandomShapes("Remove Random Shapes", Float) = 1 + + [HideInInspector][MaterialToggle]_IsTopOnly("Top Only", Float) = 0 + + [HideInInspector]_ColorCount("Color Count", Int) = 5 + [HideInInspector]_ShapeColor1("Shape Color 1", Color) = (0.067, 1, 0.467, 1) // teal + [HideInInspector]_ShapeColor2("Shape Color 2", Color) = (0.996, 0.949, 0, 1) // yellow + [HideInInspector]_ShapeColor3("Shape Color 3", Color) = (0.333, 0.765, 0.996, 1) // light blue + [HideInInspector]_ShapeColor4("Shape Color 4", Color) = (0.953, 0.302, 0.922, 1) // pink + [HideInInspector]_ShapeColor5("Shape Color 5", Color) = (0.469, 0, 0.714, 1) // purple + [HideInInspector]_ShapeColor6("Shape Color 6", Color) = (0, 0, 0, 1) + [HideInInspector]_ShapeColor7("Shape Color 7", Color) = (0, 0, 0, 1) + [HideInInspector]_ShapeColor8("Shape Color 8", Color) = (0, 0, 0, 1) + + [HideInInspector]_IsWindows("Is Windows", Float) = 0 + } + SubShader + { + Tags { "RenderType" = "Opaque" } + + LOD 100 + + CGPROGRAM + + #pragma surface surf Standard fullforwardshadows //vertex:vert + #pragma target 3.0 + #pragma shader_feature_local _SPECULARHIGHLIGHTS_OFF // needs to be defined in shader + + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/UsefulFunctions.cginc" + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/SDF_2D.cginc" + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/SDF_2D_Operations.cginc" + + struct Input + { + float3 worldPos; + float3 worldNormal; + + //float3 localPos; + //float3 localNormal; + }; + + half _Glossiness; + half _Metallic; + + sampler2D _MainTex; + const fixed _GreyscaleMainTex; + fixed4 _Color; + half _EmissionBrightness; + + half _ShapeBrightnessMultiplier; + + const fixed _IsFrame; + const fixed _IsFrameRandom; + + const fixed _RemoveRandomShapes; + + const fixed _IsTopOnly; + + int _ColorCount; + fixed4 _ShapeColor1; + fixed4 _ShapeColor2; + fixed4 _ShapeColor3; + fixed4 _ShapeColor4; + fixed4 _ShapeColor5; + fixed4 _ShapeColor6; + fixed4 _ShapeColor7; + fixed4 _ShapeColor8; + + fixed _IsWindows; + + /* + void vert(inout appdata_full v, out Input o) { + UNITY_INITIALIZE_OUTPUT(Input, o); + o.localPos = v.vertex; + o.localNormal = v.normal; + } + */ + + + // + // CUSTOM OPERATIONS BEGIN + // + float3 opFixDist(in float3 distance) + { + distance.x = opFixDist(distance.x); + distance.y = opFixDist(distance.y); + distance.z = opFixDist(distance.z); + return distance; + } + + float3 opRound(in float3 d, in float r) + { + return float3(opRound(d.x, r), opRound(d.y, r), opRound(d.z, r)); + } + + float3 opOnion(in float3 d, in float r) + { + return float3(opOnion(d.x, r), opOnion(d.y, r), opOnion(d.z, r)); + } + // + // CUSTOM OPERATIONS END + // + + + // + // CUSTOM SHAPES BEGIN + // + float3 isoCube(in float2 p, in float s) + { + float2 pos = float2(0, 0); + + // top + pos = opTxRxScale(p, float2(0, 1) * s, 0, s); + float top = sdRhombus(pos, float2(1, 1) * RhombusScale); + + // left + pos = opTxRxScale(p, float2(-0.86, -0.485) * s, opRemapRx(60), s); + float left = sdRhombus(pos, float2(1, 1) * RhombusScale); + + // right + pos = opTxRxScale(p, float2(0.86, -0.485) * s, opRemapRx(120), s); + float right = sdRhombus(pos, float2(1, 1) * RhombusScale); + + return float3(top, left, right); + } + + float3 isoFry(in float2 p, in float s) + { + float2 pos = float2(0, 0); + float3 fry = float3(0, 0, 0); + + pos = opTx(p, float2(-1.725, 2.975) * 2 * s); + fry = isoCube(pos, s); + + pos = opTx(p, float2(-1.725, 2.975) * s); + fry = min(fry, isoCube(pos, s)); + + pos = opTx(p, float2(0, 0)); + fry = min(fry, isoCube(pos, s)); + + pos = opTx(p, float2(1.725, -2.975) * s); + fry = min(fry, isoCube(pos, s)); + + pos = opTx(p, float2(1.725, -2.975) * 2 * s); + fry = min(fry, isoCube(pos, s)); + + return fry; + } + + float sdTriangleIsoscelesCentered(in float2 p, in float2 q) + { + // approximately moves the origin of the triangle to the center of it + p = opTx(p, float2(0, -(q.y * 0.55))); + return sdTriangleIsosceles(p, q); + } + + float lightning(in float2 p, in float2 s) + { + float2 pos = float2(0, 0); + float d = 0; + + pos = opTx(p, float2(-0.55, 1.715) * s); + d = sdTriangleIsoscelesCentered(pos, float2(1, -1.5 * 3) * s); + pos = opTx(p, float2(0.55, -1.715) * s); + d = min(d, sdTriangleIsoscelesCentered(pos, float2(1, 1.5 * 3) * s)); + + return d; + } + // + // CUSTOM SHAPES END + // + + + + // + // RANDOM VALUES FUNCTION BEGIN + // + struct RandValue + { + float2 uvOffset[2]; + float rand[2]; + float pos[2]; + float rot[2]; + float multi[2]; + }; + RandValue GetRandValue(in float2 uv, in float min, in float max, in float offset1, in float offset2) + { + float2 uvOffset[2]; + float rand[2]; + float pos[2]; + float rot[2]; + float multi[2]; + + uvOffset[0] = uv + offset1 * 10; + uvOffset[1] = uv + offset2 * 10; + for(int i = 0; i < 2; i++){ + rand[i] = Random(floor(uvOffset[i])); + pos[i] = Remap(rand[i], 0, 1, min, max); + rot[i] = Remap(rand[i], 0, 1, 0, PI * 2); + multi[i] = Remap(rand[i], 0, 1, 0.75, 1.25); + } + + RandValue rv = (RandValue)0; + rv.uvOffset = uvOffset; + rv.rand = rand; + rv.pos = pos; + rv.rot = rot; + rv.multi = multi; + return rv; + } + // + // RANDOM VALUES FUNCTION END + // + + void surf (Input IN, inout SurfaceOutputStandard o) + { + fixed3 c = fixed3(0, 0, 0); + fixed3 e = fixed3(0, 0, 0); + + // + // COLORS BEGIN + // + fixed3 _Colors[8]; + _Colors[0] = _ShapeColor1.rgb; + _Colors[1] = _ShapeColor2.rgb; + _Colors[2] = _ShapeColor3.rgb; + _Colors[3] = _ShapeColor4.rgb; + _Colors[4] = _ShapeColor5.rgb; + _Colors[5] = _ShapeColor6.rgb; + _Colors[6] = _ShapeColor7.rgb; + _Colors[7] = _ShapeColor8.rgb; + // + // COLORS END + // + + // + // POSITION BEGIN + // + float2 posRaw = float2(0, 0); + fixed3 axis = float3(0, 0, 0); + fixed3 isPositive = float3(0, 0, 0); + + // get the position (uv) for the axis were on + posRaw = abs(IN.worldNormal.x) > 0.5 ? IN.worldPos.zy + : abs(IN.worldNormal.y) > 0.5 ? IN.worldPos.xz + : abs(IN.worldNormal.z) > 0.5 ? IN.worldPos.xy + : float2(0, 0); + + // get what axis were on + axis.x = IN.worldNormal.x ? 1 : 0; + axis.y = IN.worldNormal.y ? 1 : 0; + axis.z = IN.worldNormal.z ? 1 : 0; + + // check if the axis is positive or negative + isPositive.x = IN.worldNormal.x > 0 ? 1 : 0; + isPositive.y = IN.worldNormal.y > 0 ? 1 : 0; + isPositive.z = IN.worldNormal.z > 0 ? 1 : 0; + // + // POSITION END + // + + // + // DRAW OBJECTS BEGIN + // + fixed3 carpetTex = tex2D(_MainTex, frac(posRaw)).rgb; + if (_GreyscaleMainTex) carpetTex = (carpetTex.r + carpetTex.g + carpetTex.b) / 3; + c = carpetTex * _Color; + + float2 pos = float2(0, 0); + float2 rp = float2(0, 0); + fixed obj = 0; + + float min = -0.3275; + float max = 0.3275; + + RandValue rv = (RandValue)0; + RandValue rvExtra = (RandValue)0; + + if (!_GreyscaleMainTex) carpetTex = (carpetTex.r + carpetTex.g + carpetTex.b) / 3; + //carpetTex = lerp(carpetTex, float3(1, 1, 1), _ShapeBrightness); + carpetTex *= _ShapeBrightnessMultiplier; + carpetTex = clamp(carpetTex, 0, 1); + + // box + pos = frac(posRaw); + pos -= 0.5; + rv = GetRandValue(posRaw, min, max, 7, 0); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 0.0625 * rv.multi[1]); + obj = sdBox(rp, float2(1, 2)); + if(_IsFrame) obj = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(obj, 0.125) : obj; + obj = opFixDist(obj); + if(_RemoveRandomShapes) obj *= rv.rand[0] > 0.1 ? 1 : 0; + c = obj > 0.5 ? 0 : c; + //e = obj > 0.5 ? 0 : e; + e += obj * carpetTex * _Colors[Remap(rv.rand[1], 0, 1, 0, _ColorCount)]; + + // circle + pos = frac(posRaw + 0.125); + pos -= 0.5; + rv = GetRandValue(posRaw + 0.125, min, max, 6, 1); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 0.125 * rv.multi[1]); + obj = sdCircle(rp, 1); + if(_IsFrame) obj = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(obj, 0.125) : obj; + obj = opFixDist(obj); + if(_RemoveRandomShapes) obj *= rv.rand[0] > 0.1 ? 1 : 0; + c = obj > 0.5 ? 0 : c; + //e = obj > 0.5 ? 0 : e; + e += obj * carpetTex * _Colors[Remap(rv.rand[1], 0, 1, 0, _ColorCount)]; + + // triangle + if (_IsWindows) + { + pos = frac(posRaw + 0.25); + pos -= 0.5; + rv = GetRandValue(posRaw + 0.25, min + 0.1, max - 0.1, 5, 2); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 0.125 * rv.multi[1]); + obj = sdTriangleIsoscelesCentered(rp, float2(1 * rv.multi[0], 1.5 * 2 * rv.multi[1])); + if(_IsFrame) obj = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(obj, 0.125) : obj; + obj = opFixDist(obj); + if(_RemoveRandomShapes) obj *= rv.rand[0] > 0.1 ? 1 : 0; + c = obj > 0.5 ? 0 : c; + //e = obj > 0.5 ? 0 : e; + e += obj * carpetTex * _Colors[Remap(rv.rand[1], 0, 1, 0, _ColorCount)]; + } + + // round box + pos = frac(posRaw + 0.375); + pos -= 0.5; + rv = GetRandValue(posRaw + 0.375, min, max, 4, 3); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 0.0625 * rv.multi[1]); + obj = opRound(sdBox(rp, float2(1, 2)), 0.25); + if(_IsFrame) obj = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(obj, 0.125) : obj; + obj = opFixDist(obj); + if(_RemoveRandomShapes) obj *= rv.rand[0] > 0.1 ? 1 : 0; + c = obj > 0.5 ? 0 : c; + //e = obj > 0.5 ? 0 : e; + e += obj * carpetTex * _Colors[Remap(rv.rand[1], 0, 1, 0, _ColorCount)]; + + // lightning + if (_IsWindows) + { + pos = frac(posRaw + 0.45); + pos -= 0.5; + rv = GetRandValue(posRaw + 0.45, min + 0.115, max - 0.115, 3, 4); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 0.0625 * rv.multi[1]); + obj = lightning(rp, float2(rv.multi[0], rv.multi[1])); + if(_IsFrame) obj = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(obj, 0.125) : obj; + obj = opFixDist(obj); + if(_RemoveRandomShapes) obj *= rv.rand[0] > 0.1 ? 1 : 0; + c = obj > 0.5 ? 0 : c; + //e = obj > 0.5 ? 0 : e; + e += obj * carpetTex * _Colors[Remap(rv.rand[1], 0, 1, 0, _ColorCount)]; + } + + // iso cube + pos = frac(posRaw + 0.5); + pos -= 0.5; + rv = GetRandValue(posRaw + 0.5, min, max, 2, 5); + rvExtra = GetRandValue(posRaw + 0.5, min, max, 4, 6); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 1); + float3 cube = isoCube(rp, 0.0625 * rv.multi[1]); + if(_IsFrame) cube = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(cube, 0.125) : cube; + cube = opFixDist(cube); + if(_RemoveRandomShapes) cube *= rv.rand[0] > 0.1 ? 1 : 0; + c = cube.x > 0.5 ? 0 : c; + c = cube.y > 0.5 ? 0 : c; + c = cube.z > 0.5 ? 0 : c; + e += cube.x * carpetTex * _Colors[Remap(rv.rand[0], 0, 1, 0, _ColorCount)]; + e += cube.y * carpetTex * _Colors[Remap(rvExtra.rand[0], 0, 1, 0, _ColorCount)]; + e += cube.z * carpetTex * _Colors[Remap(rvExtra.rand[1], 0, 1, 0, _ColorCount)]; + + // iso fry + pos = frac(posRaw + 0.625); + pos -= 0.5; + rv = GetRandValue(posRaw + 0.625, min + 0.05, max - 0.05, 1, 6); + rvExtra = GetRandValue(posRaw + 0.625, min + 0.05, max - 0.05, 3, 5); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 1); + float3 fry = isoFry(rp, 0.0625 / 3 * rv.multi[1]); + if(_IsFrame) fry = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(fry, 0.125) : fry; + fry = opFixDist(fry); + if(_RemoveRandomShapes) fry *= rv.rand[0] > 0.1 ? 1 : 0; + c = fry.x > 0.5 ? 0 : c; + c = fry.y > 0.5 ? 0 : c; + c = fry.z > 0.5 ? 0 : c; + e += fry.x * carpetTex * _Colors[Remap(rv.rand[0], 0, 1, 0, _ColorCount)]; + e += fry.y * carpetTex * _Colors[Remap(rvExtra.rand[0], 0, 1, 0, _ColorCount)]; + e += fry.z * carpetTex * _Colors[Remap(rvExtra.rand[1], 0, 1, 0, _ColorCount)]; + + // squiggly + pos = frac(posRaw + 0.75); + pos -= 0.5; + rv = GetRandValue(posRaw + 0.75, min, max, 0, 7); + + rp = opTxRxScale(pos, float2(rv.pos[0], rv.pos[1]), opRemapRx(rv.rot[1]), 0.125 * rv.multi[1]); + rp = opWobble(rp, float2(5 * rv.multi[0], 5 * rv.multi[1]), 0.2); + float segmentThickness = 0.125; + obj = sdSegment(rp, float2(0, -1.35), float2(0, 1.35)) - segmentThickness; + if(_IsFrame) obj = (_IsFrameRandom ? rv.rand[0] : 1) > 0.5 ? opOnion(obj, segmentThickness / 4) : obj; + obj = opFixDist(obj); + obj *= rv.rand[0] > 0.1 ? 1 : 0; + c = obj > 0.5 ? 0 : c; + e += obj * carpetTex * _Colors[Remap(rv.rand[1], 0, 1, 0, _ColorCount)]; + + // + // DRAW OBJECTS END + // + + //e.rg += posRaw; + + // only draw color on the top of the object + if(_IsTopOnly) + { + c = isPositive.y ? c : 0; + e = isPositive.y ? e : 0; + } + + o.Albedo = c + e; + o.Emission = e * _EmissionBrightness; + o.Smoothness = _Glossiness; + o.Metallic = _Metallic; + o.Alpha = _Color.a; + } + ENDCG + } + CustomEditor "BowlingCarpet_Editor" +} diff --git a/Tools/Shaders/Editor/BowlingCarpet_Editor.cs b/Tools/Shaders/Editor/BowlingCarpet_Editor.cs new file mode 100644 index 0000000..8bfbe08 --- /dev/null +++ b/Tools/Shaders/Editor/BowlingCarpet_Editor.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using UdonVR.EditorUtility; +using UdonVR.EditorUtility.ShaderGUI; + +public class BowlingCarpet_Editor : ShaderGUI +{ + Material targetMat; + MaterialEditor materialEditor; + MaterialProperty[] properties; + + bool showShapeColors = false; + + public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) + { + this.targetMat = materialEditor.target as Material; + this.materialEditor = materialEditor; + this.properties = properties; + + + EditorGUI.BeginChangeCheck(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + UdonVR_GUI.Header(new GUIContent("Works with 'PCFeatureEnable' Prefab", "This shader has parts dissabled for quest users they can be enabled for PC by adding the material into this script.")); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + UdonVR_ShaderGUI.CullBlendSpecularData cullBlendSpecularData = UdonVR_ShaderGUI.ShowCullBlendSpecular(targetMat); + + EditorGUILayout.Space(); + + UdonVR_ShaderGUI.GlossMetalAlphaData glossMetalAlphaData = UdonVR_ShaderGUI.ShowGlossMetalAlpha(targetMat); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + Texture mainTex = (Texture)EditorGUILayout.ObjectField(new GUIContent("Carpet Texture", "Texture to add to the carpet."), targetMat.GetTexture("_MainTex"), typeof(Texture), false); + bool greyscaleMainTex = UdonVR_GUI.ToggleButton(new GUIContent("Greyscale Carpet Texture", "Greyscales the carpet texture removing color."), System.Convert.ToBoolean(targetMat.GetFloat("_GreyscaleMainTex"))); + Color color = EditorGUILayout.ColorField(new GUIContent("Color", "Color of the carpet."), targetMat.GetColor("_Color"), true, false, false); + float emissionBrightness = EditorGUILayout.Slider(new GUIContent("Emission Brightness", "Brightness of the emission."), targetMat.GetFloat("_EmissionBrightness"), 0, 1); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + float shapeBrightnessMultiplier = EditorGUILayout.Slider(new GUIContent("Shape Brightness Multiplier", "Brightness multiplier of the shapes. (Increases base brightness and thus also emission brightness.)"), targetMat.GetFloat("_ShapeBrightnessMultiplier"), 1, 10); + + bool isFrame = System.Convert.ToBoolean(targetMat.GetFloat("_IsFrame")); + bool isFrameRandom = System.Convert.ToBoolean(targetMat.GetFloat("_IsFrameRandom")); + UdonVR_GUI.FieldArea( + new GUIContent[] { + new GUIContent("Frames", "Make the shapes into frames."), + new GUIContent("Random Frames", "Randomly makes the shapes into frames."), + }, + new UdonVR_GUI.FieldAreaValues[] + { + UdonVR_GUI.FieldAreaValues.SetValueToggle(isFrame, true), + UdonVR_GUI.FieldAreaValues.SetValueToggle(isFrameRandom, isFrame) + }, + new System.Action[] + { + (areaValues) => + { + isFrame = areaValues.boolValue.Value; + }, + (areaValues) => + { + isFrameRandom = areaValues.boolValue.Value; + } + } + ); + + bool removeRandomShapes = System.Convert.ToBoolean(targetMat.GetFloat("_RemoveRandomShapes")); + removeRandomShapes = UdonVR_GUI.ToggleButton(new GUIContent("Remove Random Shapes", "Randomly remove shapes."), removeRandomShapes); + + bool isTopOnly = System.Convert.ToBoolean(targetMat.GetFloat("_IsTopOnly")); + isTopOnly = UdonVR_GUI.ToggleButton(new GUIContent("Top Only", "Do we only draw on the top side of the object."), isTopOnly); + + EditorGUILayout.Space(); + + int colorCount = targetMat.GetInt("_ColorCount"); + Color shapeColor1 = targetMat.GetColor("_ShapeColor1"); + Color shapeColor2 = targetMat.GetColor("_ShapeColor2"); + Color shapeColor3 = targetMat.GetColor("_ShapeColor3"); + Color shapeColor4 = targetMat.GetColor("_ShapeColor4"); + Color shapeColor5 = targetMat.GetColor("_ShapeColor5"); + Color shapeColor6 = targetMat.GetColor("_ShapeColor6"); + Color shapeColor7 = targetMat.GetColor("_ShapeColor7"); + Color shapeColor8 = targetMat.GetColor("_ShapeColor8"); + + if (UdonVR_GUI.BeginButtonFoldout(new GUIContent("Show Shape Colors"), ref showShapeColors, "showShapeColors", EditorStyles.helpBox, EditorStyles.helpBox, 0, 12)) + { + colorCount = EditorGUILayout.IntSlider(new GUIContent("Number of Colors", "The number of colors to use."), colorCount, 1, 8); + shapeColor1 = EditorGUILayout.ColorField(new GUIContent("Shape Color 1"), shapeColor1, true, false, false); + shapeColor2 = EditorGUILayout.ColorField(new GUIContent("Shape Color 2"), shapeColor2, true, false, false); + shapeColor3 = EditorGUILayout.ColorField(new GUIContent("Shape Color 3"), shapeColor3, true, false, false); + shapeColor4 = EditorGUILayout.ColorField(new GUIContent("Shape Color 4"), shapeColor4, true, false, false); + shapeColor5 = EditorGUILayout.ColorField(new GUIContent("Shape Color 5"), shapeColor5, true, false, false); + shapeColor6 = EditorGUILayout.ColorField(new GUIContent("Shape Color 6"), shapeColor6, true, false, false); + shapeColor7 = EditorGUILayout.ColorField(new GUIContent("Shape Color 7"), shapeColor7, true, false, false); + shapeColor8 = EditorGUILayout.ColorField(new GUIContent("Shape Color 8"), shapeColor8, true, false, false); + } + UdonVR_GUI.EndButtonFoldout("showShapeColors"); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + + if (EditorGUI.EndChangeCheck()) + { + UdonVR_ShaderGUI.SaveCullBlendSpecular(targetMat, cullBlendSpecularData); + UdonVR_ShaderGUI.SaveGlossMetalAlpha(targetMat, glossMetalAlphaData); + + targetMat.SetTexture("_MainTex", mainTex); + targetMat.SetFloat("_GreyscaleMainTex", System.Convert.ToSingle(greyscaleMainTex)); + targetMat.SetColor("_Color", color); + targetMat.SetFloat("_EmissionBrightness", UdonVR_MathHelpers.Clamp(emissionBrightness, 0, 1)); + + targetMat.SetFloat("_ShapeBrightnessMultiplier", shapeBrightnessMultiplier); + targetMat.SetFloat("_IsFrame", System.Convert.ToSingle(isFrame)); + targetMat.SetFloat("_IsFrameRandom", System.Convert.ToSingle(isFrameRandom)); + targetMat.SetFloat("_RemoveRandomShapes", System.Convert.ToSingle(removeRandomShapes)); + targetMat.SetFloat("_IsTopOnly", System.Convert.ToSingle(isTopOnly)); + + targetMat.SetInt("_ColorCount", colorCount); + targetMat.SetColor("_ShapeColor1", shapeColor1); + targetMat.SetColor("_ShapeColor2", shapeColor2); + targetMat.SetColor("_ShapeColor3", shapeColor3); + targetMat.SetColor("_ShapeColor4", shapeColor4); + targetMat.SetColor("_ShapeColor5", shapeColor5); + targetMat.SetColor("_ShapeColor6", shapeColor6); + targetMat.SetColor("_ShapeColor7", shapeColor7); + targetMat.SetColor("_ShapeColor8", shapeColor8); + } + base.OnGUI(materialEditor, properties); + } +} diff --git a/Tools/Shaders/Editor/Flipbook_Editor.cs b/Tools/Shaders/Editor/Flipbook_Editor.cs new file mode 100644 index 0000000..ede1ef4 --- /dev/null +++ b/Tools/Shaders/Editor/Flipbook_Editor.cs @@ -0,0 +1,135 @@ +using System.ComponentModel; +using System.Threading.Tasks; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using UdonVR.EditorUtility; +using UdonVR.EditorUtility.ShaderGUI; + +public class Flipbook_Editor : ShaderGUI +{ + Material targetMat; + MaterialEditor materialEditor; + MaterialProperty[] properties; + + enum WaveType + { + Sawtooth, Triangle + }; + + + public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) + { + this.targetMat = materialEditor.target as Material; + this.materialEditor = materialEditor; + this.properties = properties; + + EditorGUI.BeginChangeCheck(); + + UdonVR_ShaderGUI.CullBlendSpecularData cullBlendSpecularData = UdonVR_ShaderGUI.ShowCullBlendSpecular(targetMat); + + EditorGUILayout.Space(); + + UdonVR_ShaderGUI.GlossMetalAlphaData glossMetalAlphaData = UdonVR_ShaderGUI.ShowGlossMetalAlpha(targetMat); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + Texture mainTex = (Texture)EditorGUILayout.ObjectField(new GUIContent("Main Texture"), targetMat.GetTexture("_MainTex"), typeof(Texture), false); + bool enableEmission = UdonVR_GUI.ToggleButton(new GUIContent("Enable Emission"), System.Convert.ToBoolean(targetMat.GetFloat("_EnableEmission"))); + Texture emissionMap = targetMat.GetTexture("_EmissionMap"); + bool emissionMapIsFlipbook = System.Convert.ToBoolean(targetMat.GetFloat("_EmissionMapIsFlipbook")); + if (enableEmission) + { + emissionMap = (Texture)EditorGUILayout.ObjectField(new GUIContent("Emission Map"), emissionMap, typeof(Texture), false); + GUILayout.BeginHorizontal(); + GUILayout.Space(20); + GUILayout.BeginVertical(); + emissionMapIsFlipbook = UdonVR_GUI.ToggleButton(new GUIContent("Emission Map Is Flipbook", "Is the emission map a flipbook and does it follow the flipbook."), emissionMapIsFlipbook); + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + } + Color color = EditorGUILayout.ColorField(new GUIContent("Color"), targetMat.GetColor("_Color"), true, false, false); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + bool enableLoop = System.Convert.ToBoolean(targetMat.GetFloat("_EnableLoop")); + float percent = targetMat.GetFloat("_Percent"); + int index = targetMat.GetInt("_Index"); + WaveType waveType = (WaveType)targetMat.GetInt("_WaveType"); + float speed = targetMat.GetFloat("_Speed"); + int columns = targetMat.GetInt("_Columns"); + int rows = targetMat.GetInt("_Rows"); + + enableLoop = UdonVR_GUI.ToggleButton(new GUIContent("Enable Loop"), enableLoop); + + GUILayout.BeginHorizontal(); + GUILayout.Space(20); + GUILayout.BeginVertical(); + + if (!enableLoop) + { + UdonVR_GUI.FieldArea( + new GUIContent[] + { + new GUIContent("Percent", "Completion percentage."), + new GUIContent("Index", "Desired index.") + }, + new UdonVR_GUI.FieldAreaValues[]{ + UdonVR_GUI.FieldAreaValues.SetValueSlider(percent, true, 0, 1), + UdonVR_GUI.FieldAreaValues.SetValueSlider(index, true, 0, (columns * rows) - 1) + }, + new System.Action[] + { + (value) => { + percent = value.floatValue.Value; + }, + (value) => { + index = (int)value.intValue.Value; + } + }, + EditorStyles.helpBox + ); + } + else + { + waveType = (WaveType)EditorGUILayout.EnumPopup(new GUIContent("Wave Type"), waveType); + speed = EditorGUILayout.Slider(new GUIContent("Speed"), speed, 0.005f, 5f); + } + + EditorGUILayout.Space(1); + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + + columns = EditorGUILayout.IntSlider(new GUIContent("Columns"), columns, 0, 32); + rows = EditorGUILayout.IntSlider(new GUIContent("Rows"), rows, 0, 32); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + if (EditorGUI.EndChangeCheck()) + { + UdonVR_ShaderGUI.SaveCullBlendSpecular(targetMat, cullBlendSpecularData); + UdonVR_ShaderGUI.SaveGlossMetalAlpha(targetMat, glossMetalAlphaData); + + targetMat.SetTexture("_MainTex", mainTex); + targetMat.SetFloat("_EnableEmission", System.Convert.ToSingle(enableEmission)); + targetMat.SetTexture("_EmissionMap", emissionMap); + targetMat.SetFloat("_EmissionMapIsFlipbook", System.Convert.ToSingle(emissionMapIsFlipbook)); + targetMat.SetColor("_Color", color); + + targetMat.SetFloat("_EnableLoop", System.Convert.ToSingle(enableLoop)); + targetMat.SetFloat("_Percent", percent); + targetMat.SetInt("_Index", index); + targetMat.SetInt("_WaveType", UdonVR_EnumHelpers.IntFromEnum(waveType)); + targetMat.SetFloat("_Speed", speed); + targetMat.SetInt("_Columns", columns); + targetMat.SetInt("_Rows", rows); + } + base.OnGUI(materialEditor, properties); + } +} \ No newline at end of file diff --git a/Tools/Shaders/Editor/RadialProgressBar_Editor.cs b/Tools/Shaders/Editor/RadialProgressBar_Editor.cs new file mode 100644 index 0000000..187a12e --- /dev/null +++ b/Tools/Shaders/Editor/RadialProgressBar_Editor.cs @@ -0,0 +1,130 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using System; + +using UdonVR.EditorUtility; + +public class RadialProgressBar_Editor : ShaderGUI +{ + Material targetMat; + MaterialEditor materialEditor; + MaterialProperty[] properties; + + bool showShaderProperties = false; + + public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) + { + this.targetMat = materialEditor.target as Material; + this.materialEditor = materialEditor; + this.properties = properties; + + EditorGUI.BeginChangeCheck(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + float percent = EditorGUILayout.Slider(new GUIContent("Percent", "Completion percentage of the progress bar."), targetMat.GetFloat("_Percent"), 0, 1); + bool flipProgressBar = UdonVR_GUI.ToggleButton(new GUIContent("Flip Progress Bar", "Flips the progress bar horizontally."), System.Convert.ToBoolean(targetMat.GetFloat("_FlipProgressBar"))); + bool isBillboard = UdonVR_GUI.ToggleButton(new GUIContent("Billboard", "Does the progress bar face towards the camera."), System.Convert.ToBoolean(targetMat.GetFloat("_IsBillboard"))); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + bool isGradient = UdonVR_GUI.ToggleButton(new GUIContent("Use Gradient", "Do we use a different color for the start and end."), System.Convert.ToBoolean(targetMat.GetFloat("_IsGradient"))); + Vector4 color1 = EditorGUILayout.ColorField(!isGradient ? new GUIContent("Color", "Color of the progress bar.") : new GUIContent("Start Color", "Starting Color."), targetMat.GetColor("_Color1"), true, true, false); + Vector4 color2 = targetMat.GetColor("_Color2"); + if (isGradient) color2 = EditorGUILayout.ColorField(new GUIContent("End Color", "Ending Color."), color2, true, true, false); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + bool isHollow = UdonVR_GUI.ToggleButton(new GUIContent("Is Hollow", "Makes the progress bar hollow."), System.Convert.ToBoolean(targetMat.GetFloat("_IsHollow"))); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + float thickness = EditorGUILayout.Slider(new GUIContent("Thickness", "Thickness of the progress bar."), targetMat.GetFloat("_Thickness"), 0.005f, 0.15f); + float radius = EditorGUILayout.Slider(new GUIContent("Radius", "Distance from the center."), targetMat.GetFloat("_Radius"), 0.05f, 0.475f); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + float rotation = EditorGUILayout.Slider(new GUIContent("Rotation", "Rotation offset of the progress bar."), targetMat.GetFloat("_Rotation"), 0f, 360f); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + float startAngle = EditorGUILayout.Slider(new GUIContent("Start Angle", "Starting angle of the progress bar."), targetMat.GetFloat("_StartAngle"), 0f, 179.9f); + float endAngle = EditorGUILayout.Slider(new GUIContent("End Angle", "Ending angle of the progress bar."), targetMat.GetFloat("_EndAngle"), 180f, 360f); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + float seperation = EditorGUILayout.Slider(new GUIContent("Seperation", "Amount of seperation to add between the start and end of the progress bar."), targetMat.GetFloat("_Seperation"), 0f, 90f); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + GUILayout.BeginVertical(EditorStyles.helpBox); + if (UdonVR_GUI.BeginButtonFoldout(new GUIContent("Show Shader Properties", ""), ref showShaderProperties, "showShaderProperties", null, EditorStyles.helpBox, 0, 12)) + { + EditorGUILayout.LabelField(new GUIContent("_Percent", "The completion amount of the progress bar.")); + EditorGUILayout.LabelField(new GUIContent("_FlipProgressBar", "Flips the progress bar horizontally.")); + EditorGUILayout.LabelField(new GUIContent("_IsBillboard", "Does the progress bar face the camera. (transform.forward away from camera.)")); + EditorGUILayout.Space(); + + EditorGUILayout.LabelField(new GUIContent("_IsGradient", "Is there a gradient being used.")); + EditorGUILayout.LabelField(new GUIContent("_Color1", "The start / default color of the progress bar.")); + EditorGUILayout.LabelField(new GUIContent("_Color2", "The ending color of the progress bar.")); + EditorGUILayout.Space(); + + EditorGUILayout.LabelField(new GUIContent("_IsHollow", "Is the progress bar hollow?")); + EditorGUILayout.Space(); + + EditorGUILayout.LabelField(new GUIContent("_Thickness", "The thickness of the progress bar.")); + EditorGUILayout.LabelField(new GUIContent("_Radius", "The distance the progress bar is from the center.")); + EditorGUILayout.Space(); + + EditorGUILayout.LabelField(new GUIContent("_Rotation", "The rotation of the progress bar.")); + EditorGUILayout.Space(); + + EditorGUILayout.LabelField(new GUIContent("_StartAngle", "Starting angle of the progress bar.")); + EditorGUILayout.LabelField(new GUIContent("_EndAngle", "Ending angle of the progress bar.")); + EditorGUILayout.LabelField(new GUIContent("_Seperation", "Amount of seperation to add between the start and end of the progress bar.")); + } + UdonVR_GUI.EndButtonFoldout("showShaderProperties"); + GUILayout.EndVertical(); + + EditorGUILayout.Space(); + + if (EditorGUI.EndChangeCheck()) + { + targetMat.SetFloat("_Percent", percent); + targetMat.SetFloat("_FlipProgressBar", System.Convert.ToSingle(flipProgressBar)); + targetMat.SetFloat("_IsBillboard", System.Convert.ToSingle(isBillboard)); + + targetMat.SetFloat("_IsGradient", System.Convert.ToSingle(isGradient)); + targetMat.SetColor("_Color1", color1); + targetMat.SetColor("_Color2", color2); + + targetMat.SetFloat("_IsHollow", System.Convert.ToSingle(isHollow)); + + targetMat.SetFloat("_Thickness", thickness); + targetMat.SetFloat("_Radius", radius); + + targetMat.SetFloat("_Rotation", rotation); + + targetMat.SetFloat("_StartAngle", startAngle); + targetMat.SetFloat("_EndAngle", endAngle); + targetMat.SetFloat("_Seperation", seperation); + } + base.OnGUI(materialEditor, properties); + } +} diff --git a/Tools/Shaders/Editor/Sparkle_Editor.cs b/Tools/Shaders/Editor/Sparkle_Editor.cs new file mode 100644 index 0000000..1b3892b --- /dev/null +++ b/Tools/Shaders/Editor/Sparkle_Editor.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + + +public class Sparkle_Editor : ShaderGUI +{ + Material targetMat; + MaterialEditor materialEditor; + MaterialProperty[] properties; + + public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) + { + this.targetMat = materialEditor.target as Material; + this.materialEditor = materialEditor; + this.properties = properties; + + EditorGUI.BeginChangeCheck(); + // code here + + /* + _MainTex ("Noise Tex", 2D) = "white" {} + _NoiseRotationSpeed("Noise Rotation Speed", Range(1, 100)) = 25 + _SparkleOffset("Sparkle Offset", Range(0, 1)) = 0.5 + _Color("Color", Color) = (0.6941177, 1, 0.9647059, 1) + _NoiseScale("Noise Scale", Vector) = (1, 1, 0, 0) + + // fresnel + [MaterialToggle] _EnableFresnel("Enable Fresnel", Float) = 0 + _FresnelBias("Fresnel Bias", Float) = -0.35 + _FresnelScale("Fresnel Scale", Float) = 1 + _FresnelPower("Fresnel Power", Float) = 0.85 + */ + + GUILayout.BeginVertical(EditorStyles.helpBox); + Texture mainTex = (Texture)EditorGUILayout.ObjectField(new GUIContent("Noise Tex"), targetMat.GetTexture("_MainTex"), typeof(Texture), false); + float noiseRotationSpeed = EditorGUILayout.Slider(new GUIContent("Noise Rotation Speed"), targetMat.GetFloat("_NoiseRotationSpeed"), 0, 100); + float sparkleOffset = EditorGUILayout.Slider(new GUIContent("Sparkle Offset"), targetMat.GetFloat("_SparkleOffset"), 0, 1); + Color color = EditorGUILayout.ColorField(new GUIContent("Color"), targetMat.GetColor("_Color"), true, false, false); + Vector2 noiseScale = EditorGUILayout.Vector2Field(new GUIContent("Noise Scale"), targetMat.GetVector("_NoiseScale")); + GUILayout.EndVertical(); + + if (EditorGUI.EndChangeCheck()) + { + // code here + targetMat.SetTexture("_MainTex", mainTex); + targetMat.SetFloat("_NoiseRotationSpeed", noiseRotationSpeed); + targetMat.SetFloat("_SparkleOffset", sparkleOffset); + targetMat.SetColor("_Color", color); + targetMat.SetVector("_NoiseScale", noiseScale); + } + base.OnGUI(materialEditor, properties); + } +} \ No newline at end of file diff --git a/Tools/Shaders/Flipbook.shader b/Tools/Shaders/Flipbook.shader new file mode 100644 index 0000000..8ecf1f1 --- /dev/null +++ b/Tools/Shaders/Flipbook.shader @@ -0,0 +1,118 @@ +Shader "_UdonVR/Toolkit/Flipbook" +{ + Properties + { + [HideInInspector][MaterialEnum(Opaque,0,Cutout,1,Fade,2)]_BlendMode("Blend Mode", Int) = 0 // allow switching transparancy options + [HideInInspector]_SrcBlend("SrcBlend", Int) = 1 + [HideInInspector]_DstBlend("DstBlend", Int) = 0 + [HideInInspector]_ZWrite("ZWrite", Int) = 1 + [HideInInspector][MaterialEnum(Off,0,Front,1,Back,2)]_Cull("Cull", Int) = 2 // allow switching culling options + [HideInInspector][ToggleOff]_SpecularHighlights("Specular Highlights", Int) = 0 // allow toggling of specular highlights + + [HideInInspector]_MainTex("Main Tex", 2D) = "white" {} + [HideInInspector]_EnableEmission("Enable Emission", Float) = 1 + [HideInInspector]_EmissionMap("Emission Map", 2D) = "white" {} + [HideInInspector][MaterialToggle]_EmissionMapIsFlipbook("Emission Map Is Flipbook", Float) = 0 + [HideInInspector]_Color("Color", Color) = (1, 1, 1, 1) + [HideInInspector][MaterialToggle]_EnableLoop("Enable Loop", Float) = 0 + [HideInInspector]_Percent("Percent", Range(0, 1)) = 0 + [HideInInspector]_Index("Index", Int) = 0 + [HideInInspector][MaterialEnum(Sawtooth,0,Triangle,1)] _WaveType("Wave Type", Int) = 0 + [HideInInspector]_Speed("Speed", Range(0.005, 5)) = 0.15 + [HideInInspector]_Columns("Columns", Int) = 1 + [HideInInspector]_Rows("Rowss", Int) = 1 + + [HideInInspector]_Glossiness("Smoothness", Range(0,1)) = 0 + [HideInInspector]_Metallic("Metallic", Range(0,1)) = 0 + [HideInInspector]_AlphaCutout("Alpha Cutout", Range(0, 1.01)) = 0.5 // the alpha cutout thresh hold + [HideInInspector]_AlphaFade("Alpha Fade", Range(0, 1)) = 0.5 + } + SubShader + { + Tags { "RenderType" = "Opaque" "PreviewType" = "plane" } + Cull [_Cull] + Blend [_SrcBlend][_DstBlend] + ZWrite [_ZWrite] + + CGPROGRAM + + #pragma surface surf Standard fullforwardshadows keepalpha + #pragma target 3.0 + #pragma shader_feature_local _SPECULARHIGHLIGHTS_OFF // needs to be defined in shader + + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/UsefulFunctions.cginc" + + struct Input + { + float2 uv_MainTex; + }; + + half _Glossiness; + half _Metallic; + fixed _AlphaCutout; + fixed _AlphaFade; + #pragma shader_feature_local _ALPHACUTOUT + #pragma shader_feature_local _ALPHAFADE + #pragma alphatest:_AlphaCutout alpha:fade + + sampler2D _MainTex; + fixed _EnableEmission; + sampler2D _EmissionMap; + fixed _EmissionMapIsFlipbook; + fixed4 _Color; + + fixed _EnableLoop; + half _Percent; + int _Index; + fixed _WaveType; + half _Speed; + int _Columns; + int _Rows; + + // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. + // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. + // #pragma instancing_options assumeuniformscaling + UNITY_INSTANCING_BUFFER_START(Props) + // put more per-instance properties here + UNITY_INSTANCING_BUFFER_END(Props) + + void surf (Input IN, inout SurfaceOutputStandard o) + { + _Speed = clampMin(_Speed, 0.005); + _Columns = clampMin(_Columns, 1); + _Rows = clampMin(_Rows, 1); + + fixed t = _Time.y * _Speed; + fixed sawtoothWave = frac(2 * (t - floor(0.5 + t))); // get sawtooth wave + fixed triangleWave = abs(2 * (t - floor(0.5 + t))); // get triangle wave + fixed waveInput = _WaveType == 0 ? sawtoothWave + : _WaveType == 1 ? triangleWave + : 0; + + float index = _EnableLoop ? floor(Remap(waveInput, 0, 1, 0, (_Columns * _Rows) - 0.5)) : Remap(_Percent, 0, 1, 0, (_Columns * _Rows) - 1); + index = _Index > 0 ? _Index : index; + index = clampMax(index, (_Columns * _Rows) - 1); + + float2 output = Flipbook(IN.uv_MainTex, _Columns, _Rows, index); + fixed4 c = tex2D(_MainTex, output) * _Color; + fixed4 e = tex2D(_EmissionMap, _EmissionMapIsFlipbook ? output : IN.uv_MainTex); + + o.Albedo = c.rgb; + o.Emission = _EnableEmission == 1 ? c.rgb * e.rgb: float3(0, 0, 0); + o.Metallic = _Metallic; + o.Smoothness = _Glossiness; + + #if defined(_ALPHACUTOUT) + clip(c.a - _AlphaCutout); + #endif + + #if defined(_ALPHAFADE) + o.Alpha = _AlphaFade; + clip(c.a - 0.5); + #endif + } + ENDCG + } + FallBack "Diffuse" + CustomEditor "Flipbook_Editor" +} diff --git a/Tools/Shaders/LavaLamp.shader b/Tools/Shaders/LavaLamp.shader new file mode 100644 index 0000000..00874d3 --- /dev/null +++ b/Tools/Shaders/LavaLamp.shader @@ -0,0 +1,142 @@ +Shader "_UdonVR/Toolkit/Unlit/LavaLamp" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _Scale("Scale", Vector) = (1, 1, 1, 0) + _Position1 ("Position1", Vector) = (0, 0, 0, 0.125) + _Position2 ("Position2", Vector) = (0, 0, 0, 0.125) + _Position3 ("Position3", Vector) = (0, 0, 0, 0.125) + _Position4 ("Position4", Vector) = (0, 0, 0, 0.125) + _Position5 ("Position5", Vector) = (0, 0, 0, 0.125) + } + SubShader + { + Tags { "RenderType"="Opawue" } + LOD 100 + + GrabPass { "_GrabpassTex" } + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/SDF_3D.cginc" + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/SDF_3D_Operations.cginc" + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/UsefulFunctions.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + + float4 pos : POSITION; + half3 normal : NORMAL; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + float3 ro : TEXCOORD1; + float3 hitPos : TEXCOORD2; + + float4 grapPassUV : TEXCOORD3; + }; + + sampler2D _GrabpassTex; + + sampler2D _MainTex; + float4 _MainTex_ST; + + float3 _Scale; + + float4 _Position1; + float4 _Position2; + float4 _Position3; + float4 _Position4; + float4 _Position5; + + v2f vert (appdata v) + { + v2f o; + + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = TRANSFORM_TEX(v.uv, _MainTex); + o.ro = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));// object space + o.hitPos = v.vertex; // object space + + o.grapPassUV = ComputeGrabScreenPos(o.vertex); + + return o; + } + + #define MAX_STEPS 100 + #define MAX_DIST 100.0 + #define SURF_DIST 0.001 + + float GetDist(float3 p){ + float d = MAX_DIST; + float3 rp = 0; + + rp = opTxRxScale(p, _Position1.xyz, 0, _Scale); + d = opSmoothUnion(d, sdSphere(rp, _Position1.w), 0.35); + + rp = opTxRxScale(p, _Position2.xyz, 0, _Scale); + d = opSmoothUnion(d, sdSphere(rp, _Position2.w), 0.35); + + rp = opTxRxScale(p, _Position3.xyz, 0, _Scale); + d = opSmoothUnion(d, sdSphere(rp, _Position3.w), 0.35); + + rp = opTxRxScale(p, _Position4.xyz, 0, _Scale); + d = opSmoothUnion(d, sdSphere(rp, _Position4.w), 0.35); + + rp = opTxRxScale(p, _Position5.xyz, 0, _Scale); + d = opSmoothUnion(d, sdSphere(rp, _Position5.w), 0.35); + + return d; + } + + float RayMarch(float3 ro, float3 rd) { + float dO = 0.0; + for (int i = 0; i < MAX_STEPS; i++) { + float3 p = ro + rd * dO; + float dS = GetDist(p); + dO += dS; + if (dO > MAX_DIST || dS < SURF_DIST) break; + } + return dO; + } + + fixed4 frag (v2f i) : SV_Target + { + float4 col = float4(1, 1, 1, 1); + float2 uv = i.uv; + + float3 ro = i.ro; + float3 rd = normalize(i.hitPos - ro); + + float d = RayMarch(ro, rd); + + float4 bgCol = tex2Dproj(_GrabpassTex, i.grapPassUV); + + if(d >= MAX_DIST){ + discard; + } + else{ + float3 p = ro + rd * d; + + float3 colR = 0.5 + 0.5 * cos(_Time.y + (p.xyz * 2 + 0.5) + float3(0, 2, 4)); + col.rgb *= colR; + } + + return col; + } + ENDCG + } + } +} diff --git a/Tools/Shaders/PCFeauture_Enable/Editor/PCFeatureEnable_Editor.cs b/Tools/Shaders/PCFeauture_Enable/Editor/PCFeatureEnable_Editor.cs new file mode 100644 index 0000000..310e123 --- /dev/null +++ b/Tools/Shaders/PCFeauture_Enable/Editor/PCFeatureEnable_Editor.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; + +using UdonVR.EditorUtility; + + +[CustomEditor(typeof(PCFeatureEnable)), CanEditMultipleObjects] +public class PCFeatureEnable_Editor : Editor +{ + // code here + + protected virtual void OnEnable() + { + // code here + } + + public override void OnInspectorGUI() + { + //base.OnInspectorGUI(); + //EditorGUILayout.Space(); + serializedObject.Update(); + DrawGUI(); + serializedObject.ApplyModifiedProperties(); + base.OnInspectorGUI(); + } + + protected virtual void DrawGUI() + { + // code here + UdonVR_GUI.Header(new GUIContent("Used to enable the parts of shaders disabled for quest users.")); + } +} \ No newline at end of file diff --git a/Tools/Shaders/PCFeauture_Enable/PCFeatureEnable.cs b/Tools/Shaders/PCFeauture_Enable/PCFeatureEnable.cs new file mode 100644 index 0000000..f4938ba --- /dev/null +++ b/Tools/Shaders/PCFeauture_Enable/PCFeatureEnable.cs @@ -0,0 +1,23 @@ +using UdonSharp; +using UnityEngine; +using VRC.SDKBase; +using VRC.Udon; + +public class PCFeatureEnable : UdonSharpBehaviour +{ + [SerializeField] private Material[] _materials; + + private void Start() + { + foreach (Material mat in _materials) + { + if (!mat.HasProperty("_IsWindows")) continue; +#if UNITY_64 + mat.SetFloat("_IsWindows", 1); +#else + mat.SetFloat("_IsWindows", 0); +#endif + } + gameObject.SetActive(false); + } +} \ No newline at end of file diff --git a/Tools/Shaders/RadialProgressbar.shader b/Tools/Shaders/RadialProgressbar.shader new file mode 100644 index 0000000..9b9c9f8 --- /dev/null +++ b/Tools/Shaders/RadialProgressbar.shader @@ -0,0 +1,139 @@ +Shader "_UdonVR/Toolkit/Unlit/RadialProgressbar" +{ + Properties + { + [HideInInspector]_Percent("Percent", Range(0, 1)) = 1 + [HideInInspector][MaterialToggle]_FlipProgressBar("Flip Progress Bar", Float) = 1 + [HideInInspector][MaterialToggle]_IsBillboard("Billboard", Float) = 0 + + [HideInInspector][MaterialToggle]_IsGradient("Gradient", Float) = 1 + [HideInInspector]_Color1("Color1", Color) = (0, 0.75, 1, 1) + [HideInInspector]_Color2("Color2", Color) = (1, 0.75, 0, 1) + + [HideInInspector][MaterialToggle]_IsHollow("Hollow", Float) = 1 + + [HideInInspector]_Thickness("Thickness", Range(0.005, 0.15)) = 0.03125 + [HideInInspector]_Radius("Radius", Range(0.05, 0.475)) = 0.375 + + [HideInInspector]_Rotation("Rotation", Range(0, 360)) = 0 + + [HideInInspector]_StartAngle("Start Angle", Range(0, 179.9)) = 0 + [HideInInspector]_EndAngle("End Angle", Range(180, 360)) = 360 + [HideInInspector]_Seperation("Seperation", Range(0, 90)) = 0 + } + SubShader + { + Tags { "RenderType"="Transparent" "Queue"="Transparent" "IgnoreProjector"="true" "PreviewType" = "plane" } + LOD 100 + CULL Off + ZWrite Off + Lighting Off + + Blend SrcAlpha OneMinusSrcAlpha + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/UsefulFunctions.cginc" + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/SDF_2D.cginc" + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/SDF_2D_Operations.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + float _Percent; + const fixed _FlipProgressBar; + const fixed _IsBillboard; + + const fixed _IsGradient; + fixed4 _Color1; + fixed4 _Color2; + + const fixed _IsHollow; + + fixed _Thickness; + fixed _Radius; + + float _Rotation; + + float _StartAngle; + float _EndAngle; + float _Seperation; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + + if(_IsBillboard) o.vertex = Billboard(v.vertex); + + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + float2 uv = i.uv; + + if (_FlipProgressBar) uv.x = Remap(uv.x, 0, 1, 1, 0); + uv -= 0.5; + + uv = opRx(uv, 0.75); + if (_Rotation > 0) uv = opRx(uv, Remap(_Rotation, 0, 360, 0, 1)); + + float a = 0; + float s = 0; + if (_IsGradient) + { + a = atan2(uv.x, uv.y); + s = fmod(a + PI2 - PIHalf, PI2) / PI2; + } + + _Thickness = (_IsHollow ? _Thickness * 0.8 : _Thickness); + if (_IsGradient) _Seperation += _Thickness * (_IsHollow ? 200 : 160); + _StartAngle = clamp(_StartAngle + _Seperation, 0, 180); + _EndAngle = clamp(_EndAngle - _Seperation, 180, 360); + + float startPercent = Remap(_StartAngle, 0, 180, 0, 0.5); + float endPercent = Remap(_EndAngle, 180, 360, 0.5, 1); + + _Percent = clamp(_Percent, 0.0001, 1); + _Percent = Remap(_Percent, 0, 1, startPercent, endPercent); + float d = sdArc(uv, Remap(startPercent, 0, 0.5, 0, PI), Remap(_Percent, 0, 1, 0, PI2), _Radius) - _Thickness; + if (_IsHollow) d = opOnion(d, _Thickness * 0.25); + d = opFixDist(d); + + //d = (d == 0 ? 0.5 : d); + clip(d - 0.5); // remove black areas + + fixed4 c = fixed4(0, 0, 0, 0); + if (_IsGradient) + { + c = d * lerp(_Color2, _Color1, clamp(s, 0, 1)); + } + else + { + c = d * _Color1; + } + + return c; + } + ENDCG + } + } + CustomEditor "RadialProgressBar_Editor" +} diff --git a/Tools/Shaders/ShaderImports/Hamster9090901_Functions.cginc b/Tools/Shaders/ShaderImports/Hamster9090901_Functions.cginc new file mode 100644 index 0000000..df888e8 --- /dev/null +++ b/Tools/Shaders/ShaderImports/Hamster9090901_Functions.cginc @@ -0,0 +1,37 @@ +/* + +A collection of functions by Hamster9090901 and is letting being used and also uses input some of their other shaders not just those associated with UdonVR + +*/ + +// include guards that keep the functions from being included more than once +#ifndef Hamster9090901_Functions + #define Hamster9090901_Functions + + float3 HueRotationRadians(float3 input, float offset) + { + float4 K = float4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + float4 P = lerp(float4(input.bg, K.wz), float4(input.gb, K.xy), step(input.b, input.g)); + float4 Q = lerp(float4(P.xyw, input.r), float4(input.r, P.yzx), step(P.x, input.r)); + float D = Q.x - min(Q.w, Q.y); + float E = 1e-10; + float3 hsv = float3(abs(Q.z + (Q.w - Q.y)/(6.0 * D + E)), D / (Q.x + E), Q.x); + + float hue = hsv.x + offset; + hsv.x = (hue < 0) ? hue + 1 : (hue > 1) ? hue - 1 : hue; + + float4 K2 = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + float3 P2 = abs(frac(hsv.xxx + K2.xyz) * 6.0 - K2.www); + return hsv.z * lerp(K2.xxx, saturate(P2 - K2.xxx), hsv.y); + } + float3 HueRotationDegrees(float3 input, float offset) + { + return HueRotationRadians(input, offset / 360); + } + + float Fresnel(float4 vertPos, float vertNormal, float bias, float scale, float power) + { + return bias + scale * pow(1.0 + dot(normalize(ObjSpaceViewDir(vertPos)), vertNormal), power); + } + +#endif \ No newline at end of file diff --git a/Tools/Shaders/ShaderImports/SDF_2D.cginc b/Tools/Shaders/ShaderImports/SDF_2D.cginc new file mode 100644 index 0000000..c10e14e --- /dev/null +++ b/Tools/Shaders/ShaderImports/SDF_2D.cginc @@ -0,0 +1,102 @@ +/* + +A collection of different 2D sdf shapes +Modified by Hamster9090901 + +*/ + +// include guards that keep the functions from being included more than once +#ifndef SDF_2D + #define SDF_2D + + #ifndef PIHalf + #define PIHalf 1.5707963268 + #endif + #ifndef PI + #define PI 3.14159265359 + #endif + #ifndef PI2 + #define PI2 6.28318530718 + #endif + + #ifndef RhombusScale + #define RhombusScale float2(1.7320508, 1) + #endif + + float sdCircle(float2 p, float r) + { + return length(p) - r; + } + + float sdBox(in float2 p, in float2 b) + { + float2 d = abs(p) - b; + return length(max(d, 0)) + min(max(d.x, d.y), 0); + } + + float sdSegment(in float2 p, in float2 a, in float2 b) + { + // to set width subtract output by desired thickness + float2 pa = p - a; + float2 ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0, 1); + return length(pa - ba * h); + } + + float ndot(in float2 a, in float2 b) { return a.x * b.x - a.y * b.y; } + float sdRhombus(in float2 p, in float2 b) + { + p = abs(p); + float h = clamp(ndot(b - 2 * p, b) / dot(b, b), -1, 1); + float d = length(p - 0.5 * b * float2(1 - h, 1 + h)); + return d * sign(p.x * b.y + p.y * b.x - b.x * b.y); + } + + float sdEquilateralTriangle( in float2 p ) + { + const float k = sqrt(3); + p.x = abs(p.x) - 1; + p.y = p.y + 1 / k; + if (p.x + k * p.y > 0 ) p = float2(p.x - k * p.y,-k * p.x - p.y) * 0.5; + p.x -= clamp(p.x, -2, 0); + return -length(p) * sign(p.y); + } + + float sdTriangleIsosceles(in float2 p, in float2 q) + { + p.x = abs(p.x); + float2 a = p - q * clamp(dot(p, q) / dot(q, q), 0, 1); + float2 b = p - q * float2(clamp(p.x / q.x, 0, 1), 1); + float s = -sign(q.y); + float2 d = min(float2(dot(a, a), s * (p.x * q.y - p.y * q.x)), float2(dot(b, b), s * (p.y-q.y))); + return -sqrt(d.x) * sign(d.y); + } + + + + float sdArc(in float2 p, in float a0, in float a1, in float r) + { + float a = fmod(atan2(p.y, p.x), PI2); + + float ap = a - a0; + if (ap < 0) + { + ap += PI2; + } + float a1p = a1 - a0; + if (a1p < 0) + { + a1p += PI2; + } + + if (ap >= a1p) + { + float2 q0 = float2(r * cos(a0), r * sin(a0)); + float2 q1 = float2(r * cos(a1), r * sin(a1)); + return min(length(p - q0), length(p - q1)); + } + + return abs(length(p) - r); + } + +#endif diff --git a/Tools/Shaders/ShaderImports/SDF_2D_Operations.cginc b/Tools/Shaders/ShaderImports/SDF_2D_Operations.cginc new file mode 100644 index 0000000..829eafc --- /dev/null +++ b/Tools/Shaders/ShaderImports/SDF_2D_Operations.cginc @@ -0,0 +1,86 @@ +/* + +A collection of different 2D sdf operations +Modified by Hamster9090901 + +*/ + +#include "UsefulFunctions.cginc" + +// include guards that keep the functions from being included more than once +#ifndef SDF_2D_Operations + #define SDF_2D_Operations + + #ifndef PI2 + #define PI2 6.28318530718 + #endif + + // positioning + float2 opTx(in float2 samplePosition, in float2 offset) + { + return samplePosition - offset; + } + + float2 opRx(in float2 samplePosition, in float rotation) + { + // expects rotation to be between (0 - 1) + float angle = rotation * PI2 * -1; + float sine, cosine; + sincos(angle, sine, cosine); + return float2(cosine * samplePosition.x + sine * samplePosition.y, cosine * samplePosition.y - sine * samplePosition.x); + } + + float2 opScale(in float2 samplePosition, in float scale) + { + return samplePosition / scale; + } + + float opRemapRx(in float rotation) + { + // remaps rotation from (0 - 6.28) or (0 - 360) to (0 - 1) + rotation = rotation <= PI2 ? Remap(rotation, 0, PI2, 0, 1) : Remap(rotation, 0, 360, 0, 1); + return rotation; + } + + float2 opTxRxScale(in float2 samplePosition, in float2 offset, in float rotation, in float scale) + { + samplePosition = opTx(samplePosition, offset); + samplePosition = opRx(samplePosition, rotation); + samplePosition = opScale(samplePosition, scale); + return samplePosition; + } + + + // effects + float opFixDist(in float distance) + { + // fixes the distance output from the sdfs to have a hard edge + return lerp(1, 0, step(0, distance)); + } + + float opRound(in float d, in float r) + { + return d - r; + } + + float opOnion(in float d, in float r) + { + return abs(d) - r; + } + + + + // weird effects + float2 opRepeat(in float2 p, in float s) + { + return fmod(p + s * 0.5, s) - s * 0.5; + } + + float2 opWobble(in float2 position, in float2 frequency, in float2 amount) + { + float2 wobble = sin(position.yx * frequency) * amount; + return position + wobble; + } + +#endif + diff --git a/Tools/Shaders/ShaderImports/SDF_3D.cginc b/Tools/Shaders/ShaderImports/SDF_3D.cginc new file mode 100644 index 0000000..fb8f984 --- /dev/null +++ b/Tools/Shaders/ShaderImports/SDF_3D.cginc @@ -0,0 +1,137 @@ +/* + +A collection of different 3D sdf shapes +Modified by Hamster9090901 + +*/ + +// include guards that keep the functions from being included more than once +#ifndef SDF_3D + #define SDF_3D + + float smin(float a, float b, float k) { + float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0); + return lerp(b, a, h) - k * h * (1.0 - h); + } + + float dot2(in float2 v) { + return dot(v, v); + } + + float dot2(in float3 v) { + return dot(v, v); + } + + float ndot(in float2 a, in float2 b) { + return a.x * b.x - a.y * b.y; + } + + // shapes + float sdSphere(float3 p, float r) { + return length(p) - r; + } + + float sdBox(float3 p, float3 s) { + p = abs(p) - s; + return length(max(p, 0.0)) + min(max(p.x, max(p.y, p.z)), 0.0); + } + + float sdRoundBox(float3 p, float3 b, float r) { + float3 q = abs(p) - b; + return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - r; + } + + float sdBoxFrame(float3 p, float3 s, float e){ + p = abs(p) - s; + float3 q = abs(p + e) - e; + return min(min( + length(max(float3(p.x, q.y, q.z), 0.0)) + min(max(p.x, max(q.y, q.z)), 0.0), + length(max(float3(q.x, p.y, q.z), 0.0)) + min(max(q.x, max(p.y, q.z)), 0.0)), + length(max(float3(q.x, q.y, p.z), 0.0)) + min(max(q.x, max(q.y, p.z)), 0.0)); + } + + float sdTorus(float3 p, float2 r) { + float x = length(p.xz) - r.x; + return length(float2(x, p.y)) - r.y; + } + + float sdCappedTorus(in float3 p, in float2 sc, in float ra, in float rb){ + p.x = abs(p.x); + float k = (sc.y * p.x > sc.x * p.y) ? dot(p.xy, sc) : length(p.xy); + return sqrt(dot(p, p) + ra * ra - 2.0 * ra * k) - rb; + } + + float sdCapsule(float3 p, float3 a, float3 b, float r) { + float3 ab = b - a; + float3 ap = p - a; + + float t = dot(ab, ap) / dot(ab, ab); + t = clamp(t, 0.0, 1.0); + + float3 c = a + t * ab; + + return length(p - c) - r; + } + + float sdCone(in float3 p, in float2 c, float h) + { + // c is the sin/cos of the angle, h is height + // Alternatively pass q instead of (c,h), + // which is the point at the base in 2D + float2 q = h * float2(c.x / c.y, -1.0); + + float2 w = float2(length(p.xz), p.y); + float2 a = w - q * clamp(dot(w, q) / dot(q, q), 0.0, 1.0); + float2 b = w - q * float2(clamp(w.x / q.x, 0.0, 1.0), 1.0); + float k = sign(q.y); + float d = min(dot(a, a), dot(b, b)); + float s = max(k * (w.x * q.y - w.y * q.x), k * (w.y - q.y)); + return sqrt(d) * sign(s); + } + + float sdCappedCylinder(float3 p, float3 a, float3 b, float r) + { + float3 ba = b - a; + float3 pa = p - a; + float baba = dot(ba, ba); + float paba = dot(pa, ba); + float x = length(pa * baba - ba * paba) - r * baba; + float y = abs(paba - baba * 0.5) - baba * 0.5; + float x2 = x * x; + float y2 = y * y * baba; + float d = (max(x, y) < 0.0) ? -min(x2, y2) : (((x > 0.0) ? x2 : 0.0) + ((y > 0.0) ? y2 : 0.0)); + return sign(d) * sqrt(abs(d)) / baba; + } + + float udQuad(float3 p, float3 a, float3 b, float3 c, float3 d) + { + float3 ba = b - a; + float3 pa = p - a; + + float3 cb = c - b; + float3 pb = p - b; + + float3 dc = d - c; + float3 pc = p - c; + + float3 ad = a - d; + float3 pd = p - d; + + float3 nor = cross(ba, ad); + + return sqrt( + (sign(dot(cross(ba, nor), pa)) + + sign(dot(cross(cb, nor), pb)) + + sign(dot(cross(dc, nor), pc)) + + sign(dot(cross(ad, nor), pd)) < 3.0) + ? + min(min(min( + dot2(ba * clamp(dot(ba, pa) / dot2(ba), 0.0, 1.0) - pa), + dot2(cb * clamp(dot(cb, pb) / dot2(cb), 0.0, 1.0) - pb)), + dot2(dc * clamp(dot(dc, pc) / dot2(dc), 0.0, 1.0) - pc)), + dot2(ad * clamp(dot(ad, pd) / dot2(ad), 0.0, 1.0) - pd)) + : + dot(nor, pa) * dot(nor, pa) / dot2(nor)); + } + +#endif \ No newline at end of file diff --git a/Tools/Shaders/ShaderImports/SDF_3D_Operations.cginc b/Tools/Shaders/ShaderImports/SDF_3D_Operations.cginc new file mode 100644 index 0000000..efb483c --- /dev/null +++ b/Tools/Shaders/ShaderImports/SDF_3D_Operations.cginc @@ -0,0 +1,96 @@ +/* + +A collection of different 3D sdf operations +Modified by Hamster9090901 + +*/ + +// include guards that keep the functions from being included more than once +#ifndef SDF_3D_Operations + #define SDF_3D_Operations + + #ifndef PI2 + #define PI2 6.28318530718 + #endif + + float2x2 Rot(float a) { + float s = sin(a); + float c = cos(a); + return float2x2(c, -s, s, c); + } + + // positioning + float3 opTx(in float3 samplePosition, in float3 offset) + { + return samplePosition - offset; + } + + float3 opRx(in float3 samplePosition, in float3 rotation) + { + // expects rotation to be between (0 - 1) + float angle = 0; + if(rotation.x){ + angle = rotation.x * PI2 * -1; + samplePosition.zy = mul(samplePosition.zy, Rot(angle)); + } + if(rotation.y){ + angle = rotation.y * PI2 * -1; + samplePosition.xz = mul(samplePosition.xz, Rot(angle)); + } + if(rotation.z){ + angle = rotation.z * PI2 * -1; + samplePosition.xy = mul(samplePosition.xy, Rot(angle)); + } + return samplePosition; + } + + float3 opScale(in float3 samplePosition, in float3 scale) + { + return samplePosition / scale; + } + + float3 opTxRxScale(in float3 samplePosition, in float3 offset, in float rotation, in float3 scale) + { + samplePosition = opTx(samplePosition, offset); + samplePosition = opRx(samplePosition, rotation); + samplePosition = opScale(samplePosition, scale); + return samplePosition; + } + + // effects + float opFixDist(in float distance) + { + // fixes the distance output from the sdfs to have a hard edge + return lerp(1, 0, step(0, distance)); + } + + float opRound(in float d, in float r) + { + return d - r; + } + + float opOnion(in float d, in float r) + { + return abs(d) - r; + } + + float opSmoothUnion(float d1, float d2, float k) { + float h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0); + return lerp(d2, d1, h) - k * h * (1.0 - h); + } + + + + // weird effects + float3 opRepeat(in float3 p, in float s) + { + return fmod(p + s * 0.5, s) - s * 0.5; + } + + float3 opWobble(in float3 position, in float3 frequency, in float3 amount) + { + float3 wobble = sin(position.xyz * frequency) * amount; + return position + wobble; + } + +#endif \ No newline at end of file diff --git a/Tools/Shaders/ShaderImports/UsefulFunctions.cginc b/Tools/Shaders/ShaderImports/UsefulFunctions.cginc new file mode 100644 index 0000000..66b1c22 --- /dev/null +++ b/Tools/Shaders/ShaderImports/UsefulFunctions.cginc @@ -0,0 +1,107 @@ +/* + +A collection of useful functions +By Hamster9090901 + +*/ + +// include guards that keep the functions from being included more than once +#ifndef UsefulFunctions + #define UsefulFunctions + + float Random(in float2 xy) + { + return frac(sin(dot(xy, float2(12.9898, 78.233))) * 43758.5453123); + } + + float Remap(float value, float low1, float high1, float low2, float high2) + { + return low2 + (value - low1) * (high2 - low2) / (high1 - low1); + } + + float3 FromRGB(in float r, in float g, in float b) + { + r = Remap(r, 0, 255, 0, 1); + g = Remap(g, 0, 255, 0, 1); + b = Remap(b, 0, 255, 0, 1); + return float3(r, g, b); + } + float3 FromRGB(in float3 rgb) + { + return FromRGB(rgb.r, rgb.g, rgb.b); + } + + float clampMin(in float value, in float min) + { + return value < min ? min : value; + } + + float clampMax(in float value, in float max) + { + return value > max ? max : value; + } + + // returns the UV position of a sprite in a sprite sheet by index based on number of rows and columns + float2 Flipbook(in float2 uv, in uint columns, in uint rows, in uint index) + { + // get single sprite size + float2 size = float2(1.0 / columns, 1.0 / rows); + int totalFrames = columns * rows; + + // wrap x and y indexes + int indexX = index % rows; + int indexY = floor((index % totalFrames) / columns); + + // get offset to sprite index + float2 offset = float2(size.x * indexX, -size.y * indexY); + + float2 newUV = uv * size; // get single sprite UV + newUV.y = newUV.y + size.y * (rows - 1); // flip Y (to start 0 from top) + + return newUV + offset; // return UV with offset + } + + // passes color though with the applied flipbook color if there is a sprite sheet + fixed4 ApplyFlipbookColor(sampler2D spriteSheet, float2 uv, fixed4 colIn, fixed4 flipbookColMultiplier, int index, int columns, int rows, fixed cutoutVal) + { + // spriteSheet | the texture sampler for the spritesheet + // index | index of sprite in the sprite sheet + // rows | number of rows in sprite sheet + // columns | number of columns in sprite sheet + // cutoutVal | used to remove black when under that value + + float2 output = Flipbook(frac(uv), columns, rows, index); // get the UV of the index + fixed4 c = tex2D(spriteSheet, output); // get the sprite + c *= flipbookColMultiplier; // add the flipbook color + + //fixed alpha = colIn.a; // get alpha + //c.a = alpha; // copy alpha over + + return (c.r <= cutoutVal && c.g <= cutoutVal && c.b <= cutoutVal) ? colIn : c; + } + + // billboards a mesh towards the camera + float4 Billboard(float4 vertex) + { + float3 vpos = mul((float3x3)unity_ObjectToWorld, vertex.xyz); + float4 worldCoord = float4(unity_ObjectToWorld._m03, unity_ObjectToWorld._m13, unity_ObjectToWorld._m23, 1); + float4 viewPos = mul(UNITY_MATRIX_V, worldCoord) + float4(vpos, 0); + float4 outPos = mul(UNITY_MATRIX_P, viewPos); + return outPos; + } + + float3 Checkerboard(float2 uv, float3 colorA, float3 colorB, float2 frequency) + { + uv = (uv.xy + 0.5) * frequency; + float4 derivatives = float4(ddx(uv), ddy(uv)); + float2 duv_length = sqrt(float2(dot(derivatives.xz, derivatives.xz), dot(derivatives.yw, derivatives.yw))); + float width = 1.0; + float2 distance3 = 4.0 * abs(frac(uv + 0.25) - 0.5) - width; + float2 scale = 0.35 / duv_length.xy; + float freqLimiter = sqrt(clamp(1.1f - max(duv_length.x, duv_length.y), 0.0, 1.0)); + float2 vector_alpha = clamp(distance3 * scale.xy, -1.0, 1.0); + float alpha = saturate(0.5f + 0.5f * vector_alpha.x * vector_alpha.y * freqLimiter); + return lerp(colorA, colorB, alpha.xxx); + } + +#endif \ No newline at end of file diff --git a/Tools/Shaders/Shader_Assets/noise.png b/Tools/Shaders/Shader_Assets/noise.png new file mode 100644 index 0000000..67cf4f3 Binary files /dev/null and b/Tools/Shaders/Shader_Assets/noise.png differ diff --git a/Tools/Shaders/Sparkle.shader b/Tools/Shaders/Sparkle.shader new file mode 100644 index 0000000..af88f59 --- /dev/null +++ b/Tools/Shaders/Sparkle.shader @@ -0,0 +1,103 @@ +// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' + +Shader "_UdonVR/Toolkit/Unlit/Sparkle" +{ + Properties + { + [HideInInspector]_MainTex ("Noise Tex", 2D) = "white" {} + [HideInInspector]_NoiseRotationSpeed ("Noise Rotation Speed", Range(1, 100)) = 25 + [HideInInspector]_SparkleOffset ("Sparkle Offset", Range(0, 1)) = 0.5 + [HideInInspector]_Color ("Color", Color) = (0.6941177, 1, 0.9647059, 1) + [HideInInspector]_NoiseScale ("Noise Scale", Vector) = (1, 1, 0, 0) + + // fresnel + /* + [MaterialToggle] _EnableFresnel("Enable Fresnel", Float) = 0 + _FresnelBias("Fresnel Bias", Float) = -0.35 + _FresnelScale("Fresnel Scale", Float) = 1 + _FresnelPower("Fresnel Power", Float) = 0.85 + */ + } + SubShader + { + Tags { "RenderType"="Opaque" } + LOD 100 + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + #include "Assets/_UdonVR/Tools/Shaders/ShaderImports/Hamster9090901_Functions.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + + float4 pos : POSITION; + half3 normal : NORMAL; + }; + + struct v2f + { + float4 vertex : SV_POSITION; + float2 uv : TEXCOORD0; + + float3 viewDir : TEXCOORD1; + //float fresnel : TEXCOORD2; + }; + + sampler2D _MainTex; + float4 _MainTex_ST; + + float _NoiseRotationSpeed; + float _SparkleOffset; + + half4 _Color; + + float4 _NoiseScale; + + float _EnableFresnel; + float _FresnelBias; + float _FresnelScale; + float _FresnelPower; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = TRANSFORM_TEX(v.uv, _MainTex); + + o.viewDir = _WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz; + + //o.fresnel = Fresnel(v.pos, v.normal, _FresnelBias, _FresnelScale, _FresnelPower); + + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + float3 noise = tex2D(_MainTex, i.uv * _NoiseScale.xy); + noise = HueRotationDegrees(noise, _NoiseRotationSpeed * _Time.y); + noise = normalize(noise - _SparkleOffset); + + float3 viewDir = 1.0 - normalize(i.viewDir); + + float d = dot(noise, viewDir); + d = saturate(d); + + float4 col = float4(0, 0, 0, 1); + col.rgb = d * _Color; + //if (_EnableFresnel) col.rgb += (1.0 - i.fresnel) * _Color.rgb; + + return col; + } + ENDCG + } + } + CustomEditor "Sparkle_Editor" +} diff --git a/Tools/UdonVR_CameraImage.cs b/Tools/UdonVR_CameraImage.cs new file mode 100644 index 0000000..69661c6 --- /dev/null +++ b/Tools/UdonVR_CameraImage.cs @@ -0,0 +1,95 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System.IO; + +public class UdonVR_CameraImage : MonoBehaviour +{ + public int resWidth = 1200; // resolution width of saved image + public int resHeight = 900; // resolution height of saved image + + private new Camera camera; // camera to take image from + + /// + /// Formats the final string to contain the date and the resolution of the image in the final name. + /// + /// Path to save image. + /// Name of saved image. + /// Width of saved image. + /// Height of saved image. + /// Formated FileName and Path. + private string ScreenShotName(string path, string name, int width, int height) + { + if (path.EndsWith("/") || path.EndsWith("\\")) path = path.Remove(path.Length - 1, 1); // remove unnessasary slashes + + path = Path.GetFullPath(path); // get full path from relative path + + return string.Format("{0}\\{1}_{2}x{3}_{4}.png", + path, name, + width, height, + System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")); + } + + /// + /// Takes an image using the camera the Script is attatched to. + /// + /// Name of file when saved. + /// Absolute path to save the file. + /// Remove the Script from the camera when finished. + public void TakeImage(string fileName, ref string filePath, bool removeComponentAfter = true) + { + if (camera == null) camera = GetComponent(); // get camera component + if (camera == null) Debug.LogError("Camera Component could not be found on GameObject."); // throw error if no camera was found + + #region Get screenshot from camera to save + RenderTexture rt = new RenderTexture(resWidth, resHeight, 24); // create render texture + Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false); // create image to save render texture to + camera.targetTexture = rt; // set camera render texture + camera.Render(); // enable camera render texture + RenderTexture.active = rt; // set active render texture to created render texture + screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0); // save pixels of render texture + camera.targetTexture = null; // remove render texture + RenderTexture.active = null; // disable render texture to be save + +#if UNITY_EDITOR + DestroyImmediate(rt); // remove render texture +#else + Destroy(rt); // remove render texture +#endif + #endregion + + #region Save image to disc + byte[] _bytes = screenShot.EncodeToPNG(); // convert screenshot to byte array + string _filePath = ScreenShotName(filePath, fileName, resWidth, resHeight); // get formated filepath + System.IO.File.WriteAllBytes(_filePath, _bytes); // save screenshot to disc + Debug.Log(string.Format("Took screenshot to: {0}", _filePath)); // send message to console that a file was saved + #endregion + + filePath = _filePath; // set filepath to the formated filepath for loading the image + + #region Remove component after screenshot is saved +#if UNITY_EDITOR + if (removeComponentAfter) DestroyImmediate(this); // remove script +#else + if (removeComponentAfter) Destroy(this); // remove script +#endif + #endregion + } + + /// + /// Takes an image using the camera the Script is attatched to. + /// + /// Name of file when saved. + /// Absolute path to save the file. + /// Sets resolution Width for image set to -1 to use default. + /// Sets resolution Height for image set to -1 to use default. + /// Remove the Script from the camera when finished. + public void TakeImage(string fileName, ref string filePath, int width = -1, int height = -1, bool removeComponentAfter = true) + { + if (width != -1) resWidth = width; + if (height != -1) resHeight = height; + + TakeImage(fileName, ref filePath, removeComponentAfter); + } +} + diff --git a/Tools/Whitelist/Toggle/Whitelist_GameObject_Toggle.cs b/Tools/Whitelist/Toggle/Whitelist_GameObject_Toggle.cs new file mode 100644 index 0000000..e8b64c5 --- /dev/null +++ b/Tools/Whitelist/Toggle/Whitelist_GameObject_Toggle.cs @@ -0,0 +1,75 @@ +//Copyright (c) 2021 UdonVR LLC +using UdonSharp; +using UnityEngine; +using VRC.SDKBase; +using VRC.Udon; + +#if !COMPILER_UDONSHARP && UNITY_EDITOR // These using statements must be wrapped in this check to prevent issues on builds +using UnityEditor; +using UdonSharpEditor; +using System.Collections.Generic; +using System.Linq; +using UdonVR.EditorUtility; +#endif + +public class Whitelist_GameObject_Toggle : UdonSharpBehaviour +{ + public string[] Players; + [Tooltip("These are OFF by default\nWhen a player's name matches the list, these will TURN ON.")] + public GameObject[] TargetsDefaultOff; + [Tooltip("These are ON by default\nWhen a player's name matches the list, these will TURN OFF.")] + public GameObject[] TargetsDefaultOn; + + private bool isMatched = false; + private void Start() + { + foreach(string _str in Players) + { + if (Networking.LocalPlayer.displayName == _str) + { + isMatched = true; + } + } + + foreach(GameObject _obj in TargetsDefaultOn) + { + _obj.SetActive(!isMatched); + } + foreach (GameObject _obj in TargetsDefaultOff) + { + _obj.SetActive(isMatched); + } + } + +#if !COMPILER_UDONSHARP && UNITY_EDITOR + [CustomEditor(typeof(Whitelist_GameObject_Toggle))] + public class EasyLocks_Button_Editor : Editor + { + private SerializedProperty TargetsDefaultOff; + private SerializedProperty TargetsDefaultOn; + private Transform baseTransform; + + private void OnEnable() + { + TargetsDefaultOff = serializedObject.FindProperty("TargetsDefaultOff"); + TargetsDefaultOn = serializedObject.FindProperty("TargetsDefaultOn"); + baseTransform = ((Whitelist_GameObject_Toggle)target).transform; + } + public void OnSceneGUI() + { + for (int i = 0; i < TargetsDefaultOff.arraySize; i++) + { + GameObject _target = (GameObject)TargetsDefaultOff.GetArrayElementAtIndex(i).objectReferenceValue; + if (_target != null) + EditorHelper.ShowTransform(_target.transform, baseTransform, false); + } + for (int i = 0; i < TargetsDefaultOn.arraySize; i++) + { + GameObject _target = (GameObject)TargetsDefaultOn.GetArrayElementAtIndex(i).objectReferenceValue; + if (_target != null) + EditorHelper.ShowTransform(_target.transform, baseTransform, false); + } + } + } +#endif +} diff --git a/Tools/Whitelist/Toggle/Whitelist_Pickup_Toggle.cs b/Tools/Whitelist/Toggle/Whitelist_Pickup_Toggle.cs new file mode 100644 index 0000000..6aacd4c --- /dev/null +++ b/Tools/Whitelist/Toggle/Whitelist_Pickup_Toggle.cs @@ -0,0 +1,75 @@ + +using UdonSharp; +using UnityEngine; +using VRC.SDKBase; +using VRC.Udon; + +#if !COMPILER_UDONSHARP && UNITY_EDITOR // These using statements must be wrapped in this check to prevent issues on builds +using UnityEditor; +using UdonSharpEditor; +using System.Collections.Generic; +using System.Linq; +using UdonVR.EditorUtility; +#endif + +public class Whitelist_Pickup_Toggle : UdonSharpBehaviour +{ + public string[] Players; + [Tooltip("These are OFF by default\nWhen a player's name matches the list, these will TURN ON.")] + public VRC_Pickup[] TargetsDefaultOff; + [Tooltip("These are ON by default\nWhen a player's name matches the list, these will TURN OFF.")] + public VRC_Pickup[] TargetsDefaultOn; + + private bool isMatched = false; + private void Start() + { + foreach(string _str in Players) + { + if (Networking.LocalPlayer.displayName == _str) + { + isMatched = true; + } + } + + foreach(VRC_Pickup _obj in TargetsDefaultOn) + { + _obj.enabled = (!isMatched); + } + foreach (VRC_Pickup _obj in TargetsDefaultOff) + { + _obj.enabled = (isMatched); + } + } + +#if !COMPILER_UDONSHARP && UNITY_EDITOR + [CustomEditor(typeof(Whitelist_Pickup_Toggle))] + public class EasyLocks_Button_Editor : Editor + { + private SerializedProperty TargetsDefaultOff; + private SerializedProperty TargetsDefaultOn; + private Transform baseTransform; + + private void OnEnable() + { + TargetsDefaultOff = serializedObject.FindProperty("TargetsDefaultOff"); + TargetsDefaultOn = serializedObject.FindProperty("TargetsDefaultOn"); + baseTransform = ((Whitelist_Pickup_Toggle)target).transform; + } + public void OnSceneGUI() + { + for (int i = 0; i < TargetsDefaultOff.arraySize; i++) + { + GameObject _target = (GameObject)TargetsDefaultOff.GetArrayElementAtIndex(i).objectReferenceValue; + if (_target != null) + EditorHelper.ShowTransform(_target.transform, baseTransform, false); + } + for (int i = 0; i < TargetsDefaultOn.arraySize; i++) + { + GameObject _target = (GameObject)TargetsDefaultOn.GetArrayElementAtIndex(i).objectReferenceValue; + if (_target != null) + EditorHelper.ShowTransform(_target.transform, baseTransform, false); + } + } + } +#endif +}