diff --git a/OpenDreamClient/Rendering/DMISpriteSystem.cs b/OpenDreamClient/Rendering/DMISpriteSystem.cs index 562f27a7be..807e04817c 100644 --- a/OpenDreamClient/Rendering/DMISpriteSystem.cs +++ b/OpenDreamClient/Rendering/DMISpriteSystem.cs @@ -1,6 +1,7 @@ using OpenDreamShared.Rendering; using Robust.Client.GameObjects; using Robust.Client.Graphics; +using Robust.Client.Player; using Robust.Shared.GameStates; using Robust.Shared.Timing; @@ -9,10 +10,11 @@ namespace OpenDreamClient.Rendering; public sealed class DMISpriteSystem : EntitySystem { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; [Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly IOverlayManager _overlayManager = default!; [Dependency] private readonly IClyde _clyde = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly MapSystem _mapSystem = default!; [Dependency] private readonly ClientScreenOverlaySystem _screenOverlaySystem = default!; @@ -20,14 +22,18 @@ public sealed class DMISpriteSystem : EntitySystem { public RenderTargetPool RenderTargetPool = default!; + private EntityQuery _spriteQuery; private DreamViewOverlay _mapOverlay = default!; public override void Initialize() { SubscribeLocalEvent(HandleComponentAdd); SubscribeLocalEvent(HandleComponentState); SubscribeLocalEvent(HandleComponentRemove); + SubscribeLocalEvent(HandleTransformMove); + SubscribeLocalEvent(HandleTileChanged); RenderTargetPool = new(_clyde); + _spriteQuery = _entityManager.GetEntityQuery(); _mapOverlay = new DreamViewOverlay(RenderTargetPool, _transformSystem, _mapSystem, _lookupSystem, _appearanceSystem, _screenOverlaySystem, _clientImagesSystem); _overlayManager.AddOverlay(_mapOverlay); } @@ -50,15 +56,28 @@ private void HandleComponentAdd(EntityUid uid, DMISpriteComponent component, ref component.Icon.SizeChanged += () => OnIconSizeChanged(uid); } - private static void HandleComponentState(EntityUid uid, DMISpriteComponent component, ref ComponentHandleState args) { + private void HandleComponentState(EntityUid uid, DMISpriteComponent component, ref ComponentHandleState args) { SharedDMISpriteComponent.DMISpriteComponentState? state = (SharedDMISpriteComponent.DMISpriteComponentState?)args.Current; if (state == null) return; + _mapOverlay.DirtyTileVisibility(); // Our icon's opacity may have changed component.ScreenLocation = state.ScreenLocation; component.Icon.SetAppearance(state.AppearanceId); } + private void HandleTransformMove(EntityUid uid, TransformComponent component, ref MoveEvent args) { + if (!_spriteQuery.TryGetComponent(uid, out var sprite)) + return; + + if (sprite.Icon.Appearance?.Opacity is true || uid == _playerManager.LocalSession?.AttachedEntity) + _mapOverlay.DirtyTileVisibility(); // A movable with opacity=TRUE, or our eye, has moved + } + + private void HandleTileChanged(ref TileChangedEvent ev) { + _mapOverlay.DirtyTileVisibility(); + } + private static void HandleComponentRemove(EntityUid uid, DMISpriteComponent component, ref ComponentRemove args) { component.Icon.Dispose(); } diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.TileVisibility.cs b/OpenDreamClient/Rendering/DreamViewOverlay.TileVisibility.cs new file mode 100644 index 0000000000..eb10285ee8 --- /dev/null +++ b/OpenDreamClient/Rendering/DreamViewOverlay.TileVisibility.cs @@ -0,0 +1,83 @@ +using OpenDreamShared.Dream; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; + +namespace OpenDreamClient.Rendering; + +internal partial class DreamViewOverlay { + private ViewAlgorithm.Tile?[,]? _tileInfo; + private bool _tileInfoDirty; + + public void DirtyTileVisibility() { + _tileInfoDirty = true; + } + + private ViewAlgorithm.Tile?[,] CalculateTileVisibility(EntityUid gridUid, MapGridComponent grid, TileRef eyeTile, int seeVis) { + using var _ = _prof.Group("visible turfs"); + + var viewRange = _interfaceManager.View; + if (_tileInfo == null || _tileInfo.GetLength(0) != viewRange.Width + 2 || + _tileInfo.GetLength(1) != viewRange.Height + 2) { + // _tileInfo hasn't been created yet or view range has changed, so create a new array. + // Leave a 1 tile buffer on each side + _tileInfo = new ViewAlgorithm.Tile[viewRange.Width + 2, viewRange.Height + 2]; + _tileInfoDirty = true; + } + + if (!_tileInfoDirty) + return _tileInfo; + + var eyeWorldPos = _mapSystem.GridTileToWorld(gridUid, grid, eyeTile.GridIndices); + var tileRefs = _mapSystem.GetTilesEnumerator(gridUid, grid, + Box2.CenteredAround(eyeWorldPos.Position, new Vector2(_tileInfo.GetLength(0), _tileInfo.GetLength(1)))); + + // Gather up all the data the view algorithm needs + while (tileRefs.MoveNext(out var tileRef)) { + var delta = tileRef.GridIndices - eyeTile.GridIndices; + var appearance = _appearanceSystem.GetTurfIcon((uint)tileRef.Tile.TypeId).Appearance; + if (appearance == null) + continue; + + int xIndex = delta.X + viewRange.CenterX; + int yIndex = delta.Y + viewRange.CenterY; + if (xIndex < 0 || yIndex < 0 || xIndex >= _tileInfo.GetLength(0) || yIndex >= _tileInfo.GetLength(1)) + continue; + + var tile = new ViewAlgorithm.Tile { + Opaque = appearance.Opacity, + Luminosity = 0, + DeltaX = delta.X, + DeltaY = delta.Y + }; + + _tileInfo[xIndex, yIndex] = tile; + } + + // Apply entities' opacity + foreach (EntityUid entity in EntitiesInView) { + // TODO use a sprite tree. + if (!_spriteQuery.TryGetComponent(entity, out var sprite)) + continue; + + var transform = _xformQuery.GetComponent(entity); + if (!sprite.IsVisible(transform, seeVis)) + continue; + if (sprite.Icon.Appearance == null) //appearance hasn't loaded yet + continue; + + var worldPos = _transformSystem.GetWorldPosition(transform); + var tilePos = _mapSystem.WorldToTile(gridUid, grid, worldPos) - eyeTile.GridIndices + viewRange.Center; + if (tilePos.X < 0 || tilePos.Y < 0 || tilePos.X >= _tileInfo.GetLength(0) || + tilePos.Y >= _tileInfo.GetLength(1)) + continue; + + var tile = _tileInfo[tilePos.X, tilePos.Y]; + if (tile != null) + tile.Opaque |= sprite.Icon.Appearance.Opacity; + } + + ViewAlgorithm.CalculateVisibility(_tileInfo); + _tileInfoDirty = false; + return _tileInfo; + } +} diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 526276bf1e..ead729a933 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -21,7 +21,7 @@ namespace OpenDreamClient.Rendering; /// /// Overlay for rendering world atoms /// -internal sealed class DreamViewOverlay : Overlay { +internal sealed partial class DreamViewOverlay : Overlay { public static ShaderInstance ColorInstance = default!; public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld; @@ -76,9 +76,6 @@ internal sealed class DreamViewOverlay : Overlay { M22 = -1 }; - // Defined here so it isn't recreated every frame - private ViewAlgorithm.Tile?[,]? _tileInfo; - public DreamViewOverlay(RenderTargetPool renderTargetPool, TransformSystem transformSystem, MapSystem mapSystem, EntityLookupSystem lookupSystem, ClientAppearanceSystem appearanceSystem, ClientScreenOverlaySystem screenOverlaySystem, ClientImagesSystem clientImagesSystem) { IoCManager.InjectDependencies(this); @@ -599,68 +596,6 @@ private void DrawPlanes(DrawingHandleWorld handle, Box2 worldAABB) { } } - private ViewAlgorithm.Tile?[,] CalculateTileVisibility(EntityUid gridUid, MapGridComponent grid, TileRef eyeTile, int seeVis) { - using var _ = _prof.Group("visible turfs"); - - var viewRange = _interfaceManager.View; - if (_tileInfo == null || _tileInfo.GetLength(0) != viewRange.Width + 2 || _tileInfo.GetLength(1) != viewRange.Height + 2) { - // _tileInfo hasn't been created yet or view range has changed, so create a new array. - // Leave a 1 tile buffer on each side - _tileInfo = new ViewAlgorithm.Tile[viewRange.Width + 2, viewRange.Height + 2]; - } - - var eyeWorldPos = _mapSystem.GridTileToWorld(gridUid, grid, eyeTile.GridIndices); - var tileRefs = _mapSystem.GetTilesEnumerator(gridUid, grid, - Box2.CenteredAround(eyeWorldPos.Position, new Vector2(_tileInfo.GetLength(0), _tileInfo.GetLength(1)))); - - // Gather up all the data the view algorithm needs - while (tileRefs.MoveNext(out var tileRef)) { - var delta = tileRef.GridIndices - eyeTile.GridIndices; - var appearance = _appearanceSystem.GetTurfIcon((uint)tileRef.Tile.TypeId).Appearance; - if (appearance == null) - continue; - - int xIndex = delta.X + viewRange.CenterX; - int yIndex = delta.Y + viewRange.CenterY; - if (xIndex < 0 || yIndex < 0 || xIndex >= _tileInfo.GetLength(0) || yIndex >= _tileInfo.GetLength(1)) - continue; - - var tile = new ViewAlgorithm.Tile { - Opaque = appearance.Opacity, - Luminosity = 0, - DeltaX = delta.X, - DeltaY = delta.Y - }; - - _tileInfo[xIndex, yIndex] = tile; - } - - // Apply entities' opacity - foreach (EntityUid entity in EntitiesInView) { - // TODO use a sprite tree. - if (!_spriteQuery.TryGetComponent(entity, out var sprite)) - continue; - - var transform = _xformQuery.GetComponent(entity); - if (!sprite.IsVisible(transform, seeVis)) - continue; - if (sprite.Icon.Appearance == null) //appearance hasn't loaded yet - continue; - - var worldPos = _transformSystem.GetWorldPosition(transform); - var tilePos = _mapSystem.WorldToTile(gridUid, grid, worldPos) - eyeTile.GridIndices + viewRange.Center; - if (tilePos.X < 0 || tilePos.Y < 0 || tilePos.X >= _tileInfo.GetLength(0) || tilePos.Y >= _tileInfo.GetLength(1)) - continue; - - var tile = _tileInfo[tilePos.X, tilePos.Y]; - if (tile != null) - tile.Opaque |= sprite.Icon.Appearance.Opacity; - } - - ViewAlgorithm.CalculateVisibility(_tileInfo); - return _tileInfo; - } - private void CollectVisibleSprites(ViewAlgorithm.Tile?[,] tiles, EntityUid gridUid, MapGridComponent grid, TileRef eyeTile, sbyte seeVis, SightFlags sight, Box2 worldAABB) { _spriteContainer.Clear();