diff --git a/OSCCollar/Config.cs b/OSCCollar/Config.cs index 33d01e0..6bd847d 100644 --- a/OSCCollar/Config.cs +++ b/OSCCollar/Config.cs @@ -1,11 +1,21 @@ -namespace VRC_Console; +using Newtonsoft.Json; + +namespace VRC_Console; internal class Config { + internal static readonly string ConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "config.json"); public string Ip = "127.0.0.1"; public int PortSend = 9000; public double Radius = 100; public double WalkStretchDeadzone = 0.1; public double RunStretch = 0.4; public int CalibrationValues = 128; + public string LeashStretchParameter = "Leash/Leash_Stretch"; + public string LeashToggleParameter = "Leash/Toggle"; + + public Config() + { + File.WriteAllText(ConfigFilePath, JsonConvert.SerializeObject(this, Formatting.Indented)); + } } \ No newline at end of file diff --git a/OSCCollar/ConsoleOutput.cs b/OSCCollar/ConsoleOutput.cs index 9e88c68..7fb715c 100644 --- a/OSCCollar/ConsoleOutput.cs +++ b/OSCCollar/ConsoleOutput.cs @@ -1,28 +1,44 @@ -using Spectre.Console; +using System.Runtime.InteropServices; namespace VRC_Console; public partial class OSCCollar { private const byte CoordinateSystemSize = 16; + private const int MinWindowHeight = 10; + private const int MinWindowWidth = 60; + + private void CheckConsoleMinSize() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; + if (Console.WindowHeight < MinWindowHeight) + Console.WindowHeight = MinWindowHeight; + if (Console.WindowWidth < MinWindowWidth) + Console.WindowWidth = MinWindowWidth; + } private void PrintConsole() { Console.Clear(); + CheckConsoleMinSize(); ValueTuple[] configValues = new[] { - new ValueTuple("IP", this._config.Ip), - new ValueTuple("Send-Osc-Port", this._config.PortSend.ToString()), + new ValueTuple("IP", Config.Ip), + new ValueTuple("Send-Osc-Port", Config.PortSend.ToString()), new ValueTuple("Receive-Osc-Port", this._osc.OSCPort.ToString()), - new ValueTuple("Calibration Values", $"{this._calibrationAverage.Count}/{this._config.CalibrationValues}"), + new ValueTuple("Calibration Values", $"{this._calibrationAverage.Count}/{Config.CalibrationValues}"), + new ValueTuple("Stretch Parameter", Config.LeashStretchParameter), + new ValueTuple("Toggle Parameter", Config.LeashToggleParameter), + new ValueTuple("AvatarId", _avatarId.Length < 1 ? "Reset Avatar to show." : _avatarId), }; ValueTuple[] variableValues = new[] { new ValueTuple("Status", $"{(_allowMoving ? Resources.OSCCollar_ConsoleOutput_StatusEnabled : Resources.OSCCollar_ConsoleOutput_StatusDisabled)}"), - new ValueTuple("GPS 1", $"{_gps1.Distance: 0.00000}"), - new ValueTuple("GPS 2", $"{_gps2.Distance: 0.00000}"), - new ValueTuple("GPS 3", $"{_gps3.Distance: 0.00000}"), + new ValueTuple("GPS 1", $"{refPos1.Distance: 0.00000}"), + new ValueTuple("GPS 2", $"{refPos2.Distance: 0.00000}"), + new ValueTuple("GPS 3", $"{refPos3.Distance: 0.00000}"), new ValueTuple(Resources.OSCCollar_ConsoleOutput_PositionVector, _unitVectorLeash.ToString()), new ValueTuple(Resources.OSCCollar_ConsoleOutput_LeashStretch, _leashStretch.ToString(" 0.00000")), new ValueTuple(Resources.OSCCollar_ConsoleOutput_MovementVector, _movementVector.ToString()), @@ -57,7 +73,7 @@ public partial class OSCCollar } Console.SetCursorPosition(0, Console.WindowHeight - 2); - Console.Write($"Last Hand Reset: {DateTime.Now.Subtract(_lastNilMessageSent):ss\\.fff}\t\tAvatarId: {(_avatarId.Length < 1 ? "Reset Avatar to show." : _avatarId)}\t\tRuntime: {DateTime.Now.Subtract(_programStarted):hh\\:mm\\:ss\\.fff}"); + Console.Write($"Last Hand Reset: {DateTime.Now.Subtract(_lastNilMessageSent):ss\\.fff}\tLast Message Received: {(_lastMessageReceived == DateTime.UnixEpoch ? "never" : DateTime.Now.Subtract(_lastMessageReceived)):ss\\.fff} {_lastMessageReceivedParameter}\tRuntime: {DateTime.Now.Subtract(_programStarted):hh\\:mm\\:ss\\.fff}"); } private String GetCoordinateSystem() diff --git a/OSCCollar/OSCCollar.cs b/OSCCollar/OSCCollar.cs index 0bcb512..320d511 100644 --- a/OSCCollar/OSCCollar.cs +++ b/OSCCollar/OSCCollar.cs @@ -5,29 +5,13 @@ namespace VRC_Console; public partial class OSCCollar { - private static readonly string ConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), "config.json"); public static void Main(string[] args) { - OSCCollar _; - if (File.Exists(ConfigFilePath)) - { - Config? config = JsonConvert.DeserializeObject(File.ReadAllText(ConfigFilePath)); - if(config is { }) - { - _ = new OSCCollar(config); - } - else - { - _ = new OSCCollar(); - } - }else - { - _ = new OSCCollar(); - } + _ = new OSCCollar(); } - private readonly Config _config; + private static readonly Config Config = File.Exists(Config.ConfigFilePath) ? JsonConvert.DeserializeObject(File.ReadAllText(Config.ConfigFilePath))! : new Config(); private readonly OSC _osc; private Task _consoleOutputTask; @@ -38,25 +22,24 @@ public partial class OSCCollar private Vector _movementVector = new(0, 0); private bool _allowMoving = true; private DateTime _lastNilMessageSent = DateTime.UnixEpoch; + private DateTime _lastMessageReceived = DateTime.UnixEpoch; + private string _lastMessageReceivedParameter = "null"; private readonly DateTime _programStarted = DateTime.Now; - private static readonly TimeSpan UpdateInterval = TimeSpan.FromMilliseconds(10); - private static readonly TimeSpan MessageMinInterval = TimeSpan.FromMilliseconds(400); - private GPS _gps1 = null!, _gps2 = null!, _gps3 = null!; + private static readonly TimeSpan UpdateInterval = TimeSpan.FromMilliseconds(10); //Minimum is 10ms for VRC to register + private static readonly TimeSpan MessageMinInterval = TimeSpan.FromMilliseconds(400); //Reset OSC movement at least once per second, to prevent hands from resetting to desktop position + private ReferencePosition refPos1 = null!, refPos2 = null!, refPos3 = null!; private readonly Queue> _calibrationAverage = new(); - private double _constantA, _constantB, _constantC, _constantD, _constantE; + private double _constantA, _constantB, _constantC, _constantD, _constantE; - private OSCCollar(Config config, bool skipSetup = true) + private OSCCollar(bool skipSetup = true) { - this._config = config; - File.WriteAllText(ConfigFilePath, JsonConvert.SerializeObject(this._config, Formatting.Indented)); - - this._osc = new OSC("OSCCollar", new Dictionary() + this._osc = new OSC("OSCCollar", new List() { - { "/avatar/parameters/GPS1", typeof(float) }, - { "/avatar/parameters/GPS2", typeof(float) }, - { "/avatar/parameters/GPS3", typeof(float) }, - { "/avatar/parameters/Leash_Stretch", typeof(float) }, - { "/avatar/parameters/Leash_Toggle", typeof(bool) }, + new("/avatar/parameters/GPS1", typeof(float), OnGPSParameterChanged), + new("/avatar/parameters/GPS2", typeof(float), OnGPSParameterChanged), + new("/avatar/parameters/GPS3", typeof(float), OnGPSParameterChanged), + new($"/avatar/parameters/{Config.LeashStretchParameter}", typeof(float), OnLeashStretchChanged), + new($"/avatar/parameters/{Config.LeashToggleParameter}", typeof(bool), OnLeashToggled), }); this._osc.OnParameterChangeEvent += OnOscParameterChangeEvent; @@ -64,10 +47,10 @@ public partial class OSCCollar if (!skipSetup) { Console.WriteLine(Resources.OSCCollar_OSCCollar_Position_your_GPS_receivers); - Console.WriteLine(Resources.OSCCollar_OSCCollar_GPS_1_position, _gps1.X, _gps1.Y); - Console.WriteLine(Resources.OSCCollar_OSCCollar_GPS_2_position, _gps2.X, _gps2.Y); - Console.WriteLine(Resources.OSCCollar_OSCCollar_GPS_3_position, _gps3.X, _gps3.Y); - Console.WriteLine(Resources.OSCCollar_OSCCollar_Radius_of_each_receiver, this._config.Radius * 2); + Console.WriteLine(Resources.OSCCollar_OSCCollar_GPS_1_position, refPos1.X, refPos1.Y); + Console.WriteLine(Resources.OSCCollar_OSCCollar_GPS_2_position, refPos2.X, refPos2.Y); + Console.WriteLine(Resources.OSCCollar_OSCCollar_GPS_3_position, refPos3.X, refPos3.Y); + Console.WriteLine(Resources.OSCCollar_OSCCollar_Radius_of_each_receiver, Config.Radius * 2); Console.ReadKey(); } @@ -75,85 +58,86 @@ public partial class OSCCollar runningThread.Start(); } - private void OnOscParameterChangeEvent(string endpoint, object? oldvalue, object? newvalue) + private void OnLeashToggled(string endpoint, object? oldValue, object? newValue) + { + this._allowMoving = (bool)(newValue ?? false); + } + + + private void OnLeashStretchChanged(string endpoint, object? oldValue, object? newValue) + { + this._leashStretch = (float)(newValue ?? 0); + } + + private void OnGPSParameterChanged(string endpoint, object? oldValue, object? newValue) { switch (endpoint) { case "/avatar/parameters/GPS1": - this._gps1.Distance = 1 - (float)(newvalue ?? 1); + this.refPos1.Distance = 1 - (float)(newValue ?? 1); break; case "/avatar/parameters/GPS2": - this._gps2.Distance = 1 - (float)(newvalue ?? 1); + this.refPos2.Distance = 1 - (float)(newValue ?? 1); break; case "/avatar/parameters/GPS3": - this._gps3.Distance = 1 - (float)(newvalue ?? 1); - break; - case "/avatar/parameters/Leash_Stretch": - this._leashStretch = (float)(newvalue ?? 0); - break; - case "/avatar/parameters/Leash_Toggle": - this._allowMoving = (bool)(newvalue ?? false); - break; - case "/avatar/change": - this._avatarId = (string)(newvalue ?? ""); + this.refPos3.Distance = 1 - (float)(newValue ?? 1); break; } } - private OSCCollar(string ip = "127.0.0.1", int portSend = 9000, double radius = 100, double walkStretchDeadzone = 0.1, double runStretch = 0.4, int calibrationValues = 128, - bool skipSetup = true) : this(new Config() - { - Ip = ip, - PortSend = portSend, - WalkStretchDeadzone = walkStretchDeadzone, - RunStretch = runStretch, - Radius = radius, - CalibrationValues = calibrationValues - }, skipSetup) + + private void OnOscParameterChangeEvent(string endpoint, object? oldValue, object? newValue) { - + _lastMessageReceived = DateTime.Now; + _lastMessageReceivedParameter = endpoint; + switch (endpoint) + { + case "/avatar/change": + this._avatarId = (string)(newValue ?? ""); + break; + } } #region Setup private void SetupGPSVars() { - this._gps1 = new(0, -this._config.Radius); - this._gps2 = new(-(this._config.Radius * 0.866), this._config.Radius / 2); - this._gps3 = new(this._config.Radius * 0.866, this._config.Radius / 2); - this._constantA = 2 * _gps2.X - 2 * _gps1.X; - this._constantB = 2 * _gps2.Y - 2 * _gps1.Y; - this._constantC = Math.Pow(_gps1.X, 2) + Math.Pow(_gps2.X, 2) - Math.Pow(_gps1.Y, 2) + Math.Pow(_gps2.Y, 2); - this._constantD = 2 * _gps3.X - 2 * _gps2.X; - this._constantE = Math.Pow(_gps2.X, 2) + Math.Pow(_gps3.X, 2) - Math.Pow(_gps2.Y, 2) + Math.Pow(_gps3.Y, 2); + this.refPos1 = new(0, -Config.Radius); + this.refPos2 = new(-(Config.Radius * 0.866), Config.Radius / 2); + this.refPos3 = new(Config.Radius * 0.866, Config.Radius / 2); + this._constantA = 2 * refPos2.X - 2 * refPos1.X; + this._constantB = 2 * refPos2.Y - 2 * refPos1.Y; + this._constantC = Math.Pow(refPos1.X, 2) + Math.Pow(refPos2.X, 2) - Math.Pow(refPos1.Y, 2) + Math.Pow(refPos2.Y, 2); + this._constantD = 2 * refPos3.X - 2 * refPos2.X; + this._constantE = Math.Pow(refPos2.X, 2) + Math.Pow(refPos3.X, 2) - Math.Pow(refPos2.Y, 2) + Math.Pow(refPos3.Y, 2); } #endregion private void CalculatePositionFromGPS() { - if (this._gps1.Distance == 0 || this._gps2.Distance == 0 || this._gps3.Distance == 0) + if (this.refPos1.Distance == 0 || this.refPos2.Distance == 0 || this.refPos3.Distance == 0) return; - double gpsFactorA = Math.Pow(_gps1.Distance, 2) - Math.Pow(_gps2.Distance, 2) - _constantC; - double gpsFactorB = Math.Pow(_gps2.Distance, 2) - Math.Pow(_gps3.Distance, 2) - _constantE; + double factorA = Math.Pow(refPos1.Distance, 2) - Math.Pow(refPos2.Distance, 2) - _constantC; + double factorB = Math.Pow(refPos2.Distance, 2) - Math.Pow(refPos3.Distance, 2) - _constantE; - double gpsPosX = (gpsFactorB / _constantD); - double gpsPosY = -1 * (gpsFactorA * _constantD - _constantA * gpsFactorB) / (_constantB * _constantD); + double calculatedPosX = (factorB / _constantD); + double calculatedPosY = -1 * (factorA * _constantD - _constantA * factorB) / (_constantB * _constantD); if (this._leashStretch == 0) - AddCalibrationValues(gpsPosX, gpsPosY); + AddCalibrationValues(calculatedPosX, calculatedPosY); ValueTuple calibrationValues = GetCalibrationValues(); - double posX = gpsPosX + calibrationValues.Item1; - double posY = gpsPosY + calibrationValues.Item2; + double calibratedPosX = calculatedPosX + calibrationValues.Item1; + double calibratedPosY = calculatedPosY + calibrationValues.Item2; - double inverseLength = 1 / Math.Sqrt(Math.Pow(posX, 2) + Math.Pow(posY, 2)); + double inverseLength = 1 / Math.Sqrt(Math.Pow(calibratedPosX, 2) + Math.Pow(calibratedPosY, 2)); - _unitVectorLeash.X = inverseLength is not Double.PositiveInfinity ? posX * inverseLength : 0; - _unitVectorLeash.Y = inverseLength is not Double.PositiveInfinity ? posY * inverseLength : 0; + _unitVectorLeash.X = inverseLength is not Double.PositiveInfinity ? calibratedPosX * inverseLength : 0; + _unitVectorLeash.Y = inverseLength is not Double.PositiveInfinity ? calibratedPosY * inverseLength : 0; - if (_leashStretch < this._config.WalkStretchDeadzone) //Below Deadzone + if (_leashStretch < Config.WalkStretchDeadzone) //Below Deadzone _movementVector = new(); - else if (_leashStretch < this._config.RunStretch) //Walk + else if (_leashStretch < Config.RunStretch) //Walk _movementVector = _unitVectorLeash.MultipliedWith(0.5); else //Run _movementVector = _unitVectorLeash.MultipliedWith(1); @@ -183,7 +167,7 @@ public partial class OSCCollar private void AddCalibrationValues(double x, double y) { this._calibrationAverage.Enqueue(new ValueTuple(x, y)); - while (this._calibrationAverage.Count > this._config.CalibrationValues) + while (this._calibrationAverage.Count > Config.CalibrationValues) this._calibrationAverage.Dequeue(); } diff --git a/OSCCollar/OSCCollar.csproj b/OSCCollar/OSCCollar.csproj index 4e0a0d2..55dd0cd 100644 --- a/OSCCollar/OSCCollar.csproj +++ b/OSCCollar/OSCCollar.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable VRC_Console diff --git a/OSCCollar/GPS.cs b/OSCCollar/ReferencePosition.cs similarity index 73% rename from OSCCollar/GPS.cs rename to OSCCollar/ReferencePosition.cs index 1993347..d56d570 100644 --- a/OSCCollar/GPS.cs +++ b/OSCCollar/ReferencePosition.cs @@ -1,12 +1,12 @@ namespace VRC_Console; -public class GPS +public class ReferencePosition { public double X { get; init; } public double Y { get; init; } public double Distance { get; set; } - public GPS(double x, double y) + public ReferencePosition(double x, double y) { this.X = x; this.Y = y; diff --git a/OSCCollar/Vector.cs b/OSCCollar/Vector.cs index 8874a63..d72228f 100644 --- a/OSCCollar/Vector.cs +++ b/OSCCollar/Vector.cs @@ -19,7 +19,7 @@ public class Vector public override string ToString() { - return $"({this.X:' '0.00000;'-'0.00000} {this.Y:' '0.00000;'-'0.00000})"; + return $"(Vector X={this.X:' '0.00000;'-'0.00000} Y={this.Y:' '0.00000;'-'0.00000})"; } public Vector MultipliedWith(double factor)