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 : 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; } } }