ParallaxGenerator.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. using System;
  2. using System.Threading;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using Nett;
  7. using Robust.Client.Utility;
  8. using Robust.Shared.Log;
  9. using Robust.Shared.Maths;
  10. using Robust.Shared.Noise;
  11. using Robust.Shared.Random;
  12. using SixLabors.ImageSharp;
  13. using SixLabors.ImageSharp.PixelFormats;
  14. using Color = Robust.Shared.Maths.Color;
  15. namespace Content.Client.Parallax
  16. {
  17. public sealed class ParallaxGenerator
  18. {
  19. private readonly List<Layer> Layers = new();
  20. public static Image<Rgba32> GenerateParallax(TomlTable config, Size size, ISawmill sawmill, List<Image<Rgba32>>? debugLayerDump, CancellationToken cancel = default)
  21. {
  22. sawmill.Debug("Generating parallax!");
  23. var generator = new ParallaxGenerator();
  24. generator._loadConfig(config);
  25. sawmill.Debug("Timing start!");
  26. var sw = new Stopwatch();
  27. sw.Start();
  28. var image = new Image<Rgba32>(Configuration.Default, size.Width, size.Height, new Rgba32(0, 0, 0, 0));
  29. var count = 0;
  30. foreach (var layer in generator.Layers)
  31. {
  32. cancel.ThrowIfCancellationRequested();
  33. layer.Apply(image);
  34. debugLayerDump?.Add(image.Clone());
  35. sawmill.Debug("Layer {0} done!", count++);
  36. }
  37. sw.Stop();
  38. sawmill.Debug("Total time: {0}", sw.Elapsed.TotalSeconds);
  39. return image;
  40. }
  41. private void _loadConfig(TomlTable config)
  42. {
  43. foreach (var layerArray in ((TomlTableArray) config.Get("layers")).Items)
  44. {
  45. switch (((TomlValue<string>) layerArray.Get("type")).Value)
  46. {
  47. case "clear":
  48. var layerClear = new LayerClear(layerArray);
  49. Layers.Add(layerClear);
  50. break;
  51. case "toalpha":
  52. var layerToAlpha = new LayerToAlpha(layerArray);
  53. Layers.Add(layerToAlpha);
  54. break;
  55. case "noise":
  56. var layerNoise = new LayerNoise(layerArray);
  57. Layers.Add(layerNoise);
  58. break;
  59. case "points":
  60. var layerPoint = new LayerPoints(layerArray);
  61. Layers.Add(layerPoint);
  62. break;
  63. default:
  64. throw new NotSupportedException();
  65. }
  66. }
  67. }
  68. private abstract class Layer
  69. {
  70. public abstract void Apply(Image<Rgba32> bitmap);
  71. }
  72. private abstract class LayerConversion : Layer
  73. {
  74. public abstract Color ConvertColor(Color input);
  75. public override void Apply(Image<Rgba32> bitmap)
  76. {
  77. var span = bitmap.GetPixelSpan();
  78. for (var y = 0; y < bitmap.Height; y++)
  79. {
  80. for (var x = 0; x < bitmap.Width; x++)
  81. {
  82. var i = y * bitmap.Width + x;
  83. span[i] = ConvertColor(span[i].ConvertImgSharp()).ConvertImgSharp();
  84. }
  85. }
  86. }
  87. }
  88. private sealed class LayerClear : LayerConversion
  89. {
  90. private readonly Color Color = Color.Black;
  91. public LayerClear(TomlTable table)
  92. {
  93. if (table.TryGetValue("color", out var tomlObject))
  94. {
  95. Color = Color.FromHex(((TomlValue<string>) tomlObject).Value);
  96. }
  97. }
  98. public override Color ConvertColor(Color input) => Color;
  99. }
  100. private sealed class LayerToAlpha : LayerConversion
  101. {
  102. public LayerToAlpha(TomlTable table)
  103. {
  104. }
  105. public override Color ConvertColor(Color input)
  106. {
  107. return new Color(input.R, input.G, input.B, MathF.Min(input.R + input.G + input.B, 1.0f));
  108. }
  109. }
  110. private sealed class LayerNoise : Layer
  111. {
  112. private readonly Color InnerColor = Color.White;
  113. private readonly Color OuterColor = Color.Black;
  114. private readonly NoiseGenerator.NoiseType NoiseType = NoiseGenerator.NoiseType.Fbm;
  115. private readonly uint Seed = 1234;
  116. private readonly float Persistence = 0.5f;
  117. private readonly float Lacunarity = (float) (Math.PI / 3);
  118. private readonly float Frequency = 1;
  119. private readonly uint Octaves = 3;
  120. private readonly float Threshold;
  121. private readonly float Power = 1;
  122. private readonly Color.BlendFactor SrcFactor = Color.BlendFactor.One;
  123. private readonly Color.BlendFactor DstFactor = Color.BlendFactor.One;
  124. public LayerNoise(TomlTable table)
  125. {
  126. if (table.TryGetValue("innercolor", out var tomlObject))
  127. {
  128. InnerColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
  129. }
  130. if (table.TryGetValue("outercolor", out tomlObject))
  131. {
  132. OuterColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
  133. }
  134. if (table.TryGetValue("seed", out tomlObject))
  135. {
  136. Seed = (uint) ((TomlValue<long>) tomlObject).Value;
  137. }
  138. if (table.TryGetValue("persistence", out tomlObject))
  139. {
  140. Persistence = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  141. }
  142. if (table.TryGetValue("lacunarity", out tomlObject))
  143. {
  144. Lacunarity = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  145. }
  146. if (table.TryGetValue("frequency", out tomlObject))
  147. {
  148. Frequency = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  149. }
  150. if (table.TryGetValue("octaves", out tomlObject))
  151. {
  152. Octaves = (uint) ((TomlValue<long>) tomlObject).Value;
  153. }
  154. if (table.TryGetValue("threshold", out tomlObject))
  155. {
  156. Threshold = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  157. }
  158. if (table.TryGetValue("sourcefactor", out tomlObject))
  159. {
  160. SrcFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
  161. }
  162. if (table.TryGetValue("destfactor", out tomlObject))
  163. {
  164. DstFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
  165. }
  166. if (table.TryGetValue("power", out tomlObject))
  167. {
  168. Power = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  169. }
  170. if (table.TryGetValue("noise_type", out tomlObject))
  171. {
  172. switch (((TomlValue<string>) tomlObject).Value)
  173. {
  174. case "fbm":
  175. NoiseType = NoiseGenerator.NoiseType.Fbm;
  176. break;
  177. case "ridged":
  178. NoiseType = NoiseGenerator.NoiseType.Ridged;
  179. break;
  180. default:
  181. throw new InvalidOperationException();
  182. }
  183. }
  184. }
  185. public override void Apply(Image<Rgba32> bitmap)
  186. {
  187. var noise = new NoiseGenerator(NoiseType);
  188. noise.SetSeed(Seed);
  189. noise.SetFrequency(Frequency);
  190. noise.SetPersistence(Persistence);
  191. noise.SetLacunarity(Lacunarity);
  192. noise.SetOctaves(Octaves);
  193. noise.SetPeriodX(bitmap.Width);
  194. noise.SetPeriodY(bitmap.Height);
  195. var threshVal = 1 / (1 - Threshold);
  196. var powFactor = 1 / Power;
  197. var span = bitmap.GetPixelSpan();
  198. for (var y = 0; y < bitmap.Height; y++)
  199. {
  200. for (var x = 0; x < bitmap.Width; x++)
  201. {
  202. // Do noise calculations.
  203. var noiseVal = MathF.Min(1, MathF.Max(0, (noise.GetNoise(x, y) + 1) / 2));
  204. // Threshold
  205. noiseVal = MathF.Max(0, noiseVal - Threshold);
  206. noiseVal *= threshVal;
  207. noiseVal = MathF.Pow(noiseVal, powFactor);
  208. // Get colors based on noise values.
  209. var srcColor = Color.InterpolateBetween(OuterColor, InnerColor, noiseVal)
  210. .WithAlpha(noiseVal);
  211. // Apply blending factors & write back.
  212. var i = y * bitmap.Width + x;
  213. var dstColor = span[i].ConvertImgSharp();
  214. span[i] = Color.Blend(dstColor, srcColor, DstFactor, SrcFactor).ConvertImgSharp();
  215. }
  216. }
  217. }
  218. }
  219. private sealed class LayerPoints : Layer
  220. {
  221. private readonly int Seed = 1234;
  222. private readonly int PointCount = 100;
  223. private readonly Color CloseColor = Color.White;
  224. private readonly Color FarColor = Color.Black;
  225. private readonly Color.BlendFactor SrcFactor = Color.BlendFactor.One;
  226. private readonly Color.BlendFactor DstFactor = Color.BlendFactor.One;
  227. // Noise mask stuff.
  228. private readonly bool Masked;
  229. private readonly NoiseGenerator.NoiseType MaskNoiseType = NoiseGenerator.NoiseType.Fbm;
  230. private readonly uint MaskSeed = 1234;
  231. private readonly float MaskPersistence = 0.5f;
  232. private readonly float MaskLacunarity = (float) (Math.PI * 2 / 3);
  233. private readonly float MaskFrequency = 1;
  234. private readonly uint MaskOctaves = 3;
  235. private readonly float MaskThreshold;
  236. private readonly int PointSize = 1;
  237. private readonly float MaskPower = 1;
  238. public LayerPoints(TomlTable table)
  239. {
  240. if (table.TryGetValue("seed", out var tomlObject))
  241. {
  242. Seed = (int) ((TomlValue<long>) tomlObject).Value;
  243. }
  244. if (table.TryGetValue("count", out tomlObject))
  245. {
  246. PointCount = (int) ((TomlValue<long>) tomlObject).Value;
  247. }
  248. if (table.TryGetValue("sourcefactor", out tomlObject))
  249. {
  250. SrcFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
  251. }
  252. if (table.TryGetValue("destfactor", out tomlObject))
  253. {
  254. DstFactor = (Color.BlendFactor) Enum.Parse(typeof(Color.BlendFactor), ((TomlValue<string>) tomlObject).Value);
  255. }
  256. if (table.TryGetValue("farcolor", out tomlObject))
  257. {
  258. FarColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
  259. }
  260. if (table.TryGetValue("closecolor", out tomlObject))
  261. {
  262. CloseColor = Color.FromHex(((TomlValue<string>) tomlObject).Value);
  263. }
  264. if (table.TryGetValue("pointsize", out tomlObject))
  265. {
  266. PointSize = (int) ((TomlValue<long>) tomlObject).Value;
  267. }
  268. // Noise mask stuff.
  269. if (table.TryGetValue("mask", out tomlObject))
  270. {
  271. Masked = ((TomlValue<bool>) tomlObject).Value;
  272. }
  273. if (table.TryGetValue("maskseed", out tomlObject))
  274. {
  275. MaskSeed = (uint) ((TomlValue<long>) tomlObject).Value;
  276. }
  277. if (table.TryGetValue("maskpersistence", out tomlObject))
  278. {
  279. MaskPersistence = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  280. }
  281. if (table.TryGetValue("masklacunarity", out tomlObject))
  282. {
  283. MaskLacunarity = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  284. }
  285. if (table.TryGetValue("maskfrequency", out tomlObject))
  286. {
  287. MaskFrequency = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  288. }
  289. if (table.TryGetValue("maskoctaves", out tomlObject))
  290. {
  291. MaskOctaves = (uint) ((TomlValue<long>) tomlObject).Value;
  292. }
  293. if (table.TryGetValue("maskthreshold", out tomlObject))
  294. {
  295. MaskThreshold = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  296. }
  297. if (table.TryGetValue("masknoise_type", out tomlObject))
  298. {
  299. switch (((TomlValue<string>) tomlObject).Value)
  300. {
  301. case "fbm":
  302. MaskNoiseType = NoiseGenerator.NoiseType.Fbm;
  303. break;
  304. case "ridged":
  305. MaskNoiseType = NoiseGenerator.NoiseType.Ridged;
  306. break;
  307. default:
  308. throw new InvalidOperationException();
  309. }
  310. }
  311. if (table.TryGetValue("maskpower", out tomlObject))
  312. {
  313. MaskPower = float.Parse(((TomlValue<string>) tomlObject).Value, CultureInfo.InvariantCulture);
  314. }
  315. }
  316. public override void Apply(Image<Rgba32> bitmap)
  317. {
  318. // Temporary buffer so we don't mess up blending.
  319. using (var buffer = new Image<Rgba32>(Configuration.Default, bitmap.Width, bitmap.Height, new Rgba32(0, 0, 0, 0)))
  320. {
  321. if (Masked)
  322. {
  323. GenPointsMasked(buffer);
  324. }
  325. else
  326. {
  327. GenPoints(buffer);
  328. }
  329. var srcSpan = buffer.GetPixelSpan();
  330. var dstSpan = bitmap.GetPixelSpan();
  331. var width = bitmap.Width;
  332. var height = bitmap.Height;
  333. for (var y = 0; y < height; y++)
  334. {
  335. for (var x = 0; x < width; x++)
  336. {
  337. var i = y * width + x;
  338. var dstColor = dstSpan[i].ConvertImgSharp();
  339. var srcColor = srcSpan[i].ConvertImgSharp();
  340. dstSpan[i] = Color.Blend(dstColor, srcColor, DstFactor, SrcFactor).ConvertImgSharp();
  341. }
  342. }
  343. }
  344. }
  345. private void GenPoints(Image<Rgba32> buffer)
  346. {
  347. var o = PointSize - 1;
  348. var random = new Random(Seed);
  349. var span = buffer.GetPixelSpan();
  350. for (var i = 0; i < PointCount; i++)
  351. {
  352. var x = random.Next(0, buffer.Width);
  353. var y = random.Next(0, buffer.Height);
  354. var dist = random.NextFloat();
  355. for (var oy = y - o; oy <= y + o; oy++)
  356. {
  357. for (var ox = x - o; ox <= x + o; ox++)
  358. {
  359. var ix = MathHelper.Mod(ox, buffer.Width);
  360. var iy = MathHelper.Mod(oy, buffer.Height);
  361. var color = Color.InterpolateBetween(FarColor, CloseColor, dist).ConvertImgSharp();
  362. span[iy * buffer.Width + ix] = color;
  363. }
  364. }
  365. }
  366. }
  367. private void GenPointsMasked(Image<Rgba32> buffer)
  368. {
  369. var o = PointSize - 1;
  370. var random = new Random(Seed);
  371. var noise = new NoiseGenerator(MaskNoiseType);
  372. noise.SetSeed(MaskSeed);
  373. noise.SetFrequency(MaskFrequency);
  374. noise.SetPersistence(MaskPersistence);
  375. noise.SetLacunarity(MaskLacunarity);
  376. noise.SetOctaves(MaskOctaves);
  377. noise.SetPeriodX(buffer.Width);
  378. noise.SetPeriodY(buffer.Height);
  379. var threshVal = 1 / (1 - MaskThreshold);
  380. var powFactor = 1 / MaskPower;
  381. const int maxPointAttemptCount = 9999;
  382. var pointAttemptCount = 0;
  383. var span = buffer.GetPixelSpan();
  384. for (var i = 0; i < PointCount; i++)
  385. {
  386. var x = random.Next(0, buffer.Width);
  387. var y = random.Next(0, buffer.Height);
  388. // Grab noise at this point.
  389. var noiseVal = MathF.Min(1, MathF.Max(0, (noise.GetNoise(x, y) + 1) / 2));
  390. // Threshold
  391. noiseVal = MathF.Max(0, noiseVal - MaskThreshold);
  392. noiseVal *= threshVal;
  393. noiseVal = MathF.Pow(noiseVal, powFactor);
  394. var randomThresh = random.NextFloat();
  395. if (randomThresh > noiseVal)
  396. {
  397. if (++pointAttemptCount <= maxPointAttemptCount)
  398. {
  399. i--;
  400. }
  401. continue;
  402. }
  403. var dist = random.NextFloat();
  404. for (var oy = y - o; oy <= y + o; oy++)
  405. {
  406. for (var ox = x - o; ox <= x + o; ox++)
  407. {
  408. var ix = MathHelper.Mod(ox, buffer.Width);
  409. var iy = MathHelper.Mod(oy, buffer.Height);
  410. var color = Color.InterpolateBetween(FarColor, CloseColor, dist).ConvertImgSharp();
  411. span[iy * buffer.Width + ix] = color;
  412. }
  413. }
  414. }
  415. }
  416. }
  417. }
  418. }