Final grid movement optimisation (#2711)

This commit is contained in:
metalgearsloth
2022-05-14 14:54:31 +10:00
committed by GitHub
parent 70fbefbe62
commit 32bdc19fe9
21 changed files with 257 additions and 112 deletions

View File

@@ -52,6 +52,7 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
@@ -88,6 +89,7 @@ namespace Robust.Client.Debugging
EntityManager,
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IInputManager>(),
IoCManager.Resolve<IMapManager>(),
IoCManager.Resolve<IResourceCache>(),
this,
Get<SharedPhysicsSystem>()));
@@ -176,6 +178,7 @@ namespace Robust.Client.Debugging
private IEntityManager _entityManager = default!;
private IEyeManager _eyeManager = default!;
private IInputManager _inputManager = default!;
private IMapManager _mapManager = default!;
private DebugPhysicsSystem _debugPhysicsSystem = default!;
private SharedPhysicsSystem _physicsSystem = default!;
@@ -187,11 +190,12 @@ namespace Robust.Client.Debugging
private HashSet<Joint> _drawnJoints = new();
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
{
_entityManager = entityManager;
_eyeManager = eyeManager;
_inputManager = inputManager;
_mapManager = mapManager;
_debugPhysicsSystem = system;
_physicsSystem = physicsSystem;
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
@@ -246,26 +250,21 @@ namespace Robust.Client.Debugging
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.COM) != 0)
{
const float Alpha = 0.25f;
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
Color color;
const float Alpha = 0.25f;
float size;
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner))
{
color = Color.Orange.WithAlpha(Alpha);
size = 1f;
}
else
{
color = Color.Purple.WithAlpha(Alpha);
size = 0.2f;
}
var color = Color.Purple.WithAlpha(Alpha);
var transform = physBody.GetTransform();
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 0.2f, color);
}
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), size, color);
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, viewBounds))
{
var physBody = _entityManager.GetComponent<PhysicsComponent>(grid.GridEntityId);
var color = Color.Orange.WithAlpha(Alpha);
var transform = physBody.GetTransform();
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 1f, color);
}
}

View File

@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
worldHandle.SetTransform(worldMatrix);
var transform = new Transform(Vector2.Zero, Angle.Zero);
gridInternal.GetMapChunks(viewport, out var chunkEnumerator);
var chunkEnumerator = gridInternal.GetMapChunks(viewport);
while (chunkEnumerator.MoveNext(out var chunk))
{

View File

@@ -50,7 +50,7 @@ namespace Robust.Client.Graphics.Clyde
var transform = _entityManager.GetComponent<TransformComponent>(grid.GridEntityId);
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
grid.GetMapChunks(worldBounds, out var enumerator);
var enumerator = grid.GetMapChunks(worldBounds);
while (enumerator.MoveNext(out var chunk))
{

View File

@@ -89,7 +89,7 @@ namespace Robust.Client.Physics
var gridXform = xformQuery.GetComponent(iGrid.GridEntityId);
worldHandle.SetTransform(gridXform.WorldMatrix);
var grid = (MapGrid)iGrid;
grid.GetMapChunks(args.WorldBounds, out var chunkEnumerator);
var chunkEnumerator = grid.GetMapChunks(args.WorldBounds);
while (chunkEnumerator.MoveNext(out var chunk))
{

View File

@@ -469,8 +469,6 @@ namespace Robust.Server.Maps
{
var gridInternal = (IMapGridInternal) grid;
var body = entManager.EnsureComponent<PhysicsComponent>(grid.GridEntityId);
var mapUid = _mapManager.GetMapEntityIdOrThrow(grid.ParentMapId);
body.Broadphase = entManager.GetComponent<BroadphaseComponent>(mapUid);
var fixtures = entManager.EnsureComponent<FixturesComponent>(grid.GridEntityId);
// Regenerate grid collision.
gridFixtures.EnsureGrid(grid.GridEntityId);

View File

@@ -54,6 +54,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public bool Island { get; set; }
[ViewVariables]
internal BroadphaseComponent? Broadphase { get; set; }
/// <summary>
@@ -659,7 +660,12 @@ namespace Robust.Shared.GameObjects
public Transform GetTransform()
{
var (worldPos, worldRot) = _entMan.GetComponent<TransformComponent>(Owner).GetWorldPositionRotation();
return GetTransform(_entMan.GetComponent<TransformComponent>(Owner));
}
public Transform GetTransform(TransformComponent xform)
{
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var xf = new Transform(worldPos, (float) worldRot.Theta);
// xf.Position -= Transform.Mul(xf.Quaternion2D, LocalCenter);

View File

@@ -42,7 +42,7 @@ namespace Robust.Shared.Map
// TODO: Use CollisionManager to get nearest edge.
// figure out closest intersect
var gridIntersect = gridSearchBox.Intersect(grid.WorldBounds);
var gridIntersect = gridSearchBox.Intersect(grid.WorldAABB);
var gridDist = (gridIntersect.Center - mapCoords.Position).LengthSquared;
if (gridDist >= distance)

View File

@@ -48,13 +48,13 @@ namespace Robust.Shared.Map
var invMatrix3 = xformComp.InvWorldMatrix;
var localAABB = invMatrix3.TransformBox(_worldAABB);
if (!localAABB.Intersects(nextGrid.LocalBounds)) continue;
if (!localAABB.Intersects(nextGrid.LocalAABB)) continue;
var intersects = false;
if (_entityManager.HasComponent<PhysicsComponent>(nextGrid.GridEntityId))
{
nextGrid.GetLocalMapChunks(localAABB, out var enumerator);
var enumerator = nextGrid.GetLocalMapChunks(localAABB);
if (!_approx)
{

View File

@@ -32,15 +32,17 @@ namespace Robust.Shared.Map
/// </summary>
ushort TileSize { get; }
Box2Rotated WorldBounds { get; }
/// <summary>
/// The bounding box of the grid in world coordinates.
/// </summary>
Box2 WorldBounds { get; }
Box2 WorldAABB { get; }
/// <summary>
/// The bounding box of the grid in local coordinates.
/// </summary>
Box2 LocalBounds { get; }
Box2 LocalAABB { get; }
/// <summary>
/// The length of a side of the square chunk in number of tiles.

View File

@@ -54,12 +54,12 @@ namespace Robust.Shared.Map
/// <summary>
/// Returns all the <see cref="MapChunk"/> intersecting the worldAABB.
/// </summary>
void GetMapChunks(Box2 worldAABB, out MapGrid.ChunkEnumerator enumerator);
MapGrid.ChunkEnumerator GetMapChunks(Box2 worldAABB);
/// <summary>
/// Returns all the <see cref="MapChunk"/> intersecting the rotated world box.
/// </summary>
void GetMapChunks(Box2Rotated worldArea, out MapGrid.ChunkEnumerator enumerator);
MapGrid.ChunkEnumerator GetMapChunks(Box2Rotated worldArea);
/// <summary>
/// Regenerates the chunk local bounds of this chunk.

View File

@@ -51,5 +51,6 @@ namespace Robust.Shared.Map
void TrueDeleteMap(MapId mapId);
GridId GenerateGridId(GridId? forcedGridId);
void OnGridAllocated(MapGridComponent gridComponent, MapGrid mapGrid);
void OnGridBoundsChange(EntityUid uid, MapGrid grid);
}
}

View File

@@ -83,14 +83,25 @@ namespace Robust.Shared.Map
/// <inheritdoc />
[ViewVariables]
public Box2 WorldBounds =>
new Box2Rotated(LocalBounds, WorldRotation, Vector2.Zero)
public Box2Rotated WorldBounds
{
get
{
var worldAABB = LocalAABB.Translated(WorldPosition);
return new Box2Rotated(worldAABB, WorldRotation, worldAABB.Center);
}
}
/// <inheritdoc />
[ViewVariables]
public Box2 WorldAABB =>
new Box2Rotated(LocalAABB, WorldRotation, Vector2.Zero)
.CalcBoundingBox()
.Translated(WorldPosition);
/// <inheritdoc />
[ViewVariables]
public Box2 LocalBounds { get; private set; }
public Box2 LocalAABB { get; private set; }
/// <inheritdoc />
[ViewVariables]
@@ -479,24 +490,23 @@ namespace Robust.Shared.Map
}
/// <inheritdoc />
public void GetMapChunks(Box2 worldAABB, out ChunkEnumerator enumerator)
public ChunkEnumerator GetMapChunks(Box2 worldAABB)
{
var localArea = InvWorldMatrix.TransformBox(worldAABB);
enumerator = new ChunkEnumerator(_chunks, localArea, ChunkSize);
var localAABB = InvWorldMatrix.TransformBox(worldAABB);
return new ChunkEnumerator(_chunks, localAABB, ChunkSize);
}
/// <inheritdoc />
public void GetMapChunks(Box2Rotated worldArea, out ChunkEnumerator enumerator)
public ChunkEnumerator GetMapChunks(Box2Rotated worldArea)
{
var matrix = InvWorldMatrix;
var localArea = matrix.TransformBox(worldArea);
enumerator = new ChunkEnumerator(_chunks, localArea, ChunkSize);
return new ChunkEnumerator(_chunks, localArea, ChunkSize);
}
public void GetLocalMapChunks(Box2 localAABB, out ChunkEnumerator enumerator)
public ChunkEnumerator GetLocalMapChunks(Box2 localAABB)
{
enumerator = new ChunkEnumerator(_chunks, localAABB, ChunkSize);
return new ChunkEnumerator(_chunks, localAABB, ChunkSize);
}
#endregion ChunkAccess
@@ -914,7 +924,7 @@ namespace Robust.Shared.Map
}
}
LocalBounds = new Box2();
LocalAABB = new Box2();
foreach (var chunk in _chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
@@ -922,18 +932,20 @@ namespace Robust.Shared.Map
if(chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (LocalBounds.Size == Vector2.Zero)
if (LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalBounds = gridBounds;
LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalBounds = LocalBounds.Union(gridBounds);
LocalAABB = LocalAABB.Union(gridBounds);
}
}
_mapManager.OnGridBoundsChange(GridEntityId, this);
if (chunkRectangles.Count == 0)
{
// May have been deleted from the bulk update above!
@@ -965,7 +977,7 @@ namespace Robust.Shared.Map
GridChunkPartition.PartitionChunk(mapChunk, out var localBounds, out var rectangles);
mapChunk.CachedBounds = localBounds;
LocalBounds = new Box2();
LocalAABB = new Box2();
foreach (var chunk in _chunks.Values)
{
var chunkBounds = chunk.CachedBounds;
@@ -973,20 +985,23 @@ namespace Robust.Shared.Map
if(chunkBounds.Size.Equals(Vector2i.Zero))
continue;
if (LocalBounds.Size == Vector2.Zero)
if (LocalAABB.Size == Vector2.Zero)
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalBounds = gridBounds;
LocalAABB = gridBounds;
}
else
{
var gridBounds = chunkBounds.Translated(chunk.Indices * chunk.ChunkSize);
LocalBounds = LocalBounds.Union(gridBounds);
LocalAABB = LocalAABB.Union(gridBounds);
}
}
if (!_entityManager.EntitySysManager.TryGetEntitySystem(out SharedGridFixtureSystem? system) ||
_entityManager.Deleted(GridEntityId)) return;
// TODO: Move this to the component when we combine.
_mapManager.OnGridBoundsChange(GridEntityId, this);
// TryGet because unit tests YAY
if (mapChunk.FilledTiles > 0)

View File

@@ -33,7 +33,6 @@ internal partial class MapManager
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, RotateEvent>(OnGridRotate);
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, EntParentChangedMessage>(OnGridParentChange);
EntityManager.EventBus.SubscribeLocalEvent<MapGridComponent, GridFixtureChangeEvent>(OnGridBoundsChange);
}
private void ShutdownGridTrees()
@@ -43,7 +42,6 @@ internal partial class MapManager
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, MoveEvent>();
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, RotateEvent>();
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, EntParentChangedMessage>();
EntityManager.EventBus.UnsubscribeLocalEvent<MapGridComponent, GridFixtureChangeEvent>();
DebugTools.Assert(_gridTrees.Count == 0);
DebugTools.Assert(_movedGrids.Count == 0);
@@ -71,7 +69,7 @@ internal partial class MapManager
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
return new Box2Rotated(grid.LocalBounds.Translated(worldPos), worldRot, worldPos).CalcBoundingBox();
return new Box2Rotated(grid.LocalAABB, worldRot).CalcBoundingBox().Translated(worldPos);
}
private void OnGridInit(GridInitializeEvent args)
@@ -171,15 +169,14 @@ internal partial class MapManager
}
}
private void OnGridBoundsChange(EntityUid uid, MapGridComponent component, GridFixtureChangeEvent args)
public void OnGridBoundsChange(EntityUid uid, MapGrid grid)
{
var grid = (MapGrid) component.Grid;
// Just MapLoader things.
if (grid.MapProxy == DynamicTree.Proxy.Free) return;
var xform = EntityManager.GetComponent<TransformComponent>(uid);
var aabb = GetWorldAABB(grid);
_gridTrees[xform.MapID].MoveProxy(grid.MapProxy, in aabb, Vector2.Zero);
_movedGrids[xform.MapID].Add(grid);
}
}

View File

@@ -67,7 +67,7 @@ internal partial class MapManager
if (physicsQuery.HasComponent(grid.GridEntityId))
{
grid.GetLocalMapChunks(localAABB, out var enumerator);
var enumerator = grid.GetLocalMapChunks(localAABB);
var transform = new Transform(worldPos, worldRot);

View File

@@ -48,6 +48,7 @@ namespace Robust.Shared.Physics.Dynamics
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPhysicsManager _physicsManager = default!;
private SharedTransformSystem _transform = default!;
internal MapId MapId { get; set; }
@@ -134,8 +135,7 @@ namespace Robust.Shared.Physics.Dynamics
{
contact.Enabled = true;
contact.IsTouching = false;
contact.IslandFlag = false;
contact.FilterFlag = false;
contact.Flags = ContactFlags.None;
// TOIFlag = false;
contact.FixtureA = fixtureA;
@@ -159,6 +159,7 @@ namespace Robust.Shared.Physics.Dynamics
public void Initialize()
{
IoCManager.InjectDependencies(this);
_transform = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SharedTransformSystem>();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.ContactMultithreadThreshold, OnContactMultithreadThreshold, true);
configManager.OnValueChanged(CVars.ContactMinimumThreads, OnContactMinimumThreads, true);
@@ -225,16 +226,10 @@ namespace Robust.Shared.Physics.Dynamics
}
/// <summary>
/// Go through the cached broadphase movement and update contacts.
/// Try to create a contact between these 2 fixtures.
/// </summary>
internal void AddPair(in FixtureProxy proxyA, in FixtureProxy proxyB)
internal void AddPair(Fixture fixtureA, int indexA, Fixture fixtureB, int indexB, ContactFlags flags = ContactFlags.None)
{
Fixture fixtureA = proxyA.Fixture;
Fixture fixtureB = proxyB.Fixture;
var indexA = proxyA.ChildIndex;
var indexB = proxyB.ChildIndex;
PhysicsComponent bodyA = fixtureA.Body;
PhysicsComponent bodyB = fixtureB.Body;
@@ -253,6 +248,7 @@ namespace Robust.Shared.Physics.Dynamics
// Call the factory.
var contact = CreateContact(fixtureA, indexA, fixtureB, indexB);
contact.Flags = flags;
// Contact creation may swap fixtures.
fixtureA = contact.FixtureA!;
@@ -274,6 +270,14 @@ namespace Robust.Shared.Physics.Dynamics
contact.BodyBNode = bodyB.Contacts.AddLast(contact);
}
/// <summary>
/// Go through the cached broadphase movement and update contacts.
/// </summary>
internal void AddPair(in FixtureProxy proxyA, in FixtureProxy proxyB)
{
AddPair(proxyA.Fixture, proxyA.ChildIndex, proxyB.Fixture, proxyB.ChildIndex);
}
internal static bool ShouldCollide(Fixture fixtureA, Fixture fixtureB)
{
return !((fixtureA.CollisionMask & fixtureB.CollisionLayer) == 0x0 &&
@@ -335,6 +339,7 @@ namespace Robust.Shared.Physics.Dynamics
// TODO: check for null instead?
// Work out which contacts are still valid before we decide to update manifolds.
var node = _activeContacts.First;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
while (node != null)
{
@@ -356,7 +361,7 @@ namespace Robust.Shared.Physics.Dynamics
}
// Is this contact flagged for filtering?
if (contact.FilterFlag)
if ((contact.Flags & ContactFlags.Filter) != 0x0)
{
// Should these bodies collide?
if (bodyB.ShouldCollide(bodyA) == false)
@@ -386,7 +391,7 @@ namespace Robust.Shared.Physics.Dynamics
*/
// Clear the filtering flag.
contact.FilterFlag = false;
contact.Flags &= ~ContactFlags.Filter;
}
bool activeA = bodyA.Awake && bodyA.BodyType != BodyType.Static;
@@ -399,12 +404,32 @@ namespace Robust.Shared.Physics.Dynamics
continue;
}
// Special-case grid contacts.
if ((contact.Flags & ContactFlags.Grid) != 0x0)
{
var xformA = xformQuery.GetComponent(bodyA.Owner);
var xformB = xformQuery.GetComponent(bodyB.Owner);
var gridABounds = fixtureA.Shape.ComputeAABB(bodyA.GetTransform(xformA), 0);
var gridBBounds = fixtureB.Shape.ComputeAABB(bodyB.GetTransform(xformB), 0);
if (!gridABounds.Intersects(gridBBounds))
{
Destroy(contact);
}
else
{
contacts[index++] = contact;
}
node = node.Next;
continue;
}
var proxyA = fixtureA.Proxies[indexA];
var proxyB = fixtureB.Proxies[indexB];
var broadphaseA = bodyA.Broadphase;
var broadphaseB = bodyB.Broadphase;
// TODO: IT MIGHT BE THE FATAABB STUFF FOR MOVEPROXY SO TRY THAT
var overlap = false;
// We can have cross-broadphase proxies hence need to change them to worldspace
@@ -417,10 +442,10 @@ namespace Robust.Shared.Physics.Dynamics
else
{
// These should really be destroyed before map changes.
DebugTools.Assert(_entityManager.GetComponent<TransformComponent>(broadphaseA.Owner).MapID == _entityManager.GetComponent<TransformComponent>(broadphaseB.Owner).MapID);
DebugTools.Assert(xformQuery.GetComponent(broadphaseA.Owner).MapID == xformQuery.GetComponent(broadphaseB.Owner).MapID);
var proxyAWorldAABB = _entityManager.GetComponent<TransformComponent>(broadphaseA.Owner).WorldMatrix.TransformBox(proxyA.AABB);
var proxyBWorldAABB = _entityManager.GetComponent<TransformComponent>(broadphaseB.Owner).WorldMatrix.TransformBox(proxyB.AABB);
var proxyAWorldAABB = _transform.GetWorldMatrix(broadphaseA.Owner, xformQuery).TransformBox(proxyA.AABB);
var proxyBWorldAABB = _transform.GetWorldMatrix(broadphaseB.Owner, xformQuery).TransformBox(proxyB.AABB);
overlap = proxyAWorldAABB.Intersects(proxyBWorldAABB);
}
}

View File

@@ -69,12 +69,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
internal ContactType Type;
/// <summary>
/// Has this contact already been added to an island?
/// </summary>
public bool IslandFlag { get; set; }
public bool FilterFlag { get; set; }
internal ContactFlags Flags = ContactFlags.None;
/// <summary>
/// Determines whether the contact is touching.
@@ -318,4 +313,24 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
return HashCode.Combine((FixtureA != null ? FixtureA.Body.Owner : EntityUid.Invalid), (FixtureB != null ? FixtureB.Body.Owner : EntityUid.Invalid));
}
}
[Flags]
internal enum ContactFlags : byte
{
None = 0,
/// <summary>
/// Has this contact already been added to an island?
/// </summary>
Island = 1 << 0,
/// <summary>
/// Does this contact need re-filtering?
/// </summary>
Filter = 1 << 1,
/// <summary>
/// Is this a special contact for grid-grid collisions
/// </summary>
Grid = 1 << 2,
}
}

View File

@@ -221,7 +221,7 @@ namespace Robust.Shared.Physics.Dynamics
{
var contact = contactNode.Value;
contactNode = contactNode.Next;
contact.IslandFlag = false;
contact.Flags &= ~ContactFlags.Island;
}
// Build and simulated islands from awake bodies.
@@ -295,7 +295,7 @@ namespace Robust.Shared.Physics.Dynamics
node = node.Next;
// Has this contact already been added to an island?
if (contact.IslandFlag) continue;
if ((contact.Flags & ContactFlags.Island) != 0x0) continue;
// Is this contact solid and touching?
if (!contact.Enabled || !contact.IsTouching) continue;
@@ -304,7 +304,7 @@ namespace Robust.Shared.Physics.Dynamics
if (contact.FixtureA?.Hard != true || contact.FixtureB?.Hard != true) continue;
_islandContacts.Add(contact);
contact.IslandFlag = true;
contact.Flags |= ContactFlags.Island;
var bodyA = contact.FixtureA!.Body;
var bodyB = contact.FixtureB!.Body;

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics
@@ -111,14 +112,16 @@ namespace Robust.Shared.Physics
/// <summary>
/// Check the AABB for each moved broadphase fixture and add any colliding entities to the movebuffer in case.
/// </summary>
private void FindGridContacts(MapId mapId)
private void FindGridContacts(
MapId mapId,
HashSet<IMapGrid> movedGrids,
EntityQuery<PhysicsComponent> bodyQuery,
EntityQuery<BroadphaseComponent> broadQuery)
{
var movedGrids = _mapManager.GetMovedGrids(mapId);
// None moved this tick
if (movedGrids.Count == 0) return;
var mapBroadphase = EntityManager.GetComponent<BroadphaseComponent>(_mapManager.GetMapEntityId(mapId));
var mapBroadphase = broadQuery.GetComponent(_mapManager.GetMapEntityId(mapId));
// This is so that if we're on a broadphase that's moving (e.g. a grid) we need to make sure anything
// we move over is getting checked for collisions, and putting it on the movebuffer is the easiest way to do so.
@@ -127,19 +130,20 @@ namespace Robust.Shared.Physics
foreach (var grid in movedGrids)
{
DebugTools.Assert(grid.ParentMapId == mapId);
var worldAABB = grid.WorldBounds;
var worldAABB = grid.WorldAABB;
var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand);
var gridBody = EntityManager.GetComponent<PhysicsComponent>(grid.GridEntityId);
var gridBody = bodyQuery.GetComponent(grid.GridEntityId);
// TODO: Use the callback for this you ape.
// Easier to just not go over each proxy as we already unioned the fixture's worldaabb.
foreach (var other in mapBroadphase.Tree.QueryAabb(_queryBuffer, enlargedAABB))
{
DebugTools.Assert(other.Fixture.Body != gridBody);
// 99% of the time it's just going to be the broadphase (for now the grid) itself.
// hence this body check makes this run significantly better.
// Also check if it's not already on the movebuffer.
if (other.Fixture.Body == gridBody || moveBuffer.ContainsKey(other)) continue;
if (moveBuffer.ContainsKey(other)) continue;
// To avoid updating during iteration.
// Don't need to transform as it's already in map terms.
@@ -153,8 +157,6 @@ namespace Robust.Shared.Physics
{
moveBuffer[proxy] = worldAABB;
}
movedGrids.Clear();
}
/// <summary>
@@ -163,11 +165,14 @@ namespace Robust.Shared.Physics
internal void FindNewContacts(MapId mapId)
{
var moveBuffer = _moveBuffer[mapId];
var movedGrids = _mapManager.GetMovedGrids(mapId);
if (moveBuffer.Count == 0) return;
var broadphaseQuery = GetEntityQuery<BroadphaseComponent>();
var physicsQuery = GetEntityQuery<PhysicsComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
// Find any entities being driven over that might need to be considered
FindGridContacts(mapId);
FindGridContacts(mapId, movedGrids, physicsQuery, broadphaseQuery);
// There is some mariana trench levels of bullshit going on.
// We essentially need to re-create Box2D's FindNewContacts but in a way that allows us to check every
@@ -177,10 +182,12 @@ namespace Robust.Shared.Physics
// FindNewContacts is inherently going to be a lot slower than Box2D's normal version so we need
// to cache a bunch of stuff to make up for it.
var contactManager = EntityManager.GetComponent<SharedPhysicsMapComponent>(_mapManager.GetMapEntityIdOrThrow(mapId)).ContactManager;
var broadphaseQuery = EntityManager.GetEntityQuery<BroadphaseComponent>();
var physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var contactManager = Comp<SharedPhysicsMapComponent>(_mapManager.GetMapEntityIdOrThrow(mapId)).ContactManager;
// Handle grids first as they're not stored on map broadphase at all.
HandleGridCollisions(mapId, contactManager, movedGrids, physicsQuery, xformQuery);
DebugTools.Assert(moveBuffer.Count > 0 || _pairBuffer.Count == 0);
// TODO: Could store fixtures by broadphase for more perf?
foreach (var (proxy, worldAABB) in moveBuffer)
@@ -234,12 +241,88 @@ namespace Robust.Shared.Physics
}
}
movedGrids.Clear();
_pairBuffer.Clear();
moveBuffer.Clear();
_gridMoveBuffer.Clear();
_mapManager.ClearMovedGrids(mapId);
}
private void HandleGridCollisions(
MapId mapId,
ContactManager contactManager,
HashSet<IMapGrid> movedGrids,
EntityQuery<PhysicsComponent> bodyQuery,
EntityQuery<TransformComponent> xformQuery)
{
foreach (var grid in movedGrids)
{
DebugTools.Assert(grid.ParentMapId == mapId);
var mapGrid = (MapGrid)grid;
var xform = xformQuery.GetComponent(grid.GridEntityId);
var (worldPos, worldRot, worldMatrix, invWorldMatrix) = xform.GetWorldPositionRotationMatrixWithInv(xformQuery);
var aabb = new Box2Rotated(grid.LocalAABB, worldRot).CalcBoundingBox().Translated(worldPos);
// TODO: Need to handle grids colliding with non-grid entities with the same layer
// (nothing in SS14 does this yet).
var transform = bodyQuery.GetComponent(grid.GridEntityId).GetTransform(xform);
_gridsPool.Clear();
foreach (var colliding in _mapManager.FindGridsIntersecting(mapId, aabb, _gridsPool, xformQuery, bodyQuery, true))
{
if (grid == colliding) continue;
var otherGrid = (MapGrid)colliding;
var otherGridBounds = colliding.WorldAABB;
var otherGridInvMatrix = colliding.InvWorldMatrix;
var otherTransform = bodyQuery.GetComponent(colliding.GridEntityId).GetTransform(xformQuery.GetComponent(colliding.GridEntityId));
// Get Grid2 AABB in grid1 ref
var aabb1 = grid.LocalAABB.Union(invWorldMatrix.TransformBox(otherGridBounds));
// TODO: AddPair has a nasty check in there that's O(n) but that's also a general physics problem.
var ourChunks = mapGrid.GetLocalMapChunks(aabb1);
// Only care about chunks on other grid overlapping us.
while (ourChunks.MoveNext(out var ourChunk))
{
var ourChunkWorld = worldMatrix.TransformBox(ourChunk.CachedBounds.Translated(ourChunk.Indices * grid.ChunkSize));
var ourChunkOtherRef = otherGridInvMatrix.TransformBox(ourChunkWorld);
var collidingChunks = otherGrid.GetLocalMapChunks(ourChunkOtherRef);
while (collidingChunks.MoveNext(out var collidingChunk))
{
foreach (var fixture in ourChunk.Fixtures)
{
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
var fixAABB = fixture.Shape.ComputeAABB(transform, i);
foreach (var otherFixture in collidingChunk.Fixtures)
{
for (var j = 0; j < otherFixture.Shape.ChildCount; j++)
{
var otherAABB = otherFixture.Shape.ComputeAABB(otherTransform, j);
if (!fixAABB.Intersects(otherAABB)) continue;
contactManager.AddPair(fixture, i, otherFixture, j, ContactFlags.Grid);
break;
}
}
}
}
}
}
}
}
}
#endregion
private void FindPairs(
FixtureProxy proxy,
Box2 worldAABB,
@@ -300,14 +383,13 @@ namespace Robust.Shared.Physics
_queryBuffer.Clear();
}
#endregion
/// <summary>
/// If our broadphase has changed then remove us from our old one and add to our new one.
/// </summary>
internal void UpdateBroadphase(PhysicsComponent body, FixturesComponent? manager = null, TransformComponent? xform = null)
{
if (!Resolve(body.Owner, ref manager, ref xform)) return;
if (!Resolve(body.Owner, ref manager, ref xform) ||
_mapManager.IsGrid(body.Owner)) return;
var oldBroadphase = body.Broadphase;
var newBroadphase = GetBroadphase(xform);
@@ -384,8 +466,9 @@ namespace Robust.Shared.Physics
// TODO: Good idea? Ehhhhhhhhhhhh
// The problem is there's some fuckery with events while an entity is initializing.
// Can probably just bypass this by doing stuff in Update / FrameUpdate again but future problem
//
if (body.Broadphase != null) return;
// Also grids are special-cased due to their high fixture count.
if (body.Broadphase != null ||
_mapManager.IsGrid(body.Owner)) return;
if (!Resolve(body.Owner, ref manager))
{
@@ -451,7 +534,7 @@ namespace Robust.Shared.Physics
foreach (var (_, contact) in fixture.Contacts)
{
contact.FilterFlag = true;
contact.Flags |= ContactFlags.Filter;
}
var broadphase = body.Broadphase;
@@ -474,7 +557,9 @@ namespace Robust.Shared.Physics
private void OnMove(EntityUid uid, PhysicsComponent component, ref MoveEvent args)
{
if (!component.CanCollide || !EntityManager.TryGetComponent(uid, out FixturesComponent? manager)) return;
if (!component.CanCollide ||
!TryComp<FixturesComponent>(uid, out var manager) ||
_mapManager.IsGrid(uid)) return;
var worldRot = Transform(uid).WorldRotation;
@@ -483,9 +568,10 @@ namespace Robust.Shared.Physics
private void OnRotate(EntityUid uid, PhysicsComponent component, ref RotateEvent args)
{
if (!component.CanCollide) return;
if (!component.CanCollide ||
_mapManager.IsGrid(uid)) return;
var xform = EntityManager.GetComponent<TransformComponent>(uid);
var xform = Transform(uid);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
DebugTools.Assert(xform.LocalRotation.Equals(args.NewRotation));
@@ -790,7 +876,7 @@ namespace Robust.Shared.Physics
var grid = (IMapGridInternal) _mapManager.GetGrid(mapGrid.GridIndex);
// Won't worry about accurate bounds checks as it's probably slower in most use cases.
grid.GetMapChunks(aabb, out var chunkEnumerator);
var chunkEnumerator = grid.GetMapChunks(aabb);
if (chunkEnumerator.MoveNext(out _))
{

View File

@@ -9,6 +9,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -470,7 +471,7 @@ namespace Robust.Shared.Physics
{
// Flag the contact for filtering at the next time step (where either
// body is awake).
contact.FilterFlag = true;
contact.Flags |= ContactFlags.Filter;
}
}
}

View File

@@ -35,8 +35,8 @@ namespace Robust.UnitTesting.Shared.Map
gridId2 = mapManager.CreateGrid(mapId);
gridEnt1 = gridId1.GridEntityId;
gridEnt2 = gridId2.GridEntityId;
physics1 = IoCManager.Resolve<IEntityManager>().GetComponent<PhysicsComponent>(gridEnt1.Value);
physics2 = IoCManager.Resolve<IEntityManager>().GetComponent<PhysicsComponent>(gridEnt2.Value);
physics1 = entManager.GetComponent<PhysicsComponent>(gridEnt1.Value);
physics2 = entManager.GetComponent<PhysicsComponent>(gridEnt2.Value);
// Can't collide static bodies and grids (at time of this writing) start as static
// (given most other games would probably prefer them as static) hence we need to make them dynamic.
physics1.BodyType = BodyType.Dynamic;

View File

@@ -56,7 +56,7 @@ namespace Robust.UnitTesting.Shared.Map
grid.SetTile(new Vector2i(-1, -2), new Tile(1));
grid.SetTile(new Vector2i(1, 2), new Tile(1));
var bounds = grid.WorldBounds;
var bounds = grid.WorldAABB;
// this is world, so add the grid world pos
Assert.That(bounds.Bottom, Is.EqualTo(-2+5));
@@ -82,7 +82,7 @@ namespace Robust.UnitTesting.Shared.Map
grid.SetTile(new Vector2i(1, 2), Tile.Empty);
var bounds = grid.WorldBounds;
var bounds = grid.WorldAABB;
// this is world, so add the grid world pos
Assert.That(bounds.Bottom, Is.EqualTo(-2+5));