From 623df261b0281facb5c2321ee3e0a02c1474550c Mon Sep 17 00:00:00 2001
From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com>
Date: Mon, 10 Jul 2023 09:58:31 -0500
Subject: [PATCH] [EzCurls] Initial tests
---
EzCurls/EzCurls.csproj | 2 +
EzCurls/InputModuleCurlAdjuster.cs | 141 +++++++++++++++++++++++++++++
EzCurls/Main.cs | 79 ++++++++++++++++
EzCurls/Properties/AssemblyInfo.cs | 29 ++++++
EzCurls/README.md | 14 +++
EzCurls/format.json | 23 +++++
NAK_CVR_Mods.sln | 6 ++
7 files changed, 294 insertions(+)
create mode 100644 EzCurls/EzCurls.csproj
create mode 100644 EzCurls/InputModuleCurlAdjuster.cs
create mode 100644 EzCurls/Main.cs
create mode 100644 EzCurls/Properties/AssemblyInfo.cs
create mode 100644 EzCurls/README.md
create mode 100644 EzCurls/format.json
diff --git a/EzCurls/EzCurls.csproj b/EzCurls/EzCurls.csproj
new file mode 100644
index 0000000..66a50a8
--- /dev/null
+++ b/EzCurls/EzCurls.csproj
@@ -0,0 +1,2 @@
+
+
diff --git a/EzCurls/InputModuleCurlAdjuster.cs b/EzCurls/InputModuleCurlAdjuster.cs
new file mode 100644
index 0000000..8ea076e
--- /dev/null
+++ b/EzCurls/InputModuleCurlAdjuster.cs
@@ -0,0 +1,141 @@
+using ABI_RC.Core.Savior;
+using ABI_RC.Systems.IK.SubSystems;
+
+namespace NAK.EzCurls;
+
+internal class InputModuleCurlAdjuster : CVRInputModule
+{
+ public static InputModuleCurlAdjuster Instance;
+
+ // Curl clamping/adjustment
+ public bool UseCurlSnapping = false;
+ public float SnappedCurlValue = 0.5f;
+ public float RangeStartPercent = 0.5f;
+ public float RangeEndPercent = 0.8f;
+
+ // Curl smoothing/averaging
+ public bool UseCurlSmoothing = false;
+ public bool DontSmoothExtremes = true;
+ public float CurlSimilarityThreshold = 0.5f;
+ public float CurlSmoothingFactor = 0.5f;
+
+ public new void Start()
+ {
+ Instance = this;
+ base.Start();
+ EzCurls.OnSettingsChanged();
+ }
+
+ public override void UpdateInput() => DoCurlAdjustments();
+ public override void UpdateImportantInput() => DoCurlAdjustments();
+
+ private void DoCurlAdjustments()
+ {
+ if (!_inputManager.individualFingerTracking)
+ return;
+
+ if (UseCurlSmoothing)
+ {
+ SmoothCurls(
+ ref _inputManager.fingerCurlLeftIndex,
+ ref _inputManager.fingerCurlLeftMiddle,
+ ref _inputManager.fingerCurlLeftRing,
+ ref _inputManager.fingerCurlLeftPinky
+ );
+
+ SmoothCurls(
+ ref _inputManager.fingerCurlRightIndex,
+ ref _inputManager.fingerCurlRightMiddle,
+ ref _inputManager.fingerCurlRightRing,
+ ref _inputManager.fingerCurlRightPinky
+ );
+ }
+
+ if (UseCurlSnapping)
+ {
+ SnapCurls(ref _inputManager.fingerCurlLeftIndex);
+ SnapCurls(ref _inputManager.fingerCurlLeftMiddle);
+ SnapCurls(ref _inputManager.fingerCurlLeftRing);
+ SnapCurls(ref _inputManager.fingerCurlLeftPinky);
+
+ SnapCurls(ref _inputManager.fingerCurlRightIndex);
+ SnapCurls(ref _inputManager.fingerCurlRightMiddle);
+ SnapCurls(ref _inputManager.fingerCurlRightRing);
+ SnapCurls(ref _inputManager.fingerCurlRightPinky);
+ }
+ }
+
+ private void SnapCurls(ref float fingerCurl)
+ {
+ if (fingerCurl >= RangeStartPercent && fingerCurl <= RangeEndPercent)
+ fingerCurl = SnappedCurlValue;
+ }
+
+ private void SmoothCurls(ref float index, ref float middle, ref float ring, ref float pinky)
+ {
+ List values = new List { index, middle, ring, pinky };
+
+ for (int i = 0; i < values.Count; i++)
+ {
+ for (int j = i + 1; j < values.Count; j++)
+ {
+ if (Math.Abs(values[i] - values[j]) <= CurlSimilarityThreshold)
+ {
+ // Compute new smoothed values
+ float smoothedValue = (values[i] + values[j]) / 2;
+
+ // Calculate smoothing factors for both values
+ float smoothingFactor1 = CalculateSmoothingFactor(values[i]);
+ float smoothingFactor2 = CalculateSmoothingFactor(values[j]);
+
+ // Adjust both values towards the smoothed value
+ values[i] = values[i] + smoothingFactor1 * CurlSmoothingFactor * (smoothedValue - values[i]);
+ values[j] = values[j] + smoothingFactor2 * CurlSmoothingFactor * (smoothedValue - values[j]);
+ }
+ }
+ }
+
+ index = values[0];
+ middle = values[1];
+ ring = values[2];
+ pinky = values[3];
+ }
+
+ private void SmoothCurlsNear(ref float index, ref float middle, ref float ring, ref float pinky)
+ {
+ List values = new List { index, middle, ring, pinky };
+
+ for (int i = 0; i < values.Count - 1; i++)
+ {
+ if (Math.Abs(values[i] - values[i + 1]) <= CurlSimilarityThreshold)
+ {
+ // Compute new smoothed value
+ float smoothedValue = (values[i] + values[i + 1]) / 2;
+
+ // Calculate smoothing factors for both values
+ float smoothingFactor1 = CalculateSmoothingFactor(values[i]);
+ float smoothingFactor2 = CalculateSmoothingFactor(values[i + 1]);
+
+ // Adjust both values towards the smoothed value
+ values[i] = values[i] + smoothingFactor1 * CurlSmoothingFactor * (smoothedValue - values[i]);
+ values[i + 1] = values[i + 1] + smoothingFactor2 * CurlSmoothingFactor * (smoothedValue - values[i + 1]);
+ }
+ }
+
+ index = values[0];
+ middle = values[1];
+ ring = values[2];
+ pinky = values[3];
+ }
+
+ // calculate the smoothing factor based on the curl value
+ private float CalculateSmoothingFactor(float curlValue)
+ {
+ if (!DontSmoothExtremes)
+ return 1f;
+
+ // Compute the distance from the center (0.5) and square it
+ float dist = curlValue - 0.5f;
+ return 1.0f - 4 * dist * dist;
+ }
+}
\ No newline at end of file
diff --git a/EzCurls/Main.cs b/EzCurls/Main.cs
new file mode 100644
index 0000000..903cf04
--- /dev/null
+++ b/EzCurls/Main.cs
@@ -0,0 +1,79 @@
+using ABI_RC.Core.Savior;
+using MelonLoader;
+using System.Reflection;
+
+namespace NAK.EzCurls;
+
+public class EzCurls : MelonMod
+{
+ internal const string SettingsCategory = nameof(EzCurls);
+
+ public static readonly MelonPreferences_Category Category =
+ MelonPreferences.CreateCategory(SettingsCategory);
+
+ public static readonly MelonPreferences_Entry EntryUseCurlSnapping =
+ Category.CreateEntry("UseCurlSnapping", true,
+ description: "Finger curl snapping to a specified value when in the specified range.");
+
+ public static readonly MelonPreferences_Entry EntrySnappedCurlValue =
+ Category.CreateEntry("SnappedCurlValue", 0.5f,
+ description: "The value to which the finger curl snaps within the range.");
+
+ public static readonly MelonPreferences_Entry EntryRangeStartPercent =
+ Category.CreateEntry("RangeStartPercent", 0.5f,
+ description: "The minimum value for the SnappedCurlValue range.");
+
+ public static readonly MelonPreferences_Entry EntryRangeEndPercent =
+ Category.CreateEntry("RangeEndPercent", 0.8f,
+ description: "The maximum value for the SnappedCurlValue range.");
+
+ public static readonly MelonPreferences_Entry EntryUseCurlSmoothing =
+ Category.CreateEntry("UseCurlSmoothing", false,
+ description: "Finger curl smoothing to average out similar finger positions.");
+
+ public static readonly MelonPreferences_Entry EntryDontSmoothExtremes =
+ Category.CreateEntry("DontSmoothExtremes", true,
+ description: "Should the finger curl smoothing be less effective on curls towards 0 or 1?");
+
+ public static readonly MelonPreferences_Entry EntryCurlSimilarityThreshold =
+ Category.CreateEntry("CurlSimilarityThreshold", 0.1f,
+ description: "The threshold for curl similarity during curl smoothing.");
+
+ public static readonly MelonPreferences_Entry EntryCurlSmoothingFactor =
+ Category.CreateEntry("CurlSmoothingFactor", 0.5f,
+ description: "The multiplier for curl smoothing.");
+
+ public override void OnInitializeMelon()
+ {
+ HarmonyInstance.Patch(
+ typeof(InputModuleSteamVR).GetMethod(nameof(InputModuleSteamVR.Start)),
+ prefix: new HarmonyLib.HarmonyMethod(typeof(EzCurls).GetMethod(nameof(OnInputModuleSteamVRStart_Prefix), BindingFlags.NonPublic | BindingFlags.Static))
+ );
+
+ foreach (MelonPreferences_Entry setting in Category.Entries)
+ setting.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged);
+ }
+
+ public static void OnSettingsChanged(object oldValue = null, object newValue = null)
+ {
+ if (InputModuleCurlAdjuster.Instance == null)
+ return;
+
+ // curl snapping
+ InputModuleCurlAdjuster.Instance.UseCurlSnapping = EntryUseCurlSnapping.Value;
+ InputModuleCurlAdjuster.Instance.SnappedCurlValue = EntrySnappedCurlValue.Value;
+ InputModuleCurlAdjuster.Instance.RangeStartPercent = EntryRangeStartPercent.Value;
+ InputModuleCurlAdjuster.Instance.RangeEndPercent = EntryRangeEndPercent.Value;
+
+ // curl smoothing
+ InputModuleCurlAdjuster.Instance.UseCurlSmoothing = EntryUseCurlSmoothing.Value;
+ InputModuleCurlAdjuster.Instance.DontSmoothExtremes = EntryDontSmoothExtremes.Value;
+ InputModuleCurlAdjuster.Instance.CurlSimilarityThreshold = EntryCurlSimilarityThreshold.Value;
+ InputModuleCurlAdjuster.Instance.CurlSmoothingFactor = EntryCurlSmoothingFactor.Value;
+ }
+
+ private static void OnInputModuleSteamVRStart_Prefix(ref InputModuleSteamVR __instance)
+ {
+ __instance.gameObject.AddComponent();
+ }
+}
\ No newline at end of file
diff --git a/EzCurls/Properties/AssemblyInfo.cs b/EzCurls/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..9aa7ad6
--- /dev/null
+++ b/EzCurls/Properties/AssemblyInfo.cs
@@ -0,0 +1,29 @@
+using MelonLoader;
+using NAK.EzCurls.Properties;
+using System.Reflection;
+
+[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyTitle(nameof(NAK.EzCurls))]
+[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
+[assembly: AssemblyProduct(nameof(NAK.EzCurls))]
+
+[assembly: MelonInfo(
+ typeof(NAK.EzCurls.EzCurls),
+ nameof(NAK.EzCurls),
+ AssemblyInfoParams.Version,
+ AssemblyInfoParams.Author,
+ downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/EzCurls"
+)]
+
+[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
+[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
+[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
+
+namespace NAK.EzCurls.Properties;
+internal static class AssemblyInfoParams
+{
+ public const string Version = "1.0.0";
+ public const string Author = "NotAKidoS";
+}
\ No newline at end of file
diff --git a/EzCurls/README.md b/EzCurls/README.md
new file mode 100644
index 0000000..12379fa
--- /dev/null
+++ b/EzCurls/README.md
@@ -0,0 +1,14 @@
+# EzCurls
+
+A mod that should hopefully make finger curls more predictable.
+
+---
+
+Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI.
+https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
+
+> This mod is an independent creation and is not affiliated with, supported by or approved by Alpha Blend Interactive.
+
+> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
+
+> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
diff --git a/EzCurls/format.json b/EzCurls/format.json
new file mode 100644
index 0000000..15f3f72
--- /dev/null
+++ b/EzCurls/format.json
@@ -0,0 +1,23 @@
+{
+ "_id": -1,
+ "name": "EzCurls",
+ "modversion": "1.0.0",
+ "gameversion": "2022r170p1",
+ "loaderversion": "0.6.1",
+ "modtype": "Mod",
+ "author": "NotAKidoS",
+ "description": "A mod that should hopefully make finger curls more predictable.",
+ "searchtags": [
+ "aas",
+ "sync",
+ "naked",
+ "buffer"
+ ],
+ "requirements": [
+ "UIExpansionKit"
+ ],
+ "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r13/EzCurls.dll",
+ "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/EzCurls/",
+ "changelog": "- Initial CVRMG release",
+ "embedcolor": "7d7d7d"
+}
\ No newline at end of file
diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln
index c06e1bf..002bf24 100644
--- a/NAK_CVR_Mods.sln
+++ b/NAK_CVR_Mods.sln
@@ -73,6 +73,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FOVAdjustment", "FOVAdjustm
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MuteSFX", "MuteSFX\MuteSFX.csproj", "{77D222FC-4AEC-4672-A87A-B860B4C39E17}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EzCurls", "EzCurls\EzCurls.csproj", "{7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -219,6 +221,10 @@ Global
{77D222FC-4AEC-4672-A87A-B860B4C39E17}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77D222FC-4AEC-4672-A87A-B860B4C39E17}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7EF74A8D-0421-4B6D-809E-40F9C2DFC7F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE