From 4a98081a6b03b145e93b00815a6a100dee972491 Mon Sep 17 00:00:00 2001 From: luvoid <72511739+luvoid@users.noreply.github.com> Date: Sun, 6 Aug 2023 20:47:29 -0400 Subject: [PATCH] Pre-Release Version 0.11 --- ButtJiggle.sln | 6 + ButtJiggle/BoneMorph_Patch.cs | 31 +- ButtJiggle/ButtJiggle.cs | 123 ++++++-- ButtJiggle/ButtJiggle.csproj | 16 +- ButtJiggle/ButtJiggleUI.cs | 210 +++++-------- ButtJiggle/ConfigurationManagerAttributes.cs | 153 ++++++++++ ButtJiggle/JiggleBoneHelper.cs | 169 +++++++---- ButtJiggle/JiggleBoneOverride.cs | 109 ++----- ButtJiggle/MaidJiggleOverride.cs | 57 ++++ ButtJiggle/Override.cs | 42 +++ ButtJiggle/Properties/AssemblyInfo.cs | 4 +- ButtJiggle/Stiffness.cs | 60 ++++ ButtJiggle/TBodyPatch.cs | 103 +++++-- ButtJiggle/UIControlFactory.cs | 169 +++++++++++ ButtJiggle/UnityRuntimeGizmos.cs | 298 +++++++++++++++++++ 15 files changed, 1210 insertions(+), 340 deletions(-) create mode 100644 ButtJiggle/ConfigurationManagerAttributes.cs create mode 100644 ButtJiggle/MaidJiggleOverride.cs create mode 100644 ButtJiggle/Override.cs create mode 100644 ButtJiggle/Stiffness.cs create mode 100644 ButtJiggle/UIControlFactory.cs create mode 100644 ButtJiggle/UnityRuntimeGizmos.cs diff --git a/ButtJiggle.sln b/ButtJiggle.sln index 850a51a..1378e15 100644 --- a/ButtJiggle.sln +++ b/ButtJiggle.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 17.3.32922.545 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ButtJiggle", "ButtJiggle\ButtJiggle.csproj", "{21D55085-3997-4A44-B09B-2B1735CA6D2A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Configuration.Json", "..\BepInEx.Configuration.Json\BepInEx.Configuration.Json\BepInEx.Configuration.Json.csproj", "{FF4B6D69-D118-4506-B65E-DB55555F7F13}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {21D55085-3997-4A44-B09B-2B1735CA6D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU {21D55085-3997-4A44-B09B-2B1735CA6D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU {21D55085-3997-4A44-B09B-2B1735CA6D2A}.Release|Any CPU.Build.0 = Release|Any CPU + {FF4B6D69-D118-4506-B65E-DB55555F7F13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF4B6D69-D118-4506-B65E-DB55555F7F13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF4B6D69-D118-4506-B65E-DB55555F7F13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF4B6D69-D118-4506-B65E-DB55555F7F13}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ButtJiggle/BoneMorph_Patch.cs b/ButtJiggle/BoneMorph_Patch.cs index 1a0bf81..53738ef 100644 --- a/ButtJiggle/BoneMorph_Patch.cs +++ b/ButtJiggle/BoneMorph_Patch.cs @@ -9,24 +9,35 @@ internal static class BoneMorph_Patch [HarmonyPrefix, HarmonyPatch(nameof(BoneMorph_.Blend))] static void Blend_Prefix(BoneMorph_ __instance) { - if (!__instance.m_tbSkin.body.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) return; + // Copy helper's transform so we can see if it gets changed - // Copy helper's transform so we can see if it get's changed - jbhHipL.CopyLocalTransformToBone(); - jbhHipR.CopyLocalTransformToBone(); + if (__instance.m_tbSkin.body.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) + { + jbhHipL.CheckForChanges(); + jbhHipR.CheckForChanges(); + } - jbhHipL.IsChangeCheck = true; - jbhHipR.IsChangeCheck = true; + if (__instance.m_tbSkin.body.TryGetPelvisHelper(out var jbhPelvis)) + { + jbhPelvis.CheckForChanges(); + } } [HarmonyPostfix, HarmonyPatch(nameof(BoneMorph_.Blend))] static void Blend_Postfix(BoneMorph_ __instance) { - if (!__instance.m_tbSkin.body.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) return; - // Copy the bones's transform and any changes back to the helper - jbhHipL.CopyLocalTransformFromBone(); - jbhHipR.CopyLocalTransformFromBone(); + + if (__instance.m_tbSkin.body.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) + { + jbhHipL.ApplyChanges(); + jbhHipR.ApplyChanges(); + } + + if (__instance.m_tbSkin.body.TryGetPelvisHelper(out var jbhPelvis)) + { + jbhPelvis.ApplyChanges(); + } } } } diff --git a/ButtJiggle/ButtJiggle.cs b/ButtJiggle/ButtJiggle.cs index 60333d3..1b76f54 100644 --- a/ButtJiggle/ButtJiggle.cs +++ b/ButtJiggle/ButtJiggle.cs @@ -1,24 +1,19 @@ using BepInEx; using BepInEx.Configuration; +using BepInEx.Configuration.Json; using BepInEx.Logging; using HarmonyLib; -using MaidStatus; -using PrivateMaidMode; -using RootMotion; +using Newtonsoft.Json; using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; +using System.IO; using System.Reflection; using System.Security; using System.Security.Permissions; -using System.Text; using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.UI; -using UniverseLib; -using UniverseLib.Input; -using UniverseLib.UI; +using UnityEngine.Events; +using static Newtonsoft.Json.JsonToken; + // If there are errors in the above using statements, restore the NuGet packages: @@ -39,8 +34,9 @@ // This is the major & minor version with an asterisk (*) appended to auto increment numbers. [assembly: AssemblyVersion(COM3D2.ButtJiggle.PluginInfo.PLUGIN_VERSION + ".*")] +[assembly: AssemblyFileVersion(COM3D2.ButtJiggle.PluginInfo.PLUGIN_VERSION)] -// These two lines tell your plugin to not give a flying fuck about accessing private variables/classes whatever. +// These two lines tell your plugin to not give a flying flip about accessing private variables/classes whatever. // It requires a publicized stubb of the library with those private objects though. [module: UnverifiableCode] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] @@ -55,7 +51,7 @@ public static class PluginInfo // The name of this plugin. public const string PLUGIN_NAME = "Butt Jiggle"; // The version of this plugin. - public const string PLUGIN_VERSION = "0.7"; + public const string PLUGIN_VERSION = "0.11"; } } @@ -76,8 +72,21 @@ public sealed partial class ButtJiggle : BaseUnityPlugin private ManualLogSource _Logger => base.Logger; internal static ConfigEntry UIHotkey; + + + internal static ConfigEntry ButtJiggle_ConfigVersion; + internal static ConfigEntry ButtJiggle_Enabled; - internal static ConfigEntry ButtJiggle_DefaultSoftness; + internal static ConfigEntry ButtJiggle_DefaultSoftness_Hip; + internal static ConfigEntry ButtJiggle_DefaultSoftness_Pelvis; + + //internal static ConfigEntry Experimental_PelvisEnabled; + + internal static ConfigEntry GlobalOverride_Enabled; + internal static ConfigEntry GlobalOverride_Json; + internal static ConfigEntryJson GlobalOverride_Settings; + + public UnityEvent OnGlobalOverrideUpdated; void Awake() { @@ -85,23 +94,45 @@ void Awake() Instance = this; // Binds the configuration. In other words it sets your ConfigEntry var to your config setup. - ButtJiggle_Enabled = Config.Bind("Butt Jiggle", "Enabled" , true, "Description"); - ButtJiggle_DefaultSoftness = Config.Bind("Butt Jiggle", "DefaultSoftness", 0.5f, "Description"); + ButtJiggle_ConfigVersion = Config.Bind("ButtJiggle", "ConfigVersion", 0.0, new ConfigDescription( + "Do not change this", + null, + new ConfigurationManagerAttributes() + { + IsAdvanced = true, + ReadOnly = true, + } + )); + + ButtJiggle_Enabled = Config.Bind("ButtJiggle", "Enabled", true); + ButtJiggle_DefaultSoftness_Hip = Config.Bind("ButtJiggle", "DefaultSoftness.Hip" , MaidJiggleOverride.Default.HipOverride.Softness.Value); + ButtJiggle_DefaultSoftness_Pelvis = Config.Bind("ButtJiggle", "DefaultSoftness.Pelvis", MaidJiggleOverride.Default.PelvisOverride.Softness.Value); + if (ButtJiggle_ConfigVersion.Value < 1.11) + { + ButtJiggle_DefaultSoftness_Pelvis.Value = MaidJiggleOverride.Default.PelvisOverride.Softness.Value; + } + + //Experimental_PelvisEnabled = Config.Bind("Experimental", "PelvisEnabled", false); + + + ConfigBindGlobalOverride(); // Add the keybind KeyboardShortcut hotkey = new KeyboardShortcut(KeyCode.A, KeyCode.LeftControl); - UIHotkey = Config.Bind("UI", "Toggle", hotkey, "Recomend using Ctrl A for 'Ass'"); + UIHotkey = Config.Bind("UI", "Toggle", hotkey, "Recommend using Ctrl A for 'Ass'"); - Logger.LogInfo("Patching ButtJiggle"); + ButtJiggle_ConfigVersion.Value = double.Parse(PluginInfo.PLUGIN_VERSION); + + Logger.LogDebug("Patching ButtJiggle"); Harmony.CreateAndPatchAll(typeof(ButtJiggle)); - Logger.LogInfo("Patching TBodyPatch"); + Logger.LogDebug("Patching TBodyPatch"); Harmony.CreateAndPatchAll(typeof(TBodyPatch)); - Logger.LogInfo("Patching BoneMorph_Patch"); + Logger.LogDebug("Patching BoneMorph_Patch"); Harmony.CreateAndPatchAll(typeof(BoneMorph_Patch)); - Logger.LogInfo("Finished patching"); + Logger.LogDebug("Finished patching"); } void Start() @@ -109,18 +140,58 @@ void Start() Universe_Init(); } - private bool m_IsHotkeyHandled = false; void Update() { - if (UIHotkey.Value.IsDown() && !m_IsHotkeyHandled) + if (UIHotkey.Value.IsDown()) { ToggleUI(); - m_IsHotkeyHandled = true; } - else if (UIHotkey.Value.IsUp()) + } + + private void ConfigBindGlobalOverride() + { + if (GlobalOverride_Enabled != null) return; + + string defaultJson = SerializeGlobalOverride(); + + GlobalOverride_Enabled = Config.Bind("GlobalOverride", "Enabled", false , "Enable global override of jiggle settings"); + //GlobalOverride_Json = Config.Bind("Global Override", "Json" , defaultJson, "The jiggle settings in JSON format" ); + GlobalOverride_Settings = Config.BindJson("GlobalOverride", "Settings", MaidJiggleOverride.Default, "The settings used for the global override"); + + if (ButtJiggle_ConfigVersion.Value < 1.11) { - m_IsHotkeyHandled = false; + GlobalOverride_Settings.Value = MaidJiggleOverride.Default; } + + JiggleBoneHelper.UseGlobalOverride = GlobalOverride_Enabled.Value; + JiggleBoneHelper.GlobalOverride = GlobalOverride_Settings.Value; + + GlobalOverride_Enabled.SettingChanged += delegate (object sender, EventArgs eventArgs) + { + JiggleBoneHelper.UseGlobalOverride = GlobalOverride_Enabled.Value; + OnGlobalOverrideUpdated.Invoke(); + }; + GlobalOverride_Settings.SettingChanged += delegate (object sender, EventArgs eventArgs) + { + JiggleBoneHelper.GlobalOverride = GlobalOverride_Settings.Value; + OnGlobalOverrideUpdated.Invoke(); + }; + } + + public static void ConfigSaveGlobalOverride() + { + Logger.LogDebug("ConfigSaveGlobalOverride"); + GlobalOverride_Enabled .Value = JiggleBoneHelper.UseGlobalOverride; + //GlobalOverride_Json .Value = SerializeGlobalOverride(); + GlobalOverride_Settings.Value = JiggleBoneHelper.GlobalOverride; + } + + private static string SerializeGlobalOverride() + { + JsonSerializer serializer = new JsonSerializer(); + StringWriter writer = new StringWriter(); + serializer.Serialize(writer, JiggleBoneHelper.GlobalOverride); + return writer.ToString(); } } } diff --git a/ButtJiggle/ButtJiggle.csproj b/ButtJiggle/ButtJiggle.csproj index 141f276..d8446b7 100644 --- a/ButtJiggle/ButtJiggle.csproj +++ b/ButtJiggle/ButtJiggle.csproj @@ -49,10 +49,10 @@ - + all - + @@ -81,17 +81,29 @@ + + + + + + + + + {ff4b6d69-d118-4506-b65e-db55555f7f13} + BepInEx.Configuration.Json + + diff --git a/ButtJiggle/ButtJiggleUI.cs b/ButtJiggle/ButtJiggleUI.cs index 030a230..8c29a39 100644 --- a/ButtJiggle/ButtJiggleUI.cs +++ b/ButtJiggle/ButtJiggleUI.cs @@ -1,12 +1,13 @@ using BepInEx; using BepInEx.Logging; -using System.Reflection; -using System.Reflection.Emit; +using System.Collections.Generic; +using System.Linq; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using UniverseLib; using UniverseLib.UI; +using UniverseLib.UI.Models; using UniverseLib.UI.Panels; using UniverseLib.Utility; @@ -72,7 +73,7 @@ private void Universe_Init() UniverseLib.Config.UniverseLibConfig config = new() { Force_Unlock_Mouse = true, - Allow_UI_Selection_Outside_UIBase = false, + Allow_UI_Selection_Outside_UIBase = true, Disable_EventSystem_Override = false, Disable_Fallback_EventSystem_Search = false, }; @@ -118,160 +119,97 @@ protected override void OnClosePanelClicked() } protected override void ConstructPanelContent() + { + try + { + SafeConstructPanelContent(); + } + catch (System.Exception ex) + { + ButtJiggle.Logger.LogError(ex); + } + } + + protected void SafeConstructPanelContent() { //Text myText = UIFactory.CreateLabel(ContentRoot, "myText", "Hello world"); //UIFactory.SetLayoutElement(myText.gameObject, minWidth: 200, minHeight: 25); - CreateControl(ContentRoot, "Debug Mode", get: () => JiggleBoneHelper.DebugMode, set: (value) => JiggleBoneHelper.DebugMode = value); + UIControlFactory.CreateControl(ContentRoot, "Debug Mode", get: () => JiggleBoneHelper.DebugMode, set: (value) => JiggleBoneHelper.DebugMode = value); var overrideColumn = UIFactory.CreateVerticalGroup(ContentRoot, "overrides", false, false, true, true, childAlignment: TextAnchor.UpperLeft); UIFactory.SetLayoutElement(overrideColumn, minWidth: 300, minHeight: 300); { - CreateControl(overrideColumn, "Use Global Override", + UIControlFactory.CreateControl(overrideColumn, "Use Global Override", get: () => JiggleBoneHelper.UseGlobalOverride, - set: (value) => JiggleBoneHelper.UseGlobalOverride = value); + set: (value) => { JiggleBoneHelper.UseGlobalOverride = value; OnGlobalOverrideUIUpdated(); }, + listen: ButtJiggle.Instance.OnGlobalOverrideUpdated); - CreateControl(overrideColumn, "Blend Value" , get: () => JiggleBoneHelper.GlobalOverride.BlendValue , set: (value) => JiggleBoneHelper.GlobalOverride.BlendValue = value); - CreateControl(overrideColumn, "Blend Value 2" , get: () => JiggleBoneHelper.GlobalOverride.BlendValue2 , set: (value) => JiggleBoneHelper.GlobalOverride.BlendValue2 = value); - CreateControl(overrideColumn, "Gravity" , get: () => JiggleBoneHelper.GlobalOverride.Gravity , set: (value) => JiggleBoneHelper.GlobalOverride.Gravity = value); - CreateControl(overrideColumn, "Clothed Stiffness" , get: () => JiggleBoneHelper.GlobalOverride.ClothedStiffness, set: (value) => JiggleBoneHelper.GlobalOverride.ClothedStiffness = value); - CreateControl(overrideColumn, "Naked Stiffness" , get: () => JiggleBoneHelper.GlobalOverride.NakedStiffness , set: (value) => JiggleBoneHelper.GlobalOverride.NakedStiffness = value); - CreateControl(overrideColumn, "Softness" , get: () => JiggleBoneHelper.GlobalOverride.Softness , set: (value) => JiggleBoneHelper.GlobalOverride.Softness = value); - CreateControl(overrideColumn, "Up & Down" , get: () => JiggleBoneHelper.GlobalOverride.UpDown , set: (value) => JiggleBoneHelper.GlobalOverride.UpDown = value); - CreateControl(overrideColumn, "Yori" , get: () => JiggleBoneHelper.GlobalOverride.Yori , set: (value) => JiggleBoneHelper.GlobalOverride.Yori = value); - CreateControl(overrideColumn, "Squash & Stretch" , get: () => JiggleBoneHelper.GlobalOverride.SquashAndStretch, set: (value) => JiggleBoneHelper.GlobalOverride.SquashAndStretch = value); - CreateControl(overrideColumn, "Front Stretch" , get: () => JiggleBoneHelper.GlobalOverride.FrontStretch , set: (value) => JiggleBoneHelper.GlobalOverride.FrontStretch = value); - CreateControl(overrideColumn, "Side Stretch" , get: () => JiggleBoneHelper.GlobalOverride.SideStretch , set: (value) => JiggleBoneHelper.GlobalOverride.SideStretch = value); - CreateControl(overrideColumn, "Enable Scale X" , get: () => JiggleBoneHelper.GlobalOverride.EnableScaleX , set: (value) => JiggleBoneHelper.GlobalOverride.EnableScaleX = value); - CreateControl(overrideColumn, "Limit Rotation" , get: () => JiggleBoneHelper.GlobalOverride.LimitRotation , set: (value) => JiggleBoneHelper.GlobalOverride.LimitRotation = value); - CreateControl(overrideColumn, "Limit Rot Decay" , get: () => JiggleBoneHelper.GlobalOverride.LimitRotDecay , set: (value) => JiggleBoneHelper.GlobalOverride.LimitRotDecay = value); - } - } + List slotColumns = null; - private GameObject CreateControl(GameObject parent, string labelText, System.Func get, UnityAction set = null) - { - GameObject control = null; - if (typeof(T) == typeof(bool)) { - if (get is not System.Func getCasted) throw new System.ArgumentException(); - if (set is not UnityAction setCasted) setCasted = null; - control = CreateBoolControl(parent, labelText, getCasted, setCasted); - } - else if (ParseUtility.CanParse(typeof(T))) - { - control = CreateParsedControl(parent, labelText, get, set); - } - else - { - var name = labelText.Replace(" ", ""); - var errorText = UIFactory.CreateLabel(parent, $"controlError_{name}", " " + labelText); - errorText.text = $"{labelText} : Could not create control for type {typeof(T).Name}"; - control = errorText.gameObject; - UIFactory.SetLayoutElement(control, minWidth: 200, minHeight: 25); - } - return control; - } + var slotDropdownRoot = UIFactory.CreateDropdown(overrideColumn, "slotSelectDropdown", out Dropdown slotSelectDropdown, "Select a slot...", 14, + (slotIndex) => + { + for (int i = 0; i < slotColumns.Count; i++) + { + slotColumns[i].SetActive(i == slotIndex); + } + } + ); + UIFactory.SetLayoutElement(slotDropdownRoot, minWidth: 300, minHeight: 25, preferredWidth: 300, preferredHeight: 25); - private GameObject CreateBoolControl(GameObject parent, string labelText, System.Func get, UnityAction set = null) - { - var name = "controlBool_" + labelText.Replace(" ", ""); - var toggleRoot = UIFactory.CreateToggle(parent, name, out var toggle, out var label); - label.text = labelText; - toggle.isOn = get(); - toggle.interactable = (set != null); - if (set != null) - { - toggle.onValueChanged.AddListener((value) => - { - set(value); - ButtJiggle.Logger.LogInfo($"{name} = {get()}"); - }); + slotColumns = new List() { + CreateJiggleBoneOverrideColumn(overrideColumn, "Hips" , () => ref JiggleBoneHelper.GlobalOverride.HipOverride , OnGlobalOverrideUIUpdated, ButtJiggle.Instance.OnGlobalOverrideUpdated), + CreateJiggleBoneOverrideColumn(overrideColumn, "Pelvis", () => ref JiggleBoneHelper.GlobalOverride.PelvisOverride, OnGlobalOverrideUIUpdated, ButtJiggle.Instance.OnGlobalOverrideUpdated), + }; + + int i = 0; + slotSelectDropdown.AddOptions( + slotColumns.Select((go) => + { + ButtJiggle.Logger.LogDebug($"Add slotSelect {go.name}"); + go.SetActive(i++ == 0); + return go.name; + }).ToList() + ); + slotSelectDropdown.RefreshShownValue(); + slotSelectDropdown.Show(); } - UIFactory.SetLayoutElement(toggleRoot, minWidth: 200, minHeight: 25); - return toggleRoot; } - private GameObject CreateParsedControl(GameObject parent, string labelText, System.Func get, UnityAction set = null) + delegate ref JiggleBoneOverride OverrideRefGetter(); + + private static GameObject CreateJiggleBoneOverrideColumn( + GameObject parent, + string name, + OverrideRefGetter refGet, + UnityAction onSet, + UnityEvent listen = null) { - var name = labelText.Replace(" ", ""); - var row = UIFactory.CreateHorizontalGroup(parent, $"control{typeof(T).Name}_{name}", false, false, true, true, childAlignment: TextAnchor.MiddleLeft); - UIFactory.SetLayoutElement(row, minWidth: 200, minHeight: 25); + var column = UIFactory.CreateVerticalGroup(parent, name, false, false, true, true, childAlignment: TextAnchor.UpperLeft); + UIFactory.SetLayoutElement(column, minWidth: 350, minHeight: 300); { - var inputFieldRef = UIFactory.CreateInputField(row, "input", labelText); - UIFactory.SetLayoutElement(inputFieldRef.GameObject, minWidth: 200, minHeight: 25); - inputFieldRef.Text = ParseUtility.ToStringForInput(get(), typeof(T)); - inputFieldRef.Component.interactable = (set != null); - if (set != null) - { - inputFieldRef.OnValueChanged += (str) => - { - if (ParseUtility.TryParse(str, typeof(T), out var newValue, out _)) { - set((T)newValue); - ButtJiggle.Logger.LogInfo($"{name} = {get()}"); - } - inputFieldRef.Text = ParseUtility.ToStringForInput(get(), typeof(T)); - }; - } - - UIFactory.CreateLabel(row, "label", " "+labelText); - UIFactory.SetLayoutElement(row, minWidth: 200, minHeight: 25); + UIControlFactory.CreateControl(column, "Blend Value (auto)", refGet: () => ref refGet().BlendValue , onSet, listen); + UIControlFactory.CreateControl(column, "Blend Value 2" , refGet: () => ref refGet().BlendValue2 , onSet, listen); + UIControlFactory.CreateControl(column, "Gravity" , refGet: () => ref refGet().Gravity , onSet, listen); + UIControlFactory.CreateControl(column, "Clothed Stiffness" , refGet: () => ref refGet().ClothedStiffness, onSet, listen); + UIControlFactory.CreateControl(column, "Naked Stiffness" , refGet: () => ref refGet().NakedStiffness , onSet, listen); + UIControlFactory.CreateControl(column, "Softness" , refGet: () => ref refGet().Softness , onSet, listen); + UIControlFactory.CreateControl(column, "Up & Down" , refGet: () => ref refGet().UpDown , onSet, listen); + UIControlFactory.CreateControl(column, "Yori" , refGet: () => ref refGet().Yori , onSet, listen); + UIControlFactory.CreateControl(column, "Squash & Stretch" , refGet: () => ref refGet().SquashAndStretch, onSet, listen); + UIControlFactory.CreateControl(column, "Front Stretch" , refGet: () => ref refGet().FrontStretch , onSet, listen); + UIControlFactory.CreateControl(column, "Side Stretch" , refGet: () => ref refGet().SideStretch , onSet, listen); + UIControlFactory.CreateControl(column, "Enable Scale X" , refGet: () => ref refGet().EnableScaleX , onSet, listen); + UIControlFactory.CreateControl(column, "Limit Rotation" , refGet: () => ref refGet().LimitRotation , onSet, listen); + UIControlFactory.CreateControl(column, "Limit Rot Decay" , refGet: () => ref refGet().LimitRotDecay , onSet, listen); } - return row; + return column; } - private GameObject CreateControl(GameObject parent, string labelText, System.Func> get, UnityAction> set = null) + private void OnGlobalOverrideUIUpdated() { - var name = labelText.Replace(" ", ""); - - var row = UIFactory.CreateHorizontalGroup(parent, $"controlOverride_{name}", false, false, true, true, childAlignment: TextAnchor.MiddleLeft); - UIFactory.SetLayoutElement(row, minWidth: 200, minHeight: 25); - { - var tempOverride = get(); - - var enabledRoot = UIFactory.CreateToggle(row, $"controlBool_{name}_Enabled", out var enableToggle, out var enabledLabel); - UIFactory.SetLayoutElement(enabledRoot, minWidth: 40, minHeight: 25, preferredWidth: 40, preferredHeight: 25); - enabledLabel.text = " | "; - enableToggle.isOn = tempOverride.Enabled; - enableToggle.interactable = (set != null); - if (set != null) - { - enableToggle.onValueChanged.AddListener((value) => - { - tempOverride = get(); - tempOverride.Enabled = value; - set(tempOverride); - ButtJiggle.Logger.LogInfo($"{name}.Enabled == {get().Enabled}"); - }); - } - - System.Func valueGet = () => - { - tempOverride = get(); - return tempOverride.Value; - }; - UnityAction valueSet = (set == null) ? null : (value) => - { - tempOverride = get(); - tempOverride.Value = value; - set(tempOverride); - ButtJiggle.Logger.LogInfo($"{name}.Value == {get().Value}"); - }; - - GameObject valueControl = null; - if (typeof(T) == typeof(Stiffness)) - { - if (valueGet is not System.Func valueGetCasted) throw new System.ArgumentException(); - if (valueSet is not UnityAction valueSetCasted) valueSetCasted = null; - - valueControl = CreateControl(row, labelText, - get: () => (Vector2)valueGetCasted(), - set: (value) => valueSetCasted((Stiffness)value)); - } - else - { - valueControl = CreateControl(row, labelText, valueGet, valueSet); - } - } - return row; + ButtJiggle.ConfigSaveGlobalOverride(); } // override other methods as desired diff --git a/ButtJiggle/ConfigurationManagerAttributes.cs b/ButtJiggle/ConfigurationManagerAttributes.cs new file mode 100644 index 0000000..d4b01a8 --- /dev/null +++ b/ButtJiggle/ConfigurationManagerAttributes.cs @@ -0,0 +1,153 @@ +namespace COM3D2.ButtJiggle +{ + /// + /// Class that specifies how a setting should be displayed inside the ConfigurationManager settings window. + /// + /// Usage: + /// This class template has to be copied inside the plugin's project and referenced by its code directly. + /// make a new instance, assign any fields that you want to override, and pass it as a tag for your setting. + /// + /// If a field is null (default), it will be ignored and won't change how the setting is displayed. + /// If a field is non-null (you assigned a value to it), it will override default behavior. + /// + /// + /// + /// Here's an example of overriding order of settings and marking one of the settings as advanced: + /// + /// // Override IsAdvanced and Order + /// Config.Bind("X", "1", 1, new ConfigDescription("", null, new ConfigurationManagerAttributes { IsAdvanced = true, Order = 3 })); + /// // Override only Order, IsAdvanced stays as the default value assigned by ConfigManager + /// Config.Bind("X", "2", 2, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 1 })); + /// Config.Bind("X", "3", 3, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 2 })); + /// + /// + /// + /// + /// You can read more and see examples in the readme at https://github.com/BepInEx/BepInEx.ConfigurationManager + /// You can optionally remove fields that you won't use from this class, it's the same as leaving them null. + /// +#pragma warning disable 0169, 0414, 0649 + internal sealed class ConfigurationManagerAttributes + { + /// + /// Should the setting be shown as a percentage (only use with value range settings). + /// + public bool? ShowRangeAsPercent; + + /// + /// Custom setting editor (OnGUI code that replaces the default editor provided by ConfigurationManager). + /// See below for a deeper explanation. Using a custom drawer will cause many of the other fields to do nothing. + /// + public System.Action CustomDrawer; + + /// + /// Custom setting editor that allows polling keyboard input with the Input (or UnityInput) class. + /// Use either CustomDrawer or CustomHotkeyDrawer, using both at the same time leads to undefined behaviour. + /// + public CustomHotkeyDrawerFunc CustomHotkeyDrawer; + + /// + /// Custom setting draw action that allows polling keyboard input with the Input class. + /// Note: Make sure to focus on your UI control when you are accepting input so user doesn't type in the search box or in another setting (best to do this on every frame). + /// If you don't draw any selectable UI controls You can use `GUIUtility.keyboardControl = -1;` on every frame to make sure that nothing is selected. + /// + /// + /// CustomHotkeyDrawer = (ConfigEntryBase setting, ref bool isEditing) => + /// { + /// if (isEditing) + /// { + /// // Make sure nothing else is selected since we aren't focusing on a text box with GUI.FocusControl. + /// GUIUtility.keyboardControl = -1; + /// + /// // Use Input.GetKeyDown and others here, remember to set isEditing to false after you're done! + /// // It's best to check Input.anyKeyDown and set isEditing to false immediately if it's true, + /// // so that the input doesn't have a chance to propagate to the game itself. + /// + /// if (GUILayout.Button("Stop")) + /// isEditing = false; + /// } + /// else + /// { + /// if (GUILayout.Button("Start")) + /// isEditing = true; + /// } + /// + /// // This will only be true when isEditing is true and you hold any key + /// GUILayout.Label("Any key pressed: " + Input.anyKey); + /// } + /// + /// + /// Setting currently being set (if available). + /// + /// + /// Set this ref parameter to true when you want the current setting drawer to receive Input events. + /// The value will persist after being set, use it to see if the current instance is being edited. + /// Remember to set it to false after you are done! + /// + public delegate void CustomHotkeyDrawerFunc(BepInEx.Configuration.ConfigEntryBase setting, ref bool isCurrentlyAcceptingInput); + + /// + /// Show this setting in the settings screen at all? If false, don't show. + /// + public bool? Browsable; + + /// + /// Category the setting is under. Null to be directly under the plugin. + /// + public string Category; + + /// + /// If set, a "Default" button will be shown next to the setting to allow resetting to default. + /// + public object DefaultValue; + + /// + /// Force the "Reset" button to not be displayed, even if a valid DefaultValue is available. + /// + public bool? HideDefaultButton; + + /// + /// Force the setting name to not be displayed. Should only be used with a to get more space. + /// Can be used together with to gain even more space. + /// + public bool? HideSettingName; + + /// + /// Optional description shown when hovering over the setting. + /// Not recommended, provide the description when creating the setting instead. + /// + public string Description; + + /// + /// Name of the setting. + /// + public string DispName; + + /// + /// Order of the setting on the settings list relative to other settings in a category. + /// 0 by default, higher number is higher on the list. + /// + public int? Order; + + /// + /// Only show the value, don't allow editing it. + /// + public bool? ReadOnly; + + /// + /// If true, don't show the setting by default. User has to turn on showing advanced settings or search for it. + /// + public bool? IsAdvanced; + + /// + /// Custom converter from setting type to string for the built-in editor textboxes. + /// + public System.Func ObjToStr; + + /// + /// Custom converter from string to setting type for the built-in editor textboxes. + /// + public System.Func StrToObj; + } + +} diff --git a/ButtJiggle/JiggleBoneHelper.cs b/ButtJiggle/JiggleBoneHelper.cs index 497056a..9236fb5 100644 --- a/ButtJiggle/JiggleBoneHelper.cs +++ b/ButtJiggle/JiggleBoneHelper.cs @@ -3,25 +3,29 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Remoting.Messaging; using System.Text; using TriLib; using UnityEngine; using UnityEngine.SocialPlatforms; -using static MathCM; -using static RootMotion.FinalIK.IKSolver; +using UnityEngine.UI; +using UnityRuntimeGizmos; namespace COM3D2.ButtJiggle { public class JiggleBoneHelper : MonoBehaviour { - public bool IsChangeCheck = false; [SerializeField] private TBody m_TBody; public Transform TargetBone; public jiggleBone Jiggle; + [field: SerializeField] public MaidJiggleOverride.Slot Slot { get; private set; } + [field: SerializeField] public bool IsChangeCheck { get; private set; } public Transform JiggleBone => Jiggle.transform; + [SerializeField] private Transform m_JiggleOffsetBone; + [SerializeField] private Transform m_JiggleDefBone; [SerializeField] private Transform m_JiggleSubBone; [SerializeField] private Transform m_JiggleOutputBone; @@ -31,68 +35,95 @@ public class JiggleBoneHelper : MonoBehaviour public static bool UseGlobalOverride = false; - public static JiggleBoneOverride GlobalOverride = JiggleBoneOverride.None; + public static MaidJiggleOverride GlobalOverride = MaidJiggleOverride.Default; public static bool DebugMode = false; - [SerializeField] private LineRenderer[] m_LineRenderers = new LineRenderer[5]; - [SerializeField] private SphereRenderer m_SphereRenderer; - private static Material m_DebugMaterial; - - public static JiggleBoneHelper CreateFromBone(Transform targetBone, TBody tbody) + [SerializeField] private LineRenderer[] m_LineRenderers = new LineRenderer[2]; + [SerializeField] private GizmoRenderer m_SphereRenderer; + [SerializeField] private Material m_DebugMaterial; + + /// + /// Converts a bone to a , wrapping it with a . + /// + /// + /// If possible, substitute references to the target bone with the returned , + /// so manipulation is applied to the helper instead of the target. + /// Manipulations that cannot be redirected to the helper can be captured with . + /// + /// The target bone to wrap. + /// The body to which the helper will be assigned. + /// The wrapping + public static JiggleBoneHelper WrapBone(Transform targetBone, TBody tbody, MaidJiggleOverride.Slot slot, Quaternion? localRotation = null, bool useSubBone = true) { + if (localRotation == null) + localRotation = Quaternion.identity; var helperBone = CopyBone(targetBone, targetBone.name + "_helper"); + var jiggleOffset = CopyBone(targetBone, targetBone.name + "_jiggleOffset"); + var jiggleBone = CopyBone(targetBone, targetBone.name + "_jiggle"); - var subBone = CopyBone(jiggleBone, jiggleBone.name + "_sub" ); + jiggleBone.SetParent(jiggleOffset, true); + jiggleBone.localRotation = localRotation.Value; + + var jiggleDef = CopyBone(jiggleBone, jiggleBone.name + "Def"); + + var subBone = CopyBone(jiggleBone, jiggleBone.name + "_sub"); subBone.SetParent(jiggleBone, true); Transform jiggleNub = jiggleBone.Find(jiggleBone.name + "_nub"); - subBone.position = (jiggleNub != null) ? jiggleNub.position : jiggleBone.TransformPoint(Vector3.right * 0.2f); + subBone.position = (jiggleNub != null) ? jiggleNub.position : jiggleBone.TransformPoint(Vector3.left * 0.2f); subBone.localEulerAngles = new Vector3(0, 0, 180); - - var outputBone = CopyBone(targetBone, targetBone.name + "_copy"); - outputBone.SetParent(subBone, true); var jb = jiggleBone.gameObject.AddComponent(); jb.BlendValue = 1; jb.BlendValue2 = 1; jb.MuneL_y = 1; //bone.name.Contains("_R") ? -1 : 1; - jb.boneAxis = new Vector3(-1, -1, 0).normalized; - jb.MuneUpDown = 30; + // jb.boneAxis = boneAxis; + jb.MuneUpDown = 0; jb.MuneYori = 0; - jb.m_fMuneYawaraka = ButtJiggle.ButtJiggle_DefaultSoftness.Value; + jb.m_fMuneYawaraka = slot switch + { + MaidJiggleOverride.Slot.Hip => ButtJiggle.ButtJiggle_DefaultSoftness_Hip.Value, + MaidJiggleOverride.Slot.Pelvis => ButtJiggle.ButtJiggle_DefaultSoftness_Pelvis.Value, + _ => throw new ArgumentOutOfRangeException(nameof(slot)), + }; + var outputBone = CopyBone(targetBone, targetBone.name + "_output"); + outputBone.SetParent(useSubBone ? subBone : jiggleBone, true); var helper = helperBone.gameObject.AddComponent(); helper.TargetBone = targetBone; helper.Jiggle = jb; + helper.Slot = slot; + helper.m_JiggleOffsetBone = jiggleOffset; + helper.m_JiggleDefBone = jiggleDef; helper.m_JiggleSubBone = subBone; helper.m_JiggleOutputBone = outputBone; helper.m_TBody = tbody; - if (GameMain.Instance.VRMode || true) + return helper; + } + + public void SetCollider(Vector3 center) + { + if (m_HitParent == null) { Transform hitParent = GameObject.Instantiate(Resources.Load("OVR/SphereParent"))?.transform; - Transform hitChild = GameObject.Instantiate(Resources.Load("OVR/SphereChild" ))?.transform; + Transform hitChild = GameObject.Instantiate(Resources.Load("OVR/SphereChild"))?.transform; hitChild.GetComponent().connectedBody = hitParent.GetComponent(); - hitParent.parent = helper.JiggleBone; - hitChild .parent = helper.JiggleBone; - hitParent.localPosition = helper.m_JiggleSubBone.localPosition; - hitChild .localPosition = helper.m_JiggleSubBone.localPosition; - - helper.m_HitParent = hitParent; - helper.m_HitChild = hitChild ; - helper.m_HitChildCollider = hitChild.GetComponent(); - var center = new Vector3(0.3f, 0.1f, -0.7f); - if (targetBone.name.Contains("_R")) - { - center.x *= -1; - } - helper.m_HitChildCollider.center = center; + hitParent.parent = JiggleBone; + hitChild.parent = JiggleBone; + hitParent.localPosition = m_JiggleSubBone.localPosition; + hitChild.localPosition = m_JiggleSubBone.localPosition; + + m_HitParent = hitParent; + m_HitChild = hitChild; + m_HitChildCollider = hitChild.GetComponent(); } - return helper; + m_HitChildCollider.center = center; } + void Start() { for (int i = 0; i < m_LineRenderers.Length; i++) @@ -102,8 +133,7 @@ void Start() if (m_HitChild != null) { - m_SphereRenderer = m_HitChild.gameObject.AddComponent(); - m_SphereRenderer.Material = m_DebugMaterial; + m_SphereRenderer = m_HitChild.gameObject.AddComponent(); } } @@ -131,14 +161,14 @@ public void LateUpdateSelf() if (IsChangeCheck) ApplyChanges(); - CopyLocalTransform(this.transform, Jiggle.transform); - Jiggle.defQtn = this.transform.localRotation; + CopyLocalTransform(this.transform, m_JiggleOffsetBone); + Jiggle.defQtn = m_JiggleDefBone.localRotation; if (UseGlobalOverride) { //ButtJiggle.Logger.LogInfo("Use Global Override!"); var origValues = JiggleBoneOverride.From(Jiggle); - GlobalOverride.ApplyTo(Jiggle); + GlobalOverride[Slot].ApplyTo(Jiggle); Jiggle.LateUpdateSelf(); origValues.ApplyTo(Jiggle); } @@ -187,19 +217,41 @@ public static void CopyTransform(Transform sourceBone, Transform targetBone) targetBone.localScale = sourceBone.localScale; } + /// + /// Check if any changes are made to the wrapped target bone + /// between now and the next call, + /// or until the next call. + /// + public void CheckForChanges() + { + CopyLocalTransformToBone(); + IsChangeCheck = true; + } + + /// + /// Apply any changes made to the wrapped target bone back to this source bone. + /// + /// If changes are not currently being checked for. public void ApplyChanges() { + if (!IsChangeCheck) throw new InvalidOperationException("Not currently checking for changes"); CopyLocalTransformFromBone(); IsChangeCheck = false; } - public void CopyLocalTransformToBone() + /// + /// Copies the transform of this helper bone to the target bone that it is wrapping. + /// + protected void CopyLocalTransformToBone() { CopyLocalTransform(this.transform, TargetBone.transform); //JiggleBone.defQtn = this.transform.localRotation; } - public void CopyLocalTransformFromBone() + /// + /// Copies the transform of the wrapped target bone to this helper bone. + /// + protected void CopyLocalTransformFromBone() { CopyLocalTransform(TargetBone.transform, this.transform); } @@ -225,16 +277,19 @@ private void DebugDraw() Vector3 localForward = Jiggle.boneAxis * Jiggle.targetDistance; Vector3 boneForward = Jiggle.transform.TransformDirection(localForward); Vector3 boneUp = Jiggle.transform.TransformDirection(Vector3.up); - Vector3 lookTarget = Jiggle.transform.position + boneForward; + Vector3 lookTarget = Jiggle.transform.position + boneForward * 0.2f; + Vector3 dynamicForward = Jiggle.dynamicPos - Jiggle.transform.position; + Vector3 dynamicTarget = dynamicForward * 0.2f + Jiggle.transform.position; Jiggle.transform.localRotation = origRotation; - DrawRay(0, Jiggle.transform.position, boneForward, Color.blue); - DrawRay(1, Jiggle.transform.position, boneUp * 0.2f, Color.green); - DrawRay(2, lookTarget, Vector3.up * 0.2f, Color.yellow); - DrawRay(3, Jiggle.dynamicPos, Vector3.up * 0.2f, Color.red); + RuntimeGizmos.DrawRay(Jiggle.transform.position, boneUp * 0.1f, Color.green); + RuntimeGizmos.DrawRay(Jiggle.transform.position, boneForward * 0.2f, Color.blue); + RuntimeGizmos.DrawRay(Jiggle.transform.position, dynamicForward * 0.2f, Color.red); + //RuntimeGizmos.DrawRay(dynamicTarget, Vector3.up * 0.1f, Color.red); - DrawBone(4, TargetBone, Color.white); + DrawBone(0, JiggleBone, Color.cyan); + DrawBone(1, TargetBone, Color.white); if (m_HitChildCollider != null) { @@ -287,12 +342,10 @@ private static Material CreateMaterial() private void DrawBone(int lineIndex, Transform bone, Color color) { Transform nub = bone.Find(bone.name + "_nub")?.transform; - Vector3 offset = (nub != null) ? nub.position - bone.position : bone.TransformDirection(Vector3.right) * 0.2f; - DrawRay(lineIndex, bone.position, offset, color); - } - - private void DrawRay(int lineIndex, Vector3 start, Vector3 dir, Color color) - { + + Vector3 start = bone.position; + Vector3 dir = (nub != null) ? nub.position - bone.position : bone.TransformDirection(Vector3.left) * 0.2f; + var line = m_LineRenderers[lineIndex]; if (line == null) { @@ -302,12 +355,14 @@ private void DrawRay(int lineIndex, Vector3 start, Vector3 dir, Color color) line.enabled = true; - line.startColor = color * 0.5f; + line.startColor = color; line.endColor = color; + line.startWidth = 1f * line.widthMultiplier; + line.endWidth = line.startWidth / 5f * line.widthMultiplier; line.SetPositions(new Vector3[] { start, start + dir }); } - + private void DrawSphereCollider(SphereCollider sphereCollider, Color color) { if (m_SphereRenderer == null) @@ -317,9 +372,7 @@ private void DrawSphereCollider(SphereCollider sphereCollider, Color color) } m_SphereRenderer.enabled = true; - m_SphereRenderer.Radius = sphereCollider.radius; - m_SphereRenderer.Center = sphereCollider.center; - m_SphereRenderer.Color = color; + m_SphereRenderer.Gizmo = RuntimeGizmos.WireSphere(sphereCollider.center, sphereCollider.radius, color); } } } diff --git a/ButtJiggle/JiggleBoneOverride.cs b/ButtJiggle/JiggleBoneOverride.cs index 026e373..fdf0f99 100644 --- a/ButtJiggle/JiggleBoneOverride.cs +++ b/ButtJiggle/JiggleBoneOverride.cs @@ -1,7 +1,12 @@ using System; +using System.Runtime.Serialization; +using Newtonsoft.Json; namespace COM3D2.ButtJiggle { + using static OverrideExtensions; + + [JsonObject] public struct JiggleBoneOverride { public Override BlendValue ; @@ -22,7 +27,8 @@ public struct JiggleBoneOverride public Override LimitRotation ; public Override LimitRotDecay ; - public static JiggleBoneOverride None = new JiggleBoneOverride() + [JsonIgnore] + public static readonly JiggleBoneOverride Default = new JiggleBoneOverride() { BlendValue = DefaultValue(1f), BlendValue2 = DefaultValue(1f), @@ -30,7 +36,7 @@ public struct JiggleBoneOverride ClothedStiffness = DefaultValue(new Stiffness(0.070f, 0.27f)), NakedStiffness = DefaultValue(new Stiffness(0.055f, 0.22f)), Softness = DefaultValue(0.5f), - UpDown = DefaultValue(30f), + UpDown = DefaultValue(0f), Yori = DefaultValue(0f), SquashAndStretch = DefaultValue(true), SideStretch = DefaultValue(-0.2f), @@ -41,6 +47,24 @@ public struct JiggleBoneOverride }; + public JiggleBoneOverride(JiggleBoneOverride toCopy) + { + BlendValue = toCopy.BlendValue ; + BlendValue2 = toCopy.BlendValue2 ; + Gravity = toCopy.Gravity ; + ClothedStiffness = toCopy.ClothedStiffness; + NakedStiffness = toCopy.NakedStiffness ; + Softness = toCopy.Softness ; + UpDown = toCopy.UpDown ; + Yori = toCopy.Yori ; + SquashAndStretch = toCopy.SquashAndStretch; + SideStretch = toCopy.SideStretch ; + FrontStretch = toCopy.FrontStretch ; + EnableScaleX = toCopy.EnableScaleX ; + LimitRotation = toCopy.LimitRotation ; + LimitRotDecay = toCopy.LimitRotDecay ; + } + public static JiggleBoneOverride From(jiggleBone jb) { JiggleBoneOverride jbOverride = new(); @@ -77,86 +101,5 @@ public void ApplyTo(jiggleBone jb) jb.m_fLimitRot = this.LimitRotation .AssignToIfEnabled(ref jb.m_fLimitRot ); jb.m_fLimitRotDecay = this.LimitRotDecay .AssignToIfEnabled(ref jb.m_fLimitRotDecay); } - - - public static Override DefaultValue(T value) - { - return new Override() - { - Enabled = false, - Value = value, - }; - } - } - - public struct Override - { - public bool Enabled; - public T Value; - - public T AssignToIfEnabled(ref T field) - { - if (Enabled) field = Value; - return Enabled ? Value : field; - } - - public static implicit operator Override(T value) - { - return new Override() - { - Enabled = true, - Value = value, - }; - } - } - - public struct Stiffness - { - public float Min; - public float Max; - - public Stiffness(float min, float max) - { - Min = min; - Max = max; - } - - public void CopyTo(float[] floats) - { - if (floats.Length < 2) throw new ArgumentException(); - floats[1] = Min; - floats[0] = Max; - } - - public static implicit operator Stiffness(float[] floats) - { - if (floats.Length < 2) throw new ArgumentException(); - return new Stiffness(floats[1], floats[0]); - } - - public static explicit operator float[](Stiffness stiffness) - { - float[] floats = new float[2]; - stiffness.CopyTo(floats); - return floats; - } - - public static explicit operator UnityEngine.Vector2(Stiffness stiffness) - { - return new UnityEngine.Vector2(stiffness.Min, stiffness.Max); - } - - public static explicit operator Stiffness(UnityEngine.Vector2 vector) - { - return new Stiffness(vector.x, vector.y); - } - } - public static class StiffnessExtensions - { - public static float[] AssignToIfEnabled(this Override stiffnessOverride, ref float[] floats) - { - if (stiffnessOverride.Enabled) stiffnessOverride.Value.CopyTo(floats); - return stiffnessOverride.Enabled ? (float[])stiffnessOverride.Value : floats; - } } } diff --git a/ButtJiggle/MaidJiggleOverride.cs b/ButtJiggle/MaidJiggleOverride.cs new file mode 100644 index 0000000..0702300 --- /dev/null +++ b/ButtJiggle/MaidJiggleOverride.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; + +namespace COM3D2.ButtJiggle +{ + using static OverrideExtensions; + + [JsonObject] + public struct MaidJiggleOverride + { + public enum Slot + { + Hip, + Pelvis, + } + + public JiggleBoneOverride HipOverride; + public JiggleBoneOverride PelvisOverride; + + [JsonIgnore] + public static readonly MaidJiggleOverride Default = new MaidJiggleOverride() + { + HipOverride = JiggleBoneOverride.Default, + PelvisOverride = new JiggleBoneOverride(JiggleBoneOverride.Default) { + Softness = DefaultValue(0.475f), + }, + }; + + [JsonIgnore] + public JiggleBoneOverride this[Slot slot] + { + get => slot switch + { + Slot.Hip => HipOverride, + Slot.Pelvis => PelvisOverride, + _ => throw new ArgumentOutOfRangeException(nameof(slot)), + }; + set + { + switch (slot) + { + case Slot.Hip: + HipOverride = value; + break; + case Slot.Pelvis: + PelvisOverride = value; + break; + default: + throw new ArgumentOutOfRangeException(nameof(slot)); + } + } + } + } +} diff --git a/ButtJiggle/Override.cs b/ButtJiggle/Override.cs new file mode 100644 index 0000000..1456bf0 --- /dev/null +++ b/ButtJiggle/Override.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace COM3D2.ButtJiggle +{ + [JsonObject] + public struct Override + { + public bool Enabled; + public T Value; + + public T AssignToIfEnabled(ref T field) + { + if (Enabled) field = Value; + return Enabled ? Value : field; + } + + public static implicit operator Override(T value) + { + return new Override() + { + Enabled = true, + Value = value, + }; + } + } + + public static class OverrideExtensions + { + public static Override DefaultValue(T value) + { + return new Override() + { + Enabled = false, + Value = value, + }; + } + } +} diff --git a/ButtJiggle/Properties/AssemblyInfo.cs b/ButtJiggle/Properties/AssemblyInfo.cs index 2aaf7f1..2ffa33b 100644 --- a/ButtJiggle/Properties/AssemblyInfo.cs +++ b/ButtJiggle/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ [assembly: AssemblyTitle("ButtJiggle")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("https://github.com/luvoid/COM3D2.ButtJiggle.Plugin")] [assembly: AssemblyProduct("ButtJiggle")] -[assembly: AssemblyCopyright("Copyright © 2022")] +[assembly: AssemblyCopyright("Copyright © luvoid 2023")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("Neutral")] diff --git a/ButtJiggle/Stiffness.cs b/ButtJiggle/Stiffness.cs new file mode 100644 index 0000000..566d29d --- /dev/null +++ b/ButtJiggle/Stiffness.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace COM3D2.ButtJiggle +{ + [JsonObject] + public struct Stiffness + { + public float Min; + public float Max; + + public Stiffness(float min, float max) + { + Min = min; + Max = max; + } + + public void CopyTo(float[] floats) + { + if (floats.Length < 2) throw new ArgumentException(); + floats[1] = Min; + floats[0] = Max; + } + + public static implicit operator Stiffness(float[] floats) + { + if (floats.Length < 2) throw new ArgumentException(); + return new Stiffness(floats[1], floats[0]); + } + + public static explicit operator float[](Stiffness stiffness) + { + float[] floats = new float[2]; + stiffness.CopyTo(floats); + return floats; + } + + public static explicit operator UnityEngine.Vector2(Stiffness stiffness) + { + return new UnityEngine.Vector2(stiffness.Min, stiffness.Max); + } + + public static explicit operator Stiffness(UnityEngine.Vector2 vector) + { + return new Stiffness(vector.x, vector.y); + } + } + + public static class OverrideStiffnessExtensions + { + public static float[] AssignToIfEnabled(this Override stiffnessOverride, ref float[] floats) + { + if (stiffnessOverride.Enabled) stiffnessOverride.Value.CopyTo(floats); + return stiffnessOverride.Enabled ? (float[])stiffnessOverride.Value : floats; + } + } +} diff --git a/ButtJiggle/TBodyPatch.cs b/ButtJiggle/TBodyPatch.cs index 618bf1c..529bbee 100644 --- a/ButtJiggle/TBodyPatch.cs +++ b/ButtJiggle/TBodyPatch.cs @@ -25,6 +25,22 @@ public static bool TryGetHipHelpers(this TBody __instance, out JiggleBoneHelper return (jbhHipL != null) && (jbhHipR != null); } + public static bool TryGetPelvisHelper(this TBody __instance, out JiggleBoneHelper jbhPelvis) + { + jbhPelvis = null; + + if (!__instance.isLoadedBody) return false; + if (__instance.boMAN) return false; + + var pelvis = __instance.Pelvis; + var pelvisSclHelper = pelvis?.Find($"{pelvis.name}_SCL__helper"); + if (pelvisSclHelper == null) return false; + + jbhPelvis = pelvisSclHelper.GetComponent(); + + return jbhPelvis != null; + } + [HarmonyPostfix, HarmonyPatch(nameof(TBody.LoadBody_R))] static void LoadBody_R(TBody __instance) { @@ -35,23 +51,39 @@ static void LoadBody_R(TBody __instance) var hipL = __instance.Hip_L; var hipR = __instance.Hip_R; + if (hipL != null && hipR != null) + { + var jbhHipL = JiggleBoneHelper.WrapBone(hipL, __instance, MaidJiggleOverride.Slot.Hip, Quaternion.Euler(0, 180, 90)); + var jbhHipR = JiggleBoneHelper.WrapBone(hipR, __instance, MaidJiggleOverride.Slot.Hip, Quaternion.Euler(0, 180, 90)); - if (hipL == null || hipR == null) return; + jbhHipL.SetCollider(new Vector3(0.0f, 0.4f, 0.1f)); + jbhHipR.SetCollider(new Vector3(0.0f, 0.4f, 0.1f)); - var jbhHipL = JiggleBoneHelper.CreateFromBone(hipL, __instance); - var jbhHipR = JiggleBoneHelper.CreateFromBone(hipR, __instance); + __instance.Hip_L = jbhHipL.transform; + __instance.Hip_R = jbhHipR.transform; + } - __instance.Hip_L = jbhHipL.transform; - __instance.Hip_R = jbhHipR.transform; + var pelvis = __instance.Pelvis; + var pelvisScl = pelvis?.Find($"{pelvis.name}_SCL_"); + if (pelvisScl != null)// && ButtJiggle.Experimental_PelvisEnabled.Value) + { + var jbhPelvis = JiggleBoneHelper.WrapBone(pelvisScl, __instance, MaidJiggleOverride.Slot.Pelvis, Quaternion.Euler(0, 0, 60), useSubBone: false); + } } [HarmonyPrefix, HarmonyPatch(nameof(TBody.LateUpdate))] static void LateUpdate(TBody __instance) { - if (!__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) return; + if (__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) + { + jbhHipL.LateUpdateSelf(); + jbhHipR.LateUpdateSelf(); + } - jbhHipL.LateUpdateSelf(); - jbhHipR.LateUpdateSelf(); + if (__instance.TryGetPelvisHelper(out var jbhPelvis)) + { + jbhPelvis.LateUpdateSelf(); + } } @@ -60,37 +92,62 @@ static void BoneMorph_FromProcItem(TBody __instance, string tag, float f) { if (__instance.boMAN) return; - if (!__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) return; - - if (tag == "HipYawaraka") + if (__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) { - jbhHipL.Jiggle.m_fMuneYawaraka = f; - jbhHipR.Jiggle.m_fMuneYawaraka = f; + if (tag == "HipYawaraka") + { + jbhHipL.Jiggle.m_fMuneYawaraka = f; + jbhHipR.Jiggle.m_fMuneYawaraka = f; + } + if (tag == "koshi") + { + ButtJiggle.Logger.LogDebug($"koshi = {f}"); + jbhHipL.Jiggle.BlendValue = Mathf.Clamp(f + .5f, 0f, 1.5f); + jbhHipR.Jiggle.BlendValue = Mathf.Clamp(f + .5f, 0f, 1.5f); + } } - if (tag == "koshi") + + if (__instance.TryGetPelvisHelper(out var jbhPelvis)) { - ButtJiggle.Logger.LogInfo($"koshi = {f}"); - jbhHipL.Jiggle.BlendValue = Mathf.Clamp(f + .5f, 0f, 1.5f); - jbhHipR.Jiggle.BlendValue = Mathf.Clamp(f + .5f, 0f, 1.5f); + if (tag == "HipYawaraka") + { + jbhPelvis.Jiggle.m_fMuneYawaraka = f; + } + if (tag == "koshi") + { + jbhPelvis.Jiggle.BlendValue = Mathf.Clamp(f + .5f, 0f, 1.5f); + } } } [HarmonyPrefix, HarmonyPatch(nameof(TBody.Update))] static void Update(TBody __instance) { - if (!__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) return; + if (__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) + { + jbhHipL.Jiggle.boBRA = !__instance.boVisible_XXX; + jbhHipR.Jiggle.boBRA = !__instance.boVisible_XXX; + } - jbhHipL.Jiggle.boBRA = !__instance.boVisible_XXX; - jbhHipR.Jiggle.boBRA = !__instance.boVisible_XXX; + if (__instance.TryGetPelvisHelper(out var jbhPelvis)) + { + jbhPelvis.Jiggle.boBRA = !__instance.boVisible_XXX; + } } [HarmonyPrefix, HarmonyPatch(nameof(TBody.WarpInit))] static void WarpInit(TBody __instance) { - if (!__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) return; + if (__instance.TryGetHipHelpers(out var jbhHipL, out var jbhHipR)) + { + jbhHipL.Jiggle.boWarpInit = true; + jbhHipR.Jiggle.boWarpInit = true; + } - jbhHipL.Jiggle.boWarpInit = true; - jbhHipR.Jiggle.boWarpInit = true; + if (__instance.TryGetPelvisHelper(out var jbhPelvis)) + { + jbhPelvis.Jiggle.boWarpInit = true; + } } } } diff --git a/ButtJiggle/UIControlFactory.cs b/ButtJiggle/UIControlFactory.cs new file mode 100644 index 0000000..8f0b915 --- /dev/null +++ b/ButtJiggle/UIControlFactory.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine.Events; +using UnityEngine; +using UniverseLib.UI; +using UniverseLib.Utility; + +namespace COM3D2.ButtJiggle +{ + internal static class UIControlFactory + { + public delegate ref T RefGetter(); + + public static GameObject CreateControl(GameObject parent, string labelText, RefGetter refGet, UnityAction onSet = null, UnityEvent listen = null) + { + return CreateControl( + parent, + labelText, + () => refGet(), + (T value) => { refGet() = value; onSet?.Invoke(); }, + listen + ); + } + + public static GameObject CreateControl(GameObject parent, string labelText, Func get, UnityAction set = null, UnityEvent listen = null) + { + GameObject control = null; + if (typeof(T) == typeof(bool)) + { + if (get is not Func getBool) throw new ArgumentException(); + if (set is not UnityAction setBool) setBool = null; + control = CreateBoolControl(parent, labelText, getBool, setBool, listen); + } + else if (ParseUtility.CanParse(typeof(T))) + { + control = CreateParsedControl(parent, labelText, get, set, listen); + } + else + { + var name = labelText.Replace(" ", ""); + var errorText = UIFactory.CreateLabel(parent, $"controlError_{name}", " " + labelText); + errorText.text = $"{labelText} : Could not create control for type {typeof(T).Name}"; + control = errorText.gameObject; + UIFactory.SetLayoutElement(control, minWidth: 200, minHeight: 25); + } + return control; + } + + public static GameObject CreateControl(GameObject parent, string labelText, RefGetter> refGet, UnityAction onSet = null, UnityEvent listen = null) + { + return CreateControl( + parent, + labelText, + () => refGet(), + (Override value) => { refGet() = value; onSet?.Invoke(); }, + listen + ); + } + + public static GameObject CreateControl(GameObject parent, string labelText, Func> get, UnityAction> set = null, UnityEvent listen = null) + { + var name = labelText.Replace(" ", ""); + + var row = UIFactory.CreateHorizontalGroup(parent, $"controlOverride_{name}", false, false, true, true, childAlignment: TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(row, minWidth: 200, minHeight: 25); + { + var enabledRoot = UIFactory.CreateToggle(row, $"controlBool_{name}_Enabled", out var enableToggle, out var enabledLabel); + UIFactory.SetLayoutElement(enabledRoot, minWidth: 40, minHeight: 25, preferredWidth: 40, preferredHeight: 25); + enabledLabel.text = " | "; + enableToggle.isOn = get().Enabled; + listen?.AddListener(() => enableToggle.isOn = get().Enabled); + enableToggle.interactable = (set != null); + if (set != null) + { + enableToggle.onValueChanged.AddListener((value) => + { + var tempOverride = get(); + tempOverride.Enabled = value; + set(tempOverride); + ButtJiggle.Logger.LogDebug($"{name}.Enabled == {get().Enabled}"); + }); + } + + Func valueGet = () => + { + var tempOverride = get(); + return tempOverride.Value; + }; + UnityAction valueSet = (set == null) ? null : (value) => + { + var tempOverride = get(); + tempOverride.Value = value; + set(tempOverride); + ButtJiggle.Logger.LogDebug($"{name}.Value == {get().Value}"); + }; + + GameObject valueControl = null; + if (typeof(T) == typeof(Stiffness)) + { + if (valueGet is not Func valueGetCasted) throw new ArgumentException(); + if (valueSet is not UnityAction valueSetCasted) valueSetCasted = null; + + valueControl = CreateControl(row, labelText, + get: () => (Vector2)valueGetCasted(), + set: (value) => valueSetCasted((Stiffness)value), + listen: listen); + } + else + { + valueControl = CreateControl(row, labelText, valueGet, valueSet, listen); + } + } + return row; + } + + + private static GameObject CreateParsedControl(GameObject parent, string labelText, Func get, UnityAction set = null, UnityEvent listen = null) + { + var name = labelText.Replace(" ", ""); + var row = UIFactory.CreateHorizontalGroup(parent, $"control{typeof(T).Name}_{name}", false, false, true, true, childAlignment: TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(row, minWidth: 200, minHeight: 25); + { + var inputFieldRef = UIFactory.CreateInputField(row, "input", labelText); + UIFactory.SetLayoutElement(inputFieldRef.GameObject, minWidth: 200, minHeight: 25); + inputFieldRef.Text = ParseUtility.ToStringForInput(get(), typeof(T)); + listen?.AddListener(() => inputFieldRef.Text = ParseUtility.ToStringForInput(get(), typeof(T))); + inputFieldRef.Component.interactable = (set != null); + if (set != null) + { + inputFieldRef.OnValueChanged += (str) => + { + if (ParseUtility.TryParse(str, typeof(T), out var newValue, out _)) + { + set((T)newValue); + ButtJiggle.Logger.LogDebug($"{name} = {get()}"); + } + inputFieldRef.Text = ParseUtility.ToStringForInput(get(), typeof(T)); + }; + } + + UIFactory.CreateLabel(row, "label", " " + labelText); + UIFactory.SetLayoutElement(row, minWidth: 200, minHeight: 25); + } + return row; + } + + private static GameObject CreateBoolControl(GameObject parent, string labelText, Func get, UnityAction set = null, UnityEvent listen = null) + { + var name = "controlBool_" + labelText.Replace(" ", ""); + var toggleRoot = UIFactory.CreateToggle(parent, name, out var toggle, out var label); + label.text = labelText; + toggle.isOn = get(); + listen?.AddListener(() => toggle.isOn = get()); + toggle.interactable = (set != null); + if (set != null) + { + toggle.onValueChanged.AddListener((value) => + { + set(value); + ButtJiggle.Logger.LogDebug($"{name} = {get()}"); + }); + } + UIFactory.SetLayoutElement(toggleRoot, minWidth: 200, minHeight: 25); + return toggleRoot; + } + } +} diff --git a/ButtJiggle/UnityRuntimeGizmos.cs b/ButtJiggle/UnityRuntimeGizmos.cs new file mode 100644 index 0000000..64de25a --- /dev/null +++ b/ButtJiggle/UnityRuntimeGizmos.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + + +namespace UnityRuntimeGizmos +{ + internal static partial class RuntimeGizmos + { + static GizmoDrawer _staticDrawer; + static GizmoDrawer StaticDrawer + { + get + { + if (_staticDrawer == null) + { + var go = new GameObject("StaticRuntimeGizmoDrawer"); + _staticDrawer = go.AddComponent(); + GameObject.DontDestroyOnLoad(go); + } + return _staticDrawer; + } + } + + + static Material _coloredMaterial; + static Material StaticMaterial + { + get + { + if (_coloredMaterial != null) return _coloredMaterial; + + Shader shader = Shader.Find("Hidden/Internal-Colored"); + + _coloredMaterial = new Material(shader) + { + hideFlags = HideFlags.HideAndDontSave + }; + _coloredMaterial.SetInt("_ZTest", 0); + _coloredMaterial.SetInt("_SrcBlend", 5); + _coloredMaterial.SetInt("_DstBlend", 10); + _coloredMaterial.SetInt("_Cull", 0); + _coloredMaterial.SetInt("_ZWrite", 0); + _coloredMaterial.renderQueue = 5000; + //material.color = this.lineColor; + return _coloredMaterial; + } + } + + static Color _defaultGizmoColor = UnityEngine.Color.white; + + + public static void Color(Color color) + { + _defaultGizmoColor = color; + } + + public static IGizmo Ray(Vector3 start, Vector3 dir, Color color) + { + LineGizmo lineGizmo = new LineGizmo(); + lineGizmo.Start = start; + lineGizmo.End = start + dir; + lineGizmo.Color = color; + return lineGizmo; + } + + public static void DrawRay(Vector3 start, Vector3 dir, Color color) + { + StaticDrawer.Enqueue(Ray(start, dir, color)); + } + + public static void DrawRay(Vector3 start, Vector3 dir) + { + DrawRay(start, dir, _defaultGizmoColor); + } + + public static IGizmo WireSphere(Vector3 center, float radius, Color color) + { + WireSphereGizmo wireSphereGizmo = new WireSphereGizmo(); + wireSphereGizmo.Center = center; + wireSphereGizmo.Radius = radius; + wireSphereGizmo.Color = color; + return wireSphereGizmo; + } + + public static void DrawWireSphere(Vector3 center, float radius, Color color) + { + StaticDrawer.Enqueue(WireSphere(center, radius, color)); + } + + public static void DrawWireSphere(Vector3 center, float radius) + { + DrawWireSphere(center, radius, _defaultGizmoColor); + } + } + + internal static partial class RuntimeGizmos // private class GizmoDrawer + { + private class GizmoDrawer : MonoBehaviour + { + private List m_Gizmos = new List(); + + public void Enqueue(IGizmo gizmo) + { + m_Gizmos.Add(gizmo); + } + + void OnRenderObject() + { + GL.PushMatrix(); + try + { + GL.MultMatrix(Matrix4x4.identity); + foreach (var gizmo in m_Gizmos) + { + try { gizmo.Draw(); } + catch (Exception ex) { Debug.LogException(ex); } + } + } + finally + { + GL.PopMatrix(); + m_Gizmos.Clear(); + } + } + } + } + + internal static partial class RuntimeGizmos // private Gizmo structs + { + + private static class GizmoDefault + { + public static void Draw(IGizmo gizmo) + { + if (gizmo.Material != null) + { + for (int i = 0; i < gizmo.Material.passCount; i++) + { + gizmo.Material.SetPass(i); + gizmo.DrawPass(i); + } + } + else + { + gizmo.DrawPass(-1); + } + } + } + + private struct LineGizmo : IGizmo + { + public Color Color { get; set; } + public Material Material { get; set; } = StaticMaterial; + + public Vector3 Start; + public Vector3 End; + + public LineGizmo() + { } + + public void Draw() => GizmoDefault.Draw(this); + public void DrawPass(int materialPassIndex) + { + GL.Begin(2); + try + { + GL.Color(Color); + GL.Vertex(Start); + GL.Vertex(End); + } + finally + { + GL.End(); + } + } + } + + private struct WireSphereGizmo : IGizmo + { + private const int DensityCount = 80; + private const float CircleAngle = 2 * Mathf.PI / DensityCount; + + public Color Color { get; set; } + public Material Material { get; set; } = StaticMaterial; + + public float Radius = 0.1f; + public Vector3 Center = Vector3.zero; + + public WireSphereGizmo() + { } + + public void Draw() => GizmoDefault.Draw(this); + public void DrawPass(int materialPassIndex) + { + DrawSphere(this.Radius, this.Center); + } + + private void DrawSphere(in float radius, in Vector3 pos) + { + DrawCircle(Vector3.forward * radius, Vector3.up * radius, pos, Color); + DrawCircle(Vector3.back * radius, Vector3.right * radius, pos, Color); + DrawCircle(Vector3.down * radius, Vector3.left * radius, pos, Color); + } + + public static void DrawCircle(in Vector3 v1, in Vector3 v2, in Vector3 pos, Color color) + { + const int densityCount = 80; + const float circleAngle = 2 * Mathf.PI / densityCount; + + GL.Begin(2); + try + { + GL.Color(color); + for (float radians = -circleAngle; radians <= Mathf.PI * 2; radians += circleAngle) + { + Vector3 vertex = v1 * Mathf.Cos(radians) + v2 * Mathf.Sin(radians) + pos; + GL.Vertex(vertex); + } + } + finally + { + GL.End(); + } + } + } + } + + internal interface IGizmo + { + Color Color { get; set; } + Material Material { get; set; } + void Draw(); + void DrawPass(int materialPassIndex); + } + + internal class GizmoRenderer : MonoBehaviour + { + public IGizmo Gizmo; + + void OnRenderObject() + { + GL.PushMatrix(); + try + { + GL.MultMatrix(this.transform.localToWorldMatrix); + try { Gizmo?.Draw(); } + catch (Exception ex) { Debug.LogException(ex); } + } + finally + { + GL.PopMatrix(); + } + } + } + + internal class GizmoCollectionRenderer : MonoBehaviour, ICollection + { + private List m_Gizmos = new List(); + + public int Count => ((ICollection)m_Gizmos).Count; + + public bool IsReadOnly => ((ICollection)m_Gizmos).IsReadOnly; + + void OnRenderObject() + { + GL.PushMatrix(); + try + { + GL.MultMatrix(this.transform.localToWorldMatrix); + foreach (var gizmo in m_Gizmos) + { + try { gizmo.Draw(); } + catch (Exception ex) { Debug.LogException(ex); } + } + } + finally + { + GL.PopMatrix(); + } + } + + public void Clear() => m_Gizmos.Clear(); + + public void CopyTo(IGizmo[] array, int arrayIndex) => m_Gizmos.CopyTo(array, arrayIndex); + + public void Add(IGizmo item) => m_Gizmos.Add(item); + + public bool Contains(IGizmo item) => m_Gizmos.Contains(item); + + public bool Remove(IGizmo item) => m_Gizmos.Remove(item); + + public IEnumerator GetEnumerator() => m_Gizmos.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)m_Gizmos).GetEnumerator(); + } +}