From 6cf5fdc5d654fb3460ea5f0e0d22c238d38ec8be Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Fri, 2 Apr 2021 20:25:16 +1100 Subject: [PATCH] Grid-trees for rendering (#1666) --- .../EntitySystems/RenderingTreeSystem.cs | 139 ++++++++++++++---- .../GameObjects/EntitySystems/SpriteSystem.cs | 32 +++- .../Graphics/Clyde/Clyde.GridRendering.cs | 4 +- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 49 +++--- .../Graphics/Clyde/Clyde.LightRendering.cs | 65 ++++---- .../EntitySystems/PhysicsSystem.cs | 2 +- .../TileLookup/GridTileLookupSystem.cs | 4 +- .../GameObjects/Systems/OccluderSystem.cs | 6 +- Robust.Shared/Map/GridEventHandler.cs | 9 +- Robust.Shared/Map/MapManager.cs | 5 +- .../BroadPhase/SharedBroadPhaseSystem.cs | 6 +- 11 files changed, 229 insertions(+), 92 deletions(-) diff --git a/Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs b/Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs index a5cdbf713b..dfd858576e 100644 --- a/Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs @@ -19,19 +19,19 @@ namespace Robust.Client.GameObjects [Dependency] private readonly IMapManagerInternal _mapManager = default!; - private readonly Dictionary _mapTrees = new(); + private readonly Dictionary> _gridTrees = new(); private readonly List _spriteQueue = new(); private readonly List _lightQueue = new(); - internal DynamicTree GetSpriteTreeForMap(MapId map) + internal DynamicTree GetSpriteTreeForMap(MapId map, GridId grid) { - return _mapTrees[map].SpriteTree; + return _gridTrees[map][grid].SpriteTree; } - internal DynamicTree GetLightTreeForMap(MapId map) + internal DynamicTree GetLightTreeForMap(MapId map, GridId grid) { - return _mapTrees[map].LightTree; + return _gridTrees[map][grid].LightTree; } public override void Initialize() @@ -44,6 +44,8 @@ namespace Robust.Client.GameObjects _mapManager.MapCreated += MapManagerOnMapCreated; _mapManager.MapDestroyed += MapManagerOnMapDestroyed; + _mapManager.OnGridCreated += MapManagerOnGridCreated; + _mapManager.OnGridRemoved += MapManagerOnGridRemoved; SubscribeLocalEvent(EntMapIdChanged); SubscribeLocalEvent(EntMoved); @@ -53,18 +55,40 @@ namespace Robust.Client.GameObjects SubscribeLocalEvent(RemoveLight); } + public override void Shutdown() + { + base.Shutdown(); + _mapManager.MapCreated -= MapManagerOnMapCreated; + _mapManager.MapDestroyed -= MapManagerOnMapDestroyed; + _mapManager.OnGridCreated -= MapManagerOnGridCreated; + _mapManager.OnGridRemoved -= MapManagerOnGridRemoved; + + UnsubscribeLocalEvent(); + UnsubscribeLocalEvent(); + UnsubscribeLocalEvent(); + UnsubscribeLocalEvent(); + UnsubscribeLocalEvent(); + UnsubscribeLocalEvent(); + } + // For these next 2 methods (the Remove* ones): // If the Transform is removed BEFORE the Sprite/Light, // then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace). // Otherwise these will still have their past MapId and that's all we need.. private void RemoveLight(RenderTreeRemoveLightMessage ev) { - _mapTrees[ev.Map].LightTree.Remove(ev.Light); + foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.LightAabbFunc(ev.Light), true)) + { + _gridTrees[ev.Map][gridId].LightTree.Remove(ev.Light); + } } private void RemoveSprite(RenderTreeRemoveSpriteMessage ev) { - _mapTrees[ev.Map].SpriteTree.Remove(ev.Sprite); + foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Map, MapTrees.SpriteAabbFunc(ev.Sprite), true)) + { + _gridTrees[ev.Map][gridId].SpriteTree.Remove(ev.Sprite); + } } private void PointLightRadiusChanged(PointLightRadiusChangedMessage ev) @@ -119,27 +143,69 @@ namespace Robust.Client.GameObjects { // Nullspace is a valid map ID for stuff to have but we also aren't gonna bother indexing it. // So that's why there's a GetValueOrDefault. - var oldMapTrees = _mapTrees.GetValueOrDefault(ev.OldMapId); - var newMapTrees = _mapTrees.GetValueOrDefault(ev.Entity.Transform.MapID); + var oldMapTrees = _gridTrees.GetValueOrDefault(ev.OldMapId); + // TODO: MMMM probably a better way to do this. if (ev.Entity.TryGetComponent(out SpriteComponent? sprite)) { - oldMapTrees?.SpriteTree.Remove(sprite); + if (oldMapTrees != null) + { + foreach (var (_, gridTree) in oldMapTrees) + { + gridTree.SpriteTree.Remove(sprite); + } + } - newMapTrees?.SpriteTree.AddOrUpdate(sprite); + var bounds = MapTrees.SpriteAabbFunc(sprite); + + foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true)) + { + Box2 gridBounds; + + if (gridId == GridId.Invalid) + { + gridBounds = bounds; + } + else + { + gridBounds = bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition); + } + _gridTrees[ev.Entity.Transform.MapID][gridId].SpriteTree.AddOrUpdate(sprite, gridBounds); + } } if (ev.Entity.TryGetComponent(out PointLightComponent? light)) { - oldMapTrees?.LightTree.Remove(light); + if (oldMapTrees != null) + { + foreach (var (_, gridTree) in oldMapTrees) + { + gridTree.LightTree.Remove(light); + } + } - newMapTrees?.LightTree.AddOrUpdate(light); + var bounds = MapTrees.LightAabbFunc(light); + + foreach (var gridId in _mapManager.FindGridIdsIntersecting(ev.Entity.Transform.MapID, bounds, true)) + { + Box2 gridBounds; + + if (gridId == GridId.Invalid) + { + gridBounds = bounds; + } + else + { + gridBounds = bounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition); + } + _gridTrees[ev.Entity.Transform.MapID][gridId].LightTree.AddOrUpdate(light, gridBounds); + } } } private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e) { - _mapTrees.Remove(e.Map); + _gridTrees.Remove(e.Map); } private void MapManagerOnMapCreated(object? sender, MapEventArgs e) @@ -149,36 +215,59 @@ namespace Robust.Client.GameObjects return; } - _mapTrees.Add(e.Map, new MapTrees()); + _gridTrees.Add(e.Map, new Dictionary + { + {GridId.Invalid, new MapTrees()} + }); + } + + private void MapManagerOnGridCreated(MapId mapId, GridId gridId) + { + _gridTrees[mapId].Add(gridId, new MapTrees()); + } + + private void MapManagerOnGridRemoved(MapId mapId, GridId gridId) + { + _gridTrees[mapId].Remove(gridId); } public override void FrameUpdate(float frameTime) { foreach (var queuedUpdateSprite in _spriteQueue) { - var transform = queuedUpdateSprite.Owner.Transform; - var map = transform.MapID; + var map = queuedUpdateSprite.Owner.Transform.MapID; if (map == MapId.Nullspace) { continue; } - var updateMapTree = _mapTrees[map].SpriteTree; - updateMapTree.AddOrUpdate(queuedUpdateSprite); + var mapTree = _gridTrees[map]; + + foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, + MapTrees.SpriteAabbFunc(queuedUpdateSprite), true)) + { + mapTree[gridId].SpriteTree.AddOrUpdate(queuedUpdateSprite); + } + queuedUpdateSprite.TreeUpdateQueued = false; } foreach (var queuedUpdateLight in _lightQueue) { - var transform = queuedUpdateLight.Owner.Transform; - var map = transform.MapID; + var map = queuedUpdateLight.Owner.Transform.MapID; if (map == MapId.Nullspace) { continue; } - var updateMapTree = _mapTrees[map].LightTree; - updateMapTree.AddOrUpdate(queuedUpdateLight); + var mapTree = _gridTrees[map]; + + foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, + MapTrees.LightAabbFunc(queuedUpdateLight), true)) + { + mapTree[gridId].LightTree.AddOrUpdate(queuedUpdateLight); + } + queuedUpdateLight.TreeUpdateQueued = false; } @@ -197,14 +286,14 @@ namespace Robust.Client.GameObjects LightTree = new DynamicTree(LightAabbFunc); } - private static Box2 SpriteAabbFunc(in SpriteComponent value) + internal static Box2 SpriteAabbFunc(in SpriteComponent value) { var worldPos = value.Owner.Transform.WorldPosition; return new Box2(worldPos, worldPos); } - private static Box2 LightAabbFunc(in PointLightComponent value) + internal static Box2 LightAabbFunc(in PointLightComponent value) { var worldPos = value.Owner.Transform.WorldPosition; diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index f0abcdfab4..a3c1708e78 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -3,6 +3,7 @@ using Robust.Client.Graphics; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; +using Robust.Shared.Maths; namespace Robust.Client.GameObjects { @@ -13,6 +14,7 @@ namespace Robust.Client.GameObjects public class SpriteSystem : EntitySystem { [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; /// public override void FrameUpdate(float frameTime) @@ -29,18 +31,32 @@ namespace Robust.Client.GameObjects return; } - var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap); - - mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) => + foreach (var gridId in _mapManager.FindGridIdsIntersecting(currentMap, pvsBounds, true)) { - if (value.IsInert) + Box2 gridBounds; + + if (gridId == GridId.Invalid) { - return true; + gridBounds = pvsBounds; + } + else + { + gridBounds = pvsBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition); } - value.FrameUpdate(state); - return true; - }, pvsBounds, approx: true); + var mapTree = renderTreeSystem.GetSpriteTreeForMap(currentMap, gridId); + + mapTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) => + { + if (value.IsInert) + { + return true; + } + + value.FrameUpdate(state); + return true; + }, gridBounds, approx: true); + } } } } diff --git a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs index 680a2a8f46..c40a352fdf 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.GridRendering.cs @@ -208,12 +208,12 @@ namespace Robust.Client.Graphics.Clyde _setChunkDirty(grid, chunk); } - private void _updateOnGridCreated(GridId gridId) + private void _updateOnGridCreated(MapId mapId, GridId gridId) { _mapChunkData.Add(gridId, new Dictionary()); } - private void _updateOnGridRemoved(GridId gridId) + private void _updateOnGridRemoved(MapId mapId, GridId gridId) { var data = _mapChunkData[gridId]; foreach (var chunkDatum in data.Values) diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index 5a1aab897e..334801e985 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -318,29 +318,44 @@ namespace Robust.Client.Graphics.Clyde { var spriteSystem = _entitySystemManager.GetEntitySystem(); - var tree = spriteSystem.GetSpriteTreeForMap(map); - - tree.QueryAabb(ref list, (( - ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state, - in SpriteComponent value) => + foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true)) { - if (value.ContainerOccluded || !value.Visible) + Box2 gridBounds; + + if (gridId == GridId.Invalid) { - return true; + gridBounds = worldBounds; + } + else + { + gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition); } - var entity = value.Owner; - var transform = entity.Transform; + var tree = spriteSystem.GetSpriteTreeForMap(map, gridId); - ref var entry = ref state.AllocAdd(); - entry.sprite = value; - entry.worldRot = transform.WorldRotation; - entry.matrix = transform.WorldMatrix; - var worldPos = entry.matrix.Transform(transform.LocalPosition); - entry.yWorldPos = worldPos.Y; - return true; + tree.QueryAabb(ref list, (( + ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state, + in SpriteComponent value) => + { + // TODO: Probably value in storing this as its own DynamicTree + if (value.ContainerOccluded || !value.Visible) + { + return true; + } - }), worldBounds, approx: true); + var entity = value.Owner; + var transform = entity.Transform; + + ref var entry = ref state.AllocAdd(); + entry.sprite = value; + entry.worldRot = transform.WorldRotation; + entry.matrix = transform.WorldMatrix; + var worldPos = entry.matrix.Transform(transform.LocalPosition); + entry.yWorldPos = worldPos.Y; + return true; + + }), gridBounds, approx: true); + } } private void DrawSplash(IRenderHandle handle) diff --git a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs index 7138e9c93d..14d68aa54c 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs @@ -497,40 +497,55 @@ namespace Robust.Client.Graphics.Clyde GetLightsToRender(MapId map, in Box2 worldBounds) { var renderingTreeSystem = _entitySystemManager.GetEntitySystem(); - var lightTree = renderingTreeSystem.GetLightTreeForMap(map); - var state = (this, worldBounds, count: 0); - lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) => + foreach (var gridId in _mapManager.FindGridIdsIntersecting(map, worldBounds, true)) { - var transform = light.Owner.Transform; + Box2 gridBounds; - if (state.count >= LightsToRenderListSize) + if (gridId == GridId.Invalid) { - // There are too many lights to fit in the static memory. - return false; + gridBounds = worldBounds; + } + else + { + gridBounds = worldBounds.Translated(-_mapManager.GetGrid(gridId).WorldPosition); } - if (!light.Enabled || light.ContainerOccluded) + var lightTree = renderingTreeSystem.GetLightTreeForMap(map, gridId); + + lightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldBounds, int count) state, in PointLightComponent light) => { + var transform = light.Owner.Transform; + + if (state.count >= LightsToRenderListSize) + { + // There are too many lights to fit in the static memory. + return false; + } + + // TODO: Don't insert into trees for these, same as sprites. + if (!light.Enabled || light.ContainerOccluded) + { + return true; + } + + var lightPos = transform.WorldMatrix.Transform(light.Offset); + + var circle = new Circle(lightPos, light.Radius); + + // If the light doesn't touch anywhere the camera can see, it doesn't matter. + if (!circle.Intersects(state.worldBounds)) + { + return true; + } + + float distanceSquared = (state.worldBounds.Center - lightPos).LengthSquared; + state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared); + return true; - } - - var lightPos = transform.WorldMatrix.Transform(light.Offset); - - var circle = new Circle(lightPos, light.Radius); - - // If the light doesn't touch anywhere the camera can see, it doesn't matter. - if (!circle.Intersects(state.worldBounds)) - { - return true; - } - - float distanceSquared = (state.worldBounds.Center - lightPos).LengthSquared; - state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared); - - return true; - }, worldBounds); + }, gridBounds); + } if (state.count > _maxLightsPerScene) { diff --git a/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs b/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs index bb5992a2de..33c368cead 100644 --- a/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/PhysicsSystem.cs @@ -34,7 +34,7 @@ namespace Robust.Server.GameObjects _mapManager.OnGridCreated -= HandleGridCreated; } - private void HandleGridCreated(GridId gridId) + private void HandleGridCreated(MapId mapId, GridId gridId) { if (!EntityManager.TryGetEntity(_mapManager.GetGrid(gridId).GridEntityId, out var gridEntity)) return; var grid = _mapManager.GetGrid(gridId); diff --git a/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs b/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs index 9c7506e8b7..8f024ea27b 100644 --- a/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs @@ -239,12 +239,12 @@ namespace Robust.Server.GameObjects GetOrCreateNode(eventArgs.NewTile.GridIndex, eventArgs.NewTile.GridIndices); } - private void HandleGridCreated(GridId gridId) + private void HandleGridCreated(MapId mapId, GridId gridId) { _graph[gridId] = new Dictionary(); } - private void HandleGridRemoval(GridId gridId) + private void HandleGridRemoval(MapId mapId, GridId gridId) { var toRemove = new List(); diff --git a/Robust.Shared/GameObjects/Systems/OccluderSystem.cs b/Robust.Shared/GameObjects/Systems/OccluderSystem.cs index 1fffe2f0ea..dca7e5d115 100644 --- a/Robust.Shared/GameObjects/Systems/OccluderSystem.cs +++ b/Robust.Shared/GameObjects/Systems/OccluderSystem.cs @@ -193,7 +193,7 @@ namespace Robust.Shared.GameObjects _gridTrees[e.Map] = new Dictionary>(); } - private void OnGridRemoved(GridId gridId) + private void OnGridRemoved(MapId mapId, GridId gridId) { foreach (var (_, gridIds) in _gridTrees) { @@ -202,10 +202,8 @@ namespace Robust.Shared.GameObjects } } - private void OnGridCreated(GridId gridId) + private void OnGridCreated(MapId mapId, GridId gridId) { - var mapId = _mapManager.GetGrid(gridId).ParentMapId; - if (!_gridTrees.TryGetValue(mapId, out var gridTree)) return; diff --git a/Robust.Shared/Map/GridEventHandler.cs b/Robust.Shared/Map/GridEventHandler.cs index 3983d62d2a..dd222500b1 100644 --- a/Robust.Shared/Map/GridEventHandler.cs +++ b/Robust.Shared/Map/GridEventHandler.cs @@ -1,4 +1,9 @@ namespace Robust.Shared.Map { - public delegate void GridEventHandler(GridId gridId); -} \ No newline at end of file + /// + /// Invoked when a grid is altered. + /// + /// Passed to the delegate given it may no longer be retrievable. + /// The index of the grid being changed. + public delegate void GridEventHandler(MapId mapId, GridId gridId); +} diff --git a/Robust.Shared/Map/MapManager.cs b/Robust.Shared/Map/MapManager.cs index 9c5d50af34..5554f9d457 100644 --- a/Robust.Shared/Map/MapManager.cs +++ b/Robust.Shared/Map/MapManager.cs @@ -486,7 +486,7 @@ namespace Robust.Shared.Map Logger.DebugS("map", $"Skipping entity binding for gridId {actualID}"); } - OnGridCreated?.Invoke(actualID); + OnGridCreated?.Invoke(currentMapID, actualID); return grid; } @@ -587,6 +587,7 @@ namespace Robust.Shared.Map return; var grid = _grids[gridID]; + var mapId = grid.ParentMapId; if (_entityManager.TryGetEntity(grid.GridEntityId, out var gridEnt) && gridEnt.LifeStage <= EntityLifeStage.Initialized) gridEnt.Delete(); @@ -594,7 +595,7 @@ namespace Robust.Shared.Map grid.Dispose(); _grids.Remove(grid.Index); - OnGridRemoved?.Invoke(gridID); + OnGridRemoved?.Invoke(mapId, gridID); if (_netManager.IsServer) _gridDeletionHistory.Add((_gameTiming.CurTick, gridID)); diff --git a/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs b/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs index 78f379308a..42c3496630 100644 --- a/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs +++ b/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs @@ -346,10 +346,8 @@ namespace Robust.Shared.Physics.Broadphase } } - private void HandleGridCreated(GridId gridId) + private void HandleGridCreated(MapId mapId, GridId gridId) { - var mapId = _mapManager.GetGrid(gridId).ParentMapId; - if (!_graph.TryGetValue(mapId, out var grids)) { grids = new Dictionary(); @@ -371,7 +369,7 @@ namespace Robust.Shared.Physics.Broadphase } - private void HandleGridRemoval(GridId gridId) + private void HandleGridRemoval(MapId mapId, GridId gridId) { foreach (var (_, grids) in _graph) {