Add option to set endpoints for stretch and toggle

This commit is contained in:
Glax 2024-09-06 00:51:28 +02:00
parent 9ab3d75e7b
commit 5f8b5f8112
6 changed files with 105 additions and 95 deletions

View File

@ -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));
}
}

View File

@ -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<string, string>[] configValues = new[]
{
new ValueTuple<string, string>("IP", this._config.Ip),
new ValueTuple<string, string>("Send-Osc-Port", this._config.PortSend.ToString()),
new ValueTuple<string, string>("IP", Config.Ip),
new ValueTuple<string, string>("Send-Osc-Port", Config.PortSend.ToString()),
new ValueTuple<string, string>("Receive-Osc-Port", this._osc.OSCPort.ToString()),
new ValueTuple<string, string>("Calibration Values", $"{this._calibrationAverage.Count}/{this._config.CalibrationValues}"),
new ValueTuple<string, string>("Calibration Values", $"{this._calibrationAverage.Count}/{Config.CalibrationValues}"),
new ValueTuple<string, string>("Stretch Parameter", Config.LeashStretchParameter),
new ValueTuple<string, string>("Toggle Parameter", Config.LeashToggleParameter),
new ValueTuple<string, string>("AvatarId", _avatarId.Length < 1 ? "Reset Avatar to show." : _avatarId),
};
ValueTuple<string, string>[] variableValues = new[]
{
new ValueTuple<string, string>("Status", $"{(_allowMoving ? Resources.OSCCollar_ConsoleOutput_StatusEnabled : Resources.OSCCollar_ConsoleOutput_StatusDisabled)}"),
new ValueTuple<string, string>("GPS 1", $"{_gps1.Distance: 0.00000}"),
new ValueTuple<string, string>("GPS 2", $"{_gps2.Distance: 0.00000}"),
new ValueTuple<string, string>("GPS 3", $"{_gps3.Distance: 0.00000}"),
new ValueTuple<string, string>("GPS 1", $"{refPos1.Distance: 0.00000}"),
new ValueTuple<string, string>("GPS 2", $"{refPos2.Distance: 0.00000}"),
new ValueTuple<string, string>("GPS 3", $"{refPos3.Distance: 0.00000}"),
new ValueTuple<string, string>(Resources.OSCCollar_ConsoleOutput_PositionVector, _unitVectorLeash.ToString()),
new ValueTuple<string, string>(Resources.OSCCollar_ConsoleOutput_LeashStretch, _leashStretch.ToString(" 0.00000")),
new ValueTuple<string, string>(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()

View File

@ -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<Config>(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<Config>(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<ValueTuple<double, double>> _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<string, Type>()
this._osc = new OSC("OSCCollar", new List<OSCEndpoint>()
{
{ "/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<double, double> 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<double, double>(x, y));
while (this._calibrationAverage.Count > this._config.CalibrationValues)
while (this._calibrationAverage.Count > Config.CalibrationValues)
this._calibrationAverage.Dequeue();
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>VRC_Console</RootNamespace>

View File

@ -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;

View File

@ -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)