From 93016ca4d2c5705c76722122e19161c7413d0f0c Mon Sep 17 00:00:00 2001 From: Gregory Lirent Date: Thu, 8 Jan 2026 21:35:06 +0300 Subject: [PATCH] fix --- .gitmodules | 3 + Configuration/AppSettings.cs | 3 + Lib/winipc-ua | 1 + Services/IPCClientService.cs | 231 +++++++++++++++++++++++++++++++++ Services/SessionService.cs | 6 + Utils/BaseRandom.cs | 94 ++++++++++++++ Utils/Delay.cs | 171 ++++++++++++++++++++++++ Utils/Perlin2D.cs | 92 +++++++++++++ Utils/Scroll.cs | 116 +++++++++++++++++ Utils/Sequences/Base.cs | 180 +++++++++++++++++++++++++ Utils/Sequences/Bezier.cs | 161 +++++++++++++++++++++++ Utils/Sequences/Consistent.cs | 97 ++++++++++++++ Utils/Sequences/Oscillation.cs | 206 +++++++++++++++++++++++++++++ Utils/Sequences/Perlin.cs | 97 ++++++++++++++ Utils/Shortcuts.cs | 185 ++++++++++++++++++++++++++ webmr-api.csproj | 9 ++ 16 files changed, 1652 insertions(+) create mode 100644 .gitmodules create mode 160000 Lib/winipc-ua create mode 100644 Services/IPCClientService.cs create mode 100644 Services/SessionService.cs create mode 100644 Utils/BaseRandom.cs create mode 100644 Utils/Delay.cs create mode 100644 Utils/Perlin2D.cs create mode 100644 Utils/Scroll.cs create mode 100644 Utils/Sequences/Base.cs create mode 100644 Utils/Sequences/Bezier.cs create mode 100644 Utils/Sequences/Consistent.cs create mode 100644 Utils/Sequences/Oscillation.cs create mode 100644 Utils/Sequences/Perlin.cs create mode 100644 Utils/Shortcuts.cs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6716d5d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Lib/winipc-ua"] + path = Lib/winipc-ua + url = https://dev.lirent.ru/lirent/winipc-ua.git diff --git a/Configuration/AppSettings.cs b/Configuration/AppSettings.cs index 363e8f5..d925dc2 100644 --- a/Configuration/AppSettings.cs +++ b/Configuration/AppSettings.cs @@ -1,12 +1,15 @@ /* This software is licensed by the MIT License, see LICENSE file */ /* Copyright © 2024-2025 Gregory Lirent */ +using WinIPC.Config; + namespace WebmrAPI.Configuration { public class AppSettings { public MonitoringSettings Monitoring { get; set; } = new MonitoringSettings(); public WebServerSettings WebServer { get; set; } = new WebServerSettings(); + public IPCServiceOptions IPCServiceOptions { get; set; } = new IPCServiceOptions(); } public class MonitoringSettings diff --git a/Lib/winipc-ua b/Lib/winipc-ua new file mode 160000 index 0000000..838ab68 --- /dev/null +++ b/Lib/winipc-ua @@ -0,0 +1 @@ +Subproject commit 838ab6852f7c3cdc6a4dab5c9fefecaa9feadc4b diff --git a/Services/IPCClientService.cs b/Services/IPCClientService.cs new file mode 100644 index 0000000..6c78ab3 --- /dev/null +++ b/Services/IPCClientService.cs @@ -0,0 +1,231 @@ +using Google.Protobuf; +using Microsoft.Extensions.Options; +using WebmrAPI.Configuration; +using WebmrAPI.Utils; +using WebmrAPI.Utils.Sequences; +using WinIPC.Models; +using WinIPC.Services; +using WinIPC.Utils; + +namespace WebmrAPI.Services +{ + public sealed class IPCClientService : IPCBaseClientService + { + public IPCClientService(ILogger logger, IOptions appConfigOptions) + : base(logger, appConfigOptions?.Value?.IPCServiceOptions.BasePipeName ?? throw new ArgumentNullException(nameof(appConfigOptions))) + { } + + public async Task> GetWindowInfos(int sessionId, IEnumerable? hwnds = null) + { + var req = new WindowsRequest(); + + if (hwnds != null) + { + foreach (var hwnd in hwnds) + { + req.Hwnds.Add((int)hwnd); + } + } + + var resp = await SendRequest(sessionId, CommandType.GetWindowsInfo, req).GetResponseAsync(); + + if (resp != null && resp.Success) + { + return PayloadHandler.ExtractPayload(resp).Data; + } + + // TODO log Error + + return new List(); + } + + public async Task GetScreenshot(int sessionId, IntPtr hwnd = 0, int x = 0, int y = 0, int width = 0, int height = 0) + { + var req = new ScreenshotRequest() + { + Hwnd = (int)hwnd, + CropPosition = new() { X = x, Y = y }, + CropSize = new() { Width = width, Height = height } + }; + + var resp = await SendRequest(sessionId, CommandType.GetScreenshot, req).GetResponseAsync(); + + if (resp != null && resp.Success) + { + return PayloadHandler.ExtractPayload(resp).Data.ToByteArray(); + } + + // TODO log Error + + return Array.Empty(); + } + + public async Task GetPixelColor(int sessionId, IntPtr hwnd = 0, int x = 0, int y = 0) + { + var req = new PixelRequest() + { + Hwnd = (int)hwnd, + PixelPosition = new() { X = x, Y = y } + }; + + var resp = await SendRequest(sessionId, CommandType.GetPixelColor, req).GetResponseAsync(); + + if (resp != null && resp.Success) + { + return (int)PayloadHandler.ExtractPayload(resp).RgbColor; + } + + // TODO log Error + + return -1; + } + + private IEnumerable GetMouseMovingPath(ISequenceGenerator pGen, DelayGenerator dGen, ref int nActions) + { + List actions = new(); + + foreach (var p in pGen) + { + var input = new MouseMoveInput() { Position = new() { X = p.X, Y = p.Y } }; + + actions.Add(new InputAction() + { + Type = InputType.MouseMoveTo, + DelayMs = (uint)dGen.Next().Value, + Payload = ByteString.CopyFrom(PayloadHandler.Serialize(input)) + }); + ++nActions; + } + + return actions; + } + + private IEnumerable GetMouseScrollSequence(ScrollAmountGenerator sGen, DelayGenerator dGen, ref int nActions) + { + List actions = new(); + + foreach (var s in sGen) + { + var input = new ScrollInput() { Offset = s }; + + actions.Add(new InputAction() + { + Type = InputType.MouseScroll, + DelayMs = (uint)dGen.Next().Value, + Payload = ByteString.CopyFrom(PayloadHandler.Serialize(input)) + }); + ++nActions; + } + + return actions; + } + + private async Task SendInputActions(int sessionId, IntPtr hwnd, IEnumerable actions, int nActions) + { + var resp = await SendRequest(sessionId, CommandType.InputAction, new InputRequest() { Hwnd = (int)hwnd, Actions = { actions } }).GetResponseAsync(); + + if (resp != null && resp.Success) + { + return nActions - PayloadHandler.ExtractPayload(resp).Count; + } + + // TODO log Error + + return -nActions; + } + + public Task MouseMove(int sessionId, ISequenceGenerator pGen, DelayGenerator dGen, IntPtr hwnd = 0) + { + int nActions = 0; + return SendInputActions(sessionId, hwnd, GetMouseMovingPath(pGen, dGen, ref nActions), nActions); + } + + public Task HoldDownAndMouseMove(int sessionId, ISequenceGenerator pGen, DelayGenerator dGen, Key key, IntPtr hwnd = 0) + { + return HoldDownAndMouseMove(sessionId, pGen, dGen, new KeyShortcut(key), hwnd); + } + + public Task HoldDownAndMouseMove(int sessionId, ISequenceGenerator pGen, DelayGenerator dGen, KeyShortcut sc, IntPtr hwnd = 0) + { + IEnumerable holdDown = sc.GetHoldDownActions(), holdUp = sc.GetHoldUpActions(); + int nActions = holdDown.Count() + holdUp.Count(); + + return SendInputActions(sessionId, hwnd, holdDown.Concat(GetMouseMovingPath(pGen, dGen, ref nActions)).Concat(holdUp), nActions); + } + + public Task MouseMoveAndPress(int sessionId, ISequenceGenerator pGen, DelayGenerator dGen, Key key, IntPtr hwnd = 0) + { + return MouseMoveAndPress(sessionId, pGen, dGen, new KeyShortcut(key), hwnd); + } + + public Task MouseMoveAndPress(int sessionId, ISequenceGenerator pGen, DelayGenerator dGen, KeyShortcut sc, IntPtr hwnd = 0) + { + return MouseMoveAndPress(sessionId, pGen, dGen, new KeyShortcutSequence(sc), hwnd); + } + + public Task MouseMoveAndPress(int sessionId, ISequenceGenerator pGen, DelayGenerator dGen, KeyShortcutSequence seq, IntPtr hwnd = 0) + { + IEnumerable press = seq.GetInputActions(); + int nActions = press.Count(); + + return SendInputActions(sessionId, hwnd, GetMouseMovingPath(pGen, dGen, ref nActions).Concat(press), nActions); + } + + public Task MouseScroll(int sessionId, ScrollAmountGenerator sGen, DelayGenerator dGen, IntPtr hwnd = 0) + { + int nActions = 0; + return SendInputActions(sessionId, hwnd, GetMouseScrollSequence(sGen, dGen, ref nActions), nActions); + } + + public Task HoldDownAndMouseScroll(int sessionId, ScrollAmountGenerator sGen, DelayGenerator dGen, Key key, IntPtr hwnd = 0) + { + return HoldDownAndMouseScroll(sessionId, sGen, dGen, new KeyShortcut(key), hwnd); + } + + public Task HoldDownAndMouseScroll(int sessionId, ScrollAmountGenerator sGen, DelayGenerator dGen, KeyShortcut sc, IntPtr hwnd = 0) + { + IEnumerable holdDown = sc.GetHoldDownActions(), holdUp = sc.GetHoldUpActions(); + int nActions = holdDown.Count() + holdUp.Count(); + + return SendInputActions(sessionId, hwnd, holdDown.Concat(GetMouseScrollSequence(sGen, dGen, ref nActions)).Concat(holdUp), nActions); + } + + public Task HoldDown(int sessionId, Key key, IntPtr hwnd = 0) + { + return HoldDown(sessionId, new KeyShortcut(key), hwnd); + } + + public Task HoldUp(int sessionId, Key key, IntPtr hwnd = 0) + { + return HoldUp(sessionId, new KeyShortcut(key), hwnd); + } + + public Task HoldDown(int sessionId, KeyShortcut sc, IntPtr hwnd = 0) + { + IEnumerable holdDown = sc.GetHoldDownActions(); + return SendInputActions(sessionId, hwnd, holdDown, holdDown.Count()); + } + + public Task HoldUp(int sessionId, KeyShortcut sc, IntPtr hwnd = 0) + { + IEnumerable holdUp = sc.GetHoldUpActions(); + return SendInputActions(sessionId, hwnd, holdUp, holdUp.Count()); + } + + public Task Press(int sessionId, Key key, IntPtr hwnd = 0) + { + return Press(sessionId, new KeyShortcut(key), hwnd); + } + + public Task Press(int sessionId, KeyShortcut sc, IntPtr hwnd = 0) + { + return Press(sessionId, new KeyShortcutSequence(sc), hwnd); + } + + public Task Press(int sessionId, KeyShortcutSequence seq, IntPtr hwnd = 0) + { + IEnumerable press = seq.GetInputActions(); + return SendInputActions(sessionId, hwnd, press, press.Count()); + } + } +} diff --git a/Services/SessionService.cs b/Services/SessionService.cs new file mode 100644 index 0000000..707e445 --- /dev/null +++ b/Services/SessionService.cs @@ -0,0 +1,6 @@ +namespace WebmrAPI.Services +{ + public class SessionService + { + } +} diff --git a/Utils/BaseRandom.cs b/Utils/BaseRandom.cs new file mode 100644 index 0000000..2455cd6 --- /dev/null +++ b/Utils/BaseRandom.cs @@ -0,0 +1,94 @@ +using System.Numerics; + +namespace WebmrAPI.Utils +{ + public class BaseRandom + { + private readonly static Random _random = new Random(); + private static readonly BaseRandom _self = new BaseRandom(); + + protected Random? _pRandom = null; + + private Random Rand { get => _pRandom == null ? _random : _pRandom; } + + public static byte[] NextGenerate(byte[] data) + { + return _self.Generate(data); + } + public static T NextGenerate(T min, T max) + where T : struct, INumber + { + return _self.Generate(min, max); + } + public static T NextSpread(T value, T offset) + where T : struct, INumber + { + return _self.Spread(value, offset); + } + + public byte[] Generate(byte[] data) + { + Rand.NextBytes(data); + return data; + } + public double Generate(double min, double max) + { + if (min > max) + { + throw new ArgumentOutOfRangeException(nameof(min), "minValue cannot be greater than maxValue."); + } + return min + (max - min) * Rand.NextDouble(); + } + public long Generate(long min, long max) + { + return Rand.NextInt64(min, max + 1); + } + public ulong Generate(ulong min, ulong max) + { + ulong res; + long lmin = (long)min; + long lmax = (long)max; + + if (lmin > lmax) res = (ulong)Rand.NextInt64(lmax, lmin + 1); + else res = (ulong)Rand.NextInt64(lmin, lmax + 1); + return UInt64.Clamp(res, min, max); + } + public uint Generate(uint min, uint max) + { + return (uint)Generate((long)min, (long)max); + } + public T Generate(T min, T max) where T : struct, INumber + { + var min32 = Int32.CreateChecked(min); + var max32 = Int32.CreateChecked(max); + + return T.CreateChecked(Rand.Next(min32, max32)); + } + public T Spread(T value, T offset) + where T : struct, INumber + { + T min, max, cur; + + cur = T.CreateChecked(Math.Abs(Double.CreateChecked(offset))); + min = value - cur; + max = value + cur; + + if (min > max) + { + var tmp = min; + min = max; + max = tmp; + } + + value = Generate(min, max); + + return value; + } + + public BaseRandom() { } + public BaseRandom(int seed) + { + _pRandom = new Random(seed); + } + } +} diff --git a/Utils/Delay.cs b/Utils/Delay.cs new file mode 100644 index 0000000..842a753 --- /dev/null +++ b/Utils/Delay.cs @@ -0,0 +1,171 @@ +using System.Collections; +using WebmrAPI.Utils.Sequences; + +namespace WebmrAPI.Utils +{ + public class Delay : BaseRandom, IComparable + { + private int _min = 0; + private int _max = 0; + private bool _mutable = true; + + public readonly static Delay None = new Delay { _mutable = false }; + public readonly static Delay Random = new Delay { _mutable = false, Min = 50, Max = 250 }; + + public int Value + { + set => _max = _min = value; + get => (_max > _min) ? Generate(_min, _max) : _min; + } + public int Min + { + get => _min > _max ? _max : _min; + set + { + if (!_mutable) + { + throw new InvalidOperationException("Object is not mutable"); + } + _min = value; + } + } + public int Max + { + get => _max < _min ? _min : _max; + set + { + if (!_mutable) + { + throw new InvalidOperationException("Object is not mutable"); + } + _max = value; + } + } + + async public Task WaitAsync() + { + await Task.Delay(Value); + } + public void Wait() + { + Thread.Sleep(Value); + } + public static bool operator ==(Delay? left, Delay? right) + { + if (left == null && right == null) return true; + if (left == null || right == null) return false; + + return left._min == right._min && left._max == right._max; + } + + public static bool operator !=(Delay? left, Delay? right) + { + return !(left == right); + } + public override bool Equals(object? obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + Delay other = (Delay)obj; + return this == other; + } + public override int GetHashCode() + { + return HashCode.Combine(_min, _max); + } + public static bool operator <(Delay left, Delay right) + { + if (left._min < right._min) + { + return true; + } + if (left._min == right._min && left._max < right._max) + { + return true; + } + return false; + } + public static bool operator >(Delay left, Delay right) + { + if (left._min > right._min) + { + return true; + } + if (left._min == right._min && left._max > right._max) + { + return true; + } + return false; + } + + public static bool operator <=(Delay left, Delay right) + { + return left < right || left == right; + } + + public static bool operator >=(Delay left, Delay right) + { + return left > right || left == right; + } + public int CompareTo(Delay? other) + { + if (other == null) + { + return 1; + } + + int result = _min.CompareTo(other._min); + if (result != 0) + { + return result; + } + + return _max.CompareTo(other._max); + } + } + + public class DelayGenerator : ISequenceGenerator + { + private ISequenceGenerator _generator; + + public Delay Min { get => new Delay { Value = _generator.Min }; } + public Delay Max { get => new Delay { Value = _generator.Max }; } + public double Progress { get => _generator.Progress; } + public double Step { get => _generator.Step; } + public bool HasValues { get => _generator.HasValues; } + + public Delay Next() + { + if (!HasValues) + { + Reset(); + } + return new Delay { Value = _generator.Next() }; + } + + public ISequenceGenerator Reset() + { + _generator.Reset(); + return this; + } + + public DelayGenerator(ISequenceGenerator generator) + { + _generator = generator; + } + public IEnumerator GetEnumerator() + { + while (HasValues) + { + yield return Next(); + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Utils/Perlin2D.cs b/Utils/Perlin2D.cs new file mode 100644 index 0000000..7585f06 --- /dev/null +++ b/Utils/Perlin2D.cs @@ -0,0 +1,92 @@ +namespace WebmrAPI.Utils +{ + public class Perlin2D : BaseRandom + { + byte[] _mTable = new byte[1024]; + + public Perlin2D(int seed = 0) + : base(seed) + { + Generate(_mTable); + } + + private double[] GetPseudoRandomGradientVector(int x, int y) + { + int v = (int)(((x * 1836311903) ^ (y * 2971215073) + 4807526976) & 1023); + v = _mTable[v] & 3; + + switch (v) + { + case 0: return new double[] { 1, 0 }; + case 1: return new double[] { -1, 0 }; + case 2: return new double[] { 0, 1 }; + default: return new double[] { 0, -1 }; + } + } + + static double QunticCurve(double t) + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + static double Lerp(double a, double b, double t) + { + return a + (b - a) * t; + } + + static double Dot(double[] a, double[] b) + { + return a[0] * b[0] + a[1] * b[1]; + } + + public double Noise(double fx, double fy) + { + int left = (int)System.Math.Floor(fx); + int top = (int)System.Math.Floor(fy); + double pointInQuadX = fx - left; + double pointInQuadY = fy - top; + + double[] topLeftGradient = GetPseudoRandomGradientVector(left, top); + double[] topRightGradient = GetPseudoRandomGradientVector(left + 1, top); + double[] bottomLeftGradient = GetPseudoRandomGradientVector(left, top + 1); + double[] bottomRightGradient = GetPseudoRandomGradientVector(left + 1, top + 1); + + double[] distanceToTopLeft = { pointInQuadX, pointInQuadY }; + double[] distanceToTopRight = { pointInQuadX - 1, pointInQuadY }; + double[] distanceToBottomLeft = { pointInQuadX, pointInQuadY - 1 }; + double[] distanceToBottomRight = { pointInQuadX - 1, pointInQuadY - 1 }; + + double tx1 = Dot(distanceToTopLeft, topLeftGradient); + double tx2 = Dot(distanceToTopRight, topRightGradient); + double bx1 = Dot(distanceToBottomLeft, bottomLeftGradient); + double bx2 = Dot(distanceToBottomRight, bottomRightGradient); + + pointInQuadX = QunticCurve(pointInQuadX); + pointInQuadY = QunticCurve(pointInQuadY); + + double tx = Lerp(tx1, tx2, pointInQuadX); + double bx = Lerp(bx1, bx2, pointInQuadX); + double tb = Lerp(tx, bx, pointInQuadY); + + return tb; + } + + public double Noise(double fx, double fy, int octaves, double persistence = 0.5) + { + double amplitude = 1; + double max = 0; + double result = 0; + + while (octaves-- > 0) + { + max += amplitude; + result += Noise(fx, fy) * amplitude; + amplitude *= persistence; + fx *= 2; + fy *= 2; + } + + return result / max; + } + } +} diff --git a/Utils/Scroll.cs b/Utils/Scroll.cs new file mode 100644 index 0000000..c629e41 --- /dev/null +++ b/Utils/Scroll.cs @@ -0,0 +1,116 @@ +using WebmrAPI.Utils.Sequences; + +namespace WebmrAPI.Utils +{ + public sealed class ScrollAmountGenerator : BaseGenerator + { + public enum Direction + { + Up, + Down + } + + public class CorrectionData + { + public Direction Dir { get; set; } + public int Order { get; set; } + } + + private double _fJitter = 0.12; + private double _fAccuracy = 0.83; + private int _nCorrections = 0; + private int _bAmount = 120; + public int TotalAmount { get; private set; } + public int CurrentAmount { get; private set; } + public int BaseAmount { get => _bAmount; private set => UpdateSpread(value, FJitter); } + public double FJitter { get => _fJitter; private set => UpdateSpread(BaseAmount, Math.Clamp(Math.Abs(value), 0.0, 1.0)); } + public int CorrectionLimit { get; private set; } = 1; + public override double Progress { get => (double)CurrentAmount / TotalAmount; protected set => base.Progress = value; } + public double FAccuracy { get => _fAccuracy; private set => _fAccuracy = Math.Clamp(Math.Abs(value), 0.0, 1.0); } + public Direction Dir { get; private set; } + public override double Step { get; protected set; } + + public delegate void CorrectionHandler(object sender, CorrectionData data); + + public event CorrectionHandler? Correction; + + public ScrollAmountGenerator SetBaseAmount(int baseAmount) { BaseAmount = baseAmount; return this; } + public ScrollAmountGenerator SetJitterFactor(double jitterFactor) { FJitter = jitterFactor; return this; } + public ScrollAmountGenerator SetCorrectionLimit(int limit) { CorrectionLimit = limit; return this; } + public ScrollAmountGenerator SetAccuracyFactor(double accuracyFactor) { FAccuracy = accuracyFactor; return this; } + + private void ToggleDirection() + { + Dir = Dir == Direction.Up ? Direction.Down : Direction.Up; + } + + private void UpdateSpread(int baseAmount, double jitterFactor) + { + var offset = baseAmount * jitterFactor; + var amount = Math.Abs(Double.CreateChecked(baseAmount)); + + _bAmount = baseAmount; + _fJitter = jitterFactor; + Min = (int)(amount - offset); + Max = (int)(amount + offset); + } + + protected override int Calc() + { + int value; + if (Generate(0, 100000) > (int)(FAccuracy * 100000)) + value = Generate(Min, Max); + else value = BaseAmount; + + if (Dir == Direction.Down) + { + value = -value; + } + + CurrentAmount += value; + return value; + } + + public override int Next() + { + if (Progress < 0) + { + Progress = 0; + return First(); + } + + if (!HasValues || IsZero(1.0 - Progress)) + { + HasValues = false; + Progress = 1.0; + return Last(); + } + else if (Progress > 1.0 && _nCorrections < CorrectionLimit) + { + ToggleDirection(); + Correction?.Invoke(this, new CorrectionData { Dir = Dir, Order = ++_nCorrections }); + } + + return Calc(); + } + + public override ISequenceGenerator Reset() + { + HasValues = true; + if ((_nCorrections & 1) != 0) + { + ToggleDirection(); + } + _nCorrections = 0; + return this; + } + + public ScrollAmountGenerator(int totalAmount, Direction dir) + : base(default, default) + { + TotalAmount = (dir == Direction.Up) ? totalAmount : -totalAmount; + Dir = dir; + UpdateSpread(BaseAmount, FJitter); + } + } +} diff --git a/Utils/Sequences/Base.cs b/Utils/Sequences/Base.cs new file mode 100644 index 0000000..3867ff1 --- /dev/null +++ b/Utils/Sequences/Base.cs @@ -0,0 +1,180 @@ +using System.Collections; +using System.Drawing; +using System.Numerics; + +namespace WebmrAPI.Utils.Sequences +{ + public interface ISequenceGenerator : IEnumerable + { + public static bool IsZero(double val, double epsilon = Double.Epsilon) + { + return Math.Abs(val) < epsilon; + } + public T Min { get; } + public T Max { get; } + public double Progress { get; } + public double Step { get; } + public bool HasValues { get; } + public T Next(); + public ISequenceGenerator Reset(); + } + + public interface INumberGenerator : ISequenceGenerator where T : struct, INumber + { + public INumberGenerator Self { get; } + public virtual double DMin { get => Double.CreateChecked(Min); } + public virtual double DMax { get => Double.CreateChecked(Max); } + public virtual double Mean { get => (DMin + DMax) / 2.0; } + } + + public interface IPointGenerator : ISequenceGenerator + { + public static double CalcDistance(Point pt1, Point pt2) + { + return Math.Sqrt(Math.Pow(pt1.X - pt2.X, 2) + Math.Pow(pt1.Y - pt2.Y, 2)); + } + public static double CalcAngle(Point pt1, Point pt2) + { + return Math.Atan2(pt2.Y - pt1.Y, pt2.X - pt1.X); + } + public static Point CalcPoint(double x, double y, double offset, double pAngle) + { + return new Point((int)Math.Round(x + offset * Math.Cos(pAngle)), (int)Math.Round(y + offset * Math.Sin(pAngle))); + } + public static Point CalcMiddle(Point start, Point end) + { + return new Point( + (int)Math.Round(start.X + (end.X - start.X) / 2.0), + (int)Math.Round(start.Y + (end.Y - start.Y) / 2.0) + ); + } + + public static (double distance, double angle) CalcDistanceAngle(Point pt1, Point pt2) + { + double dx = pt2.X - pt1.X; + double dy = pt2.Y - pt1.Y; + + return (Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)), Math.Atan2(dy, dx)); + } + + public static double DegreesToRadians(double degrees) + { + return degrees * Math.PI / 180.0; + } + + public static double RadiansToDegrees(double radians) + { + return radians * (180.0 / Math.PI); + } + + public static Point NewPointAngleDistance(Point refPt, double distance, double aRadians, double epsilon = Double.Epsilon) + { + int x, y; + + if (IsZero(distance, epsilon)) return new Point(refPt.X, refPt.Y); + + x = (int)Math.Round(refPt.X + distance * Math.Cos(aRadians)); + y = (int)Math.Round(refPt.Y + distance * Math.Sin(aRadians)); + + return new Point(x, y); + } + + public static Point NewPointDistance(Point refPt, Point targetPt, double distance, double epsilon = Double.Epsilon) + { + return NewPointAngleDistance(refPt, distance, CalcAngle(refPt, targetPt), epsilon); + } + + public static Point NewPointAngle(Point refPt, Point targetPt, double aRadians, double epsilon = Double.Epsilon) + { + return NewPointAngleDistance(refPt, CalcDistance(refPt, targetPt), aRadians, epsilon); + } + + public static Point NewPointDistanceFactor(Point refPt, Point targetPt, double factor, double epsilon = Double.Epsilon) + { + return NewPointAngleDistance(refPt, CalcDistance(refPt, targetPt) * factor, CalcAngle(refPt, targetPt), epsilon); + } + + public static Point GetRandomPointInRect(Rectangle rect) + { + double x = BaseRandom.NextGenerate(rect.X, rect.Width); + double y = BaseRandom.NextGenerate(rect.Y, rect.Height); + + return new Point((int)Math.Round(x), (int)Math.Round(y)); + } + + public IPointGenerator Self { get; } + public virtual Point Start { get => Min; } + public virtual Point End { get => Max; } + public virtual double LinearX { get => Start.X + (End.X - Start.X) * Progress; } + public virtual double LinearY { get => Start.Y + (End.Y - Start.Y) * Progress; } + } + + public abstract class BaseGenerator : BaseRandom, ISequenceGenerator + { + protected virtual double Epsilon { get; set; } = 0.01; + protected virtual bool IsZero(double val) + { + return ISequenceGenerator.IsZero(val, Epsilon); + } + private double _fShape = 1.0; + private double _fFade = 0.5; + + public T Min { get; protected set; } + public T Max { get; protected set; } + public virtual double Progress { get; protected set; } = -1.0; + public double ProgressRemainder { get => 1.0 - Progress; } + public virtual bool HasValues { get; protected set; } = true; + public double FShape { get => _fShape; protected set => _fShape = Math.Clamp(value, 0.0, 1.0); } + public double FFade { get => _fFade; protected set => _fFade = Math.Clamp(value, Epsilon, 1.0); } + protected double Scale { get => Math.Pow(Progress * ProgressRemainder * 4.0, FFade) * FShape; } + public abstract double Step { get; protected set; } + public BaseGenerator(T min, T max) + { + Min = min; + Max = max; + } + protected static double CalcStepByCount(int count) + { + return 1.0 / count; + } + + protected abstract T Calc(); + protected virtual T First() => Calc(); + protected virtual T Last() => Calc(); + + public virtual T Next() + { + if (Progress < 0) + { + Progress = 0; + return First(); + } + + if ((Progress += Step) >= 1.0) + { + HasValues = false; + Progress = 1.0; + return Last(); + } + + return Calc(); + } + public virtual ISequenceGenerator Reset() + { + HasValues = true; + Progress = -1.0; + return this; + } + public IEnumerator GetEnumerator() + { + while (HasValues) + { + yield return Next(); + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/Utils/Sequences/Bezier.cs b/Utils/Sequences/Bezier.cs new file mode 100644 index 0000000..d465cc0 --- /dev/null +++ b/Utils/Sequences/Bezier.cs @@ -0,0 +1,161 @@ +using System.Drawing; +using System.Numerics; + +namespace WebmrAPI.Utils.Sequences +{ + public abstract class BezierGenerator : BaseGenerator where TControl : struct + { + public bool IsCubic { get => Control2 != null; } + protected TControl Control1 { get; set; } + protected TControl? Control2 { get; set; } + public override double Step { get; protected set; } + protected double CalcBezier(double start, double end, double ctrl1, double? ctrl2) + { + double t = Progress; + double rt = ProgressRemainder; + + if (IsCubic && ctrl2 != null) + { + double rt3 = rt * rt * rt; + double t3 = t * t * t; + double rt2_t = rt * rt * t; + double rt_t2 = rt * t * t; + + return rt3 * start + + 3 * rt2_t * ctrl1 + + 3 * rt_t2 * ctrl2.Value + + t3 * end; + } + + double rt2 = rt * rt; + double t2 = t * t; + double rt_t = rt * t; + + return rt2 * start + + 2 * rt_t * ctrl1 + + t2 * end; + } + + public BezierGenerator(T min, T max, TControl ctrl1, TControl? ctrl2) + : base(min, max) + { + Control1 = ctrl1; + Control2 = ctrl2; + } + } + public class BezierNumberGenerator : BezierGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + private double Bezier { get => CalcBezier(Self.DMin, Self.DMax, Control1, Control2); } + public BezierNumberGenerator SetShapeFactor(double shape) { FShape = shape; return this; } + public BezierNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + protected override T Calc() + { + return T.CreateChecked(Math.Clamp(Self.Mean + (Bezier - Self.Mean) * Scale, Self.DMin, Self.DMax)); + } + + public BezierNumberGenerator(T start, T end, double ctrl1, double? ctrl2 = null, int count = 10) + : base(start, end, ctrl1, ctrl2) { Step = CalcStepByCount(count); } + } + + public class ArcNumberGenerator : BezierNumberGenerator where T : struct, INumber + { + public ArcNumberGenerator(T start, T end, int count = 10, bool bidirectional = true, bool reverse = false) + : base(start, end, Double.NaN, null, count) + { + var hMean = (Self.DMax - Self.Mean); + if (bidirectional) + { + Control1 = (reverse) ? Self.DMin : Self.DMax; + Control2 = (reverse) ? Self.DMax : Self.DMin; + } + else + { + Control1 = (reverse) ? Self.DMin : Self.DMax; + } + } + } + + public class BezierPointGenerator : BezierGenerator, IPointGenerator + { + protected readonly double _pAngle; + public IPointGenerator Self { get => this; } + public BezierPointGenerator SetShapeFactor(double shape) { FShape = shape; return this; } + public BezierPointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + private double BezierX { get => CalcBezier(Min.X, Max.X, Control1.X, Control2?.X); } + private double BezierY { get => CalcBezier(Min.Y, Max.Y, Control1.Y, Control2?.Y); } + + protected override Point Calc() + { + double dx = BezierX - Self.LinearX; + double dy = BezierY - Self.LinearY; + + return IPointGenerator.CalcPoint(Self.LinearX + dx * Scale, Self.LinearY + dy * Scale, 0, 0); + } + + public static BezierPointGenerator CreateByCount(Point start, Point end, Point ctrl1, Point? ctrl2 = null, int count = 10) + { + return new BezierPointGenerator(start, end, ctrl1, ctrl2) + { + Step = CalcStepByCount(count) + }; + } + + public static BezierPointGenerator CreateByStepSize(Point start, Point end, Point ctrl1, Point? ctrl2 = null, double pxPerStep = 5) + { + return new BezierPointGenerator(start, end, ctrl1, ctrl2, pxPerStep); + } + + public BezierPointGenerator(Point start, Point end, Point ctrl1, Point? ctrl2 = null, double pxPerStep = 5) + : this(start, end, ctrl1, ctrl2) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + public BezierPointGenerator(Point start, Point end, Point ctrl1, Point? ctrl2 = null) + : base(start, end, ctrl1, ctrl2) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } + public sealed class ArcPointGenerator : BezierPointGenerator + { + public new ArcPointGenerator SetShapeFactor(double shape) { base.SetShapeFactor(shape); return this; } + public new ArcPointGenerator SetFadeFactor(double fade) { base.SetFadeFactor(fade); return this; } + + public static ArcPointGenerator CreateByCount(Point start, Point end, bool bidirectional = false, bool reverse = false, double arcHeight = 15.0, int count = 10) + { + return new ArcPointGenerator(start, end, bidirectional, reverse, arcHeight) + { + Step = CalcStepByCount(count) + }; + } + + public static ArcPointGenerator CreateByStepSize(Point start, Point end, bool bidirectional = false, bool reverse = false, double arcHeight = 15.0, double pxPerStep = 5) + { + return new ArcPointGenerator(start, end, bidirectional, reverse, arcHeight, pxPerStep); + } + + public ArcPointGenerator(Point start, Point end, bool bidirectional = false, bool reverse = false, double arcHeight = 15.0, double pxPerStep = 5) + : this(start, end, bidirectional, reverse, arcHeight) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private ArcPointGenerator(Point start, Point end, bool bidirectional, bool reverse, double arcHeight) + : base(start, end, Point.Empty, null) + { + var height = (reverse) ? -arcHeight : arcHeight; + + if (bidirectional) + { + Control1 = IPointGenerator.NewPointAngleDistance(IPointGenerator.NewPointDistanceFactor(start, end, 1.0 / 3.0), height, _pAngle); + Control2 = IPointGenerator.NewPointAngleDistance(IPointGenerator.NewPointDistanceFactor(start, end, 2.0 / 3.0), -height, _pAngle); + } + else + { + Control1 = IPointGenerator.NewPointAngleDistance(IPointGenerator.CalcMiddle(start, end), height, _pAngle); + } + } + } +} diff --git a/Utils/Sequences/Consistent.cs b/Utils/Sequences/Consistent.cs new file mode 100644 index 0000000..2a86b8e --- /dev/null +++ b/Utils/Sequences/Consistent.cs @@ -0,0 +1,97 @@ +using System.Collections; +using System.Drawing; +using System.Numerics; + +namespace WebmrAPI.Utils.Sequences +{ + public abstract class ConsistentGenerator : ISequenceGenerator + { + private Queue> _seq = new(); + private Queue> _mem = new(); + + private int _count = 0; + private int _index = 0; + + public T Min { get; protected set; } + public T Max { get; protected set; } + public virtual double Progress { get => (double)_index / _count; } + public double Step { get => 1.0 / _count; } + public bool HasValues { get => _seq.Count > 0; } + + protected void AddGenerator(ISequenceGenerator generator) + { + Max = generator.Max; + _count += (int)(1.0 / generator.Step); + } + + public T Next() + { + if (HasValues) + { + if (_seq.Peek().HasValues) + { + _index++; + return _seq.Peek().Next(); + } + _mem.Enqueue(_seq.Dequeue().Reset()); + return Next(); + } + + return Max; + } + public ISequenceGenerator Reset() + { + while (_seq.Count > 0) + { + _mem.Enqueue(_seq.Dequeue().Reset()); + } + _seq = _mem; + _mem = new(); + _index = 0; + return this; + } + public IEnumerator GetEnumerator() + { + while (HasValues) + { + yield return Next(); + } + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + public ConsistentGenerator(ISequenceGenerator generator) + { + Min = generator.Min; + Max = generator.Max; + _count = (int)(1.0 / generator.Step); + } + } + + public sealed class ConsistentNumberGenerator : ConsistentGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public ConsistentNumberGenerator Add(ISequenceGenerator generator) + { + AddGenerator(generator); + return this; + } + + public ConsistentNumberGenerator(ISequenceGenerator generator) + : base(generator) { } + } + + public sealed class ConsistentPointGenerator : ConsistentGenerator, IPointGenerator + { + public IPointGenerator Self { get => this; } + public ConsistentPointGenerator Add(ISequenceGenerator generator) + { + AddGenerator(generator); + return this; + } + + public ConsistentPointGenerator(ISequenceGenerator generator) + : base(generator) { } + } +} diff --git a/Utils/Sequences/Oscillation.cs b/Utils/Sequences/Oscillation.cs new file mode 100644 index 0000000..302282a --- /dev/null +++ b/Utils/Sequences/Oscillation.cs @@ -0,0 +1,206 @@ +using System.Drawing; +using System.Numerics; + +namespace WebmrAPI.Utils.Sequences +{ + public abstract class OscillationGenerator : BaseGenerator + { + private int _segments = 1; + private double _amplitude = 100.0; + public double Amplitude { get => _amplitude; protected set => _amplitude = Math.Abs(value); } + public int Segments { get => _segments; protected set => _segments = value < 1 ? 1 : value; } + public override double Step { get; protected set; } + protected abstract double FOffset { get; } + public OscillationGenerator(T min, T max) + : base(min, max) { } + } + + public abstract class SawtoothGenerator : OscillationGenerator + { + protected override double FOffset { get { double s = (Progress * Segments) % 1.0; return (s < 0.5 ? s : (1.0 - s)) * 4.0 - 1.0; } } + + public SawtoothGenerator(T min, T max) + : base(min, max) { } + } + + public abstract class SineWaveGenerator : OscillationGenerator + { + protected override double FOffset { get => Math.Sin(Progress * (Segments * 2.0) * Math.PI); } + + public SineWaveGenerator(T min, T max) + : base(min, max) { } + } + + public abstract class SquareWaveGenerator : OscillationGenerator + { + protected override double FOffset { get => (Math.Floor(Progress * Segments) % 2 == 0) ? 1 : -1; } + + public SquareWaveGenerator(T min, T max) + : base(min, max) { } + } + public sealed class SawtoothNumberGenerator : SawtoothGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public SawtoothNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SawtoothNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SawtoothNumberGenerator SetSegments(int count) { Segments = count; return this; } + public SawtoothNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Amplitude * FOffset * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + public SawtoothNumberGenerator(T min, T max, int count = 10) : base(min, max) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + public sealed class SineWaveNumberGenerator : SineWaveGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public SineWaveNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SineWaveNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SineWaveNumberGenerator SetSegments(int count) { Segments = count; return this; } + public SineWaveNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Amplitude * FOffset * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + public SineWaveNumberGenerator(T min, T max, int count = 10) : base(min, max) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + public sealed class SquareWaveNumberGenerator : SquareWaveGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public SquareWaveNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SquareWaveNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SquareWaveNumberGenerator SetSegments(int count) { Segments = count; return this; } + public SquareWaveNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Amplitude * FOffset * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + public SquareWaveNumberGenerator(T min, T max, int count = 10) : base(min, max) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + + public sealed class SawtoothPointGenerator : SawtoothGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public SawtoothPointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SawtoothPointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SawtoothPointGenerator SetSegments(int count) { Segments = count; return this; } + public SawtoothPointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Amplitude * FOffset * Scale, _pAngle); + } + + public static SawtoothPointGenerator CreateByCount(Point start, Point end, int count) + { + return new SawtoothPointGenerator(start, end) { Step = CalcStepByCount(count) }; + } + + public static SawtoothPointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5) + { + return new SawtoothPointGenerator(start, end, pxPerStep); + } + + public SawtoothPointGenerator(Point start, Point end, double pxPerStep = 5) + : this(start, end) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private SawtoothPointGenerator(Point start, Point end) + : base(start, end) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } + + public sealed class SineWavePointGenerator : SineWaveGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public SineWavePointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SineWavePointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SineWavePointGenerator SetSegments(int count) { Segments = count; return this; } + public SineWavePointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Amplitude * FOffset * Scale, _pAngle); + } + + public static SineWavePointGenerator CreateByCount(Point start, Point end, int count) + { + return new SineWavePointGenerator(start, end) { Step = CalcStepByCount(count) }; + } + + public static SineWavePointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5) + { + return new SineWavePointGenerator(start, end, pxPerStep); + } + + public SineWavePointGenerator(Point start, Point end, double pxPerStep = 5) + : this(start, end) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private SineWavePointGenerator(Point start, Point end) + : base(start, end) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } + + public sealed class SquareWavePointGenerator : SquareWaveGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public SquareWavePointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public SquareWavePointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public SquareWavePointGenerator SetSegments(int count) { Segments = count; return this; } + public SquareWavePointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Amplitude * FOffset * Scale, _pAngle); + } + + public static SquareWavePointGenerator CreateByCount(Point start, Point end, int count) + { + return new SquareWavePointGenerator(start, end) { Step = CalcStepByCount(count) }; + } + + public static SquareWavePointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5) + { + return new SquareWavePointGenerator(start, end, pxPerStep); + } + + public SquareWavePointGenerator(Point start, Point end, double pxPerStep = 5) + : this(start, end) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private SquareWavePointGenerator(Point start, Point end) + : base(start, end) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } +} diff --git a/Utils/Sequences/Perlin.cs b/Utils/Sequences/Perlin.cs new file mode 100644 index 0000000..4a45803 --- /dev/null +++ b/Utils/Sequences/Perlin.cs @@ -0,0 +1,97 @@ +using System.Drawing; +using System.Numerics; + +namespace WebmrAPI.Utils.Sequences +{ + public abstract class PerlinGenerator : BaseGenerator + { + private Perlin2D _perlin; + private double _amplitude = 100.0; + public double Amplitude { get => _amplitude; protected set => _amplitude = Math.Abs(value); } + public double Persistance { get; protected set; } = 0.5; + public double Frequency { get; protected set; } = 5.0; + public int Octaves { get; protected set; } = 4; + public double Seed { get; private set; } + public override double Step { get; protected set; } + protected double Noise { get => _perlin.Noise(Progress * Frequency, Seed, Octaves, Persistance); } + public PerlinGenerator(T min, T max, double seed) + : base(min, max) + { + Seed = seed; + _perlin = new Perlin2D((int)(Seed * 0x000fffff)); + } + } + + public sealed class PerlinNumberGenerator : PerlinGenerator, INumberGenerator where T : struct, INumber + { + public INumberGenerator Self { get => this; } + public PerlinNumberGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public PerlinNumberGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public PerlinNumberGenerator SetOctaves(int octaves) { Octaves = octaves; return this; } + public PerlinNumberGenerator SetPersistence(double peristance) { Persistance = peristance; return this; } + public PerlinNumberGenerator SetFrequency(double frequency) { Frequency = frequency; return this; } + public PerlinNumberGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override T Calc() + { + var offs = Noise * Scale * ((Self.DMax - Self.DMin) / 2.0); + + return T.CreateChecked(Math.Clamp(Self.Mean + offs, Self.DMin, Self.DMax)); + } + private static double GetSeed(T min, T max, double seed) + { + if (Double.IsNormal(seed)) return seed; + return (Double.CreateChecked(min) + Double.CreateChecked(max)) / 1000.0; + } + public PerlinNumberGenerator(T min, T max, int count = 10, double seed = Double.NaN) : base(min, max, GetSeed(min, max, seed)) + { + Step = CalcStepByCount(count); + Amplitude = 1.0; // Set default + } + } + + public sealed class PerlinPointGenerator : PerlinGenerator, IPointGenerator + { + private readonly double _pAngle; + public IPointGenerator Self { get => this; } + public PerlinPointGenerator SetShapeFactor(double fShape) { FShape = fShape; return this; } + public PerlinPointGenerator SetFadeFactor(double fade) { FFade = fade; return this; } + public PerlinPointGenerator SetOctaves(int octaves) { Octaves = octaves; return this; } + public PerlinPointGenerator SetPersistence(double peristance) { Persistance = peristance; return this; } + public PerlinPointGenerator SetFrequency(double frequency) { Frequency = frequency; return this; } + public PerlinPointGenerator SetAmplitude(double amplitude) { Amplitude = amplitude; return this; } + protected override Point Calc() + { + return IPointGenerator.CalcPoint(Self.LinearX, Self.LinearY, Noise * Amplitude * Scale, _pAngle); + } + + private static double GetSeed(Point start, Point end, double seed) + { + if (Double.IsNormal(seed)) return seed; + return (start.X + end.Y) / 1000.0; + } + + public static PerlinPointGenerator CreateByCount(Point start, Point end, int count, double seed = Double.NaN) + { + return new PerlinPointGenerator(start, end, seed) + { + Step = CalcStepByCount(count) + }; + } + public static PerlinPointGenerator CreateByStepSize(Point start, Point end, double pxPerStep = 5, double seed = Double.NaN) + { + return new PerlinPointGenerator(start, end, pxPerStep, seed); + } + + public PerlinPointGenerator(Point start, Point end, double pxPerStep = 5, double seed = Double.NaN) + : this(start, end, seed) + { + Step = pxPerStep / IPointGenerator.CalcDistance(start, end); + } + + private PerlinPointGenerator(Point start, Point end, double seed) + : base(start, end, GetSeed(start, end, seed)) + { + _pAngle = IPointGenerator.CalcAngle(start, end) + Math.PI / 2.0; + } + } +} diff --git a/Utils/Shortcuts.cs b/Utils/Shortcuts.cs new file mode 100644 index 0000000..c0e843b --- /dev/null +++ b/Utils/Shortcuts.cs @@ -0,0 +1,185 @@ +using Google.Protobuf; +using WebmrAPI.Utils.Sequences; +using WinIPC.Models; +using WinIPC.Utils; + +namespace WebmrAPI.Utils +{ + public class Key + { + public Button Code { get; private set; } + + public Key(Button code) { Code = code; } + + public Key(string key) + { + Button code; + + switch (key.ToLowerInvariant()) + { + case "-": case "_": Code = Button.Sub; break; + case "+": case "=": Code = Button.Add; break; + case "~": case "`": Code = Button.Tilde; break; + case "<": case ",": Code = Button.Comma; break; + case ">": case ".": Code = Button.Period; break; + case ":": case ";": Code = Button.Colon; break; + case "{": case "[": Code = Button.LBracket; break; + case "}": case "]": Code = Button.RBracket; break; + case "\\": case "|": Code = Button.BSol; break; + case "/": case "?": Code = Button.Sol; break; + case "'": case "\"": Code = Button.Quote; break; + case "num+": Code = Button.NumAdd; break; + case "num-": Code = Button.NumSub; break; + case "num*": Code = Button.NumMul; break; + case "num/": Code = Button.NumDiv; break; + case "num.": Code = Button.NumDec; break; + + case "a": case "b": case "c": case "d": case "e": case "f": + case "g": case "h": case "i": case "j": case "k": case "l": + case "m": case "n": case "o": case "p": case "q": case "r": + case "s": case "t": case "u": case "v": case "w": case "x": + case "y": case "z": case "0": case "1": case "2": case "3": + case "4": case "5": case "6": case "7": case "8": case "9": + if (Enum.TryParse($"Key{key}", out code)) + { + Code = code; + break; + } + else goto default; + case "shift": case "ctrl": case "alt": case "win": + if (Enum.TryParse($"L{key}", out code)) + { + Code = code; + break; + } + else goto default; + default: + if (Enum.TryParse(key, out code)) + { + Code = code; + break; + } + else throw new ArgumentException($"Unexpected key \"{key}\""); + } + } + } + + public class KeyShortcutSequence + { + private Queue> _queue = new(); + + public KeyShortcutSequence(KeyShortcut key) { Next(key); } + + public KeyShortcutSequence Next(KeyShortcut shortcut, int delay = 0) + { + _queue.Enqueue(new KeyValuePair(shortcut, (uint)delay)); + return this; + } + + public IEnumerable GetInputActions() + { + var actions = new InputAction[0]; + + do + { + var kvpair = _queue.Dequeue(); + + if (actions.Length > 0) + { + actions[actions.Length - 1].DelayMs += kvpair.Value; + } + + actions = actions.Concat(kvpair.Key.GetInputActions()).ToArray(); + } while (_queue.Count > 0); + + return actions; + } + } + + public class KeyShortcut + { + private readonly static DelayGenerator _dGen = new(new PerlinNumberGenerator(75, 200, Int32.MaxValue)); + private Queue _queue = new(); + + public KeyShortcut(Key key) { And(key); } + public KeyShortcut(string key) { And(key); } + + public KeyShortcut And(Key key) + { + _queue.Enqueue(key); + return this; + } + + public KeyShortcut And(string key) + { + return And(new Key(key)); + } + + public IEnumerable GetHoldDownActions() + { + Key key; + var actions = new InputAction[_queue.Count]; + + for (int i = 0, n = _queue.Count; i < n;) + { + _queue.Enqueue(key = _queue.Dequeue()); + actions[i++] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyDown, + Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + } + + return actions; + } + + public IEnumerable GetHoldUpActions() + { + Key key; + var actions = new InputAction[_queue.Count]; + + for (int n = _queue.Count; n > 0;) + { + _queue.Enqueue(key = _queue.Dequeue()); + actions[--n] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyUp, + Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + } + + return actions; + } + + public IEnumerable GetInputActions() + { + Key key; + int n = _queue.Count * 2; + var actions = new InputAction[n]; + + for (int i = 0; i < n;) + { + _queue.Enqueue(key = _queue.Dequeue()); + + actions[i++] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyDown, + Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + + actions[--n] = new InputAction() + { + DelayMs = (uint)_dGen.Next().Value, + Type = InputType.KeyUp, + Payload = ByteString.CopyFrom(PayloadHandler.Serialize(new ButtonInput { Button = key.Code })) + }; + } + + return actions; + } + } + +} diff --git a/webmr-api.csproj b/webmr-api.csproj index b820bdc..3cffe09 100644 --- a/webmr-api.csproj +++ b/webmr-api.csproj @@ -27,5 +27,14 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + +