Improve atmos debug overlay (#22520)

This commit is contained in:
Leon Friedrich
2023-12-15 17:18:00 -05:00
committed by GitHub
parent 477327f952
commit 68765a4753
4 changed files with 300 additions and 243 deletions

View File

@@ -1,22 +1,16 @@
using System.Collections.Generic;
using Content.Client.Atmos.Overlays;
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.GameTicking;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Content.Client.Atmos.EntitySystems
{
[UsedImplicitly]
internal sealed class AtmosDebugOverlaySystem : SharedAtmosDebugOverlaySystem
{
private readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> _tileData =
new();
public readonly Dictionary<EntityUid, AtmosDebugOverlayMessage> TileData = new();
// Configuration set by debug commands and used by AtmosDebugOverlay {
/// <summary>Value source for display</summary>
@@ -48,20 +42,20 @@ namespace Content.Client.Atmos.EntitySystems
private void OnGridRemoved(GridRemovalEvent ev)
{
if (_tileData.ContainsKey(ev.EntityUid))
if (TileData.ContainsKey(ev.EntityUid))
{
_tileData.Remove(ev.EntityUid);
TileData.Remove(ev.EntityUid);
}
}
private void HandleAtmosDebugOverlayMessage(AtmosDebugOverlayMessage message)
{
_tileData[GetEntity(message.GridId)] = message;
TileData[GetEntity(message.GridId)] = message;
}
private void HandleAtmosDebugOverlayDisableMessage(AtmosDebugOverlayDisableMessage ev)
{
_tileData.Clear();
TileData.Clear();
}
public override void Shutdown()
@@ -74,24 +68,12 @@ namespace Content.Client.Atmos.EntitySystems
public void Reset(RoundRestartCleanupEvent ev)
{
_tileData.Clear();
TileData.Clear();
}
public bool HasData(EntityUid gridId)
{
return _tileData.ContainsKey(gridId);
}
public AtmosDebugOverlayData? GetData(EntityUid gridIndex, Vector2i indices)
{
if (!_tileData.TryGetValue(gridIndex, out var srcMsg))
return null;
var relative = indices - srcMsg.BaseIdx;
if (relative.X < 0 || relative.Y < 0 || relative.X >= LocalViewRange || relative.Y >= LocalViewRange)
return null;
return srcMsg.OverlayData[relative.X + relative.Y * LocalViewRange];
return TileData.ContainsKey(gridId);
}
}

View File

@@ -1,37 +1,57 @@
using System.Linq;
using System.Numerics;
using Content.Client.Atmos.EntitySystems;
using Content.Client.Resources;
using Content.Shared.Atmos;
using Content.Shared.Atmos.EntitySystems;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using AtmosDebugOverlayData = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData;
using DebugMessage = Content.Shared.Atmos.EntitySystems.SharedAtmosDebugOverlaySystem.AtmosDebugOverlayMessage;
namespace Content.Client.Atmos.Overlays
namespace Content.Client.Atmos.Overlays;
public sealed class AtmosDebugOverlay : Overlay
{
public sealed class AtmosDebugOverlay : Overlay
{
private readonly AtmosDebugOverlaySystem _atmosDebugOverlaySystem;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IInputManager _input = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
[Dependency] private readonly IResourceCache _cache = default!;
private readonly SharedTransformSystem _transform;
private readonly AtmosDebugOverlaySystem _system;
private readonly SharedMapSystem _map;
private readonly Font _font;
private List<(Entity<MapGridComponent>, DebugMessage)> _grids = new();
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private List<Entity<MapGridComponent>> _grids = new();
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
internal AtmosDebugOverlay(AtmosDebugOverlaySystem system)
{
IoCManager.InjectDependencies(this);
_atmosDebugOverlaySystem = system;
_system = system;
_transform = _entManager.System<SharedTransformSystem>();
_map = _entManager.System<SharedMapSystem>();
_font = _cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
}
protected override void Draw(in OverlayDrawArgs args)
{
var drawHandle = args.WorldHandle;
if (args.Space == OverlaySpace.ScreenSpace)
{
DrawTooltip(args);
return;
}
var mapId = args.Viewport.Eye!.Position.MapId;
var worldBounds = args.WorldBounds;
var handle = args.WorldHandle;
GetGrids(args.MapId, args.WorldBounds);
// IF YOU ARE ABOUT TO INTRODUCE CHUNKING OR SOME OTHER OPTIMIZATION INTO THIS CODE:
// -- THINK! --
@@ -40,53 +60,40 @@ namespace Content.Client.Atmos.Overlays
// 3. "Is this going to make it harder for atmos programmers to add data that may not be chunk-friendly into the atmos debugger?"
// Nanotrasen needs YOU! to avoid premature optimization in critical debugging tools - 20kdc
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref _grids, (EntityUid uid, MapGridComponent grid,
ref List<Entity<MapGridComponent>> state) =>
foreach (var (grid, msg) in _grids)
{
state.Add((uid, grid));
return true;
});
foreach (var (uid, mapGrid) in _grids)
{
if (!_atmosDebugOverlaySystem.HasData(uid) ||
!_entManager.TryGetComponent<TransformComponent>(uid, out var xform))
continue;
drawHandle.SetTransform(xform.WorldMatrix);
for (var pass = 0; pass < 2; pass++)
{
foreach (var tile in mapGrid.GetTilesIntersecting(worldBounds))
{
var dataMaybeNull = _atmosDebugOverlaySystem.GetData(uid, tile.GridIndices);
if (dataMaybeNull != null)
{
var data = (SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData) dataMaybeNull;
if (pass == 0)
{
// -- Mole Count --
float total = 0;
switch (_atmosDebugOverlaySystem.CfgMode)
{
case AtmosDebugOverlayMode.TotalMoles:
foreach (var f in data.Moles)
{
total += f;
handle.SetTransform(_transform.GetWorldMatrix(grid));
DrawData(msg, handle);
}
break;
case AtmosDebugOverlayMode.GasMoles:
total = data.Moles[_atmosDebugOverlaySystem.CfgSpecificGas];
break;
case AtmosDebugOverlayMode.Temperature:
total = data.Temperature;
break;
handle.SetTransform(Matrix3.Identity);
}
var interp = (total - _atmosDebugOverlaySystem.CfgBase) / _atmosDebugOverlaySystem.CfgScale;
private void DrawData(DebugMessage msg,
DrawingHandleWorld handle)
{
foreach (var data in msg.OverlayData)
{
if (data != null)
DrawGridTile(data.Value, handle);
}
}
private void DrawGridTile(AtmosDebugOverlayData data,
DrawingHandleWorld handle)
{
DrawFill(data, handle);
DrawBlocked(data, handle);
}
private void DrawFill(AtmosDebugOverlayData data, DrawingHandleWorld handle)
{
var tile = data.Indices;
var fill = GetFillData(data);
var interp = (fill - _system.CfgBase) / _system.CfgScale;
Color res;
if (_atmosDebugOverlaySystem.CfgCBM)
if (_system.CfgCBM)
{
// Greyscale interpolation
res = Color.InterpolateBetween(Color.Black, Color.White, interp);
@@ -103,85 +110,163 @@ namespace Content.Client.Atmos.Overlays
res = Color.InterpolateBetween(Color.LimeGreen, Color.Blue, (interp - 0.5f) * 2);
}
}
res = res.WithAlpha(0.75f);
drawHandle.DrawRect(Box2.FromDimensions(new Vector2(tile.X, tile.Y), new Vector2(1, 1)), res);
handle.DrawRect(Box2.FromDimensions(new Vector2(tile.X, tile.Y), new Vector2(1, 1)), res);
}
else if (pass == 1)
private float GetFillData(AtmosDebugOverlayData data)
{
// -- Blocked Directions --
void CheckAndShowBlockDir(AtmosDirection dir)
if (data.Moles == null)
return 0;
switch (_system.CfgMode)
{
if (data.BlockDirection.HasFlag(dir))
case AtmosDebugOverlayMode.TotalMoles:
var total = 0f;
foreach (var f in data.Moles)
{
total += f;
}
return total;
case AtmosDebugOverlayMode.GasMoles:
return data.Moles[_system.CfgSpecificGas];
default:
return data.Temperature;
}
}
private void DrawBlocked(AtmosDebugOverlayData data, DrawingHandleWorld handle)
{
var tile = data.Indices;
var tileCentre = tile + 0.5f * Vector2.One;
CheckAndShowBlockDir(data, handle, AtmosDirection.North, tileCentre);
CheckAndShowBlockDir(data, handle, AtmosDirection.South, tileCentre);
CheckAndShowBlockDir(data, handle, AtmosDirection.East, tileCentre);
CheckAndShowBlockDir(data, handle, AtmosDirection.West, tileCentre);
// -- Pressure Direction --
if (data.PressureDirection != AtmosDirection.Invalid)
{
DrawPressureDirection(handle, data.PressureDirection, tileCentre, Color.Blue);
}
else if (data.LastPressureDirection != AtmosDirection.Invalid)
{
DrawPressureDirection(handle, data.LastPressureDirection, tileCentre, Color.LightGray);
}
// -- Excited Groups --
if (data.InExcitedGroup is {} grp)
{
var basisA = tile;
var basisB = tile + new Vector2(1.0f, 1.0f);
var basisC = tile + new Vector2(0.0f, 1.0f);
var basisD = tile + new Vector2(1.0f, 0.0f);
var color = Color.White // Use first three nibbles for an unique color... Good enough?
.WithRed(grp & 0x000F)
.WithGreen((grp & 0x00F0) >> 4)
.WithBlue((grp & 0x0F00) >> 8);
handle.DrawLine(basisA, basisB, color);
handle.DrawLine(basisC, basisD, color);
}
if (data.IsSpace)
handle.DrawCircle(tileCentre, 0.15f, Color.Yellow);
if (data.MapAtmosphere)
handle.DrawCircle(tileCentre, 0.1f, Color.Orange);
if (data.NoGrid)
handle.DrawCircle(tileCentre, 0.05f, Color.Black);
}
private void CheckAndShowBlockDir(AtmosDebugOverlayData data, DrawingHandleWorld handle, AtmosDirection dir,
Vector2 tileCentre)
{
if (!data.BlockDirection.HasFlag(dir))
return;
// Account for South being 0.
var atmosAngle = dir.ToAngle() - Angle.FromDegrees(90);
var atmosAngleOfs = atmosAngle.ToVec() * 0.45f;
var atmosAngleOfsR90 = new Vector2(atmosAngleOfs.Y, -atmosAngleOfs.X);
var tileCentre = new Vector2(tile.X + 0.5f, tile.Y + 0.5f);
var basisA = tileCentre + atmosAngleOfs - atmosAngleOfsR90;
var basisB = tileCentre + atmosAngleOfs + atmosAngleOfsR90;
drawHandle.DrawLine(basisA, basisB, Color.Azure);
handle.DrawLine(basisA, basisB, Color.Azure);
}
}
CheckAndShowBlockDir(AtmosDirection.North);
CheckAndShowBlockDir(AtmosDirection.South);
CheckAndShowBlockDir(AtmosDirection.East);
CheckAndShowBlockDir(AtmosDirection.West);
void DrawPressureDirection(
private void DrawPressureDirection(
DrawingHandleWorld handle,
AtmosDirection d,
TileRef t,
Vector2 center,
Color color)
{
// Account for South being 0.
var atmosAngle = d.ToAngle() - Angle.FromDegrees(90);
var atmosAngleOfs = atmosAngle.ToVec() * 0.4f;
var tileCentre = new Vector2(t.X + 0.5f, t.Y + 0.5f);
var basisA = tileCentre;
var basisB = tileCentre + atmosAngleOfs;
handle.DrawLine(basisA, basisB, color);
handle.DrawLine(center, center + atmosAngleOfs, color);
}
// -- Pressure Direction --
if (data.PressureDirection != AtmosDirection.Invalid)
private void DrawTooltip(in OverlayDrawArgs args)
{
DrawPressureDirection(drawHandle, data.PressureDirection, tile, Color.Blue);
}
else if (data.LastPressureDirection != AtmosDirection.Invalid)
var handle = args.ScreenHandle;
var mousePos = _input.MouseScreenPosition;
if (!mousePos.IsValid)
return;
if (_ui.MouseGetControl(mousePos) is not IViewportControl viewport)
return;
var coords= viewport.PixelToMap(mousePos.Position);
var box = Box2.CenteredAround(coords.Position, 3 * Vector2.One);
GetGrids(coords.MapId, new Box2Rotated(box));
foreach (var (grid, msg) in _grids)
{
DrawPressureDirection(drawHandle, data.LastPressureDirection, tile, Color.LightGray);
}
var tilePos = new Vector2(tile.X, tile.Y);
// -- Excited Groups --
if (data.InExcitedGroup != 0)
var index = _map.WorldToTile(grid, grid, coords.Position);
foreach (var data in msg.OverlayData)
{
var basisA = tilePos;
var basisB = tilePos + new Vector2(1.0f, 1.0f);
var basisC = tilePos + new Vector2(0.0f, 1.0f);
var basisD = tilePos + new Vector2(1.0f, 0.0f);
var color = Color.White // Use first three nibbles for an unique color... Good enough?
.WithRed( data.InExcitedGroup & 0x000F)
.WithGreen((data.InExcitedGroup & 0x00F0) >>4)
.WithBlue( (data.InExcitedGroup & 0x0F00) >>8);
drawHandle.DrawLine(basisA, basisB, color);
drawHandle.DrawLine(basisC, basisD, color);
}
// -- Space Tiles --
if (data.IsSpace)
if (data?.Indices == index)
{
drawHandle.DrawCircle(tilePos + Vector2.One/2, 0.125f, Color.Orange);
}
}
DrawTooltip(handle, mousePos.Position, data.Value);
return;
}
}
}
}
drawHandle.SetTransform(Matrix3.Identity);
private void DrawTooltip(DrawingHandleScreen handle, Vector2 pos, AtmosDebugOverlayData data)
{
var lineHeight = _font.GetLineHeight(1f);
var offset = new Vector2(0, lineHeight);
var moles = data.Moles == null
? "No Air"
: data.Moles.Sum().ToString();
handle.DrawString(_font, pos, $"Moles: {moles}");
pos += offset;
handle.DrawString(_font, pos, $"Temp: {data.Temperature}");
pos += offset;
handle.DrawString(_font, pos, $"Excited: {data.InExcitedGroup?.ToString() ?? "None"}");
pos += offset;
handle.DrawString(_font, pos, $"Space: {data.IsSpace}");
pos += offset;
handle.DrawString(_font, pos, $"Map: {data.MapAtmosphere}");
pos += offset;
handle.DrawString(_font, pos, $"NoGrid: {data.NoGrid}");
}
private void GetGrids(MapId mapId, Box2Rotated box)
{
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, box, ref _grids, (EntityUid uid, MapGridComponent grid,
ref List<(Entity<MapGridComponent>, DebugMessage)> state) =>
{
if (_system.TileData.TryGetValue(uid, out var data))
state.Add(((uid, grid), data));
return true;
});
}
}

View File

@@ -20,7 +20,7 @@ namespace Content.Server.Atmos.EntitySystems
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
/// <summary>
@@ -67,7 +67,7 @@ namespace Content.Server.Atmos.EntitySystems
}
var message = new AtmosDebugOverlayDisableMessage();
RaiseNetworkEvent(message, observer.ConnectedClient);
RaiseNetworkEvent(message, observer.Channel);
return true;
}
@@ -84,12 +84,10 @@ namespace Content.Server.Atmos.EntitySystems
RemoveObserver(observer);
return false;
}
else
{
AddObserver(observer);
return true;
}
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
@@ -99,19 +97,22 @@ namespace Content.Server.Atmos.EntitySystems
}
}
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile, bool mapIsSpace)
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile)
{
var gases = new float[Atmospherics.AdjustedNumberOfGases];
if (tile == null)
return default;
if (tile?.Air == null)
{
return new AtmosDebugOverlayData(Atmospherics.TCMB, gases, AtmosDirection.Invalid, tile?.LastPressureDirection ?? AtmosDirection.Invalid, 0, tile?.BlockedAirflow ?? AtmosDirection.Invalid, tile?.Space ?? mapIsSpace);
}
else
{
NumericsHelpers.Add(gases, tile.Air.Moles);
return new AtmosDebugOverlayData(tile.Air.Temperature, gases, tile.PressureDirection, tile.LastPressureDirection, tile.ExcitedGroup?.GetHashCode() ?? 0, tile.BlockedAirflow, tile.Space);
}
return new AtmosDebugOverlayData(
tile.GridIndices,
tile.Air?.Temperature ?? default,
tile.Air?.Moles,
tile.PressureDirection,
tile.LastPressureDirection,
tile.BlockedAirflow,
tile.ExcitedGroup?.GetHashCode(),
tile.Space,
false,
false);
}
public override void Update(float frameTime)
@@ -135,12 +136,9 @@ namespace Content.Server.Atmos.EntitySystems
if (session.AttachedEntity is not {Valid: true} entity)
continue;
var transform = EntityManager.GetComponent<TransformComponent>(entity);
var mapUid = transform.MapUid;
var mapIsSpace = _atmosphereSystem.IsTileSpace(null, mapUid, Vector2i.Zero);
var worldBounds = Box2.CenteredAround(transform.WorldPosition,
var transform = Transform(entity);
var pos = _transform.GetWorldPosition(transform);
var worldBounds = Box2.CenteredAround(pos,
new Vector2(LocalViewRange, LocalViewRange));
_grids.Clear();
@@ -157,8 +155,8 @@ namespace Content.Server.Atmos.EntitySystems
continue;
var entityTile = _mapSystem.GetTileRef(grid, grid, transform.Coordinates).GridIndices;
var baseTile = new Vector2i(entityTile.X - (LocalViewRange / 2), entityTile.Y - (LocalViewRange / 2));
var debugOverlayContent = new AtmosDebugOverlayData[LocalViewRange * LocalViewRange];
var baseTile = new Vector2i(entityTile.X - LocalViewRange / 2, entityTile.Y - LocalViewRange / 2);
var debugOverlayContent = new AtmosDebugOverlayData?[LocalViewRange * LocalViewRange];
var index = 0;
for (var y = 0; y < LocalViewRange; y++)
@@ -166,11 +164,13 @@ namespace Content.Server.Atmos.EntitySystems
for (var x = 0; x < LocalViewRange; x++)
{
var vector = new Vector2i(baseTile.X + x, baseTile.Y + y);
debugOverlayContent[index++] = ConvertTileToData(gridAtmos.Tiles.TryGetValue(vector, out var tile) ? tile : null, mapIsSpace);
gridAtmos.Tiles.TryGetValue(vector, out var tile);
debugOverlayContent[index++] = tile == null ? null : ConvertTileToData(tile);
}
}
RaiseNetworkEvent(new AtmosDebugOverlayMessage(GetNetEntity(grid), baseTile, debugOverlayContent), session.ConnectedClient);
var msg = new AtmosDebugOverlayMessage(GetNetEntity(grid), baseTile, debugOverlayContent);
RaiseNetworkEvent(msg, session.Channel);
}
}
}

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Map;
using System.Numerics;
using Robust.Shared.Serialization;
namespace Content.Shared.Atmos.EntitySystems
@@ -10,27 +10,17 @@ namespace Content.Shared.Atmos.EntitySystems
protected float AccumulatedFrameTime;
[Serializable, NetSerializable]
public readonly struct AtmosDebugOverlayData
{
public readonly float Temperature;
public readonly float[] Moles;
public readonly AtmosDirection PressureDirection;
public readonly AtmosDirection LastPressureDirection;
public readonly int InExcitedGroup;
public readonly AtmosDirection BlockDirection;
public readonly bool IsSpace;
public AtmosDebugOverlayData(float temperature, float[] moles, AtmosDirection pressureDirection, AtmosDirection lastPressureDirection, int inExcited, AtmosDirection blockDirection, bool isSpace)
{
Temperature = temperature;
Moles = moles;
PressureDirection = pressureDirection;
LastPressureDirection = lastPressureDirection;
InExcitedGroup = inExcited;
BlockDirection = blockDirection;
IsSpace = isSpace;
}
}
public readonly record struct AtmosDebugOverlayData(
Vector2 Indices,
float Temperature,
float[]? Moles,
AtmosDirection PressureDirection,
AtmosDirection LastPressureDirection,
AtmosDirection BlockDirection,
int? InExcitedGroup,
bool IsSpace,
bool MapAtmosphere,
bool NoGrid);
/// <summary>
/// Invalid tiles for the gas overlay.
@@ -43,9 +33,9 @@ namespace Content.Shared.Atmos.EntitySystems
public Vector2i BaseIdx { get; }
// LocalViewRange*LocalViewRange
public AtmosDebugOverlayData[] OverlayData { get; }
public AtmosDebugOverlayData?[] OverlayData { get; }
public AtmosDebugOverlayMessage(NetEntity gridIndices, Vector2i baseIdx, AtmosDebugOverlayData[] overlayData)
public AtmosDebugOverlayMessage(NetEntity gridIndices, Vector2i baseIdx, AtmosDebugOverlayData?[] overlayData)
{
GridId = gridIndices;
BaseIdx = baseIdx;