| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- using System;
- using System.Threading;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Globalization;
- using Nett;
- using Robust.Client.Utility;
- using Robust.Shared.Log;
- using Robust.Shared.Maths;
- using Robust.Shared.Noise;
- using Robust.Shared.Random;
- using SixLabors.ImageSharp;
- using SixLabors.ImageSharp.PixelFormats;
- using Color = Robust.Shared.Maths.Color;
- namespace Content.Client.Parallax
- {
- public sealed class ParallaxGenerator
- {
- private readonly List<Layer> Layers = new();
- public static Image<Rgba32> GenerateParallax(TomlTable config, Size size, ISawmill sawmill, List<Image<Rgba32>>? debugLayerDump, CancellationToken cancel = default)
- {
- sawmill.Debug("Generating parallax!");
- var generator = new ParallaxGenerator();
- generator._loadConfig(config);
- sawmill.Debug("Timing start!");
- var sw = new Stopwatch();
- sw.Start();
- var image = new Image<Rgba32>(Configuration.Default, size.Width, size.Height, new Rgba32(0, 0, 0, 0));
- var count = 0;
- foreach (var layer in generator.Layers)
- {
- cancel.ThrowIfCancellationRequested();
- layer.Apply(image);
- debugLayerDump?.Add(image.Clone());
- sawmill.Debug("Layer {0} done!", count++);
- }
- sw.Stop();
- sawmill.Debug("Total time: {0}", sw.Elapsed.TotalSeconds);
- return image;
- }
- private void _loadConfig(TomlTable config)
- {
- foreach (var layerArray in ((TomlTableArray) config.Get("layers")).Items)
- {
- switch (((TomlValue<string>) layerArray.Get("type")).Value)
- {
- case "clear":
- var layerClear = new LayerClear(layerArray);
- Layers.Add(layerClear);
- break;
- case "toalpha":
- var layerToAlpha = new LayerToAlpha(layerArray);
- Layers.Add(layerToAlpha);
- break;
- case "noise":
- var layerNoise = new LayerNoise(layerArray);
- Layers.Add(layerNoise);
- break;
- case "points":
- var layerPoint = new LayerPoints(layerArray);
- Layers.Add(layerPoint);
- break;
- default:
- throw new NotSupportedException();
- }
- }
- }
- private abstract class Layer
- {
- public abstract void Apply(Image<Rgba32> bitmap);
- }
- private abstract class LayerConversion : Layer
- {
- public abstract Color ConvertColor(Color input);
- public override void Apply(Image<Rgba32> bitmap)
- {
- var span = bitmap.GetPixelSpan();
- for (var y = 0; y < bitmap.Height; y++)
- {
- for (var x = 0; x < bitmap.Width; x++)
- {
- var i = y * bitmap.Width + x;
- span[i] = ConvertColor(span[i].ConvertImgSharp()).ConvertImgSharp();
- }
- }
- }
- }
- private sealed class LayerClear : LayerConversion
- {
- private readonly Color Color = Color.Black;
- public LayerClear(TomlTable table)
- {
- if (table.TryGetValue("color", out var tomlObject))
- {
- Color = Color.FromHex(((TomlValue<string>) tomlObject).Value);
- }
- }
- public override Color ConvertColor(Color input) => Color;
- }
- private sealed class LayerToAlpha : LayerConversion
- {
- public LayerToAlpha(TomlTable table)
- {
- }
- public override Color ConvertColor(Color input)
- {
- return new Color(input.R, input.G, input.B, MathF.Min(input.R + input.G + input.B, 1.0f));
- }
- }
- private sealed class LayerNoise : Layer
- {
- private readonly Color InnerColor = Color.White;
- private readonly Color OuterColor = Color.Black;
- private readonly NoiseGenerator.NoiseType NoiseType = NoiseGenerator.NoiseType.Fbm;
- private readonly uint Seed = 1234;
- private readonly float Persistence = 0.5f;
- private readonly float Lacunarity = (float) (Math.PI / 3);
- private readonly float Frequency = 1;
- private readonly uint Octaves = 3;
- private readonly float Threshold;
- private readonly float Power = 1;
- private readonly Color.BlendFactor SrcFactor = Color.BlendFactor.One;
- private readonly Color.BlendFactor DstFactor = Color.BlendFactor.One;
- public LayerNoise(TomlTable table)
- {
- if (table.TryGetValue("innercolor", out var tomlObject))
- {
- InnerColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("outercolor", out tomlObject))
- {
- OuterColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("seed", out tomlObject))
- {
- Seed = (uint) ((TomlValue<long>) tomlObject).Value;
- }
- if (table.TryGetValue("persistence", out tomlObject))
- {
- Persistence = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("lacunarity", out tomlObject))
- {
- Lacunarity = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("frequency", out tomlObject))
- {
- Frequency = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("octaves", out tomlObject))
- {
- Octaves = (uint) ((TomlValue<long>) tomlObject).Value;
- }
- if (table.TryGetValue("threshold", out tomlObject))
- {
- Threshold = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("sourcefactor", out tomlObject))
- {
- SrcFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("destfactor", out tomlObject))
- {
- DstFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("power", out tomlObject))
- {
- Power = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("noise_type", out tomlObject))
- {
- switch (((TomlValue<string>) tomlObject).Value)
- {
- case "fbm":
- NoiseType = NoiseGenerator.NoiseType.Fbm;
- break;
- case "ridged":
- NoiseType = NoiseGenerator.NoiseType.Ridged;
- break;
- default:
- throw new InvalidOperationException();
- }
- }
- }
- public override void Apply(Image<Rgba32> bitmap)
- {
- var noise = new NoiseGenerator(NoiseType);
- noise.SetSeed(Seed);
- noise.SetFrequency(Frequency);
- noise.SetPersistence(Persistence);
- noise.SetLacunarity(Lacunarity);
- noise.SetOctaves(Octaves);
- noise.SetPeriodX(bitmap.Width);
- noise.SetPeriodY(bitmap.Height);
- var threshVal = 1 / (1 - Threshold);
- var powFactor = 1 / Power;
- var span = bitmap.GetPixelSpan();
- for (var y = 0; y < bitmap.Height; y++)
- {
- for (var x = 0; x < bitmap.Width; x++)
- {
- // Do noise calculations.
- var noiseVal = MathF.Min(1, MathF.Max(0, (noise.GetNoise(x, y) + 1) / 2));
- // Threshold
- noiseVal = MathF.Max(0, noiseVal - Threshold);
- noiseVal *= threshVal;
- noiseVal = MathF.Pow(noiseVal, powFactor);
- // Get colors based on noise values.
- var srcColor = Color.InterpolateBetween(OuterColor, InnerColor, noiseVal)
- .WithAlpha(noiseVal);
- // Apply blending factors & write back.
- var i = y * bitmap.Width + x;
- var dstColor = span[i].ConvertImgSharp();
- span[i] = Color.Blend(dstColor, srcColor, DstFactor, SrcFactor).ConvertImgSharp();
- }
- }
- }
- }
- private sealed class LayerPoints : Layer
- {
- private readonly int Seed = 1234;
- private readonly int PointCount = 100;
- private readonly Color CloseColor = Color.White;
- private readonly Color FarColor = Color.Black;
- private readonly Color.BlendFactor SrcFactor = Color.BlendFactor.One;
- private readonly Color.BlendFactor DstFactor = Color.BlendFactor.One;
- // Noise mask stuff.
- private readonly bool Masked;
- private readonly NoiseGenerator.NoiseType MaskNoiseType = NoiseGenerator.NoiseType.Fbm;
- private readonly uint MaskSeed = 1234;
- private readonly float MaskPersistence = 0.5f;
- private readonly float MaskLacunarity = (float) (Math.PI * 2 / 3);
- private readonly float MaskFrequency = 1;
- private readonly uint MaskOctaves = 3;
- private readonly float MaskThreshold;
- private readonly int PointSize = 1;
- private readonly float MaskPower = 1;
- public LayerPoints(TomlTable table)
- {
- if (table.TryGetValue("seed", out var tomlObject))
- {
- Seed = (int) ((TomlValue<long>) tomlObject).Value;
- }
- if (table.TryGetValue("count", out tomlObject))
- {
- PointCount = (int) ((TomlValue<long>) tomlObject).Value;
- }
- if (table.TryGetValue("sourcefactor", out tomlObject))
- {
- SrcFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("destfactor", out tomlObject))
- {
- DstFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("farcolor", out tomlObject))
- {
- FarColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("closecolor", out tomlObject))
- {
- CloseColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
- }
- if (table.TryGetValue("pointsize", out tomlObject))
- {
- PointSize = (int) ((TomlValue<long>) tomlObject).Value;
- }
- // Noise mask stuff.
- if (table.TryGetValue("mask", out tomlObject))
- {
- Masked = ((TomlValue<bool>) tomlObject).Value;
- }
- if (table.TryGetValue("maskseed", out tomlObject))
- {
- MaskSeed = (uint) ((TomlValue<long>) tomlObject).Value;
- }
- if (table.TryGetValue("maskpersistence", out tomlObject))
- {
- MaskPersistence = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("masklacunarity", out tomlObject))
- {
- MaskLacunarity = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("maskfrequency", out tomlObject))
- {
- MaskFrequency = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("maskoctaves", out tomlObject))
- {
- MaskOctaves = (uint) ((TomlValue<long>) tomlObject).Value;
- }
- if (table.TryGetValue("maskthreshold", out tomlObject))
- {
- MaskThreshold = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- if (table.TryGetValue("masknoise_type", out tomlObject))
- {
- switch (((TomlValue<string>) tomlObject).Value)
- {
- case "fbm":
- MaskNoiseType = NoiseGenerator.NoiseType.Fbm;
- break;
- case "ridged":
- MaskNoiseType = NoiseGenerator.NoiseType.Ridged;
- break;
- default:
- throw new InvalidOperationException();
- }
- }
- if (table.TryGetValue("maskpower", out tomlObject))
- {
- MaskPower = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
- }
- }
- public override void Apply(Image<Rgba32> bitmap)
- {
- // Temporary buffer so we don't mess up blending.
- using (var buffer = new Image<Rgba32>(Configuration.Default, bitmap.Width, bitmap.Height, new Rgba32(0, 0, 0, 0)))
- {
- if (Masked)
- {
- GenPointsMasked(buffer);
- }
- else
- {
- GenPoints(buffer);
- }
- var srcSpan = buffer.GetPixelSpan();
- var dstSpan = bitmap.GetPixelSpan();
- var width = bitmap.Width;
- var height = bitmap.Height;
- for (var y = 0; y < height; y++)
- {
- for (var x = 0; x < width; x++)
- {
- var i = y * width + x;
- var dstColor = dstSpan[i].ConvertImgSharp();
- var srcColor = srcSpan[i].ConvertImgSharp();
- dstSpan[i] = Color.Blend(dstColor, srcColor, DstFactor, SrcFactor).ConvertImgSharp();
- }
- }
- }
- }
- private void GenPoints(Image<Rgba32> buffer)
- {
- var o = PointSize - 1;
- var random = new Random(Seed);
- var span = buffer.GetPixelSpan();
- for (var i = 0; i < PointCount; i++)
- {
- var x = random.Next(0, buffer.Width);
- var y = random.Next(0, buffer.Height);
- var dist = random.NextFloat();
- for (var oy = y - o; oy <= y + o; oy++)
- {
- for (var ox = x - o; ox <= x + o; ox++)
- {
- var ix = MathHelper.Mod(ox, buffer.Width);
- var iy = MathHelper.Mod(oy, buffer.Height);
- var color = Color.InterpolateBetween(FarColor, CloseColor, dist).ConvertImgSharp();
- span[iy * buffer.Width + ix] = color;
- }
- }
- }
- }
- private void GenPointsMasked(Image<Rgba32> buffer)
- {
- var o = PointSize - 1;
- var random = new Random(Seed);
- var noise = new NoiseGenerator(MaskNoiseType);
- noise.SetSeed(MaskSeed);
- noise.SetFrequency(MaskFrequency);
- noise.SetPersistence(MaskPersistence);
- noise.SetLacunarity(MaskLacunarity);
- noise.SetOctaves(MaskOctaves);
- noise.SetPeriodX(buffer.Width);
- noise.SetPeriodY(buffer.Height);
- var threshVal = 1 / (1 - MaskThreshold);
- var powFactor = 1 / MaskPower;
- const int maxPointAttemptCount = 9999;
- var pointAttemptCount = 0;
- var span = buffer.GetPixelSpan();
- for (var i = 0; i < PointCount; i++)
- {
- var x = random.Next(0, buffer.Width);
- var y = random.Next(0, buffer.Height);
- // Grab noise at this point.
- var noiseVal = MathF.Min(1, MathF.Max(0, (noise.GetNoise(x, y) + 1) / 2));
- // Threshold
- noiseVal = MathF.Max(0, noiseVal - MaskThreshold);
- noiseVal *= threshVal;
- noiseVal = MathF.Pow(noiseVal, powFactor);
- var randomThresh = random.NextFloat();
- if (randomThresh > noiseVal)
- {
- if (++pointAttemptCount <= maxPointAttemptCount)
- {
- i--;
- }
- continue;
- }
- var dist = random.NextFloat();
- for (var oy = y - o; oy <= y + o; oy++)
- {
- for (var ox = x - o; ox <= x + o; ox++)
- {
- var ix = MathHelper.Mod(ox, buffer.Width);
- var iy = MathHelper.Mod(oy, buffer.Height);
- var color = Color.InterpolateBetween(FarColor, CloseColor, dist).ConvertImgSharp();
- span[iy * buffer.Width + ix] = color;
- }
- }
- }
- }
- }
- }
- }
|