using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using CameraUnlock.Core.Aim; using CameraUnlock.Core.Config; using CameraUnlock.Core.Data; using CameraUnlock.Core.Math; using CameraUnlock.Core.Processing; using CameraUnlock.Core.Processing.AxisTransform; using CameraUnlock.Core.Protocol; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("CameraUnlock Mods")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Core shared library for CameraUnlock head tracking mods")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a3d52bd5a721ee09791549291eb39372d89796c7")] [assembly: AssemblyProduct("CameraUnlock.Core")] [assembly: AssemblyTitle("CameraUnlock.Core")] [assembly: AssemblyVersion("1.0.0.0")] namespace CameraUnlock.Core.Tracking { public static class StaticHeadTrackingCore { private static OpenTrackReceiver _receiver; private static TrackingProcessor _processor; private static IHeadTrackingConfig _config; private static bool _initialized; private static bool _enabled = true; private static bool _hasAutoRecentered; private static Action _log; public static bool IsEnabled { get { return _enabled; } set { if (_enabled != value) { _enabled = value; _log?.Invoke(_enabled ? "Tracking enabled" : "Tracking disabled"); if (!_enabled) { Reset(); } } } } public static bool IsReceiving { get { if (_receiver != null) { return _receiver.IsReceiving; } return false; } } public static bool IsRemoteConnection { get { if (_receiver != null) { return _receiver.IsRemoteConnection; } return false; } } public static bool IsInitialized => _initialized; public static IHeadTrackingConfig Config => _config; public static TrackingProcessor Processor => _processor; public static OpenTrackReceiver Receiver => _receiver; public static void Initialize(IHeadTrackingConfig config, Action log = null) { if (_initialized) { log?.Invoke("StaticHeadTrackingCore already initialized"); return; } _config = config; _log = log; _initialized = true; _log?.Invoke("StaticHeadTrackingCore initializing..."); _processor = new TrackingProcessor { Sensitivity = config.Sensitivity, SmoothingFactor = 0f }; _receiver = new OpenTrackReceiver(); _receiver.Log = _log; if (_receiver.Start(config.UdpPort)) { _log?.Invoke($"Listening on UDP port {config.UdpPort}"); } _enabled = config.EnableOnStartup; } public static bool Update(float deltaTime) { if (!_initialized || !_enabled || _receiver == null || _processor == null) { return false; } if (!_receiver.IsReceiving) { return false; } if (!_hasAutoRecentered) { _hasAutoRecentered = true; _receiver.Recenter(); _log?.Invoke("Auto-recentered on first connection"); } return true; } public static TrackingPose GetProcessedPose(float deltaTime) { if (!_initialized || _receiver == null || _processor == null) { return new TrackingPose(0f, 0f, 0f, 0L); } TrackingPose latestPose = _receiver.GetLatestPose(); return _processor.Process(latestPose, deltaTime); } public static void GetProcessedRotation(float deltaTime, out float yaw, out float pitch, out float roll) { TrackingPose processedPose = GetProcessedPose(deltaTime); yaw = processedPose.Yaw; pitch = processedPose.Pitch; roll = processedPose.Roll; } public static void GetAimScreenOffset(float yaw, float pitch, float roll, float horizontalFov, float verticalFov, float screenWidth, float screenHeight, out float offsetX, out float offsetY) { ScreenOffsetCalculator.Calculate(yaw, pitch, roll, horizontalFov, verticalFov, screenWidth, screenHeight, 1f, out offsetX, out offsetY); } public static void GetAimScreenPosition(float yaw, float pitch, float roll, float horizontalFov, float verticalFov, float screenWidth, float screenHeight, out float posX, out float posY) { GetAimScreenOffset(yaw, pitch, roll, horizontalFov, verticalFov, screenWidth, screenHeight, out var offsetX, out var offsetY); posX = screenWidth * 0.5f + offsetX; posY = screenHeight * 0.5f + offsetY; } public static void Recenter() { _receiver?.Recenter(); _processor?.Reset(); _log?.Invoke("Recentered"); } public static bool Toggle() { IsEnabled = !IsEnabled; return IsEnabled; } public static void Reset() { _processor?.Reset(); _hasAutoRecentered = false; } public static void Shutdown() { if (_receiver != null) { _receiver.Stop(); _receiver = null; } _processor = null; _config = null; _initialized = false; _hasAutoRecentered = false; _log?.Invoke("StaticHeadTrackingCore shut down"); _log = null; } } } namespace CameraUnlock.Core.State { public static class CommonMenuScenes { public static string[] Default { get; } = new string[22] { "MainMenu", "Menu", "Main Menu", "StartMenu", "Start Menu", "TitleScreen", "Title", "Credits", "Loading", "LoadingScreen", "Splash", "Intro", "Cutscene", "Options", "Settings", "Pause", "PauseMenu", "GameOver", "Game Over", "EndGame", "Victory", "Defeat" }; public static HashSet CreateDefaultSet() { return new HashSet(Default, StringComparer.OrdinalIgnoreCase); } } public enum GameContext { Unknown, Menu, Loading, Gameplay, Cutscene } public abstract class GameStateDetectorBase : IGameStateDetector, IDisposable { public const float DefaultCheckIntervalSeconds = 0.1f; private readonly float _checkIntervalSeconds; private readonly GetCurrentTime _getTime; private readonly HashSet _menuSceneNames; private bool _cachedIsInGameplay; private float _lastCheckTime; private bool _disposed; private string _cachedSceneName; private bool _cachedIsMenuScene; public bool IsInGameplay { get { if (_disposed) { return false; } float num = _getTime(); if (num - _lastCheckTime < _checkIntervalSeconds) { return _cachedIsInGameplay; } _lastCheckTime = num; _cachedIsInGameplay = CheckGameplayState(); return _cachedIsInGameplay; } } protected GameStateDetectorBase(GetCurrentTime getTime, float checkIntervalSeconds = 0.1f) { _getTime = getTime ?? throw new ArgumentNullException("getTime"); _checkIntervalSeconds = checkIntervalSeconds; _menuSceneNames = CommonMenuScenes.CreateDefaultSet(); _cachedSceneName = string.Empty; } public void InvalidateCache() { _lastCheckTime = 0f; } protected virtual bool CheckGameplayState() { string currentSceneName = GetCurrentSceneName(); if (!string.Equals(_cachedSceneName, currentSceneName, StringComparison.Ordinal)) { _cachedSceneName = currentSceneName ?? string.Empty; _cachedIsMenuScene = !string.IsNullOrEmpty(_cachedSceneName) && _menuSceneNames.Contains(_cachedSceneName); } if (_cachedIsMenuScene) { return false; } if (IsGamePaused()) { return false; } if (IsCursorVisible()) { return false; } if (IsMenuVisible()) { return false; } if (IsInventoryOpen()) { return false; } if (IsTextInputActive()) { return false; } if (IsPlayerDead()) { return false; } if (!HasCameraControl()) { return false; } return true; } protected abstract string GetCurrentSceneName(); protected virtual bool IsGamePaused() { return false; } protected virtual bool IsCursorVisible() { return false; } protected virtual bool IsMenuVisible() { return false; } protected virtual bool IsInventoryOpen() { return false; } protected virtual bool IsTextInputActive() { return false; } protected virtual bool IsPlayerDead() { return false; } protected virtual bool HasCameraControl() { return true; } public void AddMenuScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { throw new ArgumentException("Scene name cannot be null or empty", "sceneName"); } _menuSceneNames.Add(sceneName); if (string.Equals(_cachedSceneName, sceneName, StringComparison.OrdinalIgnoreCase)) { _cachedIsMenuScene = true; } } public bool RemoveMenuScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { throw new ArgumentException("Scene name cannot be null or empty", "sceneName"); } bool num = _menuSceneNames.Remove(sceneName); if (num && string.Equals(_cachedSceneName, sceneName, StringComparison.OrdinalIgnoreCase)) { _cachedIsMenuScene = false; } return num; } public void ClearMenuScenes() { _menuSceneNames.Clear(); _cachedIsMenuScene = false; } public void Dispose() { if (!_disposed) { _disposed = true; OnDispose(); } } protected virtual void OnDispose() { } } public interface IGameStateDetector : IDisposable { bool IsInGameplay { get; } void InvalidateCache(); } public delegate bool GameplayConditionCheck(); public delegate float GetCurrentTime(); public static class TrackingState { private static int _enabledState; public static bool IsEnabled => Interlocked.CompareExchange(ref _enabledState, 0, 0) == 1; public static event Action OnStateChanged; public static void Initialize(bool enabled) { Interlocked.Exchange(ref _enabledState, enabled ? 1 : 0); } public static bool Toggle() { int num; int num2; do { num = Interlocked.CompareExchange(ref _enabledState, 0, 0); num2 = ((num == 0) ? 1 : 0); } while (Interlocked.CompareExchange(ref _enabledState, num2, num) != num); bool flag = num2 == 1; TrackingState.OnStateChanged?.Invoke(flag); return flag; } public static void Enable() { if (Interlocked.Exchange(ref _enabledState, 1) == 0) { TrackingState.OnStateChanged?.Invoke(obj: true); } } public static void Disable() { if (Interlocked.Exchange(ref _enabledState, 0) == 1) { TrackingState.OnStateChanged?.Invoke(obj: false); } } public static void SetEnabled(bool enabled) { if (enabled) { Enable(); } else { Disable(); } } } } namespace CameraUnlock.Core.Protocol { public interface ITrackingDataSource { bool IsReceiving { get; } bool IsRemoteConnection { get; } bool IsFailed { get; } TrackingPose GetLatestPose(); void GetRawRotation(out float yaw, out float pitch, out float roll); void Recenter(); } public static class OpenTrackPacket { public const int MinPacketSize = 48; public const int XOffset = 0; public const int YOffset = 8; public const int ZOffset = 16; public const int YawOffset = 24; public const int PitchOffset = 32; public const int RollOffset = 40; public const float CmToMeters = 0.01f; public static bool TryParse(byte[] data, out TrackingPose pose) { pose = default(TrackingPose); if (data == null || data.Length < 48) { return false; } double num = BitConverter.ToDouble(data, 24); double num2 = BitConverter.ToDouble(data, 32); double num3 = BitConverter.ToDouble(data, 40); if (double.IsNaN(num) || double.IsInfinity(num) || double.IsNaN(num2) || double.IsInfinity(num2) || double.IsNaN(num3) || double.IsInfinity(num3)) { return false; } pose = new TrackingPose((float)num, (float)num2, (float)num3); return true; } public static bool TryParsePosition(byte[] data, out PositionData position) { position = default(PositionData); if (data == null || data.Length < 48) { return false; } double num = BitConverter.ToDouble(data, 0); double num2 = BitConverter.ToDouble(data, 8); double num3 = BitConverter.ToDouble(data, 16); if (double.IsNaN(num) || double.IsInfinity(num) || double.IsNaN(num2) || double.IsInfinity(num2) || double.IsNaN(num3) || double.IsInfinity(num3)) { return false; } position = new PositionData((float)num * 0.01f, (float)num2 * 0.01f, (float)num3 * 0.01f); return true; } } public sealed class OpenTrackReceiver : ITrackingDataSource, IDisposable { public const int DefaultPort = 4242; private const int ReceiveTimeoutMs = 100; private const int DisconnectThreshold = 50; private const int RetryIntervalMs = 5000; private const int RetryLogIntervalMs = 30000; public const int DefaultMaxDataAgeMs = 500; private UdpClient _udpClient; private Thread _receiveThread; private volatile bool _isRunning; private volatile bool _isConnected; private int _consecutiveTimeouts; private bool _disposed; private volatile bool _retrying; private Thread _retryThread; private int _port; private volatile float _rotationPitch; private volatile float _rotationYaw; private volatile float _rotationRoll; private long _timestampTicks; private volatile bool _isRemoteConnection; private volatile float _positionX; private volatile float _positionY; private volatile float _positionZ; private float _offsetYaw; private float _offsetPitch; private float _offsetRoll; private float _offsetX; private float _offsetY; private float _offsetZ; private readonly object _offsetLock = new object(); private readonly CoordinateTransformer _transformer; public bool HasTransformer => _transformer != null; public bool IsReceiving => _isConnected; public bool IsFailed { get; private set; } public bool IsRemoteConnection => _isRemoteConnection; public Action Log { get; set; } public OpenTrackReceiver() : this(null) { } public OpenTrackReceiver(CoordinateTransformer transformer) { _transformer = transformer; } public bool Start(int port = 4242) { if (_disposed) { return false; } if (_isRunning) { return true; } if (_retrying) { return false; } IsFailed = false; _port = port; try { _udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, port)); _udpClient.Client.ReceiveTimeout = 100; } catch (SocketException) { IsFailed = true; Log?.Invoke($"Failed to bind UDP port {port} -- will retry every {5}s"); StartRetryLoop(); return false; } _isRunning = true; _receiveThread = new Thread(ReceiveLoop) { Name = "CameraUnlock-OpenTrackReceiver", IsBackground = true }; _receiveThread.Start(); return true; } private void StartRetryLoop() { _retrying = true; _retryThread = new Thread(RetryLoop) { Name = "CameraUnlock-PortRetry", IsBackground = true }; _retryThread.Start(); } private void RetryLoop() { int num = 6; int num2 = 0; while (_retrying && !_disposed) { for (int i = 0; i < 50; i++) { if (!_retrying || _disposed) { return; } Thread.Sleep(100); } if (!_retrying || _disposed) { break; } num2++; try { UdpClient udpClient = new UdpClient(new IPEndPoint(IPAddress.Any, _port)); udpClient.Client.ReceiveTimeout = 100; if (!_retrying || _disposed) { try { udpClient.Close(); break; } catch (SocketException) { break; } } _udpClient = udpClient; IsFailed = false; _retrying = false; _isRunning = true; _receiveThread = new Thread(ReceiveLoop) { Name = "CameraUnlock-OpenTrackReceiver", IsBackground = true }; _receiveThread.Start(); Log?.Invoke($"Bound UDP port {_port} after {num2} retries"); break; } catch (SocketException) { if (num2 % num == 0) { Log?.Invoke($"Still waiting for UDP port {_port} ({num2 * 5000 / 1000}s elapsed)"); } } } } public void Stop() { _isRunning = false; _retrying = false; Thread retryThread = _retryThread; _retryThread = null; retryThread?.Join(1000); Thread receiveThread = _receiveThread; _receiveThread = null; receiveThread?.Join(1000); UdpClient udpClient = _udpClient; _udpClient = null; if (udpClient != null) { try { udpClient.Close(); } catch (SocketException) { } } _isConnected = false; } public TrackingPose GetRawPose() { float rotationYaw = _rotationYaw; float rotationPitch = _rotationPitch; float rotationRoll = _rotationRoll; long timestampTicks = Interlocked.Read(ref _timestampTicks); return new TrackingPose(rotationYaw, rotationPitch, rotationRoll, timestampTicks); } public void GetRawRotation(out float yaw, out float pitch, out float roll) { pitch = _rotationPitch; yaw = _rotationYaw; roll = _rotationRoll; } public TrackingPose GetLatestPose() { float num = _rotationYaw; float num2 = _rotationPitch; float num3 = _rotationRoll; long timestampTicks = Interlocked.Read(ref _timestampTicks); Monitor.Enter(_offsetLock); try { num -= _offsetYaw; num2 -= _offsetPitch; num3 -= _offsetRoll; } finally { Monitor.Exit(_offsetLock); } return new TrackingPose(num, num2, num3, timestampTicks); } public TrackingPose GetLatestPoseTransformed() { TrackingPose trackingPose = GetLatestPose(); if (_transformer != null) { trackingPose = _transformer.Transform(trackingPose); } return trackingPose; } public PositionData GetLatestPosition() { float num = _positionX; float num2 = _positionY; float num3 = _positionZ; long timestampTicks = Interlocked.Read(ref _timestampTicks); Monitor.Enter(_offsetLock); try { num -= _offsetX; num2 -= _offsetY; num3 -= _offsetZ; } finally { Monitor.Exit(_offsetLock); } return new PositionData(num, num2, num3, timestampTicks); } public bool IsDataFresh(int maxAgeMs = 500) { long num = Interlocked.Read(ref _timestampTicks); if (num == 0L) { return false; } return (double)(Stopwatch.GetTimestamp() - num) * 1000.0 / (double)Stopwatch.Frequency < (double)maxAgeMs; } public void Recenter() { Monitor.Enter(_offsetLock); try { _offsetYaw = _rotationYaw; _offsetPitch = _rotationPitch; _offsetRoll = _rotationRoll; _offsetX = _positionX; _offsetY = _positionY; _offsetZ = _positionZ; } finally { Monitor.Exit(_offsetLock); } } public void ResetOffset() { Monitor.Enter(_offsetLock); try { _offsetYaw = 0f; _offsetPitch = 0f; _offsetRoll = 0f; _offsetX = 0f; _offsetY = 0f; _offsetZ = 0f; } finally { Monitor.Exit(_offsetLock); } } private void ReceiveLoop() { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); while (_isRunning) { try { if (_udpClient == null) { break; } byte[] array = _udpClient.Receive(ref remoteEP); if (array.Length >= 48) { if (OpenTrackPacket.TryParse(array, out var pose)) { _rotationYaw = pose.Yaw; _rotationPitch = pose.Pitch; _rotationRoll = pose.Roll; Interlocked.Exchange(ref _timestampTicks, Stopwatch.GetTimestamp()); } if (OpenTrackPacket.TryParsePosition(array, out var position)) { _positionX = position.X; _positionY = position.Y; _positionZ = position.Z; } _isRemoteConnection = !IPAddress.IsLoopback(remoteEP.Address); _isConnected = true; _consecutiveTimeouts = 0; } } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.TimedOut) { _consecutiveTimeouts++; if (_consecutiveTimeouts >= 50) { _isConnected = false; } } else if (ex.SocketErrorCode == SocketError.Interrupted) { break; } } catch (ObjectDisposedException) { break; } } } public void Dispose() { if (!_disposed) { _disposed = true; Stop(); } } } } namespace CameraUnlock.Core.Processing { public sealed class CenterOffsetManager { private TrackingPose _centerOffset; private Quat4 _centerQuaternionInverse = Quat4.Identity; private bool _hasValidCenter; public TrackingPose CenterOffset => _centerOffset; public bool HasValidCenter => _hasValidCenter; public void SetCenter(TrackingPose pose) { _centerOffset = new TrackingPose(pose.Yaw, pose.Pitch, pose.Roll, 0L); _centerQuaternionInverse = QuaternionUtils.FromYawPitchRoll(pose.Yaw, pose.Pitch, pose.Roll).Inverse; _hasValidCenter = true; } public void SetCenter(float yaw, float pitch, float roll) { _centerOffset = new TrackingPose(yaw, pitch, roll, 0L); _centerQuaternionInverse = QuaternionUtils.FromYawPitchRoll(yaw, pitch, roll).Inverse; _hasValidCenter = true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public TrackingPose ApplyOffset(TrackingPose pose) { if (!_hasValidCenter) { return pose; } return pose.SubtractOffset(_centerOffset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ApplyOffset(float yaw, float pitch, float roll, out float outYaw, out float outPitch, out float outRoll) { if (!_hasValidCenter) { outYaw = yaw; outPitch = pitch; outRoll = roll; } else { outYaw = yaw - _centerOffset.Yaw; outPitch = pitch - _centerOffset.Pitch; outRoll = roll - _centerOffset.Roll; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Quat4 ApplyOffsetQuat(Quat4 inputQ) { if (!_hasValidCenter) { return inputQ; } return _centerQuaternionInverse * inputQ; } public void ComposeAdditionalOffset(Quat4 relativeQ) { _centerQuaternionInverse = relativeQ.Inverse * _centerQuaternionInverse; QuaternionUtils.ToEulerYXZ(_centerQuaternionInverse.Inverse, out var yaw, out var pitch, out var roll); _centerOffset = new TrackingPose(yaw, pitch, roll, 0L); _hasValidCenter = true; } public void Reset() { _centerOffset = default(TrackingPose); _centerQuaternionInverse = Quat4.Identity; _hasValidCenter = false; } } public interface IInfluenceModifier { float GetInfluence(); void Reset(); } public class CompositeInfluenceModifier : IInfluenceModifier { private readonly IInfluenceModifier[] _modifiers; public CompositeInfluenceModifier(params IInfluenceModifier[] modifiers) { _modifiers = modifiers ?? new IInfluenceModifier[0]; } public float GetInfluence() { float num = 1f; for (int i = 0; i < _modifiers.Length; i++) { num *= _modifiers[i].GetInfluence(); if (num <= 0f) { return 0f; } } return num; } public void Reset() { for (int i = 0; i < _modifiers.Length; i++) { _modifiers[i].Reset(); } } } public class ToggleInfluenceModifier : IInfluenceModifier { private bool _enabled = true; public bool Enabled { get { return _enabled; } set { _enabled = value; } } public float GetInfluence() { if (!_enabled) { return 0f; } return 1f; } public void Reset() { _enabled = true; } } public interface ITrackingProcessor { TrackingPose Process(TrackingPose rawPose, float deltaTime); void Reset(); } public sealed class PoseInterpolator { private const float IntervalBlend = 0.3f; private const float DefaultSampleInterval = 1f / 30f; private const float MinSampleInterval = 0.001f; private const float MaxSampleInterval = 0.2f; private float _fromYaw; private float _fromPitch; private float _fromRoll; private float _toYaw; private float _toPitch; private float _toRoll; private long _lastTimestampTicks; private float _progress; private float _sampleInterval = 1f / 30f; private float _timeSinceLastNewSample; private bool _hasFirstSample; private bool _hasSecondSample; public float MaxExtrapolationFraction { get; set; } = 0.5f; public TrackingPose Update(TrackingPose rawPose, float deltaTime) { if (!rawPose.IsValid) { return rawPose; } _timeSinceLastNewSample += deltaTime; if (rawPose.TimestampTicks != _lastTimestampTicks) { if (!_hasFirstSample) { _fromYaw = rawPose.Yaw; _fromPitch = rawPose.Pitch; _fromRoll = rawPose.Roll; _toYaw = rawPose.Yaw; _toPitch = rawPose.Pitch; _toRoll = rawPose.Roll; _lastTimestampTicks = rawPose.TimestampTicks; _progress = 1f; _timeSinceLastNewSample = 0f; _hasFirstSample = true; return rawPose; } if (_timeSinceLastNewSample > 0.001f) { if (!_hasSecondSample) { _sampleInterval = _timeSinceLastNewSample; _hasSecondSample = true; } else { _sampleInterval += (_timeSinceLastNewSample - _sampleInterval) * 0.3f; } if (_sampleInterval < 0.001f) { _sampleInterval = 0.001f; } if (_sampleInterval > 0.2f) { _sampleInterval = 0.2f; } } float num = 1f + MaxExtrapolationFraction; float num2 = ((_progress < 0f) ? 0f : ((_progress > num) ? num : _progress)); _fromYaw += (_toYaw - _fromYaw) * num2; _fromPitch += (_toPitch - _fromPitch) * num2; _fromRoll += (_toRoll - _fromRoll) * num2; _toYaw = rawPose.Yaw; _toPitch = rawPose.Pitch; _toRoll = rawPose.Roll; _lastTimestampTicks = rawPose.TimestampTicks; _progress = 0f; _timeSinceLastNewSample = 0f; } _progress += deltaTime / _sampleInterval; float num3 = 1f + MaxExtrapolationFraction; float num4 = ((_progress > num3) ? num3 : ((_progress < 0f) ? 0f : _progress)); float yaw = _fromYaw + (_toYaw - _fromYaw) * num4; float pitch = _fromPitch + (_toPitch - _fromPitch) * num4; float roll = _fromRoll + (_toRoll - _fromRoll) * num4; return new TrackingPose(yaw, pitch, roll, rawPose.TimestampTicks); } public void Reset() { _fromYaw = 0f; _fromPitch = 0f; _fromRoll = 0f; _toYaw = 0f; _toPitch = 0f; _toRoll = 0f; _lastTimestampTicks = 0L; _progress = 0f; _sampleInterval = 1f / 30f; _timeSinceLastNewSample = 0f; _hasFirstSample = false; _hasSecondSample = false; } } public sealed class PositionInterpolator { private const float IntervalBlend = 0.3f; private const float DefaultSampleInterval = 1f / 30f; private const float MinSampleInterval = 0.001f; private const float MaxSampleInterval = 0.2f; private float _fromX; private float _fromY; private float _fromZ; private float _toX; private float _toY; private float _toZ; private long _lastTimestampTicks; private float _progress; private float _sampleInterval = 1f / 30f; private float _timeSinceLastNewSample; private bool _hasFirstSample; private bool _hasSecondSample; public float MaxExtrapolationFraction { get; set; } = 0.5f; public PositionData Update(PositionData rawPosition, float deltaTime) { if (!rawPosition.IsValid) { return rawPosition; } _timeSinceLastNewSample += deltaTime; if (rawPosition.TimestampTicks != _lastTimestampTicks) { if (!_hasFirstSample) { _fromX = rawPosition.X; _fromY = rawPosition.Y; _fromZ = rawPosition.Z; _toX = rawPosition.X; _toY = rawPosition.Y; _toZ = rawPosition.Z; _lastTimestampTicks = rawPosition.TimestampTicks; _progress = 1f; _timeSinceLastNewSample = 0f; _hasFirstSample = true; return rawPosition; } if (_timeSinceLastNewSample > 0.001f) { if (!_hasSecondSample) { _sampleInterval = _timeSinceLastNewSample; _hasSecondSample = true; } else { _sampleInterval += (_timeSinceLastNewSample - _sampleInterval) * 0.3f; } if (_sampleInterval < 0.001f) { _sampleInterval = 0.001f; } if (_sampleInterval > 0.2f) { _sampleInterval = 0.2f; } } float num = 1f + MaxExtrapolationFraction; float num2 = ((_progress < 0f) ? 0f : ((_progress > num) ? num : _progress)); _fromX += (_toX - _fromX) * num2; _fromY += (_toY - _fromY) * num2; _fromZ += (_toZ - _fromZ) * num2; _toX = rawPosition.X; _toY = rawPosition.Y; _toZ = rawPosition.Z; _lastTimestampTicks = rawPosition.TimestampTicks; _progress = 0f; _timeSinceLastNewSample = 0f; } _progress += deltaTime / _sampleInterval; float num3 = 1f + MaxExtrapolationFraction; float num4 = ((_progress > num3) ? num3 : ((_progress < 0f) ? 0f : _progress)); float x = _fromX + (_toX - _fromX) * num4; float y = _fromY + (_toY - _fromY) * num4; float z = _fromZ + (_toZ - _fromZ) * num4; return new PositionData(x, y, z, rawPosition.TimestampTicks); } public void Reset() { _fromX = 0f; _fromY = 0f; _fromZ = 0f; _toX = 0f; _toY = 0f; _toZ = 0f; _lastTimestampTicks = 0L; _progress = 0f; _sampleInterval = 1f / 30f; _timeSinceLastNewSample = 0f; _hasFirstSample = false; _hasSecondSample = false; } } public sealed class PositionProcessor { private Vec3 _center; private Vec3 _smoothedPosition; private bool _hasSmoothedValue; public PositionSettings Settings { get; set; } = PositionSettings.Default; public float TrackerPivotForward { get; set; } = 0.01f; public Vec3 Process(PositionData raw, Quat4 processedRotationQ, float deltaTime) { if (!raw.IsValid) { return Vec3.Zero; } Vec3 vec = raw.ToVec3() - _center; if (TrackerPivotForward > 0f) { Vec3 vec2 = new Vec3(0f, 0f, TrackerPivotForward); Vec3 vec3 = processedRotationQ.Rotate(vec2) - vec2; vec -= vec3; } float num = vec.X * Settings.SensitivityX; float num2 = vec.Y * Settings.SensitivityY; float num3 = vec.Z * Settings.SensitivityZ; if (Settings.InvertX) { num = 0f - num; } if (Settings.InvertY) { num2 = 0f - num2; } if (Settings.InvertZ) { num3 = 0f - num3; } Vec3 smoothedPosition = new Vec3(num, num2, num3); float effectiveSmoothing = SmoothingUtils.GetEffectiveSmoothing(Settings.Smoothing); if (!_hasSmoothedValue) { _smoothedPosition = smoothedPosition; _hasSmoothedValue = true; } else { float t = SmoothingUtils.CalculateSmoothingFactor(effectiveSmoothing, deltaTime); _smoothedPosition = new Vec3(MathUtils.Lerp(_smoothedPosition.X, smoothedPosition.X, t), MathUtils.Lerp(_smoothedPosition.Y, smoothedPosition.Y, t), MathUtils.Lerp(_smoothedPosition.Z, smoothedPosition.Z, t)); } return new Vec3(MathUtils.Clamp(_smoothedPosition.X, 0f - Settings.LimitX, Settings.LimitX), MathUtils.Clamp(_smoothedPosition.Y, 0f - Settings.LimitY, Settings.LimitY), MathUtils.Clamp(_smoothedPosition.Z, 0f - Settings.LimitZBack, Settings.LimitZ)); } public void SetCenter(PositionData centerPosition) { _center = centerPosition.ToVec3(); } public void ResetSmoothing() { _smoothedPosition = Vec3.Zero; _hasSmoothedValue = false; } public void Reset() { _center = Vec3.Zero; _smoothedPosition = Vec3.Zero; _hasSmoothedValue = false; } } public sealed class SmoothedEulerState { private Quat4 _smoothed; private bool _initialized; public void Update(float yaw, float pitch, float roll, float smoothing, float deltaTime, out float smoothedYaw, out float smoothedPitch, out float smoothedRoll) { if (smoothing < 0.001f) { _initialized = false; smoothedYaw = yaw; smoothedPitch = pitch; smoothedRoll = roll; return; } Quat4 quat = QuaternionUtils.FromYawPitchRoll(yaw, pitch, roll); if (!_initialized) { _smoothed = quat; _initialized = true; smoothedYaw = yaw; smoothedPitch = pitch; smoothedRoll = roll; } else { float t = SmoothingUtils.CalculateSmoothingFactor(smoothing, deltaTime); _smoothed = QuaternionUtils.Slerp(_smoothed, quat, t); QuaternionUtils.ToEulerYXZ(_smoothed, out smoothedYaw, out smoothedPitch, out smoothedRoll); } } public void Reset() { _smoothed = Quat4.Identity; _initialized = false; } } public sealed class TrackingProcessor : ITrackingProcessor { private readonly CenterOffsetManager _centerManager = new CenterOffsetManager(); private float _smoothedYaw; private float _smoothedPitch; private float _smoothedRoll; private bool _hasSmoothedValue; public SensitivitySettings Sensitivity { get; set; } = SensitivitySettings.Default; public DeadzoneSettings Deadzone { get; set; } = DeadzoneSettings.None; public float SmoothingFactor { get; set; } public CenterOffsetManager CenterManager => _centerManager; public void GetSmoothedRotation(out float yaw, out float pitch, out float roll) { yaw = _smoothedYaw; pitch = _smoothedPitch; roll = _smoothedRoll; } public TrackingPose Process(TrackingPose rawPose, float deltaTime) { if (!rawPose.IsValid) { return rawPose; } Quat4 inputQ = QuaternionUtils.FromYawPitchRoll(rawPose.Yaw, rawPose.Pitch, rawPose.Roll); QuaternionUtils.ToEulerYXZ(_centerManager.ApplyOffsetQuat(inputQ), out var yaw, out var pitch, out var roll); yaw = DeadzoneUtils.Apply(yaw, Deadzone.Yaw); pitch = DeadzoneUtils.Apply(pitch, Deadzone.Pitch); roll = DeadzoneUtils.Apply(roll, Deadzone.Roll); float effectiveSmoothing = SmoothingUtils.GetEffectiveSmoothing(SmoothingFactor); if (!_hasSmoothedValue) { _smoothedYaw = yaw; _smoothedPitch = pitch; _smoothedRoll = roll; _hasSmoothedValue = true; } else { _smoothedYaw = SmoothingUtils.Smooth(_smoothedYaw, yaw, effectiveSmoothing, deltaTime); _smoothedPitch = SmoothingUtils.Smooth(_smoothedPitch, pitch, effectiveSmoothing, deltaTime); _smoothedRoll = SmoothingUtils.Smooth(_smoothedRoll, roll, effectiveSmoothing, deltaTime); } return new TrackingPose(_smoothedYaw, _smoothedPitch, _smoothedRoll, rawPose.TimestampTicks).ApplySensitivity(Sensitivity); } public void Recenter() { Quat4 relativeQ = QuaternionUtils.FromYawPitchRoll(_smoothedYaw, _smoothedPitch, _smoothedRoll); _centerManager.ComposeAdditionalOffset(relativeQ); _smoothedYaw = 0f; _smoothedPitch = 0f; _smoothedRoll = 0f; } public void RecenterTo(TrackingPose pose) { _centerManager.SetCenter(pose); _smoothedYaw = 0f; _smoothedPitch = 0f; _smoothedRoll = 0f; } public void ResetSmoothing() { _smoothedYaw = 0f; _smoothedPitch = 0f; _smoothedRoll = 0f; _hasSmoothedValue = false; } public void Reset() { _centerManager.Reset(); _smoothedYaw = 0f; _smoothedPitch = 0f; _smoothedRoll = 0f; _hasSmoothedValue = false; } } } namespace CameraUnlock.Core.Processing.AxisTransform { public enum AxisSource { Yaw, Pitch, Roll, X, Y, Z, None } public enum TargetAxis { Yaw, Pitch, Roll } public class AxisConfig { public AxisSource Source { get; set; } public TargetAxis Target { get; set; } public float Sensitivity { get; set; } = 1f; public bool Inverted { get; set; } public float DeadzoneMin { get; set; } public float DeadzoneMax { get; set; } public float MinLimit { get; set; } = -180f; public float MaxLimit { get; set; } = 180f; public bool EnableLimits { get; set; } public SensitivityCurve SensitivityCurve { get; set; } public float CurveStrength { get; set; } = 1f; public Func CustomCurveFunc { get; set; } public float MaxInputRange { get; set; } = 180f; public float TransformValue(float input) { if (Source == AxisSource.None) { return 0f; } input = ApplyDeadzone(input); float num = ApplySensitivityCurve(input); float num2 = input * Sensitivity * num; if (Inverted) { num2 = 0f - num2; } if (EnableLimits) { num2 = System.Math.Max(MinLimit, System.Math.Min(MaxLimit, num2)); } return num2; } private float ApplyDeadzone(float input) { float num = System.Math.Abs(input); float num2 = ((input >= 0f) ? 1f : (-1f)); if (num < DeadzoneMin) { return 0f; } if (DeadzoneMax > DeadzoneMin && num < DeadzoneMax) { float num3 = DeadzoneMax - DeadzoneMin; float num4 = (num - DeadzoneMin) / num3; return num2 * num4 * DeadzoneMax; } return input; } private float ApplySensitivityCurve(float input) { float val = System.Math.Abs(input) / MaxInputRange; val = System.Math.Max(0f, System.Math.Min(1f, val)); return SensitivityCurveUtils.ApplyCurve(SensitivityCurve, val, CurveStrength, CustomCurveFunc); } public AxisConfig Clone() { return new AxisConfig { Source = Source, Target = Target, Sensitivity = Sensitivity, Inverted = Inverted, DeadzoneMin = DeadzoneMin, DeadzoneMax = DeadzoneMax, MinLimit = MinLimit, MaxLimit = MaxLimit, EnableLimits = EnableLimits, SensitivityCurve = SensitivityCurve, CurveStrength = CurveStrength, CustomCurveFunc = CustomCurveFunc, MaxInputRange = MaxInputRange }; } } public class MappingConfig { public AxisConfig YawConfig { get; set; } public AxisConfig PitchConfig { get; set; } public AxisConfig RollConfig { get; set; } public MappingConfig() { YawConfig = new AxisConfig { Source = AxisSource.Yaw, Target = TargetAxis.Yaw }; PitchConfig = new AxisConfig { Source = AxisSource.Pitch, Target = TargetAxis.Pitch }; RollConfig = new AxisConfig { Source = AxisSource.Roll, Target = TargetAxis.Roll }; } public void ApplyMapping(float[] rawData, out float yaw, out float pitch, out float roll) { if (rawData == null || rawData.Length < 6) { throw new ArgumentException("rawData must contain at least 6 elements [yaw, pitch, roll, x, y, z]", "rawData"); } yaw = ApplyAxisMapping(YawConfig, rawData); pitch = ApplyAxisMapping(PitchConfig, rawData); roll = ApplyAxisMapping(RollConfig, rawData); } public void ApplyMapping(float rawYaw, float rawPitch, float rawRoll, out float yaw, out float pitch, out float roll) { yaw = ApplyAxisMappingDirect(YawConfig, rawYaw, rawPitch, rawRoll); pitch = ApplyAxisMappingDirect(PitchConfig, rawYaw, rawPitch, rawRoll); roll = ApplyAxisMappingDirect(RollConfig, rawYaw, rawPitch, rawRoll); } private float ApplyAxisMappingDirect(AxisConfig config, float rawYaw, float rawPitch, float rawRoll) { float input; switch (config.Source) { case AxisSource.None: return 0f; case AxisSource.Yaw: input = rawYaw; break; case AxisSource.Pitch: input = rawPitch; break; case AxisSource.Roll: input = rawRoll; break; default: input = 0f; break; } return config.TransformValue(input); } private float ApplyAxisMapping(AxisConfig config, float[] rawData) { if (config.Source == AxisSource.None) { return 0f; } return config.TransformValue(config.Source switch { AxisSource.Yaw => rawData[0], AxisSource.Pitch => rawData[1], AxisSource.Roll => rawData[2], AxisSource.X => rawData[3], AxisSource.Y => rawData[4], AxisSource.Z => rawData[5], _ => 0f, }); } public void ResetToDefault() { YawConfig = new AxisConfig { Source = AxisSource.Yaw, Target = TargetAxis.Yaw, Sensitivity = 1f }; PitchConfig = new AxisConfig { Source = AxisSource.Pitch, Target = TargetAxis.Pitch, Sensitivity = 1f }; RollConfig = new AxisConfig { Source = AxisSource.Roll, Target = TargetAxis.Roll, Sensitivity = 1f }; } public void LoadPreset(MappingPreset preset) { MappingPresets.ApplyPreset(this, preset); } public MappingConfig Clone() { return new MappingConfig { YawConfig = (YawConfig?.Clone() ?? new AxisConfig { Source = AxisSource.Yaw, Target = TargetAxis.Yaw }), PitchConfig = (PitchConfig?.Clone() ?? new AxisConfig { Source = AxisSource.Pitch, Target = TargetAxis.Pitch }), RollConfig = (RollConfig?.Clone() ?? new AxisConfig { Source = AxisSource.Roll, Target = TargetAxis.Roll }) }; } } public enum MappingPreset { Default, InvertedPitch, NoRoll, HighSensitivity, LowSensitivity, Competitive, Simulation } public static class MappingPresets { public static void ApplyPreset(MappingConfig config, MappingPreset preset) { if (config == null) { throw new ArgumentNullException("config"); } config.ResetToDefault(); switch (preset) { case MappingPreset.InvertedPitch: config.PitchConfig.Inverted = true; break; case MappingPreset.NoRoll: config.RollConfig.Source = AxisSource.None; break; case MappingPreset.HighSensitivity: config.YawConfig.Sensitivity = 1.5f; config.PitchConfig.Sensitivity = 1.5f; config.RollConfig.Sensitivity = 1.2f; break; case MappingPreset.LowSensitivity: config.YawConfig.Sensitivity = 0.7f; config.PitchConfig.Sensitivity = 0.7f; config.RollConfig.Sensitivity = 0.5f; break; case MappingPreset.Competitive: config.YawConfig.Sensitivity = 1.2f; config.YawConfig.SensitivityCurve = SensitivityCurve.Quadratic; config.YawConfig.CurveStrength = 0.5f; config.YawConfig.DeadzoneMin = 0.5f; config.PitchConfig.Sensitivity = 0.9f; config.PitchConfig.DeadzoneMin = 0.5f; config.RollConfig.Source = AxisSource.None; break; case MappingPreset.Simulation: config.YawConfig.SensitivityCurve = SensitivityCurve.SCurve; config.YawConfig.CurveStrength = 0.7f; config.PitchConfig.SensitivityCurve = SensitivityCurve.SCurve; config.PitchConfig.CurveStrength = 0.7f; config.RollConfig.SensitivityCurve = SensitivityCurve.SCurve; config.RollConfig.CurveStrength = 0.7f; break; case MappingPreset.Default: break; } } public static MappingConfig CreateFromPreset(MappingPreset preset) { MappingConfig mappingConfig = new MappingConfig(); ApplyPreset(mappingConfig, preset); return mappingConfig; } } public enum SensitivityCurve { Linear, Quadratic, Cubic, Exponential, Logarithmic, SCurve, Custom } public static class SensitivityCurveUtils { private const double Exp2Minus1 = 6.38905609893065; public static float ApplyCurve(SensitivityCurve curve, float normalizedInput, float strength, Func customCurveFunc = null) { normalizedInput = System.Math.Max(0f, System.Math.Min(1f, normalizedInput)); float b; switch (curve) { case SensitivityCurve.Linear: b = 1f; break; case SensitivityCurve.Quadratic: b = normalizedInput * normalizedInput; break; case SensitivityCurve.Cubic: b = normalizedInput * normalizedInput * normalizedInput; break; case SensitivityCurve.Exponential: b = (float)((System.Math.Exp((double)normalizedInput * 2.0) - 1.0) / 6.38905609893065); break; case SensitivityCurve.Logarithmic: b = (float)System.Math.Log10((double)normalizedInput * 9.0 + 1.0); break; case SensitivityCurve.SCurve: b = normalizedInput * normalizedInput * (3f - 2f * normalizedInput); break; case SensitivityCurve.Custom: if (customCurveFunc == null) { throw new ArgumentException("Custom curve function must be provided when using SensitivityCurve.Custom", "customCurveFunc"); } b = customCurveFunc(normalizedInput); break; default: b = 1f; break; } return MathUtils.Lerp(1f, b, strength); } } } namespace CameraUnlock.Core.Math { public static class AngleUtils { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float NormalizeAngle(float angle) { if (angle >= -180f && angle <= 180f) { return angle; } angle %= 360f; if (angle > 180f) { angle -= 360f; } else if (angle < -180f) { angle += 360f; } return angle; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double NormalizeAngle(double angle) { if (angle >= -180.0 && angle <= 180.0) { return angle; } angle %= 360.0; if (angle > 180.0) { angle -= 360.0; } else if (angle < -180.0) { angle += 360.0; } return angle; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float ShortestAngleDelta(float from, float to) { return NormalizeAngle(to - from); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float ToRadians(float degrees) { return degrees * ((float)System.Math.PI / 180f); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float ToDegrees(float radians) { return radians * (180f / (float)System.Math.PI); } } public static class DeadzoneUtils { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Apply(float value, float deadzone) { if (deadzone <= 0f) { return value; } float num = ((value >= 0f) ? value : (0f - value)); if (num <= deadzone) { return 0f; } return ((value >= 0f) ? 1f : (-1f)) * (num - deadzone); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Apply(double value, double deadzone) { if (deadzone <= 0.0) { return value; } double num = ((value >= 0.0) ? value : (0.0 - value)); if (num <= deadzone) { return 0.0; } return ((value >= 0.0) ? 1.0 : (-1.0)) * (num - deadzone); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TrackingPose Apply(TrackingPose pose, DeadzoneSettings deadzone) { return new TrackingPose(Apply(pose.Yaw, deadzone.Yaw), Apply(pose.Pitch, deadzone.Pitch), Apply(pose.Roll, deadzone.Roll), pose.TimestampTicks); } } public static class MathConstants { public const float Pi = (float)System.Math.PI; public const float TwoPi = (float)System.Math.PI * 2f; public const float DegToRad = (float)System.Math.PI / 180f; public const float RadToDeg = 180f / (float)System.Math.PI; } public static class MathUtils { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Clamp(float value, float min, float max) { if (value < min) { return min; } if (value > max) { return max; } return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Clamp01(float value) { if (value < 0f) { return 0f; } if (value > 1f) { return 1f; } return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Lerp(float a, float b, float t) { return a + (b - a) * t; } } public static class QuaternionUtils { private const float DegToHalfRad = (float)System.Math.PI / 360f; private const float SlerpLinearThreshold = 0.9995f; private const float NormalizationEpsilon = 0.0001f; public static readonly Quat4 Identity = Quat4.Identity; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quat4 FromYawPitchRoll(float yaw, float pitch, float roll) { float num = yaw * ((float)System.Math.PI / 360f); float num2 = pitch * ((float)System.Math.PI / 360f); float num3 = roll * ((float)System.Math.PI / 360f); float num4 = (float)System.Math.Cos(num); float num5 = (float)System.Math.Sin(num); float num6 = (float)System.Math.Cos(num2); float num7 = (float)System.Math.Sin(num2); float num8 = (float)System.Math.Cos(num3); float num9 = (float)System.Math.Sin(num3); float w = num4 * num6 * num8 + num5 * num7 * num9; float x = num4 * num7 * num8 + num5 * num6 * num9; float y = num5 * num6 * num8 - num4 * num7 * num9; float z = num4 * num6 * num9 - num5 * num7 * num8; return new Quat4(x, y, z, w); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quat4 Multiply(Quat4 a, Quat4 b) { return new Quat4(a.W * b.X + a.X * b.W + a.Y * b.Z - a.Z * b.Y, a.W * b.Y - a.X * b.Z + a.Y * b.W + a.Z * b.X, a.W * b.Z + a.X * b.Y - a.Y * b.X + a.Z * b.W, a.W * b.W - a.X * b.X - a.Y * b.Y - a.Z * b.Z); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quat4 Inverse(Quat4 q) { return q.Inverse; } public static Quat4 Slerp(Quat4 a, Quat4 b, float t) { float num = a.Dot(b); float num2 = 1f; if (num < 0f) { num2 = -1f; num = 0f - num; } if (num > 0.9995f) { return Normalize(new Quat4(a.X + t * (num2 * b.X - a.X), a.Y + t * (num2 * b.Y - a.Y), a.Z + t * (num2 * b.Z - a.Z), a.W + t * (num2 * b.W - a.W))); } float num3 = (float)System.Math.Acos(num); float num4 = (float)System.Math.Sin(num3); float num5 = 1f / num4; float num6 = (float)System.Math.Sin((1f - t) * num3) * num5; float num7 = num2 * (float)System.Math.Sin(t * num3) * num5; return new Quat4(num6 * a.X + num7 * b.X, num6 * a.Y + num7 * b.Y, num6 * a.Z + num7 * b.Z, num6 * a.W + num7 * b.W); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quat4 Normalize(Quat4 q) { float num = q.X * q.X + q.Y * q.Y + q.Z * q.Z + q.W * q.W; if (num < 9.999999E-09f) { return Quat4.Identity; } float num2 = 1f / (float)System.Math.Sqrt(num); return new Quat4(q.X * num2, q.Y * num2, q.Z * num2, q.W * num2); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void ToEulerYXZ(Quat4 q, out float yaw, out float pitch, out float roll) { float num = 2f * (q.W * q.X - q.Y * q.Z); if (num >= 1f) { pitch = 90f; yaw = (float)(System.Math.Atan2(2f * (q.X * q.Z + q.W * q.Y), 1f - 2f * (q.X * q.X + q.Y * q.Y)) * 57.2957763671875); roll = 0f; } else if (num <= -1f) { pitch = -90f; yaw = (float)(System.Math.Atan2(2f * (q.X * q.Z + q.W * q.Y), 1f - 2f * (q.X * q.X + q.Y * q.Y)) * 57.2957763671875); roll = 0f; } else { pitch = (float)(System.Math.Asin(num) * 57.2957763671875); yaw = (float)(System.Math.Atan2(2f * (q.X * q.Z + q.W * q.Y), 1f - 2f * (q.X * q.X + q.Y * q.Y)) * 57.2957763671875); roll = (float)(System.Math.Atan2(2f * (q.X * q.Y + q.W * q.Z), 1f - 2f * (q.X * q.X + q.Z * q.Z)) * 57.2957763671875); } } } public static class SmoothingUtils { public const float BaselineSmoothing = 0.15f; public const float FrameInterpolationSpeed = 50f; private const float MaxSmoothing = 0.1f; private const float SpeedRange = 49.9f; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float CalculateSmoothingFactor(float smoothing, float deltaTime) { float num = 50f - 49.9f * smoothing; if (num > 50f) { num = 50f; } if (num < 0.1f) { num = 0.1f; } return 1f - (float)System.Math.Exp((0f - num) * deltaTime); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Smooth(float current, float target, float smoothing, float deltaTime) { float num = CalculateSmoothingFactor(smoothing, deltaTime); return current + (target - current) * num; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Smooth(double current, double target, float smoothing, float deltaTime) { float num = CalculateSmoothingFactor(smoothing, deltaTime); return current + (target - current) * (double)num; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float GetEffectiveSmoothing(float baseSmoothing) { if (baseSmoothing < 0.15f) { return 0.15f; } return baseSmoothing; } } } namespace CameraUnlock.Core.Input { public interface IHotkeyListener { void OnHotkeyToggle(bool enabled); void OnHotkeyRecenter(); } public delegate bool KeyDownCheck(int keyCode); public delegate bool TextInputActiveCheck(); public delegate void ToggleEventHandler(bool enabled); public delegate void RecenterEventHandler(); public sealed class HotkeyHandler { private readonly KeyDownCheck _keyDownCheck; private readonly TextInputActiveCheck _textInputCheck; private readonly IHotkeyListener _listener; private readonly float _cooldownSeconds; private int _toggleKeyCode; private int _recenterKeyCode; private float _lastToggleTime; private float _lastRecenterTime; private bool _isEnabled = true; private int _toggleCount; private int _recenterCount; public bool IsEnabled { get { return _isEnabled; } set { _isEnabled = value; } } public int ToggleCount => _toggleCount; public int RecenterCount => _recenterCount; public event ToggleEventHandler OnToggled; [Obsolete("Use OnToggled or IHotkeyListener instead")] public event RecenterEventHandler OnToggle; public event RecenterEventHandler OnRecenter; public HotkeyHandler(KeyDownCheck keyDownCheck, TextInputActiveCheck textInputCheck = null, float cooldownSeconds = 0.3f) : this(keyDownCheck, textInputCheck, null, cooldownSeconds) { } public HotkeyHandler(KeyDownCheck keyDownCheck, TextInputActiveCheck textInputCheck, IHotkeyListener listener, float cooldownSeconds = 0.3f) { _keyDownCheck = keyDownCheck; _textInputCheck = textInputCheck; _listener = listener; _cooldownSeconds = cooldownSeconds; } public void SetToggleKey(int keyCode) { _toggleKeyCode = keyCode; } public void SetRecenterKey(int keyCode) { _recenterKeyCode = keyCode; } public void Update(float currentTime) { if (_textInputCheck != null && _textInputCheck()) { return; } if (_toggleKeyCode != 0 && _keyDownCheck(_toggleKeyCode) && currentTime - _lastToggleTime >= _cooldownSeconds) { _lastToggleTime = currentTime; _toggleCount++; _isEnabled = !_isEnabled; if (_listener != null) { _listener.OnHotkeyToggle(_isEnabled); } if (this.OnToggled != null) { this.OnToggled(_isEnabled); } if (this.OnToggle != null) { this.OnToggle(); } } if (_recenterKeyCode != 0 && _keyDownCheck(_recenterKeyCode) && currentTime - _lastRecenterTime >= _cooldownSeconds) { _lastRecenterTime = currentTime; _recenterCount++; if (_listener != null) { _listener.OnHotkeyRecenter(); } if (this.OnRecenter != null) { this.OnRecenter(); } } } public bool Toggle() { _toggleCount++; _isEnabled = !_isEnabled; if (_listener != null) { _listener.OnHotkeyToggle(_isEnabled); } if (this.OnToggled != null) { this.OnToggled(_isEnabled); } if (this.OnToggle != null) { this.OnToggle(); } return _isEnabled; } public void ResetCounts() { _toggleCount = 0; _recenterCount = 0; } } public static class CommonKeyCodes { public const int None = 0; public const int Home = 278; public const int End = 279; public const int F1 = 282; public const int F2 = 283; public const int F3 = 284; public const int F4 = 285; public const int F5 = 286; public const int F6 = 287; public const int F7 = 288; public const int F8 = 289; public const int F9 = 290; public const int F10 = 291; public const int F11 = 292; public const int F12 = 293; } } namespace CameraUnlock.Core.Diagnostics { public sealed class PerformanceMonitor { private static readonly double TicksToMicroseconds = 1000000.0 / (double)Stopwatch.Frequency; private readonly long[] _samples; private readonly int _sampleCount; private int _sampleIndex; private int _validSampleCount; private long _runningSum; private readonly Stopwatch _stopwatch; private long _currentStart; public double AverageMicroseconds { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (_validSampleCount == 0) { return 0.0; } return (double)_runningSum / (double)_validSampleCount * TicksToMicroseconds; } } public PerformanceMonitor(int sampleCount = 60) { _sampleCount = ((sampleCount > 0) ? sampleCount : 60); _samples = new long[_sampleCount]; _stopwatch = new Stopwatch(); _stopwatch.Start(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void BeginTiming() { _currentStart = _stopwatch.ElapsedTicks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EndTiming() { long ticks = _stopwatch.ElapsedTicks - _currentStart; RecordTicksInternal(ticks); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RecordTicks(long ticks) { RecordTicksInternal(ticks); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RecordTicksInternal(long ticks) { long num = _samples[_sampleIndex]; _runningSum -= num; _runningSum += ticks; if (num == 0L && ticks > 0) { _validSampleCount++; } else if (num > 0 && ticks == 0L) { _validSampleCount--; } _samples[_sampleIndex] = ticks; _sampleIndex = (_sampleIndex + 1) % _sampleCount; } public void Reset() { for (int i = 0; i < _samples.Length; i++) { _samples[i] = 0L; } _sampleIndex = 0; _validSampleCount = 0; _runningSum = 0L; } public string GetReport(string label) { return $"{label}: {AverageMicroseconds:F1}µs"; } } public static class PerformanceStats { private static readonly double TicksPerMicrosecond = (double)Stopwatch.Frequency / 1000000.0; private static long _totalFrames; private static long _trackedFrames; private static long _skippedFrames; private static long _totalTrackingTicks; private static long _maxTrackingTicks; private static long _packetsReceived; private static long _packetsDropped; public static long TotalFrames => Interlocked.Read(ref _totalFrames); public static long TrackedFrames => Interlocked.Read(ref _trackedFrames); public static long SkippedFrames => Interlocked.Read(ref _skippedFrames); public static double AverageTrackingMicroseconds { get { long num = Interlocked.Read(ref _trackedFrames); if (num == 0L) { return 0.0; } return (double)Interlocked.Read(ref _totalTrackingTicks) / TicksPerMicrosecond / (double)num; } } public static double MaxTrackingMicroseconds => (double)Interlocked.Read(ref _maxTrackingTicks) / TicksPerMicrosecond; public static long PacketsReceived => Interlocked.Read(ref _packetsReceived); public static long PacketsDropped => Interlocked.Read(ref _packetsDropped); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RecordTrackedFrame(long elapsedTicks) { Interlocked.Increment(ref _totalFrames); Interlocked.Increment(ref _trackedFrames); Interlocked.Add(ref _totalTrackingTicks, elapsedTicks); long num; do { num = Interlocked.Read(ref _maxTrackingTicks); } while (elapsedTicks > num && Interlocked.CompareExchange(ref _maxTrackingTicks, elapsedTicks, num) != num); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RecordSkippedFrame() { Interlocked.Increment(ref _totalFrames); Interlocked.Increment(ref _skippedFrames); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RecordPacketReceived() { Interlocked.Increment(ref _packetsReceived); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RecordPacketDropped() { Interlocked.Increment(ref _packetsDropped); } public static void Reset() { Interlocked.Exchange(ref _totalFrames, 0L); Interlocked.Exchange(ref _trackedFrames, 0L); Interlocked.Exchange(ref _skippedFrames, 0L); Interlocked.Exchange(ref _totalTrackingTicks, 0L); Interlocked.Exchange(ref _maxTrackingTicks, 0L); Interlocked.Exchange(ref _packetsReceived, 0L); Interlocked.Exchange(ref _packetsDropped, 0L); } public static string GetSummary() { return $"Frames: {TotalFrames} total, {TrackedFrames} tracked, {SkippedFrames} skipped | Tracking: {AverageTrackingMicroseconds:F1}us avg, {MaxTrackingMicroseconds:F1}us max | UDP: {PacketsReceived} received, {PacketsDropped} dropped"; } } } namespace CameraUnlock.Core.Data { public sealed class CoordinateTransformer { public AxisMapping YawMapping { get; set; } public AxisMapping PitchMapping { get; set; } public AxisMapping RollMapping { get; set; } public CoordinateTransformer() { YawMapping = new AxisMapping(SourceAxis.Yaw, invert: false); PitchMapping = new AxisMapping(SourceAxis.Pitch, invert: false); RollMapping = new AxisMapping(SourceAxis.Roll, invert: false); } public CoordinateTransformer(AxisMapping yaw, AxisMapping pitch, AxisMapping roll) { YawMapping = yaw; PitchMapping = pitch; RollMapping = roll; } public TrackingPose Transform(TrackingPose pose) { return new TrackingPose(ApplyMapping(pose, YawMapping), ApplyMapping(pose, PitchMapping), ApplyMapping(pose, RollMapping), pose.TimestampTicks); } public void Transform(float yaw, float pitch, float roll, out float outYaw, out float outPitch, out float outRoll) { outYaw = ApplyMapping(yaw, pitch, roll, YawMapping); outPitch = ApplyMapping(yaw, pitch, roll, PitchMapping); outRoll = ApplyMapping(yaw, pitch, roll, RollMapping); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ApplyMapping(TrackingPose pose, AxisMapping mapping) { return ApplyMapping(pose.Yaw, pose.Pitch, pose.Roll, mapping); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float ApplyMapping(float yaw, float pitch, float roll, AxisMapping mapping) { float num = mapping.Source switch { SourceAxis.Yaw => yaw, SourceAxis.Pitch => pitch, SourceAxis.Roll => roll, _ => throw new InvalidOperationException("Unknown source axis: " + mapping.Source), }; if (mapping.Invert) { num = 0f - num; } return num; } public static CoordinateTransformer CreateOpenTrackToUnity() { return new CoordinateTransformer(new AxisMapping(SourceAxis.Yaw, invert: false), new AxisMapping(SourceAxis.Pitch, invert: true), new AxisMapping(SourceAxis.Roll, invert: false)); } public static CoordinateTransformer CreatePassthrough() { return new CoordinateTransformer(); } public static CoordinateTransformer CreateFromInversions(bool invertYaw, bool invertPitch, bool invertRoll) { return new CoordinateTransformer(new AxisMapping(SourceAxis.Yaw, invertYaw), new AxisMapping(SourceAxis.Pitch, invertPitch), new AxisMapping(SourceAxis.Roll, invertRoll)); } } public enum SourceAxis { Yaw, Pitch, Roll } public struct AxisMapping : IEquatable { public SourceAxis Source { get; } public bool Invert { get; } public AxisMapping(SourceAxis source, bool invert) { Source = source; Invert = invert; } public bool Equals(AxisMapping other) { if (Source == other.Source) { return Invert == other.Invert; } return false; } public override bool Equals(object obj) { if (obj is AxisMapping) { return Equals((AxisMapping)obj); } return false; } public override int GetHashCode() { return ((int)Source * 397) ^ Invert.GetHashCode(); } public static bool operator ==(AxisMapping left, AxisMapping right) { return left.Equals(right); } public static bool operator !=(AxisMapping left, AxisMapping right) { return !left.Equals(right); } } public struct DeadzoneSettings : IEquatable { public float Yaw { get; } public float Pitch { get; } public float Roll { get; } public static DeadzoneSettings None => new DeadzoneSettings(0f, 0f, 0f); public static DeadzoneSettings Default => new DeadzoneSettings(0.5f, 0.5f, 0.5f); public DeadzoneSettings(float yaw, float pitch, float roll) { Yaw = yaw; Pitch = pitch; Roll = roll; } public static DeadzoneSettings Uniform(float deadzone) { return new DeadzoneSettings(deadzone, deadzone, deadzone); } public bool Equals(DeadzoneSettings other) { if (Yaw == other.Yaw && Pitch == other.Pitch) { return Roll == other.Roll; } return false; } public override bool Equals(object obj) { if (obj is DeadzoneSettings) { return Equals((DeadzoneSettings)obj); } return false; } public override int GetHashCode() { return ((17 * 31 + Yaw.GetHashCode()) * 31 + Pitch.GetHashCode()) * 31 + Roll.GetHashCode(); } public static bool operator ==(DeadzoneSettings left, DeadzoneSettings right) { return left.Equals(right); } public static bool operator !=(DeadzoneSettings left, DeadzoneSettings right) { return !left.Equals(right); } } public struct PositionData : IEquatable { public float X { get; } public float Y { get; } public float Z { get; } public long TimestampTicks { get; } public bool IsValid => TimestampTicks != 0; public static PositionData Zero => new PositionData(0f, 0f, 0f, Stopwatch.GetTimestamp()); public PositionData(float x, float y, float z, long timestampTicks) { X = x; Y = y; Z = z; TimestampTicks = timestampTicks; } public PositionData(float x, float y, float z) : this(x, y, z, Stopwatch.GetTimestamp()) { } public Vec3 ToVec3() { return new Vec3(X, Y, Z); } public PositionData SubtractOffset(PositionData offset) { return new PositionData(X - offset.X, Y - offset.Y, Z - offset.Z, TimestampTicks); } public bool Equals(PositionData other) { if (X == other.X && Y == other.Y) { return Z == other.Z; } return false; } public override bool Equals(object obj) { if (obj is PositionData) { return Equals((PositionData)obj); } return false; } public override int GetHashCode() { return ((17 * 31 + X.GetHashCode()) * 31 + Y.GetHashCode()) * 31 + Z.GetHashCode(); } public override string ToString() { return $"PositionData(X:{X:F4}, Y:{Y:F4}, Z:{Z:F4})"; } public static bool operator ==(PositionData left, PositionData right) { return left.Equals(right); } public static bool operator !=(PositionData left, PositionData right) { return !left.Equals(right); } } public struct PositionSettings { public float SensitivityX { get; } public float SensitivityY { get; } public float SensitivityZ { get; } public float LimitX { get; } public float LimitY { get; } public float LimitZ { get; } public float LimitZBack { get; } public float Smoothing { get; } public bool InvertX { get; } public bool InvertY { get; } public bool InvertZ { get; } public static PositionSettings Default => new PositionSettings(1f, 1f, 1f, 0.3f, 0.2f, 0.4f, 0.1f, 0.15f); public PositionSettings(float sensitivityX, float sensitivityY, float sensitivityZ, float limitX, float limitY, float limitZ, float limitZBack, float smoothing, bool invertX = false, bool invertY = false, bool invertZ = false) { SensitivityX = sensitivityX; SensitivityY = sensitivityY; SensitivityZ = sensitivityZ; LimitX = limitX; LimitY = limitY; LimitZ = limitZ; LimitZBack = limitZBack; Smoothing = smoothing; InvertX = invertX; InvertY = invertY; InvertZ = invertZ; } } public readonly struct Quat4 { public readonly float X; public readonly float Y; public readonly float Z; public readonly float W; public static Quat4 Identity => new Quat4(0f, 0f, 0f, 1f); public Quat4 Negated { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return new Quat4(0f - X, 0f - Y, 0f - Z, 0f - W); } } public Quat4 Inverse { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return new Quat4(0f - X, 0f - Y, 0f - Z, W); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Quat4(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public float Dot(Quat4 other) { return X * other.X + Y * other.Y + Z * other.Z + W * other.W; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vec3 Rotate(Vec3 v) { float num = X + X; float num2 = Y + Y; float num3 = Z + Z; float num4 = X * num; float num5 = Y * num2; float num6 = Z * num3; float num7 = X * num2; float num8 = X * num3; float num9 = Y * num3; float num10 = W * num; float num11 = W * num2; float num12 = W * num3; return new Vec3((1f - num5 - num6) * v.X + (num7 - num12) * v.Y + (num8 + num11) * v.Z, (num7 + num12) * v.X + (1f - num4 - num6) * v.Y + (num9 - num10) * v.Z, (num8 - num11) * v.X + (num9 + num10) * v.Y + (1f - num4 - num5) * v.Z); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Quat4 Multiply(Quat4 b) { return new Quat4(W * b.X + X * b.W + Y * b.Z - Z * b.Y, W * b.Y - X * b.Z + Y * b.W + Z * b.X, W * b.Z + X * b.Y - Y * b.X + Z * b.W, W * b.W - X * b.X - Y * b.Y - Z * b.Z); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quat4 operator *(Quat4 a, Quat4 b) { return a.Multiply(b); } } public struct SensitivitySettings : IEquatable { public float Yaw { get; } public float Pitch { get; } public float Roll { get; } public bool InvertYaw { get; } public bool InvertPitch { get; } public bool InvertRoll { get; } public static SensitivitySettings Default => new SensitivitySettings(1f, 1f, 1f); public SensitivitySettings(float yaw, float pitch, float roll, bool invertYaw = false, bool invertPitch = false, bool invertRoll = false) { Yaw = yaw; Pitch = pitch; Roll = roll; InvertYaw = invertYaw; InvertPitch = invertPitch; InvertRoll = invertRoll; } public static SensitivitySettings Uniform(float sensitivity) { return new SensitivitySettings(sensitivity, sensitivity, sensitivity); } public SensitivitySettings WithYaw(float yaw) { return new SensitivitySettings(yaw, Pitch, Roll, InvertYaw, InvertPitch, InvertRoll); } public SensitivitySettings WithPitch(float pitch) { return new SensitivitySettings(Yaw, pitch, Roll, InvertYaw, InvertPitch, InvertRoll); } public SensitivitySettings WithRoll(float roll) { return new SensitivitySettings(Yaw, Pitch, roll, InvertYaw, InvertPitch, InvertRoll); } public bool Equals(SensitivitySettings other) { if (Yaw == other.Yaw && Pitch == other.Pitch && Roll == other.Roll && InvertYaw == other.InvertYaw && InvertPitch == other.InvertPitch) { return InvertRoll == other.InvertRoll; } return false; } public override bool Equals(object obj) { if (obj is SensitivitySettings) { return Equals((SensitivitySettings)obj); } return false; } public override int GetHashCode() { return (((((17 * 31 + Yaw.GetHashCode()) * 31 + Pitch.GetHashCode()) * 31 + Roll.GetHashCode()) * 31 + InvertYaw.GetHashCode()) * 31 + InvertPitch.GetHashCode()) * 31 + InvertRoll.GetHashCode(); } public static bool operator ==(SensitivitySettings left, SensitivitySettings right) { return left.Equals(right); } public static bool operator !=(SensitivitySettings left, SensitivitySettings right) { return !left.Equals(right); } } public struct TrackingPose : IEquatable { public const int DefaultFreshnessMs = 500; public float Yaw { get; } public float Pitch { get; } public float Roll { get; } public long TimestampTicks { get; } public bool IsValid => TimestampTicks != 0; public bool IsDataFresh => IsRecent(500); public static TrackingPose Zero => new TrackingPose(0f, 0f, 0f, Stopwatch.GetTimestamp()); public TrackingPose(float yaw, float pitch, float roll, long timestampTicks) { Yaw = yaw; Pitch = pitch; Roll = roll; TimestampTicks = timestampTicks; } public TrackingPose(float yaw, float pitch, float roll) : this(yaw, pitch, roll, Stopwatch.GetTimestamp()) { } public bool IsRecent(int maxAgeMs) { if (TimestampTicks == 0L) { return false; } return (double)(Stopwatch.GetTimestamp() - TimestampTicks) * 1000.0 / (double)Stopwatch.Frequency < (double)maxAgeMs; } public TrackingPose SubtractOffset(TrackingPose offset) { return new TrackingPose(Yaw - offset.Yaw, Pitch - offset.Pitch, Roll - offset.Roll, TimestampTicks); } public TrackingPose ApplySensitivity(SensitivitySettings sensitivity) { float num = Yaw * sensitivity.Yaw; float num2 = Pitch * sensitivity.Pitch; float num3 = Roll * sensitivity.Roll; if (sensitivity.InvertYaw) { num = 0f - num; } if (sensitivity.InvertPitch) { num2 = 0f - num2; } if (sensitivity.InvertRoll) { num3 = 0f - num3; } return new TrackingPose(num, num2, num3, TimestampTicks); } public bool Equals(TrackingPose other) { if (Yaw == other.Yaw && Pitch == other.Pitch) { return Roll == other.Roll; } return false; } public override bool Equals(object obj) { if (obj is TrackingPose) { return Equals((TrackingPose)obj); } return false; } public override int GetHashCode() { return ((17 * 31 + Yaw.GetHashCode()) * 31 + Pitch.GetHashCode()) * 31 + Roll.GetHashCode(); } public override string ToString() { return $"TrackingPose(Y:{Yaw:F2}, P:{Pitch:F2}, R:{Roll:F2})"; } public static bool operator ==(TrackingPose left, TrackingPose right) { return left.Equals(right); } public static bool operator !=(TrackingPose left, TrackingPose right) { return !left.Equals(right); } } public readonly struct Vec3 { public readonly float X; public readonly float Y; public readonly float Z; public static Vec3 Zero => new Vec3(0f, 0f, 0f); public static Vec3 Forward => new Vec3(0f, 0f, 1f); public static Vec3 Up => new Vec3(0f, 1f, 0f); public static Vec3 Right => new Vec3(1f, 0f, 0f); public float SqrMagnitude { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return X * X + Y * Y + Z * Z; } } public float Magnitude { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (float)System.Math.Sqrt(X * X + Y * Y + Z * Z); } } public Vec3 Normalized { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { float num = X * X + Y * Y + Z * Z; if (num < 1E-08f) { return Zero; } float num2 = 1f / (float)System.Math.Sqrt(num); return new Vec3(X * num2, Y * num2, Z * num2); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vec3(float x, float y, float z) { X = x; Y = y; Z = z; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vec3 operator +(Vec3 a, Vec3 b) { return new Vec3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vec3 operator -(Vec3 a, Vec3 b) { return new Vec3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vec3 operator *(Vec3 v, float s) { return new Vec3(v.X * s, v.Y * s, v.Z * s); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vec3 operator *(float s, Vec3 v) { return new Vec3(v.X * s, v.Y * s, v.Z * s); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vec3 operator -(Vec3 v) { return new Vec3(0f - v.X, 0f - v.Y, 0f - v.Z); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float Dot(Vec3 a, Vec3 b) { return a.X * b.X + a.Y * b.Y + a.Z * b.Z; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vec3 Lerp(Vec3 a, Vec3 b, float t) { return new Vec3(a.X + (b.X - a.X) * t, a.Y + (b.Y - a.Y) * t, a.Z + (b.Z - a.Z) * t); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vec3 Cross(Vec3 a, Vec3 b) { return new Vec3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); } public override string ToString() { return $"({X:F3}, {Y:F3}, {Z:F3})"; } } } namespace CameraUnlock.Core.Config { public static class ConfigParsingUtils { public static bool TryParseColor(string value, out float[] rgba) { rgba = new float[4] { 1f, 1f, 1f, 1f }; if (string.IsNullOrEmpty(value)) { return false; } string[] array = value.Split(new char[1] { ',' }); if (array.Length < 3) { return false; } float result = 1f; if (!TryParseFloat(array[0], out var result2) || !TryParseFloat(array[1], out var result3) || !TryParseFloat(array[2], out var result4)) { return false; } if (array.Length >= 4 && !TryParseFloat(array[3], out result)) { result = 1f; } if (result2 > 1f || result3 > 1f || result4 > 1f || result > 1f) { result2 /= 255f; result3 /= 255f; result4 /= 255f; if (result > 1f) { result /= 255f; } } rgba[0] = MathUtils.Clamp01(result2); rgba[1] = MathUtils.Clamp01(result3); rgba[2] = MathUtils.Clamp01(result4); rgba[3] = MathUtils.Clamp01(result); return true; } public static bool TryParseFloat(string value, out float result) { if (string.IsNullOrEmpty(value)) { result = 0f; return false; } return float.TryParse(value.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result); } public static bool TryParseInt(string value, out int result) { if (string.IsNullOrEmpty(value)) { result = 0; return false; } return int.TryParse(value.Trim(), out result); } public static bool TryParseBool(string value, out bool result) { result = false; if (string.IsNullOrEmpty(value)) { return false; } switch (value.Trim().ToLowerInvariant()) { case "true": case "yes": case "1": result = true; return true; case "false": case "no": case "0": result = false; return true; default: return false; } } public static Dictionary ParseIniFile(string filePath) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!File.Exists(filePath)) { return dictionary; } string[] array = File.ReadAllLines(filePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (string.IsNullOrEmpty(text) || text.StartsWith("#") || text.StartsWith(";") || text.StartsWith("[")) { continue; } int num = text.IndexOf('='); if (num > 0) { string key = text.Substring(0, num).Trim(); string text2 = text.Substring(num + 1).Trim(); if (text2.Length >= 2 && ((text2.StartsWith("\"") && text2.EndsWith("\"")) || (text2.StartsWith("'") && text2.EndsWith("'")))) { text2 = text2.Substring(1, text2.Length - 2); } dictionary[key] = text2; } } return dictionary; } public static string GetAssemblyDirectory(Assembly assembly) { if ((object)assembly == null) { return string.Empty; } string location = assembly.Location; if (string.IsNullOrEmpty(location)) { return string.Empty; } return Path.GetDirectoryName(location) ?? string.Empty; } } public class HeadTrackingConfigData : IHeadTrackingConfig { public int UdpPort { get; set; } = 4242; public bool EnableOnStartup { get; set; } = true; public SensitivitySettings Sensitivity { get; set; } = SensitivitySettings.Default; public string RecenterKeyName { get; set; } = "Home"; public string ToggleKeyName { get; set; } = "End"; public bool AimDecouplingEnabled { get; set; } = true; public bool ShowDecoupledReticle { get; set; } = true; public float[] ReticleColorRgba { get; set; } = new float[4] { 1f, 1f, 1f, 1f }; public float Smoothing { get; set; } public static HeadTrackingConfigData LoadFromFile(string filePath, Action log = null) { HeadTrackingConfigData headTrackingConfigData = new HeadTrackingConfigData(); try { Dictionary dictionary = ConfigParsingUtils.ParseIniFile(filePath); if (dictionary.Count == 0) { log?.Invoke("No config file found, using defaults"); return headTrackingConfigData; } headTrackingConfigData.ApplyValues(dictionary, log); log?.Invoke("Config loaded successfully"); } catch (Exception ex) { log?.Invoke($"Config load error (using defaults): {ex.Message}"); } return headTrackingConfigData; } public void ApplyValues(Dictionary values, Action log = null) { float yaw = Sensitivity.Yaw; float pitch = Sensitivity.Pitch; float roll = Sensitivity.Roll; bool invertYaw = Sensitivity.InvertYaw; bool invertPitch = Sensitivity.InvertPitch; bool invertRoll = Sensitivity.InvertRoll; foreach (KeyValuePair value2 in values) { string text = value2.Key.ToLowerInvariant().Replace("_", "").Replace("-", ""); string value = value2.Value; bool result2; float result; switch (text) { case "udpport": case "port": { if (ConfigParsingUtils.TryParseInt(value, out var result3)) { UdpPort = result3; } break; } case "enabled": case "enableonstartup": if (ConfigParsingUtils.TryParseBool(value, out result2)) { EnableOnStartup = result2; } break; case "yawsens": case "yawsensitivity": if (ConfigParsingUtils.TryParseFloat(value, out result)) { yaw = result; } break; case "pitchsens": case "pitchsensitivity": if (ConfigParsingUtils.TryParseFloat(value, out result)) { pitch = result; } break; case "rollsensitivity": case "rollsens": if (ConfigParsingUtils.TryParseFloat(value, out result)) { roll = result; } break; case "invertyaw": if (ConfigParsingUtils.TryParseBool(value, out result2)) { invertYaw = result2; } break; case "invertpitch": if (ConfigParsingUtils.TryParseBool(value, out result2)) { invertPitch = result2; } break; case "invertroll": if (ConfigParsingUtils.TryParseBool(value, out result2)) { invertRoll = result2; } break; case "centerkey": case "recenterkey": RecenterKeyName = value; break; case "togglekey": ToggleKeyName = value; break; case "aimdecouple": case "decoupleaim": case "aimdecoupling": if (ConfigParsingUtils.TryParseBool(value, out result2)) { AimDecouplingEnabled = result2; } break; case "showreticle": case "showcrosshair": case "showdecoupledreticle": if (ConfigParsingUtils.TryParseBool(value, out result2)) { ShowDecoupledReticle = result2; } break; case "crosshaircolor": case "reticlecolor": { if (ConfigParsingUtils.TryParseColor(value, out var rgba)) { ReticleColorRgba = rgba; } break; } case "smoothing": if (ConfigParsingUtils.TryParseFloat(value, out result)) { Smoothing = System.Math.Max(0f, System.Math.Min(1f, result)); } break; } } Sensitivity = new SensitivitySettings(yaw, pitch, roll, invertYaw, invertPitch, invertRoll); } public static string GetDefaultConfigPath(Assembly assembly, string fileName = "HeadTracking.cfg") { return Path.Combine(ConfigParsingUtils.GetAssemblyDirectory(assembly), fileName); } } public interface IConfigChangeNotifier { event EventHandler ConfigChanged; } public interface INotifyingHeadTrackingConfig : IHeadTrackingConfig, IConfigChangeNotifier { } public interface IHeadTrackingConfig { int UdpPort { get; } bool EnableOnStartup { get; } SensitivitySettings Sensitivity { get; } string RecenterKeyName { get; } string ToggleKeyName { get; } bool AimDecouplingEnabled { get; } bool ShowDecoupledReticle { get; } float[] ReticleColorRgba { get; } float Smoothing { get; } } } namespace CameraUnlock.Core.Config.Profiles { public class ConfigProfile { public string Name { get; set; } public string Description { get; set; } public string GameName { get; set; } public DateTime CreatedDate { get; set; } public DateTime ModifiedDate { get; set; } public bool IsDefault { get; set; } public bool IsReadOnly { get; set; } public Dictionary Settings { get; set; } public MappingConfig AxisMapping { get; set; } public ConfigProfile() { Name = "New Profile"; Description = ""; GameName = "General"; CreatedDate = DateTime.Now; ModifiedDate = DateTime.Now; Settings = new Dictionary(); AxisMapping = new MappingConfig(); } public ConfigProfile(string name) : this() { Name = name; } public ConfigProfile(string name, string description, string gameName = "General") : this() { Name = name; Description = description; GameName = gameName; } public void ExportFromAdapter(IProfileSettings adapter) { if (adapter == null) { throw new ArgumentNullException("adapter"); } Settings = adapter.ExportSettings(); GameName = adapter.GameName; ModifiedDate = DateTime.Now; } public void ImportToAdapter(IProfileSettings adapter) { if (adapter == null) { throw new ArgumentNullException("adapter"); } adapter.ImportSettings(Settings); adapter.SaveConfig(); } public ConfigProfile Clone(string newName) { if (string.IsNullOrEmpty(newName)) { throw new ArgumentException("New name cannot be null or empty", "newName"); } return new ConfigProfile { Name = newName, Description = Description + " (Copy)", GameName = GameName, CreatedDate = DateTime.Now, ModifiedDate = DateTime.Now, IsDefault = false, IsReadOnly = false, Settings = new Dictionary(Settings), AxisMapping = (AxisMapping?.Clone() ?? new MappingConfig()) }; } public T GetSetting(string key, T defaultValue = default(T)) { if (Settings == null || !Settings.TryGetValue(key, out var value)) { return defaultValue; } if (value is T) { return (T)value; } return (T)Convert.ChangeType(value, typeof(T)); } public void SetSetting(string key, object value) { if (Settings == null) { Settings = new Dictionary(); } Settings[key] = value; ModifiedDate = DateTime.Now; } } public interface IProfileSettings { string GameName { get; } Dictionary ExportSettings(); void ImportSettings(Dictionary settings); void SaveConfig(); } public class ProfileManager { private readonly string _profilesDirectory; private readonly Dictionary _profiles; private ConfigProfile _activeProfile; private string _activeProfileName; public const string ProfileExtension = ".profile"; public string ProfilesDirectory => _profilesDirectory; public ConfigProfile ActiveProfile => _activeProfile; public string ActiveProfileName => _activeProfileName; public IDictionary Profiles => _profiles; public event Action ProfileChanged; public ProfileManager(string profilesDirectory) { if (string.IsNullOrEmpty(profilesDirectory)) { throw new ArgumentException("Profiles directory cannot be null or empty", "profilesDirectory"); } _profilesDirectory = profilesDirectory; _profiles = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!Directory.Exists(_profilesDirectory)) { Directory.CreateDirectory(_profilesDirectory); } LoadAllProfiles(); if (_profiles.Count == 0) { CreateDefaultProfiles(); } } public void LoadAllProfiles() { _profiles.Clear(); if (Directory.Exists(_profilesDirectory)) { string[] files = Directory.GetFiles(_profilesDirectory, "*.profile"); foreach (string obj in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(obj); ConfigProfile value = ProfileSerializer.ReadFromFile(obj); _profiles[fileNameWithoutExtension] = value; } } } public void CreateDefaultProfiles() { ConfigProfile configProfile = new ConfigProfile("Default", "Default configuration for most games") { IsDefault = true }; configProfile.AxisMapping.ResetToDefault(); _profiles["Default"] = configProfile; SaveProfile(configProfile); ConfigProfile configProfile2 = new ConfigProfile("FPS_Competitive", "Optimized for competitive FPS games", "FPS"); configProfile2.AxisMapping.LoadPreset(MappingPreset.Competitive); _profiles["FPS_Competitive"] = configProfile2; SaveProfile(configProfile2); ConfigProfile configProfile3 = new ConfigProfile("Simulation", "Realistic head movement for simulation games", "Simulation"); configProfile3.AxisMapping.LoadPreset(MappingPreset.Simulation); _profiles["Simulation"] = configProfile3; SaveProfile(configProfile3); } public void LoadProfile(string profileName) { if (!_profiles.TryGetValue(profileName, out var value)) { throw new KeyNotFoundException("Profile not found: " + profileName); } _activeProfile = value; _activeProfileName = profileName; this.ProfileChanged?.Invoke(profileName); } public void SaveProfile(ConfigProfile profile) { if (profile == null) { throw new ArgumentNullException("profile"); } if (profile.IsReadOnly) { throw new InvalidOperationException("Cannot save read-only profile: " + profile.Name); } profile.ModifiedDate = DateTime.Now; string profilePath = GetProfilePath(profile.Name); ProfileSerializer.WriteToFile(profile, profilePath); _profiles[profile.Name] = profile; } public ConfigProfile CreateProfile(string name, string description, string gameName = "General") { if (_profiles.ContainsKey(name)) { throw new InvalidOperationException("Profile already exists: " + name); } ConfigProfile configProfile = new ConfigProfile(name, description, gameName); configProfile.AxisMapping.ResetToDefault(); _profiles[name] = configProfile; SaveProfile(configProfile); return configProfile; } public void DeleteProfile(string profileName) { if (!_profiles.TryGetValue(profileName, out var value)) { throw new KeyNotFoundException("Profile not found: " + profileName); } if (value.IsReadOnly || value.IsDefault) { throw new InvalidOperationException("Cannot delete protected profile: " + profileName); } _profiles.Remove(profileName); string profilePath = GetProfilePath(profileName); if (File.Exists(profilePath)) { File.Delete(profilePath); } if (_activeProfileName != null && _activeProfileName.Equals(profileName, StringComparison.OrdinalIgnoreCase)) { if (_profiles.ContainsKey("Default")) { LoadProfile("Default"); return; } if (_profiles.Count > 0) { LoadProfile(_profiles.Keys.First()); return; } _activeProfile = null; _activeProfileName = null; } } public ConfigProfile DuplicateProfile(string sourceName, string newName) { if (!_profiles.TryGetValue(sourceName, out var value)) { throw new KeyNotFoundException("Source profile not found: " + sourceName); } if (_profiles.ContainsKey(newName)) { throw new InvalidOperationException("Profile already exists: " + newName); } ConfigProfile configProfile = value.Clone(newName); configProfile.IsDefault = false; configProfile.IsReadOnly = false; _profiles[newName] = configProfile; SaveProfile(configProfile); return configProfile; } public List GetProfileNames() { List list = _profiles.Keys.ToList(); list.Sort(StringComparer.OrdinalIgnoreCase); return list; } public ConfigProfile GetProfile(string name) { _profiles.TryGetValue(name, out var value); return value; } public bool ProfileExists(string name) { return _profiles.ContainsKey(name); } public void SaveCurrentToActiveProfile(IProfileSettings adapter) { if (_activeProfile == null) { throw new InvalidOperationException("No active profile"); } if (_activeProfile.IsReadOnly) { throw new InvalidOperationException("Cannot save to read-only profile"); } _activeProfile.ExportFromAdapter(adapter); SaveProfile(_activeProfile); } public void ApplyActiveProfileToConfig(IProfileSettings adapter) { if (_activeProfile == null) { throw new InvalidOperationException("No active profile"); } _activeProfile.ImportToAdapter(adapter); } private string GetProfilePath(string profileName) { return Path.Combine(_profilesDirectory, profileName + ".profile"); } } public static class ProfileSerializer { private const string FileHeader = "# CameraUnlock Configuration Profile"; private const string DateFormat = "yyyy-MM-dd HH:mm:ss"; private static bool IsNullOrWhiteSpace(string value) { if (value == null) { return true; } for (int i = 0; i < value.Length; i++) { if (!char.IsWhiteSpace(value[i])) { return false; } } return true; } private static bool TryParseEnum(string value, out T result) where T : struct { result = default(T); if (string.IsNullOrEmpty(value)) { return false; } try { result = (T)Enum.Parse(typeof(T), value, ignoreCase: true); return true; } catch (ArgumentException) { return false; } } public static string Serialize(ConfigProfile profile) { if (profile == null) { throw new ArgumentNullException("profile"); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# CameraUnlock Configuration Profile"); stringBuilder.AppendLine("# Generated: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); stringBuilder.AppendLine(); stringBuilder.AppendLine("Name=" + profile.Name); stringBuilder.AppendLine("Description=" + profile.Description); stringBuilder.AppendLine("GameName=" + (profile.GameName ?? "General")); stringBuilder.AppendLine("CreatedDate=" + profile.CreatedDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); stringBuilder.AppendLine("ModifiedDate=" + profile.ModifiedDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture)); stringBuilder.AppendLine("IsDefault=" + profile.IsDefault); stringBuilder.AppendLine("IsReadOnly=" + profile.IsReadOnly); stringBuilder.AppendLine(); if (profile.Settings != null && profile.Settings.Count > 0) { stringBuilder.AppendLine("# Configuration Settings"); foreach (KeyValuePair setting in profile.Settings) { string text = SerializeValue(setting.Value); stringBuilder.AppendLine("Setting." + setting.Key + "=" + text); } stringBuilder.AppendLine(); } if (profile.AxisMapping != null) { stringBuilder.AppendLine("# Axis Mapping Configuration"); SerializeAxisConfig(stringBuilder, "Yaw", profile.AxisMapping.YawConfig); SerializeAxisConfig(stringBuilder, "Pitch", profile.AxisMapping.PitchConfig); SerializeAxisConfig(stringBuilder, "Roll", profile.AxisMapping.RollConfig); } return stringBuilder.ToString(); } public static void WriteToFile(ConfigProfile profile, string filePath) { if (profile == null) { throw new ArgumentNullException("profile"); } if (string.IsNullOrEmpty(filePath)) { throw new ArgumentNullException("filePath"); } string contents = Serialize(profile); File.WriteAllText(filePath, contents, Encoding.UTF8); } public static ConfigProfile Deserialize(string content) { if (content == null) { throw new ArgumentNullException("content"); } ConfigProfile configProfile = new ConfigProfile(); string[] array = content.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { if (!IsNullOrWhiteSpace(text) && !text.StartsWith("#")) { int num = text.IndexOf('='); if (num >= 0) { string key = text.Substring(0, num).Trim(); string value = text.Substring(num + 1).Trim(); ParseLine(configProfile, key, value); } } } return configProfile; } public static ConfigProfile ReadFromFile(string filePath) { if (string.IsNullOrEmpty(filePath)) { throw new ArgumentNullException("filePath"); } if (!File.Exists(filePath)) { throw new FileNotFoundException("Profile file not found", filePath); } return Deserialize(File.ReadAllText(filePath, Encoding.UTF8)); } private static void SerializeAxisConfig(StringBuilder sb, string axisName, AxisConfig config) { if (config != null) { string text = "AxisMapping." + axisName + "."; sb.AppendLine(text + "Source=" + config.Source); sb.AppendLine(text + "Sensitivity=" + config.Sensitivity.ToString("F4", CultureInfo.InvariantCulture)); sb.AppendLine(text + "Inverted=" + config.Inverted); sb.AppendLine(text + "DeadzoneMin=" + config.DeadzoneMin.ToString("F4", CultureInfo.InvariantCulture)); sb.AppendLine(text + "DeadzoneMax=" + config.DeadzoneMax.ToString("F4", CultureInfo.InvariantCulture)); sb.AppendLine(text + "MinLimit=" + config.MinLimit.ToString("F4", CultureInfo.InvariantCulture)); sb.AppendLine(text + "MaxLimit=" + config.MaxLimit.ToString("F4", CultureInfo.InvariantCulture)); sb.AppendLine(text + "EnableLimits=" + config.EnableLimits); sb.AppendLine(text + "SensitivityCurve=" + config.SensitivityCurve); sb.AppendLine(text + "CurveStrength=" + config.CurveStrength.ToString("F4", CultureInfo.InvariantCulture)); } } private static string SerializeValue(object value) { if (value == null) { return ""; } if (value is float num) { return num.ToString("F6", CultureInfo.InvariantCulture); } if (value is double num2) { return num2.ToString("F6", CultureInfo.InvariantCulture); } if (value is bool flag) { return flag.ToString(); } if (value is Enum @enum) { return @enum.ToString(); } return value.ToString(); } private static void ParseLine(ConfigProfile profile, string key, string value) { switch (key) { case "Name": profile.Name = value; return; case "Description": profile.Description = value; return; case "GameName": profile.GameName = value; return; case "CreatedDate": { if (DateTime.TryParseExact(value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result2)) { profile.CreatedDate = result2; } return; } case "ModifiedDate": { if (DateTime.TryParseExact(value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) { profile.ModifiedDate = result; } return; } case "IsDefault": { if (bool.TryParse(value, out var result4)) { profile.IsDefault = result4; } return; } case "IsReadOnly": { if (bool.TryParse(value, out var result3)) { profile.IsReadOnly = result3; } return; } } if (key.StartsWith("Setting.")) { string key2 = key.Substring(8); profile.Settings[key2] = ParseValue(value); } else if (key.StartsWith("AxisMapping.")) { ParseAxisMapping(profile, key.Substring(12), value); } } private static object ParseValue(string value) { if (bool.TryParse(value, out var result)) { return result; } if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return result2; } if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result3)) { return result3; } return value; } private static void ParseAxisMapping(ConfigProfile profile, string key, string value) { if (profile.AxisMapping == null) { profile.AxisMapping = new MappingConfig(); } string[] array = key.Split(new char[1] { '.' }); if (array.Length < 2) { return; } AxisConfig axisConfig; switch (array[0]) { default: return; case "Yaw": axisConfig = profile.AxisMapping.YawConfig; break; case "Pitch": axisConfig = profile.AxisMapping.PitchConfig; break; case "Roll": axisConfig = profile.AxisMapping.RollConfig; break; } string text = array[1]; if (text == null) { return; } float result; bool result2; switch (text.Length) { case 11: switch (text[9]) { case 't': if (text == "Sensitivity" && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { axisConfig.Sensitivity = result; } break; case 'i': if (text == "DeadzoneMin" && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { axisConfig.DeadzoneMin = result; } break; case 'a': if (text == "DeadzoneMax" && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { axisConfig.DeadzoneMax = result; } break; } break; case 8: switch (text[1]) { case 'n': if (text == "Inverted" && bool.TryParse(value, out result2)) { axisConfig.Inverted = result2; } break; case 'i': if (text == "MinLimit" && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { axisConfig.MinLimit = result; } break; case 'a': if (text == "MaxLimit" && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { axisConfig.MaxLimit = result; } break; } break; case 6: { if (text == "Source" && TryParseEnum(value, out var result4)) { axisConfig.Source = result4; } break; } case 12: if (text == "EnableLimits" && bool.TryParse(value, out result2)) { axisConfig.EnableLimits = result2; } break; case 16: { if (text == "SensitivityCurve" && TryParseEnum(value, out var result3)) { axisConfig.SensitivityCurve = result3; } break; } case 13: if (text == "CurveStrength" && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { axisConfig.CurveStrength = result; } break; case 7: case 9: case 10: case 14: case 15: break; } } } } namespace CameraUnlock.Core.Aim { public static class AimDecoupler { public static Vec3 ComputeAimDirectionLocal(Quat4 trackingRotation) { return trackingRotation.Inverse.Rotate(Vec3.Forward); } public static Vec3 ComputeAimDirectionLocal(float yawDegrees, float pitchDegrees, float rollDegrees) { return QuaternionUtils.FromYawPitchRoll(yawDegrees, pitchDegrees, rollDegrees).Inverse.Rotate(Vec3.Forward); } public static void ComputeAimDirection(float yawDegrees, float pitchDegrees, float rollDegrees, out float aimX, out float aimY, out float aimZ) { Vec3 vec = ComputeAimDirectionLocal(yawDegrees, pitchDegrees, rollDegrees); aimX = vec.X; aimY = vec.Y; aimZ = vec.Z; } public static Quat4 ComputeInverseTracking(float yawDegrees, float pitchDegrees, float rollDegrees) { return QuaternionUtils.FromYawPitchRoll(yawDegrees, pitchDegrees, rollDegrees).Inverse; } public static void ComputeInverseTracking(float yawDegrees, float pitchDegrees, float rollDegrees, out float qx, out float qy, out float qz, out float qw) { Quat4 quat = ComputeInverseTracking(yawDegrees, pitchDegrees, rollDegrees); qx = quat.X; qy = quat.Y; qz = quat.Z; qw = quat.W; } } public interface IAimDirectionProvider { Vec3 AimDirection { get; } bool IsTrackingActive { get; } } public delegate Vec3 GetAimDirectionDelegate(); public sealed class ForwardAimProvider : IAimDirectionProvider { public static readonly ForwardAimProvider Instance = new ForwardAimProvider(); public Vec3 AimDirection => Vec3.Forward; public bool IsTrackingActive => false; private ForwardAimProvider() { } } public sealed class DelegateAimProvider : IAimDirectionProvider { private readonly GetAimDirectionDelegate _getAimDirection; private readonly Func _isTrackingActive; public Vec3 AimDirection => _getAimDirection(); public bool IsTrackingActive => _isTrackingActive(); public DelegateAimProvider(GetAimDirectionDelegate getAimDirection, Func isTrackingActive) { _getAimDirection = getAimDirection; _isTrackingActive = isTrackingActive; } } public static class ScreenOffsetCalculator { public static void Calculate(float yawDegrees, float pitchDegrees, float rollDegrees, float horizontalFov, float verticalFov, float screenWidth, float screenHeight, float compensationScale, out float offsetX, out float offsetY) { float num = screenWidth * 0.5f; float num2 = screenHeight * 0.5f; float num3 = (float)System.Math.Tan(horizontalFov * ((float)System.Math.PI / 180f) * 0.5f); float num4 = (float)System.Math.Tan(verticalFov * ((float)System.Math.PI / 180f) * 0.5f); float num5 = yawDegrees * ((float)System.Math.PI / 180f); float num6 = pitchDegrees * ((float)System.Math.PI / 180f); float num7 = (float)System.Math.Sin(num5); float num8 = (float)System.Math.Cos(num5); float num9 = (float)System.Math.Sin(num6); float num10 = (float)System.Math.Cos(num6); float rotatedX = 0f - num7; float rotatedY = num9 * num8; float num11 = num10 * num8; ApplyRollRotation(rotatedX, rotatedY, rollDegrees, out rotatedX, out rotatedY); offsetX = rotatedX / num11 / num3 * num * compensationScale; offsetY = rotatedY / num11 / num4 * num2 * compensationScale; } public static void PrecomputeFovTangents(float horizontalFov, float verticalFov, out float tanHalfFovX, out float tanHalfFovY) { tanHalfFovX = (float)System.Math.Tan(horizontalFov * ((float)System.Math.PI / 180f) * 0.5f); tanHalfFovY = (float)System.Math.Tan(verticalFov * ((float)System.Math.PI / 180f) * 0.5f); } public static void CalculatePrecomputed(float yawDegrees, float pitchDegrees, float rollDegrees, float tanHalfFovX, float tanHalfFovY, float halfWidth, float halfHeight, float compensationScale, out float offsetX, out float offsetY) { float num = yawDegrees * ((float)System.Math.PI / 180f); float num2 = pitchDegrees * ((float)System.Math.PI / 180f); float num3 = (float)System.Math.Sin(num); float num4 = (float)System.Math.Cos(num); float num5 = (float)System.Math.Sin(num2); float num6 = (float)System.Math.Cos(num2); float rotatedX = 0f - num3; float rotatedY = num5 * num4; float num7 = num6 * num4; ApplyRollRotation(rotatedX, rotatedY, rollDegrees, out rotatedX, out rotatedY); offsetX = rotatedX / num7 / tanHalfFovX * halfWidth * compensationScale; offsetY = rotatedY / num7 / tanHalfFovY * halfHeight * compensationScale; } public static float CalculateHorizontalFov(float verticalFov, float aspectRatio) { float num = (float)System.Math.Tan(verticalFov * ((float)System.Math.PI / 180f) * 0.5f); return 2f * (float)System.Math.Atan(num * aspectRatio) / ((float)System.Math.PI / 180f); } public static void ApplyRollRotation(float x, float y, float rollDegrees, out float rotatedX, out float rotatedY) { if (System.Math.Abs(rollDegrees) < 0.001f) { rotatedX = x; rotatedY = y; return; } float num = (0f - rollDegrees) * ((float)System.Math.PI / 180f); float num2 = (float)System.Math.Cos(num); float num3 = (float)System.Math.Sin(num); rotatedX = x * num2 - y * num3; rotatedY = x * num3 + y * num2; } } }