diff --git a/Robust.Client/Debugging/DebugDrawing.cs b/Robust.Client/Debugging/DebugDrawing.cs index 6f8c6f5bee..cc338894a9 100644 --- a/Robust.Client/Debugging/DebugDrawing.cs +++ b/Robust.Client/Debugging/DebugDrawing.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Robust.Client.Graphics; using Robust.Client.Input; @@ -171,7 +171,7 @@ namespace Robust.Client.Debugging // all entities have a TransformComponent var transform = physBody.Owner.Transform; - var worldBox = physBody.GetWorldAABB(_mapManager); + var worldBox = physBody.GetWorldAABB(); if (worldBox.IsEmpty()) continue; foreach (var fixture in physBody.Fixtures) diff --git a/Robust.Client/GameObjects/EntitySystems/DebugGridTileLookupSystem.cs b/Robust.Client/GameObjects/EntitySystems/DebugGridTileLookupSystem.cs new file mode 100644 index 0000000000..f6c18d0ef9 --- /dev/null +++ b/Robust.Client/GameObjects/EntitySystems/DebugGridTileLookupSystem.cs @@ -0,0 +1,134 @@ +#if DEBUG +using System; +using System.Text; +using Robust.Client.Graphics; +using Robust.Client.Input; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Console; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using Robust.Shared.Maths; + +namespace Robust.Client.GameObjects +{ + internal sealed class DebugGridTileLookupSystem : EntitySystem + { + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IInputManager _inputManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; + + public bool Enabled + { + get => _enabled; + set + { + if (_enabled == value) return; + _enabled = value; + + if (_enabled) + { + _label.Visible = true; + LastTile = default; + } + else + { + _label.Text = null; + _label.Visible = false; + } + } + } + + private bool _enabled; + + private (GridId Grid, Vector2i Indices) LastTile; + + // Label and shit that follows cursor + private Label _label = new() + { + Visible = false, + }; + + public override void Initialize() + { + base.Initialize(); + SubscribeNetworkEvent(HandleSentEntities); + IoCManager.Resolve().StateRoot.AddChild(_label); + } + + public override void Shutdown() + { + base.Shutdown(); + UnsubscribeNetworkEvent(); + IoCManager.Resolve().StateRoot.RemoveChild(_label); + } + + private void RequestEntities(GridId gridId, Vector2i indices) + { + if (gridId == GridId.Invalid) return; + RaiseNetworkEvent(new RequestGridTileLookupMessage(gridId, indices)); + } + + private void HandleSentEntities(SendGridTileLookupMessage message) + { + if (!Enabled) return; + var text = new StringBuilder(); + text.AppendLine($"GridId: {LastTile.Grid}, Tile: {LastTile.Indices}"); + + for (var i = 0; i < message.Entities.Count; i++) + { + var uid = message.Entities[i]; + + if (!EntityManager.TryGetEntity(uid, out var entity)) continue; + + text.AppendLine(entity.ToString()); + } + + _label.Text = text.ToString(); + } + + public override void FrameUpdate(float frameTime) + { + base.FrameUpdate(frameTime); + if (!Enabled) return; + + var mousePos = _inputManager.MouseScreenPosition; + var worldPos = _eyeManager.ScreenToMap(mousePos); + + GridId gridId; + Vector2i tile; + + if (_mapManager.TryFindGridAt(worldPos, out var grid)) + { + gridId = grid.Index; + tile = grid.WorldToTile(worldPos.Position); + } + else + { + gridId = GridId.Invalid; + tile = new Vector2i((int) MathF.Floor(worldPos.Position.X), (int) MathF.Floor(worldPos.Position.Y)); + } + + LayoutContainer.SetPosition(_label, mousePos.Position); + + if ((gridId, tile).Equals(LastTile)) return; + + _label.Text = null; + LastTile = (gridId, tile); + RequestEntities(gridId, tile); + } + } + + internal sealed class RequestTileEntities : IConsoleCommand + { + public string Command => "tilelookup"; + public string Description => "Used for debugging GridTileLookupSystem"; + public string Help => $"{Command}"; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + EntitySystem.Get().Enabled ^= true; + } + } +} +#endif diff --git a/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs b/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs index cf56283605..86734bc52c 100644 --- a/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs +++ b/Robust.Server/GameObjects/EntitySystems/TileLookup/GridTileLookupSystem.cs @@ -16,6 +16,7 @@ namespace Robust.Server.GameObjects [UsedImplicitly] public sealed class GridTileLookupSystem : EntitySystem { + [Dependency] private readonly IEntityLookup _lookup = default!; [Dependency] private readonly IMapManager _mapManager = default!; private readonly Dictionary> _graph = @@ -198,16 +199,16 @@ namespace Robust.Server.GameObjects private Box2 GetEntityBox(IEntity entity) { - // Need to clip the aabb as anything with an edge intersecting another tile might be picked up, such as walls. - if (entity.TryGetComponent(out IPhysBody? physics)) - return new Box2(physics.GetWorldAABB().BottomLeft + 0.01f, physics.GetWorldAABB().TopRight - 0.01f); - - // Don't want to accidentally get neighboring tiles unless we're near an edge - return Box2.CenteredAround(entity.Transform.Coordinates.ToMapPos(EntityManager), Vector2.One / 2); + var aabb = _lookup.GetWorldAabbFromEntity(entity); + return aabb.IsEmpty() ? aabb : aabb.Enlarged(-0.1f); } public override void Initialize() { + base.Initialize(); +#if DEBUG + SubscribeNetworkEvent(HandleRequest); +#endif SubscribeLocalEvent(HandleEntityMove); SubscribeLocalEvent(HandleEntityInitialized); SubscribeLocalEvent(HandleEntityDeleted); @@ -225,6 +226,14 @@ namespace Robust.Server.GameObjects _mapManager.TileChanged -= HandleTileChanged; } +#if DEBUG + private void HandleRequest(RequestGridTileLookupMessage message, EntitySessionEventArgs args) + { + var entities = GetEntitiesIntersecting(message.GridId, message.Indices).Select(e => e.Uid).ToList(); + RaiseNetworkEvent(new SendGridTileLookupMessage(message.GridId, message.Indices, entities), args.SenderSession.ConnectedClient); + } +#endif + private void HandleEntityInitialized(EntityInitializedMessage message) { HandleEntityAdd(message.Entity); diff --git a/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponent.Physics.cs b/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponent.Physics.cs index 6fcca97edf..fcbb0672dc 100644 --- a/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponent.Physics.cs +++ b/Robust.Shared/GameObjects/Components/Collidable/PhysicsComponent.Physics.cs @@ -477,35 +477,18 @@ namespace Robust.Shared.GameObjects Dirty(); } - public Box2 GetWorldAABB(IMapManager? mapManager = null) + public Box2 GetWorldAABB(Vector2? worldPosition = null, Angle? worldRotation = null) { - mapManager ??= IoCManager.Resolve(); - var bounds = new Box2(); + worldRotation ??= Owner.Transform.WorldRotation; + worldPosition ??= Owner.Transform.WorldPosition; + var bounds = new Box2(worldPosition.Value, worldPosition.Value); foreach (var fixture in _fixtures) { - foreach (var (gridId, proxies) in fixture.Proxies) - { - Vector2 offset; - - if (gridId == GridId.Invalid) - { - offset = Vector2.Zero; - } - else - { - offset = mapManager.GetGrid(gridId).WorldPosition; - } - - foreach (var proxy in proxies) - { - var shapeBounds = proxy.AABB.Translated(offset); - bounds = bounds.IsEmpty() ? shapeBounds : bounds.Union(shapeBounds); - } - } + bounds = bounds.Union(fixture.Shape.CalculateLocalBounds(worldRotation.Value).Translated(worldPosition.Value)); } - return bounds.IsEmpty() ? Box2.UnitCentered.Translated(Owner.Transform.WorldPosition) : bounds; + return bounds; } /// diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.cs index 65f55d41d3..6834354bec 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.cs @@ -278,7 +278,7 @@ namespace Robust.Shared.GameObjects { if (entity.TryGetComponent(out var component)) { - return GetEntitiesIntersecting(entity.Transform.MapID, component.GetWorldAABB(_mapManager), approximate); + return GetEntitiesIntersecting(entity.Transform.MapID, component.GetWorldAABB(), approximate); } return GetEntitiesIntersecting(entity.Transform.Coordinates, approximate); @@ -295,7 +295,7 @@ namespace Robust.Shared.GameObjects { if (entity.TryGetComponent(out IPhysBody? component)) { - if (component.GetWorldAABB(_mapManager).Contains(mapPosition)) + if (component.GetWorldAABB().Contains(mapPosition)) return true; } else @@ -341,7 +341,7 @@ namespace Robust.Shared.GameObjects { if (entity.TryGetComponent(out var component)) { - return GetEntitiesInRange(entity.Transform.MapID, component.GetWorldAABB(_mapManager), range, approximate); + return GetEntitiesInRange(entity.Transform.MapID, component.GetWorldAABB(), range, approximate); } return GetEntitiesInRange(entity.Transform.Coordinates, range, approximate); @@ -474,13 +474,14 @@ namespace Robust.Shared.GameObjects public Box2 GetWorldAabbFromEntity(in IEntity ent) { if (ent.Deleted) - return new Box2(0, 0, 0, 0); + return new Box2(); // TODO: God this disgusts me but it's a bandaid for now, see tuple.extract + + var worldPosition = ent.Transform.WorldPosition; if (ent.TryGetComponent(out IPhysBody? collider)) - return collider.GetWorldAABB(_mapManager); + return collider.GetWorldAABB(worldPosition); - var pos = ent.Transform.WorldPosition; - return new Box2(pos, pos); + return new Box2(worldPosition, worldPosition); } #endregion diff --git a/Robust.Shared/GameObjects/Systems/SharedGridTileLookupSystem.cs b/Robust.Shared/GameObjects/Systems/SharedGridTileLookupSystem.cs new file mode 100644 index 0000000000..19512aebf8 --- /dev/null +++ b/Robust.Shared/GameObjects/Systems/SharedGridTileLookupSystem.cs @@ -0,0 +1,37 @@ +#if DEBUG +using System; +using System.Collections.Generic; +using Robust.Shared.Map; +using Robust.Shared.Maths; +using Robust.Shared.Serialization; + +namespace Robust.Shared.GameObjects +{ + [Serializable, NetSerializable] + public sealed class RequestGridTileLookupMessage : EntityEventArgs + { + public GridId GridId; + public Vector2i Indices; + + public RequestGridTileLookupMessage(GridId gridId, Vector2i indices) + { + GridId = gridId; + Indices = indices; + } + } + + [Serializable, NetSerializable] + public sealed class SendGridTileLookupMessage : EntityEventArgs + { + public GridId GridId; + public Vector2i Indices; + + public List Entities { get; } + + public SendGridTileLookupMessage(GridId gridId, Vector2i indices, List entities) + { + Entities = entities; + } + } +} +#endif diff --git a/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs b/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs index 9a97030344..998d8d28d9 100644 --- a/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs +++ b/Robust.Shared/Physics/BroadPhase/SharedBroadPhaseSystem.cs @@ -125,7 +125,7 @@ namespace Robust.Shared.Physics.Broadphase public float IntersectionPercent(PhysicsComponent bodyA, PhysicsComponent bodyB) { // TODO: Use actual shapes and not just the AABB? - return bodyA.GetWorldAABB(_mapManager).IntersectPercentage(bodyB.GetWorldAABB(_mapManager)); + return bodyA.GetWorldAABB().IntersectPercentage(bodyB.GetWorldAABB()); } public override void Initialize() @@ -159,7 +159,8 @@ namespace Robust.Shared.Physics.Broadphase if (moveEvent.Sender.Deleted || !moveEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent)) continue; - SynchronizeFixtures(physicsComponent, moveEvent.NewPosition.ToMapPos(EntityManager) - moveEvent.OldPosition.ToMapPos(EntityManager), moveEvent.WorldAABB); + var mapPosition = moveEvent.NewPosition.ToMapPos(EntityManager); + SynchronizeFixtures(physicsComponent, mapPosition - moveEvent.OldPosition.ToMapPos(EntityManager), mapPosition, moveEvent.WorldAABB); } while (_queuedRotateEvent.Count > 0) @@ -173,7 +174,7 @@ namespace Robust.Shared.Physics.Broadphase if (rotateEvent.Sender.Deleted || !rotateEvent.Sender.TryGetComponent(out PhysicsComponent? physicsComponent)) return; - SynchronizeFixtures(physicsComponent, Vector2.Zero, rotateEvent.WorldAABB); + SynchronizeFixtures(physicsComponent, Vector2.Zero, null, rotateEvent.WorldAABB); } _handledThisTick.Clear(); @@ -523,9 +524,7 @@ namespace Robust.Shared.Physics.Broadphase /// /// Move all of the fixtures on this body. /// - /// - /// - private void SynchronizeFixtures(PhysicsComponent body, Vector2 displacement, Box2? worldAABB = null) + private void SynchronizeFixtures(PhysicsComponent body, Vector2 displacement, Vector2? worldPosition = null, Box2? worldAABB = null) { // If the entity's still being initialized it might have MoveEvent called (might change in future?) if (!_lastBroadPhases.TryGetValue(body, out var oldBroadPhases)) @@ -533,8 +532,13 @@ namespace Robust.Shared.Physics.Broadphase return; } + // TODO: These will need swept broadPhases + worldPosition ??= body.Owner.Transform.WorldPosition; + var worldRotation = body.Owner.Transform.WorldRotation; + + var mapId = body.Owner.Transform.MapID; - worldAABB ??= body.GetWorldAABB(_mapManager); + worldAABB ??= body.GetWorldAABB(worldPosition, worldRotation); // 99% of the time this is going to be 1, maybe 2, so HashSet probably slower? @@ -561,9 +565,6 @@ namespace Robust.Shared.Physics.Broadphase } // Update retained broadphases - // TODO: These will need swept broadPhases - var offset = body.Owner.Transform.WorldPosition; - var worldRotation = body.Owner.Transform.WorldRotation; foreach (var broadPhase in oldBroadPhases) { @@ -575,19 +576,20 @@ namespace Robust.Shared.Physics.Broadphase { if (!fixture.Proxies.TryGetValue(gridId, out var proxies)) continue; + var gridPosition = worldPosition.Value; + double gridRotation = worldRotation; + + if (gridId != GridId.Invalid) + { + var grid = _mapManager.GetGrid(gridId); + gridPosition -= grid.WorldPosition; + // TODO: Should probably have a helper for this + gridRotation = worldRotation - EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation; + } + foreach (var proxy in proxies) { - double gridRotation = worldRotation; - - if (gridId != GridId.Invalid) - { - var grid = _mapManager.GetGrid(gridId); - offset -= grid.WorldPosition; - // TODO: Should probably have a helper for this - gridRotation = worldRotation - body.Owner.EntityManager.GetEntity(grid.GridEntityId).Transform.WorldRotation; - } - - var aabb = fixture.Shape.CalculateLocalBounds(gridRotation).Translated(offset); + var aabb = fixture.Shape.CalculateLocalBounds(gridRotation).Translated(gridPosition); proxy.AABB = aabb; broadPhase.MoveProxy(proxy.ProxyId, in aabb, displacement); diff --git a/Robust.Shared/Physics/DynamicTree.cs b/Robust.Shared/Physics/DynamicTree.cs index e4e18fac97..e513254ce8 100644 --- a/Robust.Shared/Physics/DynamicTree.cs +++ b/Robust.Shared/Physics/DynamicTree.cs @@ -291,6 +291,7 @@ namespace Robust.Shared.Physics var item = tuple.tree.GetUserData(proxy)!; if (!tuple.approx) { + // TODO: The DynamicTree already stores AABBs so do we even need this? I know we fatten them but ehh var precise = tuple.extract(item); if (!precise.Intersects(tuple.aabb)) { diff --git a/Robust.Shared/Physics/Dynamics/Fixture.cs b/Robust.Shared/Physics/Dynamics/Fixture.cs index 01242b3d32..74f1cda682 100644 --- a/Robust.Shared/Physics/Dynamics/Fixture.cs +++ b/Robust.Shared/Physics/Dynamics/Fixture.cs @@ -334,9 +334,9 @@ namespace Robust.Shared.Physics.Dynamics mapManager ??= IoCManager.Resolve(); broadPhaseSystem ??= EntitySystem.Get(); - var worldAABB = Body.GetWorldAABB(mapManager); var worldPosition = Body.Owner.Transform.WorldPosition; var worldRotation = Body.Owner.Transform.WorldRotation; + var worldAABB = Body.GetWorldAABB(worldPosition, worldRotation); foreach (var gridId in mapManager.FindGridIdsIntersecting(mapId, worldAABB, true)) { diff --git a/Robust.Shared/Physics/Dynamics/PhysicsMap.cs b/Robust.Shared/Physics/Dynamics/PhysicsMap.cs index b11f617f0e..4dce47fe28 100644 --- a/Robust.Shared/Physics/Dynamics/PhysicsMap.cs +++ b/Robust.Shared/Physics/Dynamics/PhysicsMap.cs @@ -27,6 +27,7 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Physics.Broadphase; using Robust.Shared.Physics.Dynamics.Contacts; using Robust.Shared.Physics.Dynamics.Joints; using Robust.Shared.Utility; @@ -474,7 +475,7 @@ namespace Robust.Shared.Physics.Dynamics // We'll store the WorldAABB on the MoveEvent given a lot of stuff ends up re-calculating it. foreach (var (transform, physics) in _deferredUpdates) { - transform.RunDeferred(physics.GetWorldAABB(_mapManager)); + transform.RunDeferred(physics.GetWorldAABB()); } _deferredUpdates.Clear(); @@ -513,12 +514,11 @@ namespace Robust.Shared.Physics.Dynamics // I tried not running prediction for non-contacted entities but unfortunately it looked like shit // when contact broke so if you want to try that then GOOD LUCK. // prediction && !seed.Predict || - // AHHH need a way to ignore paused for mapping (seed.Paused && !seed.Owner.TryGetComponent(out IMoverComponent)) || - if ((prediction && !seed.Predict) || - (seed.Paused && !seed.IgnorePaused) || + if (prediction && !seed.Predict || seed.Island || - !seed.CanCollide || - seed.BodyType == BodyType.Static) continue; + seed.BodyType == BodyType.Static || + (seed.Paused && !seed.IgnorePaused) || + !seed.CanCollide) continue; // Start of a new island _island.Clear(); diff --git a/Robust.Shared/Physics/IPhysBody.cs b/Robust.Shared/Physics/IPhysBody.cs index f830575ac2..88cb704c55 100644 --- a/Robust.Shared/Physics/IPhysBody.cs +++ b/Robust.Shared/Physics/IPhysBody.cs @@ -30,7 +30,7 @@ namespace Robust.Shared.Physics /// /// AABB of this entity in world space. /// - Box2 GetWorldAABB(IMapManager? mapManager = null); + Box2 GetWorldAABB(Vector2? worldPosition = null, Angle? worldRotation = null); /// /// Whether or not this body can collide.