From daa5a344137d02e72bb9443aa20fec4afc0e0857 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Mon, 29 Jun 2020 20:23:08 +0200 Subject: [PATCH] Guess we're doing render trees for lights and occluders aswell. --- .../Light/ClientOccluderComponent.cs | 17 ++ .../Components/Light/PointLightComponent.cs | 20 +- .../EntitySystems/RenderingTreeSystem.cs | 241 ++++++++++++++++++ .../GameObjects/EntitySystems/SpriteSystem.cs | 110 +------- Robust.Client/Graphics/Clyde/Clyde.HLR.cs | 37 +-- .../Graphics/Clyde/Clyde.LightRendering.cs | 35 +-- .../Components/Light/OccluderComponent.cs | 5 + 7 files changed, 315 insertions(+), 150 deletions(-) create mode 100644 Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs diff --git a/Robust.Client/GameObjects/Components/Light/ClientOccluderComponent.cs b/Robust.Client/GameObjects/Components/Light/ClientOccluderComponent.cs index d453958b6c..003a62af3b 100644 --- a/Robust.Client/GameObjects/Components/Light/ClientOccluderComponent.cs +++ b/Robust.Client/GameObjects/Components/Light/ClientOccluderComponent.cs @@ -15,6 +15,8 @@ namespace Robust.Client.GameObjects [ViewVariables] internal OccluderDir Occluding { get; private set; } [ViewVariables] internal uint UpdateGeneration { get; set; } + public bool TreeUpdateQueued { get; set; } + public override bool Enabled { get => base.Enabled; @@ -93,6 +95,11 @@ namespace Robust.Client.GameObjects CheckDir(Direction.West, OccluderDir.West); } + protected override void BoundingBoxChanged() + { + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new OccluderBoundingBoxChangedMessage(this)); + } + [Flags] internal enum OccluderDir : byte { @@ -103,4 +110,14 @@ namespace Robust.Client.GameObjects West = 1 << 3, } } + + internal struct OccluderBoundingBoxChangedMessage + { + public OccluderBoundingBoxChangedMessage(ClientOccluderComponent occluder) + { + Occluder = occluder; + } + + public ClientOccluderComponent Occluder { get; } + } } diff --git a/Robust.Client/GameObjects/Components/Light/PointLightComponent.cs b/Robust.Client/GameObjects/Components/Light/PointLightComponent.cs index faa15b6f6c..b578456ff7 100644 --- a/Robust.Client/GameObjects/Components/Light/PointLightComponent.cs +++ b/Robust.Client/GameObjects/Components/Light/PointLightComponent.cs @@ -4,10 +4,8 @@ using Robust.Client.ResourceManagement; using Robust.Shared.Animations; using Robust.Shared.GameObjects; using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.Interfaces.Network; using Robust.Shared.IoC; using Robust.Shared.Maths; -using Robust.Shared.Players; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; @@ -18,6 +16,8 @@ namespace Robust.Client.GameObjects public override string Name => "PointLight"; public override uint? NetID => NetIDs.POINT_LIGHT; + internal bool TreeUpdateQueued { get; set; } + [ViewVariables(VVAccess.ReadWrite)] public Color Color { @@ -114,7 +114,11 @@ namespace Robust.Client.GameObjects public float Radius { get => _radius; - set => _radius = value; + set + { + _radius = value; + Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedMessage(this)); + } } /// @@ -175,4 +179,14 @@ namespace Robust.Client.GameObjects Color = newState.Color; } } + + public struct PointLightRadiusChangedMessage + { + public PointLightComponent PointLightComponent { get; } + + public PointLightRadiusChangedMessage(PointLightComponent pointLightComponent) + { + PointLightComponent = pointLightComponent; + } + } } diff --git a/Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs b/Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs new file mode 100644 index 0000000000..ca0cd4d05d --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/RenderingTreeSystem.cs @@ -0,0 +1,241 @@ +using System.Collections.Generic; +using JetBrains.Annotations; +using Robust.Client.Physics; +using Robust.Shared.GameObjects.Components.Transform; +using Robust.Shared.GameObjects.EntitySystemMessages; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Physics; + +namespace Robust.Client.GameObjects.EntitySystems +{ + /// + /// Keeps track of s for various rendering-related components. + /// + [UsedImplicitly] + public sealed class RenderingTreeSystem : EntitySystem + { + [Dependency] private readonly IMapManagerInternal _mapManager = default!; + + private readonly Dictionary _mapTrees = new Dictionary(); + + private readonly List _spriteQueue = new List(); + private readonly List _occluderQueue = new List(); + private readonly List _lightQueue = new List(); + + internal DynamicTree GetSpriteTreeForMap(MapId map) + { + return _mapTrees[map].SpriteTree; + } + + internal DynamicTree GetOccluderTreeForMap(MapId map) + { + return _mapTrees[map].OccluderTree; + } + + internal DynamicTree GetLightTreeForMap(MapId map) + { + return _mapTrees[map].LightTree; + } + + public override void Initialize() + { + base.Initialize(); + + UpdatesBefore.Add(typeof(SpriteSystem)); + UpdatesAfter.Add(typeof(TransformSystem)); + UpdatesAfter.Add(typeof(PhysicsSystem)); + + + _mapManager.MapCreated += MapManagerOnMapCreated; + _mapManager.MapDestroyed += MapManagerOnMapDestroyed; + + SubscribeLocalEvent(EntMapIdChanged); + SubscribeLocalEvent(EntMoved); + SubscribeLocalEvent(EntParentChanged); + SubscribeLocalEvent(OccluderBoundingBoxChanged); + SubscribeLocalEvent(PointLightRadiusChanged); + } + + private void PointLightRadiusChanged(PointLightRadiusChangedMessage ev) + { + QueueUpdateLight(ev.PointLightComponent); + } + + private void OccluderBoundingBoxChanged(OccluderBoundingBoxChangedMessage ev) + { + QueueUpdateOccluder(ev.Occluder); + } + + private void EntParentChanged(EntParentChangedMessage ev) + { + UpdateEntity(ev.Entity); + } + + private void EntMoved(MoveEvent ev) + { + UpdateEntity(ev.Sender); + } + + private void UpdateEntity(IEntity entity) + { + if (entity.TryGetComponent(out SpriteComponent spriteComponent)) + { + if (!spriteComponent.TreeUpdateQueued) + { + spriteComponent.TreeUpdateQueued = true; + + _spriteQueue.Add(spriteComponent); + } + } + + if (entity.TryGetComponent(out ClientOccluderComponent occluder)) + { + QueueUpdateOccluder(occluder); + } + + if (entity.TryGetComponent(out PointLightComponent light)) + { + QueueUpdateLight(light); + } + + foreach (var child in entity.Transform.Children) + { + UpdateEntity(child.Owner); + } + } + + private void QueueUpdateLight(PointLightComponent light) + { + if (!light.TreeUpdateQueued) + { + light.TreeUpdateQueued = true; + + _lightQueue.Add(light); + } + } + + private void QueueUpdateOccluder(ClientOccluderComponent occluder) + { + if (!occluder.TreeUpdateQueued) + { + occluder.TreeUpdateQueued = true; + + _occluderQueue.Add(occluder); + } + } + + private void EntMapIdChanged(EntMapIdChangedMessage ev) + { + var oldMapTrees = _mapTrees[ev.OldMapId]; + var newMapTrees = _mapTrees[ev.Entity.Transform.MapID]; + + if (ev.Entity.TryGetComponent(out SpriteComponent sprite)) + { + oldMapTrees.SpriteTree.Remove(sprite); + + newMapTrees.SpriteTree.AddOrUpdate(sprite); + } + + if (ev.Entity.TryGetComponent(out ClientOccluderComponent occluder)) + { + oldMapTrees.OccluderTree.Remove(occluder); + + newMapTrees.OccluderTree.AddOrUpdate(occluder); + } + + if (ev.Entity.TryGetComponent(out PointLightComponent light)) + { + oldMapTrees.LightTree.Remove(light); + + newMapTrees.LightTree.AddOrUpdate(light); + } + } + + private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e) + { + _mapTrees.Remove(e.Map); + } + + private void MapManagerOnMapCreated(object? sender, MapEventArgs e) + { + _mapTrees.Add(e.Map, new MapTrees()); + } + + public override void FrameUpdate(float frameTime) + { + foreach (var queuedUpdateSprite in _spriteQueue) + { + var transform = queuedUpdateSprite.Owner.Transform; + var map = transform.MapID; + var updateMapTree = _mapTrees[map].SpriteTree; + + updateMapTree.AddOrUpdate(queuedUpdateSprite); + queuedUpdateSprite.TreeUpdateQueued = false; + } + + foreach (var queuedUpdateLight in _lightQueue) + { + var transform = queuedUpdateLight.Owner.Transform; + var map = transform.MapID; + var updateMapTree = _mapTrees[map].LightTree; + + updateMapTree.AddOrUpdate(queuedUpdateLight); + queuedUpdateLight.TreeUpdateQueued = false; + } + + foreach (var queuedUpdateOccluder in _occluderQueue) + { + var transform = queuedUpdateOccluder.Owner.Transform; + var map = transform.MapID; + var updateMapTree = _mapTrees[map].OccluderTree; + + updateMapTree.AddOrUpdate(queuedUpdateOccluder); + queuedUpdateOccluder.TreeUpdateQueued = false; + } + + _spriteQueue.Clear(); + _lightQueue.Clear(); + _occluderQueue.Clear(); + } + + private sealed class MapTrees + { + public readonly DynamicTree SpriteTree; + public readonly DynamicTree LightTree; + public readonly DynamicTree OccluderTree; + + public MapTrees() + { + SpriteTree = new DynamicTree(SpriteAabbFunc); + LightTree = new DynamicTree(LightAabbFunc); + OccluderTree = new DynamicTree(OccluderAabbFunc); + } + + private static Box2 SpriteAabbFunc(in SpriteComponent value) + { + var worldPos = value.Owner.Transform.WorldPosition; + + return new Box2(worldPos, worldPos); + } + + private static Box2 LightAabbFunc(in PointLightComponent value) + { + var worldPos = value.Owner.Transform.WorldPosition; + + var boxSize = value.Radius * 2; + return Box2.CenteredAround(worldPos, (boxSize, boxSize)); + } + + private static Box2 OccluderAabbFunc(in ClientOccluderComponent value) + { + var worldPos = value.Owner.Transform.WorldPosition; + + return value.BoundingBox.Translated(worldPos); + } + } + } +} diff --git a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs index 87e0c81843..995b6cbbb3 100644 --- a/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/SpriteSystem.cs @@ -1,15 +1,7 @@ -using System.Collections.Generic; -using JetBrains.Annotations; +using JetBrains.Annotations; using Robust.Client.Interfaces.Graphics.ClientEye; -using Robust.Client.Physics; -using Robust.Shared.GameObjects.Components.Transform; -using Robust.Shared.GameObjects.EntitySystemMessages; using Robust.Shared.GameObjects.Systems; -using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Map; -using Robust.Shared.Maths; -using Robust.Shared.Physics; namespace Robust.Client.GameObjects.EntitySystems { @@ -20,113 +12,17 @@ namespace Robust.Client.GameObjects.EntitySystems public class SpriteSystem : EntitySystem { [Dependency] private readonly IEyeManager _eyeManager = default!; - [Dependency] private readonly IMapManagerInternal _mapManager = default!; - - private readonly Dictionary> _mapTrees = - new Dictionary>(); - - // Queue of sprites to - private readonly List _updateTreeQueue = new List(); - - public DynamicTree GetTreeForMap(MapId map) - { - return _mapTrees[map]; - } - - public override void Initialize() - { - base.Initialize(); - - _mapManager.MapCreated += MapManagerOnMapCreated; - _mapManager.MapDestroyed += MapManagerOnMapDestroyed; - - SubscribeLocalEvent(EntMapIdChanged); - SubscribeLocalEvent(EntMoved); - SubscribeLocalEvent(EntParentChanged); - - UpdatesAfter.Add(typeof(TransformSystem)); - UpdatesAfter.Add(typeof(PhysicsSystem)); - } - - private void EntParentChanged(EntParentChangedMessage ev) - { - UpdateEntity(ev.Entity); - } - - private void EntMoved(MoveEvent ev) - { - UpdateEntity(ev.Sender); - } - - private void UpdateEntity(IEntity entity) - { - if (entity.TryGetComponent(out SpriteComponent spriteComponent)) - { - if (!spriteComponent.TreeUpdateQueued) - { - spriteComponent.TreeUpdateQueued = true; - - _updateTreeQueue.Add(spriteComponent); - } - } - - foreach (var child in entity.Transform.Children) - { - UpdateEntity(child.Owner); - } - } - - private void EntMapIdChanged(EntMapIdChangedMessage ev) - { - if (!ev.Entity.TryGetComponent(out SpriteComponent sprite)) - { - return; - } - - var mapTree = _mapTrees[ev.OldMapId]; - mapTree.Remove(sprite); - - mapTree = _mapTrees[ev.Entity.Transform.MapID]; - mapTree.AddOrUpdate(sprite); - } - - private void MapManagerOnMapDestroyed(object? sender, MapEventArgs e) - { - _mapTrees.Remove(e.Map); - } - - private void MapManagerOnMapCreated(object? sender, MapEventArgs e) - { - _mapTrees.Add(e.Map, new DynamicTree(TreeExtractAabbFunc)); - } - - private static Box2 TreeExtractAabbFunc(in SpriteComponent value) - { - var worldPos = value.Owner.Transform.WorldPosition; - - return new Box2(worldPos, worldPos); - } /// public override void FrameUpdate(float frameTime) { - foreach (var queuedUpdateSprite in _updateTreeQueue) - { - var transform = queuedUpdateSprite.Owner.Transform; - var map = transform.MapID; - var updateMapTree = _mapTrees[map]; - - updateMapTree.AddOrUpdate(queuedUpdateSprite); - queuedUpdateSprite.TreeUpdateQueued = false; - } - - _updateTreeQueue.Clear(); + var renderTreeSystem = EntitySystemManager.GetEntitySystem(); // So we could calculate the correct size of the entities based on the contents of their sprite... // Or we can just assume that no entity is larger than 10x10 and get a stupid easy check. var pvsBounds = _eyeManager.GetWorldViewport().Enlarged(5); - var mapTree = _mapTrees[_eyeManager.CurrentMap]; + var mapTree = renderTreeSystem.GetSpriteTreeForMap(_eyeManager.CurrentMap); var pvsEntities = mapTree.Query(pvsBounds, true); diff --git a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs index 933696ea4e..159f8f5fc8 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.HLR.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.HLR.cs @@ -84,26 +84,29 @@ namespace Robust.Client.Graphics.Clyde var worldBounds = Box2.CenteredAround(eye.Position.Position, _framebufferSize / (float) EyeManager.PixelsPerMeter * eye.Zoom); - using (DebugGroup("Lights")) + if (_eyeManager.CurrentMap != MapId.Nullspace) { - DrawLightsAndFov(worldBounds, eye); - } + using (DebugGroup("Lights")) + { + DrawLightsAndFov(worldBounds, eye); + } - using (DebugGroup("Grids")) - { - _drawGrids(worldBounds); - } + using (DebugGroup("Grids")) + { + _drawGrids(worldBounds); + } - using (DebugGroup("Entities")) - { - DrawEntities(worldBounds); - } + using (DebugGroup("Entities")) + { + DrawEntities(worldBounds); + } - RenderOverlays(OverlaySpace.WorldSpace); + RenderOverlays(OverlaySpace.WorldSpace); - if (_lightManager.Enabled && eye.DrawFov) - { - ApplyFovToBuffer(eye); + if (_lightManager.Enabled && eye.DrawFov) + { + ApplyFovToBuffer(eye); + } } _lightingReady = false; @@ -217,9 +220,9 @@ namespace Robust.Client.Graphics.Clyde [MethodImpl(MethodImplOptions.NoInlining)] private void ProcessSpriteEntities(MapId map, Box2 worldBounds, RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list) { - var spriteSystem = _entitySystemManager.GetEntitySystem(); + var spriteSystem = _entitySystemManager.GetEntitySystem(); - var tree = spriteSystem.GetTreeForMap(map); + var tree = spriteSystem.GetSpriteTreeForMap(map); var sprites = tree.Query(worldBounds, true); diff --git a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs index c174c5e5ee..2dbed3cd12 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.LightRendering.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Collections.Generic; using OpenToolkit.Graphics.OpenGL4; using Robust.Client.GameObjects; +using Robust.Client.GameObjects.EntitySystems; using Robust.Client.Graphics.ClientEye; using Robust.Client.Interfaces.Graphics; using Robust.Client.Interfaces.Graphics.ClientEye; @@ -464,31 +465,27 @@ namespace Robust.Client.Graphics.Clyde // (if the occluder is in the current lights at all, it's still not between the light and the world bounds). var expandedBounds = worldBounds; - foreach (var component in _componentManager.GetAllComponents()) + var renderingTreeSystem = _entitySystemManager.GetEntitySystem(); + var lightTree = renderingTreeSystem.GetLightTreeForMap(map); + + foreach (var component in lightTree.Query(worldBounds)) { var transform = component.Owner.Transform; - if (!component.Enabled || transform.MapID != map) + if (!component.Enabled) { continue; } var lightPos = transform.WorldMatrix.Transform(component.Offset); - var lightBounds = Box2.CenteredAround(lightPos, Vector2.One * component.Radius * 2); - - if (!lightBounds.Intersects(worldBounds)) - { - continue; - } - lights.Add((component, lightPos)); expandedBounds = expandedBounds.ExtendToContain(lightPos); if (lights.Count == MaxLightsPerScene) { - // TODO: Allow more than 64 lights. + // TODO: Allow more than MaxLightsPerScene lights. break; } } @@ -634,10 +631,6 @@ namespace Robust.Client.Graphics.Clyde using var _ = DebugGroup(nameof(UpdateOcclusionGeometry)); - // TODO: More accurate bounds check. - // Yeah just enlarge it a wee bit to accomodate for large occluders maybe. Oh well. - expandedBounds.Enlarged(2); - var arrayBuffer = ArrayPool.Shared.Rent(maxOccluders * 8); var indexBuffer = ArrayPool.Shared.Rent(maxOccluders * 20); @@ -646,15 +639,18 @@ namespace Robust.Client.Graphics.Clyde try { + var renderingTreeSystem = _entitySystemManager.GetEntitySystem(); + var occluderTree = renderingTreeSystem.GetOccluderTreeForMap(map); + var ai = 0; var ami = 0; var ii = 0; var imi = 0; - foreach (var occluder in _componentManager.GetAllComponents()) + foreach (var occluder in occluderTree.Query(expandedBounds)) { var transform = occluder.Owner.Transform; - if (!occluder.Enabled || transform.MapID != map) + if (!occluder.Enabled) { continue; } @@ -662,13 +658,6 @@ namespace Robust.Client.Graphics.Clyde var worldTransform = transform.WorldMatrix; var box = occluder.BoundingBox; - var centerPos = (worldTransform.R0C2, worldTransform.R1C2); - - if (!expandedBounds.Contains(centerPos)) - { - continue; - } - // So uh, angle 0 = east... Apparently... // We account for that here so I don't go insane. var (tlX, tlY) = worldTransform.Transform(box.BottomLeft); diff --git a/Robust.Shared/GameObjects/Components/Light/OccluderComponent.cs b/Robust.Shared/GameObjects/Components/Light/OccluderComponent.cs index 7850005464..c7b08769f0 100644 --- a/Robust.Shared/GameObjects/Components/Light/OccluderComponent.cs +++ b/Robust.Shared/GameObjects/Components/Light/OccluderComponent.cs @@ -21,9 +21,14 @@ namespace Robust.Shared.GameObjects { _boundingBox = value; Dirty(); + BoundingBoxChanged(); } } + protected virtual void BoundingBoxChanged() + { + } + [ViewVariables(VVAccess.ReadWrite)] public virtual bool Enabled {