diff --git a/.idea/.idea.VRC_Collar/.idea/indexLayout.xml b/.idea/.idea.VRC_Collar/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.VRC_Collar/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.VRC_Collar/.idea/vcs.xml b/.idea/.idea.VRC_Collar/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/.idea.VRC_Collar/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OSCCollar/OSCCollar.cs b/OSCCollar/OSCCollar.cs
new file mode 100644
index 0000000..89e622b
--- /dev/null
+++ b/OSCCollar/OSCCollar.cs
@@ -0,0 +1,78 @@
+using BuildSoft.OscCore;
+
+namespace VRC_Console;
+
+public class OSCCollar
+{
+ private OscServer Server { get; init; }
+ private OscClient Client { get; init; }
+ private float _collarAngle, _collarStretch;
+ private const float StretchDeadzone = 0.1f;
+ private const float TurnDeadZone = 5f / 180f; //5 degrees
+ private DateTime _lastMessageSent = DateTime.UnixEpoch;
+ private bool _sendingMessage = false;
+
+ public OSCCollar()
+ {
+ this.Server = new OscServer(9001);
+ this.Client = new OscClient("127.0.0.1", 9000);
+ this._collarAngle = 0;
+ this._collarStretch = 0;
+ Server.TryAddMethodPair("/avatar/parameters/Collar_Angle", CollarAngleHandle, HandleCollarChange);
+ Server.TryAddMethodPair("/avatar/parameters/Collar_Stretch", CollarStretchHandle, HandleCollarChange);
+
+ Thread sendNullInputThread = new Thread(SendNullInput);
+ sendNullInputThread.Start();
+ }
+
+ private void CollarAngleHandle(OscMessageValues messageValues)
+ {
+ this._collarAngle = messageValues.ReadFloatElement(0);
+ }
+
+ private void CollarStretchHandle(OscMessageValues messageValues)
+ {
+ this._collarStretch = messageValues.ReadFloatElement(0);
+ }
+
+ private void SendNullInput()
+ {
+ while (true)
+ {
+ if (!_sendingMessage)
+ {
+ this.Client.Send("/input/Vertical", 0f);
+ this.Client.Send("/input/LookHorizontal", 0f);
+ this._lastMessageSent = DateTime.Now;
+ }
+ Thread.Sleep(400);
+ }
+ }
+
+ private static readonly TimeSpan MessageTimeoutSpan = TimeSpan.FromMilliseconds(10);
+ private void HandleCollarChange()
+ {
+ _sendingMessage = true;
+ TimeSpan messageTimeout = _lastMessageSent.Add(MessageTimeoutSpan).Subtract(DateTime.Now);
+ if(messageTimeout > TimeSpan.FromMilliseconds(0))
+ Thread.Sleep(messageTimeout);
+
+ if (this._collarStretch > StretchDeadzone)
+ this.Client.Send("/input/Vertical", this._collarStretch);
+ else
+ this.Client.Send("/input/Vertical", 0f);
+
+ if (Math.Abs(this._collarAngle) > TurnDeadZone)
+ this.Client.Send("/input/LookHorizontal", CalculateTurnPower(this._collarAngle));
+ else
+ this.Client.Send("/input/LookHorizontal", 0f);
+ _sendingMessage = false;
+ }
+
+ private const float MaxAngle = 180f;
+ private const float MaxPower = 0.5f;
+ private static float CalculateTurnPower(float boneAngle)
+ {
+ return (boneAngle / MaxAngle) * MaxPower;
+ }
+}
\ No newline at end of file
diff --git a/OSCCollar/OSCCollar.csproj b/OSCCollar/OSCCollar.csproj
new file mode 100644
index 0000000..4c837a3
--- /dev/null
+++ b/OSCCollar/OSCCollar.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+ VRC_Console
+
+
+
+
+
+
+
diff --git a/VRC_Collar.sln b/VRC_Collar.sln
index a55ff74..0d630ee 100644
--- a/VRC_Collar.sln
+++ b/VRC_Collar.sln
@@ -1,8 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OSCCollar", "OSCCollar\OSCCollar.csproj", "{1D3DEA0F-4D6A-473D-AD77-834F4BC6FB02}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1D3DEA0F-4D6A-473D-AD77-834F4BC6FB02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1D3DEA0F-4D6A-473D-AD77-834F4BC6FB02}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1D3DEA0F-4D6A-473D-AD77-834F4BC6FB02}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1D3DEA0F-4D6A-473D-AD77-834F4BC6FB02}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
EndGlobal
diff --git a/VRC_Collar.sln.DotSettings b/VRC_Collar.sln.DotSettings
new file mode 100644
index 0000000..9d7df3f
--- /dev/null
+++ b/VRC_Collar.sln.DotSettings
@@ -0,0 +1,3 @@
+
+ OSC
+ True
\ No newline at end of file