AtmosMonitoringConsoleNavMapControl.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. using Content.Client.Pinpointer.UI;
  2. using Content.Shared.Atmos.Components;
  3. using Content.Shared.Pinpointer;
  4. using Robust.Client.Graphics;
  5. using Robust.Shared.Collections;
  6. using Robust.Shared.Map.Components;
  7. using System.Linq;
  8. using System.Numerics;
  9. namespace Content.Client.Atmos.Consoles;
  10. public sealed partial class AtmosMonitoringConsoleNavMapControl : NavMapControl
  11. {
  12. [Dependency] private readonly IEntityManager _entManager = default!;
  13. public bool ShowPipeNetwork = true;
  14. public int? FocusNetId = null;
  15. private const int ChunkSize = 4;
  16. private readonly Color _basePipeNetColor = Color.LightGray;
  17. private readonly Color _unfocusedPipeNetColor = Color.DimGray;
  18. private List<AtmosMonitoringConsoleLine> _atmosPipeNetwork = new();
  19. private Dictionary<Color, Color> _sRGBLookUp = new Dictionary<Color, Color>();
  20. // Look up tables for merging continuous lines. Indexed by line color
  21. private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _horizLines = new();
  22. private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _horizLinesReversed = new();
  23. private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _vertLines = new();
  24. private Dictionary<Color, Dictionary<Vector2i, Vector2i>> _vertLinesReversed = new();
  25. public AtmosMonitoringConsoleNavMapControl() : base()
  26. {
  27. PostWallDrawingAction += DrawAllPipeNetworks;
  28. }
  29. protected override void UpdateNavMap()
  30. {
  31. base.UpdateNavMap();
  32. if (!_entManager.TryGetComponent<AtmosMonitoringConsoleComponent>(Owner, out var console))
  33. return;
  34. if (!_entManager.TryGetComponent<MapGridComponent>(MapUid, out var grid))
  35. return;
  36. _atmosPipeNetwork = GetDecodedAtmosPipeChunks(console.AtmosPipeChunks, grid);
  37. }
  38. private void DrawAllPipeNetworks(DrawingHandleScreen handle)
  39. {
  40. if (!ShowPipeNetwork)
  41. return;
  42. // Draw networks
  43. if (_atmosPipeNetwork != null && _atmosPipeNetwork.Any())
  44. DrawPipeNetwork(handle, _atmosPipeNetwork);
  45. }
  46. private void DrawPipeNetwork(DrawingHandleScreen handle, List<AtmosMonitoringConsoleLine> atmosPipeNetwork)
  47. {
  48. var offset = GetOffset();
  49. offset = offset with { Y = -offset.Y };
  50. if (WorldRange / WorldMaxRange > 0.5f)
  51. {
  52. var pipeNetworks = new Dictionary<Color, ValueList<Vector2>>();
  53. foreach (var chunkedLine in atmosPipeNetwork)
  54. {
  55. var start = ScalePosition(chunkedLine.Origin - offset);
  56. var end = ScalePosition(chunkedLine.Terminus - offset);
  57. if (!pipeNetworks.TryGetValue(chunkedLine.Color, out var subNetwork))
  58. subNetwork = new ValueList<Vector2>();
  59. subNetwork.Add(start);
  60. subNetwork.Add(end);
  61. pipeNetworks[chunkedLine.Color] = subNetwork;
  62. }
  63. foreach ((var color, var subNetwork) in pipeNetworks)
  64. {
  65. if (subNetwork.Count > 0)
  66. handle.DrawPrimitives(DrawPrimitiveTopology.LineList, subNetwork.Span, color);
  67. }
  68. }
  69. else
  70. {
  71. var pipeVertexUVs = new Dictionary<Color, ValueList<Vector2>>();
  72. foreach (var chunkedLine in atmosPipeNetwork)
  73. {
  74. var leftTop = ScalePosition(new Vector2
  75. (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
  76. Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
  77. - offset);
  78. var rightTop = ScalePosition(new Vector2
  79. (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
  80. Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
  81. - offset);
  82. var leftBottom = ScalePosition(new Vector2
  83. (Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
  84. Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
  85. - offset);
  86. var rightBottom = ScalePosition(new Vector2
  87. (Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
  88. Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
  89. - offset);
  90. if (!pipeVertexUVs.TryGetValue(chunkedLine.Color, out var pipeVertexUV))
  91. pipeVertexUV = new ValueList<Vector2>();
  92. pipeVertexUV.Add(leftBottom);
  93. pipeVertexUV.Add(leftTop);
  94. pipeVertexUV.Add(rightBottom);
  95. pipeVertexUV.Add(leftTop);
  96. pipeVertexUV.Add(rightBottom);
  97. pipeVertexUV.Add(rightTop);
  98. pipeVertexUVs[chunkedLine.Color] = pipeVertexUV;
  99. }
  100. foreach ((var color, var pipeVertexUV) in pipeVertexUVs)
  101. {
  102. if (pipeVertexUV.Count > 0)
  103. handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, pipeVertexUV.Span, color);
  104. }
  105. }
  106. }
  107. private List<AtmosMonitoringConsoleLine> GetDecodedAtmosPipeChunks(Dictionary<Vector2i, AtmosPipeChunk>? chunks, MapGridComponent? grid)
  108. {
  109. var decodedOutput = new List<AtmosMonitoringConsoleLine>();
  110. if (chunks == null || grid == null)
  111. return decodedOutput;
  112. // Clear stale look up table values
  113. _horizLines.Clear();
  114. _horizLinesReversed.Clear();
  115. _vertLines.Clear();
  116. _vertLinesReversed.Clear();
  117. // Generate masks
  118. var northMask = (ulong)1 << 0;
  119. var southMask = (ulong)1 << 1;
  120. var westMask = (ulong)1 << 2;
  121. var eastMask = (ulong)1 << 3;
  122. foreach ((var chunkOrigin, var chunk) in chunks)
  123. {
  124. var list = new List<AtmosMonitoringConsoleLine>();
  125. foreach (var ((netId, hexColor), atmosPipeData) in chunk.AtmosPipeData)
  126. {
  127. // Determine the correct coloration for the pipe
  128. var color = Color.FromHex(hexColor) * _basePipeNetColor;
  129. if (FocusNetId != null && FocusNetId != netId)
  130. color *= _unfocusedPipeNetColor;
  131. // Get the associated line look up tables
  132. if (!_horizLines.TryGetValue(color, out var horizLines))
  133. {
  134. horizLines = new();
  135. _horizLines[color] = horizLines;
  136. }
  137. if (!_horizLinesReversed.TryGetValue(color, out var horizLinesReversed))
  138. {
  139. horizLinesReversed = new();
  140. _horizLinesReversed[color] = horizLinesReversed;
  141. }
  142. if (!_vertLines.TryGetValue(color, out var vertLines))
  143. {
  144. vertLines = new();
  145. _vertLines[color] = vertLines;
  146. }
  147. if (!_vertLinesReversed.TryGetValue(color, out var vertLinesReversed))
  148. {
  149. vertLinesReversed = new();
  150. _vertLinesReversed[color] = vertLinesReversed;
  151. }
  152. // Loop over the chunk
  153. for (var tileIdx = 0; tileIdx < ChunkSize * ChunkSize; tileIdx++)
  154. {
  155. if (atmosPipeData == 0)
  156. continue;
  157. var mask = (ulong)SharedNavMapSystem.AllDirMask << tileIdx * SharedNavMapSystem.Directions;
  158. if ((atmosPipeData & mask) == 0)
  159. continue;
  160. var relativeTile = GetTileFromIndex(tileIdx);
  161. var tile = (chunk.Origin * ChunkSize + relativeTile) * grid.TileSize;
  162. tile = tile with { Y = -tile.Y };
  163. // Calculate the draw point offsets
  164. var vertLineOrigin = (atmosPipeData & northMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
  165. new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 1f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
  166. var vertLineTerminus = (atmosPipeData & southMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
  167. new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
  168. var horizLineOrigin = (atmosPipeData & eastMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
  169. new Vector2(grid.TileSize * 1f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
  170. var horizLineTerminus = (atmosPipeData & westMask << tileIdx * SharedNavMapSystem.Directions) > 0 ?
  171. new Vector2(grid.TileSize * 0f, -grid.TileSize * 0.5f) : new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f);
  172. // Since we can have pipe lines that have a length of a half tile,
  173. // double the vectors and convert to vector2i so we can merge them
  174. AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + horizLineOrigin, 2), ConvertVector2ToVector2i(tile + horizLineTerminus, 2), horizLines, horizLinesReversed);
  175. AddOrUpdateNavMapLine(ConvertVector2ToVector2i(tile + vertLineOrigin, 2), ConvertVector2ToVector2i(tile + vertLineTerminus, 2), vertLines, vertLinesReversed);
  176. }
  177. }
  178. }
  179. // Scale the vector2is back down and convert to vector2
  180. foreach (var (color, horizLines) in _horizLines)
  181. {
  182. // Get the corresponding sRBG color
  183. var sRGB = GetsRGBColor(color);
  184. foreach (var (origin, terminal) in horizLines)
  185. decodedOutput.Add(new AtmosMonitoringConsoleLine
  186. (ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
  187. }
  188. foreach (var (color, vertLines) in _vertLines)
  189. {
  190. // Get the corresponding sRBG color
  191. var sRGB = GetsRGBColor(color);
  192. foreach (var (origin, terminal) in vertLines)
  193. decodedOutput.Add(new AtmosMonitoringConsoleLine
  194. (ConvertVector2iToVector2(origin, 0.5f), ConvertVector2iToVector2(terminal, 0.5f), sRGB));
  195. }
  196. return decodedOutput;
  197. }
  198. private Vector2 ConvertVector2iToVector2(Vector2i vector, float scale = 1f)
  199. {
  200. return new Vector2(vector.X * scale, vector.Y * scale);
  201. }
  202. private Vector2i ConvertVector2ToVector2i(Vector2 vector, float scale = 1f)
  203. {
  204. return new Vector2i((int)MathF.Round(vector.X * scale), (int)MathF.Round(vector.Y * scale));
  205. }
  206. private Vector2i GetTileFromIndex(int index)
  207. {
  208. var x = index / ChunkSize;
  209. var y = index % ChunkSize;
  210. return new Vector2i(x, y);
  211. }
  212. private Color GetsRGBColor(Color color)
  213. {
  214. if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
  215. {
  216. sRGB = Color.ToSrgb(color);
  217. _sRGBLookUp[color] = sRGB;
  218. }
  219. return sRGB;
  220. }
  221. }
  222. public struct AtmosMonitoringConsoleLine
  223. {
  224. public readonly Vector2 Origin;
  225. public readonly Vector2 Terminus;
  226. public readonly Color Color;
  227. public AtmosMonitoringConsoleLine(Vector2 origin, Vector2 terminus, Color color)
  228. {
  229. Origin = origin;
  230. Terminus = terminus;
  231. Color = color;
  232. }
  233. }