win32-automation-agent/Domain/Utils/PerlinGenerator.cs
2026-01-08 21:33:01 +03:00

188 lines
7.4 KiB
C#

using System.Drawing;
using System.Numerics;
namespace AutoAgent.Domain.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;
}
}
public abstract class PerlinGenerator<T> : BaseGenerator<T>
{
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<T> : PerlinGenerator<T>, INumberGenerator<T> where T : struct, INumber<T>
{
public INumberGenerator<T> Self { get => this; }
public PerlinNumberGenerator<T> SetShapeFactor(double fShape) { FShape = fShape; return this; }
public PerlinNumberGenerator<T> SetFadeFactor(double fade) { FFade = fade; return this; }
public PerlinNumberGenerator<T> SetOctaves(int octaves) { Octaves = octaves; return this; }
public PerlinNumberGenerator<T> SetPersistence(double peristance) { Persistance = peristance; return this; }
public PerlinNumberGenerator<T> SetFrequency(double frequency) { Frequency = frequency; return this; }
public PerlinNumberGenerator<T> 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<Point>, 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;
}
}
}