mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Accurate grid bounds (#2027)
* Fright night * Shitty bounds working * No more fixture leak * Optimise TryFindGridAt Should be O(1) now instead of the previous O(n) for number of fixtures * ambush * Merge stuffies * Merge to master * Fixes I guess * Fix client sync * Fix grid deserialization * Jank test * Fix deferral shitfuckery * Optimise and remove * Fixes * Just werk dam u * Optimisations * Bunch of fixes * FINALLY IT'S DONE * Fixes * Fix * Comment
This commit is contained in:
@@ -176,12 +176,18 @@ namespace Robust.Client.Debugging
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
var pTransform = physBody.GetTransform();
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
|
||||
var aabb = shape.ComputeAABB(pTransform, 0);
|
||||
worldHandle.DrawRect(aabb, Color.Blue, false);
|
||||
}
|
||||
|
||||
foreach (var joint in physBody.Joints)
|
||||
|
||||
@@ -64,19 +64,21 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(currentMap, viewport))
|
||||
{
|
||||
var mapGrid = (IMapGridInternal) grid;
|
||||
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
|
||||
|
||||
var worldPos = gridEnt.Transform.WorldPosition;
|
||||
var worldRot = gridEnt.Transform.WorldRotation;
|
||||
if (!_entityManager.ComponentManager.TryGetComponent<PhysicsComponent>(gridEnt.Uid, out var body)) continue;
|
||||
|
||||
foreach (var chunk in mapGrid.GetMapChunks(viewport))
|
||||
var transform = body.GetTransform();
|
||||
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
var chunkBounds = chunk.CalcWorldBounds(worldPos, worldRot);
|
||||
var aabb = chunkBounds.CalcBoundingBox();
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
var aabb = fixture.Shape.ComputeAABB(transform, i);
|
||||
|
||||
args.WorldHandle.DrawRect(chunkBounds, Color.Green.WithAlpha(0.2f), true);
|
||||
args.WorldHandle.DrawRect(aabb, Color.Red, false);
|
||||
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.2f));
|
||||
args.WorldHandle.DrawRect(aabb, Color.Red.WithAlpha(0.5f), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,11 +47,16 @@ namespace Robust.Server.Map
|
||||
// Seemed easier than having this method on GridFixtureSystem
|
||||
if (!TryGetGrid(chunk.GridId, out var grid) ||
|
||||
!ComponentManager.TryGetComponent(grid.GridEntityId, out PhysicsComponent? body) ||
|
||||
chunk.Fixture == null) return;
|
||||
chunk.Fixtures.Count == 0) return;
|
||||
|
||||
// TODO: Like MapManager injecting this is a PITA so need to work out an easy way to do it.
|
||||
// Maybe just add like a PostInject method that gets called way later?
|
||||
EntitySystem.Get<SharedBroadphaseSystem>().DestroyFixture(body, chunk.Fixture);
|
||||
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
|
||||
foreach (var fixture in chunk.Fixtures)
|
||||
{
|
||||
broadphaseSystem.DestroyFixture(body, fixture);
|
||||
}
|
||||
}
|
||||
|
||||
public GameStateMapData? GetStateData(GameTick fromTick)
|
||||
|
||||
@@ -11,6 +11,8 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
@@ -443,11 +445,47 @@ namespace Robust.Server.Maps
|
||||
/// </summary>
|
||||
private void ApplyGridFixtures()
|
||||
{
|
||||
var compManager = IoCManager.Resolve<IComponentManager>();
|
||||
var entManager = IoCManager.Resolve<IEntityManager>();
|
||||
var gridFixtures = EntitySystem.Get<GridFixtureSystem>();
|
||||
var broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
|
||||
foreach (var gridId in _mapManager.GetAllGrids())
|
||||
foreach (var grid in Grids)
|
||||
{
|
||||
gridFixtures.ProcessGrid(gridId.Index);
|
||||
var gridInternal = (IMapGridInternal) grid;
|
||||
var body = compManager.EnsureComponent<PhysicsComponent>(entManager.GetEntity(grid.GridEntityId));
|
||||
gridFixtures.ProcessGrid(gridInternal);
|
||||
|
||||
// Need to go through and double-check we don't have any hanging-on fixtures that
|
||||
// no longer apply (e.g. due to an update in GridFixtureSystem)
|
||||
var toRemove = new List<Fixture>();
|
||||
|
||||
foreach (var fixture in body.Fixtures)
|
||||
{
|
||||
var found = false;
|
||||
|
||||
foreach (var (_, chunk) in gridInternal.GetMapChunks())
|
||||
{
|
||||
foreach (var cFixture in chunk.Fixtures)
|
||||
{
|
||||
if (!cFixture.Equals(fixture)) continue;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (found) break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
toRemove.Add(fixture);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fixture in toRemove)
|
||||
{
|
||||
broadphaseSystem.DestroyFixture(fixture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,6 @@ namespace Robust.Server.Maps
|
||||
}
|
||||
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
chunk.RegenerateCollision();
|
||||
mapMan.SuppressOnTileChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
|
||||
namespace Robust.Server.Physics
|
||||
{
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.Physics
|
||||
{
|
||||
@@ -17,156 +7,5 @@ namespace Robust.Server.Physics
|
||||
/// </summary>
|
||||
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
|
||||
// We'll defer gridfixture updates during deserialization because MapLoader is interesting
|
||||
private Dictionary<GridId, HashSet<MapChunk>> _queuedFixtureUpdates = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
|
||||
SubscribeLocalEvent<RegenerateChunkCollisionEvent>(HandleCollisionRegenerate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue the chunk to generate (if cooldown > 0) or immediately process it.
|
||||
/// </summary>
|
||||
/// <param name="ev"></param>
|
||||
private void HandleCollisionRegenerate(RegenerateChunkCollisionEvent ev)
|
||||
{
|
||||
// TODO: Probably shouldn't do this but MapLoader can lead to a lot of ordering nonsense hence we
|
||||
// need to defer for it to ensure the fixtures get attached to the chunks properly.
|
||||
if (!EntityManager.EntityExists(_mapManager.GetGrid(ev.Chunk.GridId).GridEntityId))
|
||||
{
|
||||
if (!_queuedFixtureUpdates.TryGetValue(ev.Chunk.GridId, out var chunks))
|
||||
{
|
||||
chunks = new HashSet<MapChunk>();
|
||||
_queuedFixtureUpdates[ev.Chunk.GridId] = chunks;
|
||||
}
|
||||
|
||||
chunks.Add(ev.Chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
RegenerateCollision(ev.Chunk);
|
||||
}
|
||||
|
||||
public void ProcessGrid(GridId gridId)
|
||||
{
|
||||
if (!_queuedFixtureUpdates.TryGetValue(gridId, out var chunks))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mapManager.GridExists(gridId))
|
||||
{
|
||||
_queuedFixtureUpdates.Remove(gridId);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
RegenerateCollision(chunk);
|
||||
}
|
||||
|
||||
_queuedFixtureUpdates.Remove(gridId);
|
||||
}
|
||||
|
||||
private void RegenerateCollision(MapChunk chunk)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(chunk.GridId, out var grid) ||
|
||||
!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) return;
|
||||
|
||||
DebugTools.Assert(chunk.ValidTiles > 0);
|
||||
|
||||
// Currently this is gonna be hella simple.
|
||||
if (!gridEnt.TryGetComponent(out PhysicsComponent? physicsComponent))
|
||||
{
|
||||
Logger.ErrorS("physics", $"Trying to regenerate collision for {gridEnt} that doesn't have {nameof(physicsComponent)}");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Lots of stuff here etc etc, make changes to mapgridchunk.
|
||||
var bounds = chunk.CalcLocalBounds();
|
||||
|
||||
// So something goes on with the chunk's internal bounds caching where if there's no data the bound is 0 or something?
|
||||
if (bounds.IsEmpty()) return;
|
||||
|
||||
var origin = chunk.Indices * chunk.ChunkSize;
|
||||
bounds = bounds.Translated(origin);
|
||||
|
||||
// So we store a reference to the fixture on the chunk because it's easier to cross-reference it.
|
||||
// This is because when we get multiple fixtures per chunk there's no easy way to tell which the old one
|
||||
// corresponds with.
|
||||
// We also ideally want to avoid re-creating the fixture every time a tile changes and pushing that data
|
||||
// to the client hence we diff it.
|
||||
|
||||
// Additionally, we need to handle map deserialization where content may have stored its own data
|
||||
// on the grid (e.g. mass) which we want to preserve.
|
||||
var oldFixture = chunk.Fixture;
|
||||
|
||||
var polyShape = new PolygonShape();
|
||||
Span<Vector2> vertices = stackalloc Vector2[4];
|
||||
vertices[0] = bounds.BottomLeft;
|
||||
vertices[1] = bounds.BottomRight;
|
||||
vertices[2] = bounds.TopRight;
|
||||
vertices[3] = bounds.TopLeft;
|
||||
polyShape.SetVertices(vertices);
|
||||
|
||||
var newFixture = new Fixture(
|
||||
polyShape,
|
||||
MapGridHelpers.CollisionGroup,
|
||||
MapGridHelpers.CollisionGroup,
|
||||
true) {ID = GetChunkId(chunk),
|
||||
Body = physicsComponent};
|
||||
|
||||
// Check if we have an existing fixture on MapGrid
|
||||
var existingFixture = physicsComponent.GetFixture(newFixture.ID);
|
||||
var same = true;
|
||||
|
||||
// Some fucky shit but we gotta handle map deserialization.
|
||||
if (existingFixture is {Shape: PolygonShape poly})
|
||||
{
|
||||
var newPoly = (PolygonShape) newFixture.Shape;
|
||||
|
||||
if (!poly.EqualsApprox(newPoly))
|
||||
same = false;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
same = false;
|
||||
}
|
||||
|
||||
// TODO: Chunk will likely need multiple fixtures but future sloth problem lmao idiot
|
||||
if (same)
|
||||
{
|
||||
// If we're deserializing map this can occur so just update it.
|
||||
if (oldFixture == null && existingFixture != null)
|
||||
{
|
||||
chunk.Fixture = existingFixture;
|
||||
existingFixture.CollisionMask = MapGridHelpers.CollisionGroup;
|
||||
existingFixture.CollisionLayer = MapGridHelpers.CollisionGroup;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldFixture != null)
|
||||
_broadphase.DestroyFixture(physicsComponent, oldFixture);
|
||||
|
||||
_broadphase.CreateFixture(physicsComponent, newFixture);
|
||||
chunk.Fixture = newFixture;
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixture = oldFixture, NewFixture = newFixture});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GridFixtureChangeEvent : EntityEventArgs
|
||||
{
|
||||
public Fixture? OldFixture { get; init; }
|
||||
public Fixture? NewFixture { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Robust.Shared.Maths
|
||||
public readonly int Height => Math.Abs(Top - Bottom);
|
||||
public readonly Vector2i Size => new(Width, Height);
|
||||
|
||||
public readonly int Area => Width * Height;
|
||||
|
||||
public Box2i(Vector2i bottomLeft, Vector2i topRight)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
|
||||
@@ -454,6 +454,13 @@ namespace Robust.Shared
|
||||
* PHYSICS
|
||||
*/
|
||||
|
||||
// Grid fixtures
|
||||
/// <summary>
|
||||
/// I'ma be real with you: the only reason this exists is to get tests working.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> GenerateGridFixtures =
|
||||
CVarDef.Create("physics.grid_fixtures", true, CVar.REPLICATED);
|
||||
|
||||
// - Contacts
|
||||
public static readonly CVarDef<int> ContactMultithreadThreshold =
|
||||
CVarDef.Create("physics.contact_multithread_threshold", 32);
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Internal.TypeSystem;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
|
||||
|
||||
@@ -377,6 +374,17 @@ namespace Robust.Shared.GameObjects
|
||||
&& netSet.ContainsKey(netId);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T EnsureComponent<T>(IEntity entity) where T : Component, new()
|
||||
{
|
||||
if (TryGetComponent<T>(entity.Uid, out var component))
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
return AddComponent<T>(entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T GetComponent<T>(EntityUid uid)
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Robust.Shared.GameObjects
|
||||
private set
|
||||
{
|
||||
if (_gridId.Equals(value)) return;
|
||||
|
||||
_gridId = value;
|
||||
foreach (var transformComponent in Children)
|
||||
{
|
||||
|
||||
@@ -121,6 +121,8 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>True if the entity has a component with the given network ID, otherwise false.</returns>
|
||||
bool HasComponent(EntityUid uid, ushort netId);
|
||||
|
||||
T EnsureComponent<T>(IEntity entity) where T : Component, new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the component of a specific type.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public abstract class SharedGridFixtureSystem : EntitySystem
|
||||
{
|
||||
internal string GetChunkId(MapChunk chunk)
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
return $"grid_chunk-{chunk.Indices.X}-{chunk.Indices.Y}";
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(SharedBroadphaseSystem));
|
||||
IoCManager.Resolve<IConfigurationManager>().OnValueChanged(CVars.GenerateGridFixtures, SetEnabled, true);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
IoCManager.Resolve<IConfigurationManager>().UnsubValueChanged(CVars.GenerateGridFixtures, SetEnabled);
|
||||
}
|
||||
|
||||
private void SetEnabled(bool value) => _enabled = value;
|
||||
|
||||
internal void ProcessGrid(IMapGridInternal gridInternal)
|
||||
{
|
||||
// Just in case there's any deleted we'll ToArray
|
||||
foreach (var (_, chunk) in gridInternal.GetMapChunks().ToArray())
|
||||
{
|
||||
chunk.RegenerateCollision();
|
||||
}
|
||||
}
|
||||
|
||||
internal void RegenerateCollision(MapChunk chunk, List<Box2i> rectangles)
|
||||
{
|
||||
if (!_enabled) return;
|
||||
|
||||
if (!_mapManager.TryGetGrid(chunk.GridId, out var grid) ||
|
||||
!EntityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) return;
|
||||
|
||||
DebugTools.Assert(chunk.ValidTiles > 0);
|
||||
|
||||
if (!gridEnt.TryGetComponent(out PhysicsComponent? physicsComponent))
|
||||
{
|
||||
Logger.ErrorS("physics", $"Trying to regenerate collision for {gridEnt} that doesn't have {nameof(physicsComponent)}");
|
||||
return;
|
||||
}
|
||||
|
||||
var origin = chunk.Indices * chunk.ChunkSize;
|
||||
|
||||
// So we store a reference to the fixture on the chunk because it's easier to cross-reference it.
|
||||
// This is because when we get multiple fixtures per chunk there's no easy way to tell which the old one
|
||||
// corresponds with.
|
||||
// We also ideally want to avoid re-creating the fixture every time a tile changes and pushing that data
|
||||
// to the client hence we diff it.
|
||||
|
||||
// Additionally, we need to handle map deserialization where content may have stored its own data
|
||||
// on the grid (e.g. mass) which we want to preserve.
|
||||
var newFixtures = new List<Fixture>();
|
||||
|
||||
var oldFixtures = chunk.Fixtures.ToList();
|
||||
|
||||
Span<Vector2> vertices = stackalloc Vector2[4];
|
||||
|
||||
foreach (var rectangle in rectangles)
|
||||
{
|
||||
var bounds = rectangle.Translated(origin);
|
||||
var poly = new PolygonShape();
|
||||
|
||||
vertices[0] = bounds.BottomLeft;
|
||||
vertices[1] = bounds.BottomRight;
|
||||
vertices[2] = bounds.TopRight;
|
||||
vertices[3] = bounds.TopLeft;
|
||||
|
||||
poly.SetVertices(vertices);
|
||||
|
||||
var newFixture = new Fixture(
|
||||
poly,
|
||||
MapGridHelpers.CollisionGroup,
|
||||
MapGridHelpers.CollisionGroup,
|
||||
true) {ID = $"grid_chunk-{bounds.Left}-{bounds.Bottom}",
|
||||
Body = physicsComponent};
|
||||
|
||||
newFixtures.Add(newFixture);
|
||||
}
|
||||
|
||||
foreach (var oldFixture in chunk.Fixtures.ToArray())
|
||||
{
|
||||
var existing = false;
|
||||
|
||||
// Handle deleted / updated fixtures
|
||||
// (TODO: Check IDs and cross-reference for updates?)
|
||||
for (var i = newFixtures.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var fixture = newFixtures[i];
|
||||
if (!oldFixture.Equals(fixture)) continue;
|
||||
existing = true;
|
||||
newFixtures.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
|
||||
// Doesn't align with any new fixtures so delete
|
||||
if (existing) continue;
|
||||
|
||||
chunk.Fixtures.Remove(oldFixture);
|
||||
_broadphase.DestroyFixture(physicsComponent, oldFixture);
|
||||
}
|
||||
|
||||
// Anything remaining is a new fixture (or at least, may have not serialized onto the chunk yet).
|
||||
foreach (var fixture in newFixtures)
|
||||
{
|
||||
var existingFixture = physicsComponent.GetFixture(fixture.ID);
|
||||
// Check if it's the same (otherwise remove anyway).
|
||||
if (existingFixture?.Shape is PolygonShape poly &&
|
||||
poly.EqualsApprox((PolygonShape) fixture.Shape))
|
||||
{
|
||||
chunk.Fixtures.Add(existingFixture);
|
||||
continue;
|
||||
}
|
||||
|
||||
chunk.Fixtures.Add(fixture);
|
||||
_broadphase.CreateFixture(physicsComponent, fixture);
|
||||
}
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Uid,new GridFixtureChangeEvent {OldFixtures = oldFixtures, NewFixtures = chunk.Fixtures});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GridFixtureChangeEvent : EntityEventArgs
|
||||
{
|
||||
public List<Fixture> OldFixtures { get; init; } = default!;
|
||||
public List<Fixture> NewFixtures { get; init; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -101,17 +102,35 @@ namespace Robust.Shared.GameObjects
|
||||
Logger.DebugS("physics", $"Found {_controllers.Count} physics controllers.");
|
||||
|
||||
IoCManager.Resolve<IIslandManager>().Initialize();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange, true);
|
||||
}
|
||||
|
||||
private void HandlePhysicsMapInit(EntityUid uid, SharedPhysicsMapComponent component, ComponentInit args)
|
||||
{
|
||||
component.ContactManager = new ContactManager();
|
||||
IoCManager.InjectDependencies(component);
|
||||
component.BroadphaseSystem = Get<SharedBroadphaseSystem>();
|
||||
component.PhysicsSystem = this;
|
||||
component.ContactManager = new();
|
||||
component.ContactManager.Initialize();
|
||||
component.ContactManager.MapId = component.MapId;
|
||||
|
||||
component.ContactManager.KinematicControllerCollision += KinematicControllerCollision;
|
||||
}
|
||||
|
||||
private void OnAutoClearChange(bool value)
|
||||
{
|
||||
foreach (var component in ComponentManager.EntityQuery<SharedPhysicsMapComponent>(true))
|
||||
{
|
||||
component.AutoClearForces = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePhysicsMapRemove(EntityUid uid, SharedPhysicsMapComponent component, ComponentRemove args)
|
||||
{
|
||||
component.ContactManager.KinematicControllerCollision -= KinematicControllerCollision;
|
||||
component.ContactManager.Shutdown();
|
||||
}
|
||||
|
||||
public T GetController<T>() where T : VirtualController
|
||||
@@ -204,6 +223,9 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
MapManager.MapCreated -= HandleMapCreated;
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.UnsubValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
}
|
||||
|
||||
protected abstract void HandleMapCreated(object? sender, MapEventArgs eventArgs);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -8,118 +9,72 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
internal static class GridChunkPartition
|
||||
{
|
||||
public static void PartitionChunk(IMapChunk chunk, out Box2i bounds)
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="chunk"></param>
|
||||
/// <param name="bounds">The overall bounds that covers every rectangle.</param>
|
||||
/// <param name="rectangles">Each individual rectangle comprising the chunk's bounds</param>
|
||||
public static void PartitionChunk(IMapChunk chunk, out Box2i bounds, out List<Box2i> rectangles)
|
||||
{
|
||||
var size = chunk.ChunkSize;
|
||||
rectangles = new List<Box2i>();
|
||||
|
||||
// copy 2d img
|
||||
bool[,] image = new bool[size,size];
|
||||
// TODO: Use the existing PartitionChunk version because that one is likely faster and you can Span that shit.
|
||||
// Convert each line into boxes as long as they can be.
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
var origin = 0;
|
||||
var running = false;
|
||||
|
||||
for(ushort x=0;x<size;x++)
|
||||
for (ushort y = 0; y < size; y++)
|
||||
image[x, y] = !chunk.GetTile(x, y).IsEmpty;
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
if (!chunk.GetTile(x, y).IsEmpty)
|
||||
{
|
||||
running = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Partition(size, size, image, out var blocks, out var blockCount);
|
||||
// Still empty
|
||||
if (running)
|
||||
{
|
||||
rectangles.Add(new Box2i(origin, y, x, y + 1));
|
||||
}
|
||||
|
||||
origin = x + 1;
|
||||
running = false;
|
||||
}
|
||||
|
||||
if (running)
|
||||
rectangles.Add(new Box2i(origin, y, chunk.ChunkSize, y + 1));
|
||||
}
|
||||
|
||||
// Patch them together as available
|
||||
for (var i = rectangles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var box = rectangles[i];
|
||||
for (var j = i - 1; j >= 0; j--)
|
||||
{
|
||||
var other = rectangles[j];
|
||||
|
||||
// Gone down as far as we can go.
|
||||
if (other.Top < box.Bottom) break;
|
||||
|
||||
if (box.Left == other.Left && box.Right == other.Right)
|
||||
{
|
||||
box = new Box2i(box.Left, other.Bottom, box.Right, box.Top);
|
||||
rectangles[i] = box;
|
||||
rectangles.RemoveAt(j);
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bounds = new Box2i();
|
||||
|
||||
// convert blocks to rectangles array.
|
||||
for(int i=0;i< blockCount; i++)
|
||||
foreach (var rectangle in rectangles)
|
||||
{
|
||||
var block = blocks[i];
|
||||
|
||||
// block are in indices and rotated 90 degrees :(
|
||||
|
||||
var left = block.y1;
|
||||
var right = block.y2 + 1;
|
||||
var bottom = block.x1;
|
||||
var top = block.x2 + 1;
|
||||
|
||||
if(bounds.Size.Equals(Vector2i.Zero))
|
||||
bounds = new Box2i(left, bottom, right, top);
|
||||
else
|
||||
bounds = bounds.Union(new Box2i(left, bottom, right, top));
|
||||
}
|
||||
}
|
||||
|
||||
private struct Block
|
||||
{
|
||||
public int x1;
|
||||
public int x2;
|
||||
public int y1;
|
||||
public int y2;
|
||||
}
|
||||
|
||||
private static void Partition(in int L, in int W, in bool[,] img, out Block[] block, out int blockno)
|
||||
{
|
||||
// Credit: http://utopia.duth.gr/spiliot/papers/j7.pdf
|
||||
|
||||
block = new Block[L*W];
|
||||
|
||||
int[] p = new int[W];
|
||||
int[] c = new int[W];
|
||||
|
||||
int kp = 0;
|
||||
blockno = 0;
|
||||
|
||||
for (int y = 0; y < L; y++)
|
||||
{
|
||||
int x1 = 0;
|
||||
int x2 = 0;
|
||||
int kc = 0;
|
||||
bool intervalfound = false;
|
||||
int j_last = 0;
|
||||
int j_curr = 0;
|
||||
for (int x = 0; x < W; x++)
|
||||
{
|
||||
bool try2match = false;
|
||||
if (img[y,x] && !intervalfound)
|
||||
{
|
||||
intervalfound = true;
|
||||
x1 = x;
|
||||
}
|
||||
if (!img[y,x] && intervalfound)
|
||||
{
|
||||
intervalfound = false;
|
||||
x2 = x - 1;
|
||||
try2match = true;
|
||||
}
|
||||
if (x == W - 1 && img[y,x] && intervalfound)
|
||||
{
|
||||
x2 = x;
|
||||
try2match = true;
|
||||
}
|
||||
if (try2match)
|
||||
{
|
||||
bool intervalmatched = false;
|
||||
for (int j = j_last; j < kp && x1 >= block[p[j]].x1; j++)
|
||||
if (x1 == block[p[j]].x1 && x2 == block[p[j]].x2)
|
||||
{
|
||||
c[kc] = p[j];
|
||||
block[p[j]].y2 = y;
|
||||
intervalmatched = true;
|
||||
j_curr = j;
|
||||
}
|
||||
|
||||
j_last = j_curr;
|
||||
if (!intervalmatched)
|
||||
{
|
||||
block[blockno].x1 = x1;
|
||||
block[blockno].x2 = x2;
|
||||
block[blockno].y1 = y;
|
||||
block[blockno].y2 = y;
|
||||
c[kc] = blockno++;
|
||||
}
|
||||
|
||||
if (!intervalmatched)
|
||||
kc++;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < kc; i++)
|
||||
p[i] = c[i];
|
||||
|
||||
kp = kc;
|
||||
bounds = bounds.IsEmpty() ? rectangle : bounds.Union(rectangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Shared.Map
|
||||
/// <inheritdoc />
|
||||
internal interface IMapChunkInternal : IMapChunk
|
||||
{
|
||||
Fixture? Fixture { get; set; }
|
||||
List<Fixture> Fixtures { get; set; }
|
||||
|
||||
bool SuppressCollisionRegeneration { get; set; }
|
||||
|
||||
|
||||
@@ -27,12 +27,7 @@ namespace Robust.Shared.Map
|
||||
/// <param name="pos">Position of the entity in local tile indices.</param>
|
||||
void AnchoredEntDirty(Vector2i pos);
|
||||
|
||||
/// <summary>
|
||||
/// Regenerates anything that is based on chunk collision data.
|
||||
/// This wouldn't even be separate if not for the whole "ability to suppress automatic collision regeneration" thing.
|
||||
/// As it is, YamlGridSerializer performs manual collision regeneration and that wasn't properly getting propagated to the grid. Thus, this needs to exist.
|
||||
/// </summary>
|
||||
void NotifyChunkCollisionRegenerated(MapChunk chunk);
|
||||
void UpdateAABB();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the chunk at the given indices. If the chunk does not exist,
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Robust.Shared.Map
|
||||
|
||||
private Box2i _cachedBounds;
|
||||
|
||||
public Fixture? Fixture { get; set; }
|
||||
public List<Fixture> Fixtures { get; set; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameTick LastTileModifiedTick { get; private set; }
|
||||
@@ -284,8 +284,13 @@ namespace Robust.Shared.Map
|
||||
}
|
||||
|
||||
// generate collision rects
|
||||
GridChunkPartition.PartitionChunk(this, out _cachedBounds);
|
||||
_grid.NotifyChunkCollisionRegenerated(this);
|
||||
GridChunkPartition.PartitionChunk(this, out _cachedBounds, out var rectangles);
|
||||
|
||||
_grid.UpdateAABB();
|
||||
|
||||
// TryGet because unit tests YAY
|
||||
if (ValidTiles > 0 && EntitySystem.TryGet(out SharedGridFixtureSystem? system))
|
||||
system.RegenerateCollision(this, rectangles);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Robust.Shared.Map
|
||||
/// Expands the AABB for this grid when a new tile is added. If the tile is already inside the existing AABB,
|
||||
/// nothing happens. If it is outside, the AABB is expanded to fit the new tile.
|
||||
/// </summary>
|
||||
private void UpdateAABB()
|
||||
public void UpdateAABB()
|
||||
{
|
||||
LocalBounds = new Box2();
|
||||
foreach (var chunk in _chunks.Values)
|
||||
@@ -195,24 +195,6 @@ namespace Robust.Shared.Map
|
||||
_mapManager.RaiseOnTileChanged(tileRef, oldTile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void NotifyChunkCollisionRegenerated(MapChunk chunk)
|
||||
{
|
||||
// TODO: Ideally we wouldn't have LocalBounds on the grid and we could just treat it like a physics object
|
||||
// (eventually, well into the future).
|
||||
// For now we'll just attach a fixture to each chunk.
|
||||
|
||||
// Not raising directed because the grid's EntityUid isn't set yet.
|
||||
// Don't call GridFixtureSystem directly because it's server-only.
|
||||
if (chunk.ValidTiles > 0)
|
||||
IoCManager
|
||||
.Resolve<IEntityManager>()
|
||||
.EventBus
|
||||
.RaiseEvent(EventSource.Local, new RegenerateChunkCollisionEvent(chunk));
|
||||
|
||||
UpdateAABB();
|
||||
}
|
||||
|
||||
#region TileAccess
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -19,8 +19,6 @@ namespace Robust.Shared.Map
|
||||
[Dependency] protected readonly IComponentManager ComponentManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private SharedGridFixtureSystem _gridFixtures = default!;
|
||||
|
||||
public IGameTiming GameTiming => _gameTiming;
|
||||
|
||||
public IEntityManager EntityManager => _entityManager;
|
||||
@@ -85,8 +83,6 @@ namespace Robust.Shared.Map
|
||||
|
||||
Logger.DebugS("map", "Starting...");
|
||||
|
||||
_gridFixtures = EntitySystem.Get<SharedGridFixtureSystem>();
|
||||
|
||||
if (!_maps.Contains(MapId.Nullspace))
|
||||
{
|
||||
CreateMap(MapId.Nullspace);
|
||||
@@ -534,7 +530,9 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
foreach (var (_, mapGrid) in _grids)
|
||||
{
|
||||
if (mapGrid.ParentMapId != mapId || !mapGrid.WorldBounds.Contains(worldPos))
|
||||
// So not sure if doing the transform for WorldBounds and early out here is faster than just
|
||||
// checking if we have a relevant chunk, need to profile.
|
||||
if (mapGrid.ParentMapId != mapId)
|
||||
continue;
|
||||
|
||||
// Turn the worldPos into a localPos and work out the relevant chunk we need to check
|
||||
@@ -550,26 +548,13 @@ namespace Robust.Shared.Map
|
||||
var chunkIndices = mapGrid.GridTileToChunkIndices(tile);
|
||||
|
||||
if (!mapGrid.HasChunk(chunkIndices)) continue;
|
||||
if (!gridEnt.TryGetComponent(out PhysicsComponent? body)) continue;
|
||||
|
||||
var gridPos = gridEnt.Transform.WorldPosition;
|
||||
var gridRot = gridEnt.Transform.WorldRotation;
|
||||
|
||||
var transform = new Transform(gridPos, (float) gridRot);
|
||||
// TODO: Client never associates Fixtures with chunks hence we need to look it up by ID.
|
||||
var chunk = mapGrid.GetChunk(chunkIndices);
|
||||
var id = _gridFixtures.GetChunkId((MapChunk) chunk);
|
||||
var fixture = body.GetFixture(id);
|
||||
var chunkTile = chunk.GetTileRef(chunk.GridTileToChunkTile(tile));
|
||||
|
||||
if (fixture == null) continue;
|
||||
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
// TODO: Use CollisionManager once it's done.
|
||||
if (!fixture.Shape.ComputeAABB(transform, i).Contains(worldPos)) continue;
|
||||
grid = mapGrid;
|
||||
return true;
|
||||
}
|
||||
if (chunkTile.Tile.IsEmpty) continue;
|
||||
grid = mapGrid;
|
||||
return true;
|
||||
}
|
||||
|
||||
grid = null;
|
||||
@@ -582,45 +567,56 @@ namespace Robust.Shared.Map
|
||||
return TryFindGridAt(mapCoordinates.MapId, mapCoordinates.Position, out grid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2 worldArea)
|
||||
{
|
||||
// So despite the fact we have accurate bounds the reason I didn't make this tile-based is because
|
||||
// at some stage we may want to overwrite the default behavior e.g. if you allow diagonals
|
||||
foreach (var (_, grid) in _grids)
|
||||
{
|
||||
if (grid.ParentMapId != mapId || !grid.WorldBounds.Intersects(worldArea)) continue;
|
||||
|
||||
var found = false;
|
||||
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
|
||||
var body = gridEnt.GetComponent<PhysicsComponent>();
|
||||
var transform = new Transform(gridEnt.Transform.WorldPosition, (float) gridEnt.Transform.WorldRotation);
|
||||
var anyChunks = false;
|
||||
var xformComp = _entityManager.ComponentManager.GetComponent<TransformComponent>(gridEnt.Uid);
|
||||
var transform = new Transform(xformComp.WorldPosition, xformComp.WorldRotation);
|
||||
|
||||
foreach (var chunk in grid.GetMapChunks(worldArea))
|
||||
if (_entityManager.ComponentManager.TryGetComponent<PhysicsComponent>(gridEnt.Uid, out var body))
|
||||
{
|
||||
anyChunks = true;
|
||||
var id = _gridFixtures.GetChunkId((MapChunk) chunk);
|
||||
var fixture = body.GetFixture(id);
|
||||
var intersects = false;
|
||||
|
||||
if (fixture == null) continue;
|
||||
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
foreach (var chunk in grid.GetMapChunks(worldArea))
|
||||
{
|
||||
// TODO: Need to use CollisionManager to test detailed overlap
|
||||
if (!fixture.Shape.ComputeAABB(transform, i).Intersects(worldArea)) continue;
|
||||
yield return grid;
|
||||
found = true;
|
||||
break;
|
||||
foreach (var fixture in chunk.Fixtures)
|
||||
{
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
if (!fixture.Shape.ComputeAABB(transform, i).Intersects(worldArea)) continue;
|
||||
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (intersects) break;
|
||||
}
|
||||
|
||||
if (intersects) break;
|
||||
}
|
||||
|
||||
if (found) break;
|
||||
if (intersects)
|
||||
{
|
||||
yield return grid;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyChunks && worldArea.Contains(transform.Position))
|
||||
if (grid.ChunkCount == 0 && worldArea.Contains(transform.Position))
|
||||
{
|
||||
yield return grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IMapGrid> FindGridsIntersecting(MapId mapId, Box2Rotated worldArea)
|
||||
{
|
||||
var worldBounds = worldArea.CalcBoundingBox();
|
||||
@@ -629,33 +625,40 @@ namespace Robust.Shared.Map
|
||||
{
|
||||
if (grid.ParentMapId != mapId || !grid.WorldBounds.Intersects(worldBounds)) continue;
|
||||
|
||||
var found = false;
|
||||
var gridEnt = _entityManager.GetEntity(grid.GridEntityId);
|
||||
var body = gridEnt.GetComponent<PhysicsComponent>();
|
||||
var transform = new Transform(gridEnt.Transform.WorldPosition, (float) gridEnt.Transform.WorldRotation);
|
||||
var anyChunks = false;
|
||||
var xformComp = _entityManager.ComponentManager.GetComponent<TransformComponent>(gridEnt.Uid);
|
||||
var transform = new Transform(xformComp.WorldPosition, xformComp.WorldRotation);
|
||||
|
||||
foreach (var chunk in grid.GetMapChunks(worldArea))
|
||||
if (_entityManager.ComponentManager.TryGetComponent<PhysicsComponent>(gridEnt.Uid, out var body))
|
||||
{
|
||||
anyChunks = true;
|
||||
var id = _gridFixtures.GetChunkId((MapChunk) chunk);
|
||||
var fixture = body.GetFixture(id);
|
||||
var intersects = false;
|
||||
|
||||
if (fixture == null) continue;
|
||||
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
foreach (var chunk in grid.GetMapChunks(worldArea))
|
||||
{
|
||||
// TODO: Need to use CollisionManager to test detailed overlap
|
||||
if (!fixture.Shape.ComputeAABB(transform, i).Intersects(worldBounds)) continue;
|
||||
yield return grid;
|
||||
found = true;
|
||||
break;
|
||||
foreach (var fixture in chunk.Fixtures)
|
||||
{
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
if (!fixture.Shape.ComputeAABB(transform, i).Intersects(worldBounds)) continue;
|
||||
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (intersects) break;
|
||||
}
|
||||
|
||||
if (intersects) break;
|
||||
}
|
||||
|
||||
if (found) break;
|
||||
if (intersects)
|
||||
{
|
||||
yield return grid;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyChunks && worldArea.Contains(transform.Position))
|
||||
if (grid.ChunkCount == 0 && worldArea.Contains(transform.Position))
|
||||
{
|
||||
yield return grid;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -40,14 +39,14 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IIslandManager _islandManager = default!;
|
||||
|
||||
private SharedBroadphaseSystem _broadphaseSystem = default!;
|
||||
private SharedPhysicsSystem _physicsSystem = default!;
|
||||
internal SharedBroadphaseSystem BroadphaseSystem = default!;
|
||||
internal SharedPhysicsSystem PhysicsSystem = default!;
|
||||
|
||||
public override string Name => "PhysicsMap";
|
||||
|
||||
internal ContactManager ContactManager = default!;
|
||||
|
||||
private bool _autoClearForces;
|
||||
public bool AutoClearForces;
|
||||
|
||||
/// <summary>
|
||||
/// Change the global gravity vector.
|
||||
@@ -123,34 +122,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
public MapId MapId => Owner.Transform.MapID;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_broadphaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
_physicsSystem = EntitySystem.Get<SharedPhysicsSystem>();
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
ContactManager.Initialize();
|
||||
ContactManager.MapId = MapId;
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.OnValueChanged(CVars.AutoClearForces, OnAutoClearChange, true);
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
ContactManager.Shutdown();
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
configManager.UnsubValueChanged(CVars.AutoClearForces, OnAutoClearChange);
|
||||
}
|
||||
|
||||
private void OnAutoClearChange(bool value)
|
||||
{
|
||||
_autoClearForces = value;
|
||||
}
|
||||
|
||||
#region AddRemove
|
||||
public void AddAwakeBody(PhysicsComponent body)
|
||||
{
|
||||
@@ -217,7 +188,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// If the joint prevents collisions, then flag any contacts for filtering.
|
||||
if (!joint.CollideConnected)
|
||||
{
|
||||
_physicsSystem.FilterContactsForJoint(joint);
|
||||
PhysicsSystem.FilterContactsForJoint(joint);
|
||||
}
|
||||
|
||||
bodyA.Dirty();
|
||||
@@ -399,12 +370,12 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Box2D does this at the end of a step and also here when there's a fixture update.
|
||||
// Given external stuff can move bodies we'll just do this here.
|
||||
// Unfortunately this NEEDS to be predicted to make pushing remotely fucking good.
|
||||
_broadphaseSystem.FindNewContacts(MapId);
|
||||
BroadphaseSystem.FindNewContacts(MapId);
|
||||
|
||||
var invDt = frameTime > 0.0f ? 1.0f / frameTime : 0.0f;
|
||||
var dtRatio = _invDt0 * frameTime;
|
||||
|
||||
foreach (var controller in _physicsSystem.Controllers)
|
||||
foreach (var controller in PhysicsSystem.Controllers)
|
||||
{
|
||||
controller.UpdateBeforeMapSolve(prediction, this, frameTime);
|
||||
}
|
||||
@@ -422,13 +393,13 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
// TODO: SolveTOI
|
||||
|
||||
foreach (var controller in _physicsSystem.Controllers)
|
||||
foreach (var controller in PhysicsSystem.Controllers)
|
||||
{
|
||||
controller.UpdateAfterMapSolve(prediction, this, frameTime);
|
||||
}
|
||||
|
||||
// Box2d recommends clearing (if you are) during fixed updates rather than variable if you are using it
|
||||
if (!prediction && _autoClearForces)
|
||||
if (!prediction && AutoClearForces)
|
||||
ClearForces();
|
||||
|
||||
_invDt0 = invDt;
|
||||
|
||||
@@ -504,6 +504,11 @@ namespace Robust.Shared.Physics
|
||||
CreateFixture(body, fixture);
|
||||
}
|
||||
|
||||
public void DestroyFixture(Fixture fixture)
|
||||
{
|
||||
DestroyFixture(fixture.Body, fixture);
|
||||
}
|
||||
|
||||
public void DestroyFixture(PhysicsComponent body, Fixture fixture)
|
||||
{
|
||||
// TODO: Assert world locked
|
||||
|
||||
@@ -3,14 +3,19 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using NUnit.Framework;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Physics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
using GridFixtureSystem = Robust.Client.GameObjects.GridFixtureSystem;
|
||||
|
||||
namespace Robust.UnitTesting
|
||||
{
|
||||
@@ -68,7 +73,6 @@ namespace Robust.UnitTesting
|
||||
|
||||
// Required systems
|
||||
var systems = IoCManager.Resolve<IEntitySystemManager>();
|
||||
systems.LoadExtraSystemType<GridFixtureSystem>();
|
||||
systems.Initialize();
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
@@ -102,6 +106,11 @@ namespace Robust.UnitTesting
|
||||
compFactory.RegisterClass<EntityLookupComponent>();
|
||||
}
|
||||
|
||||
if (!compFactory.AllRegisteredTypes.Contains(typeof(SharedPhysicsMapComponent)))
|
||||
{
|
||||
compFactory.RegisterClass<PhysicsMapComponent>();
|
||||
}
|
||||
|
||||
if(entMan.EventBus == null)
|
||||
{
|
||||
entMan.Startup();
|
||||
|
||||
@@ -4,13 +4,11 @@ using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Maps;
|
||||
using Robust.Server.Physics;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -105,6 +103,7 @@ entities:
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var mapId = map.CreateMap();
|
||||
map.GetMapEntity(mapId).EnsureComponent<PhysicsMapComponent>();
|
||||
var mapLoad = IoCManager.Resolve<IMapLoader>();
|
||||
var grid = mapLoad.LoadBlueprint(mapId, "/TestMap.yml");
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using Robust.Shared.Maths;
|
||||
namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
[TestFixture, Parallelizable, TestOf(typeof(GridChunkPartition))]
|
||||
internal class GridChunkPartition_Tests
|
||||
internal class GridChunkPartition_Tests : RobustUnitTest
|
||||
{
|
||||
// origin is top left
|
||||
private static readonly int[] _testMiscTiles = {
|
||||
@@ -24,7 +24,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
var chunk = ChunkFactory(4, _testMiscTiles);
|
||||
|
||||
// Act
|
||||
GridChunkPartition.PartitionChunk(chunk, out var bounds);
|
||||
GridChunkPartition.PartitionChunk(chunk, out var bounds, out _);
|
||||
|
||||
// box origin is top left
|
||||
// algorithm goes down columns of array, starting on left side, then moves right, expanding rectangles to the right
|
||||
@@ -53,7 +53,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
var chunk = ChunkFactory(4, _testJoinTiles);
|
||||
|
||||
// Act
|
||||
GridChunkPartition.PartitionChunk(chunk, out var bounds);
|
||||
GridChunkPartition.PartitionChunk(chunk, out var bounds, out _);
|
||||
|
||||
Assert.That(bounds, Is.EqualTo(new Box2i(1, 0, 3, 3)));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Physics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -23,7 +21,6 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var gridFixtures = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GridFixtureSystem>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
@@ -47,14 +44,15 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
// Now do 2 tiles (same chunk)
|
||||
grid.SetTile(Vector2i.One, new Tile(1));
|
||||
|
||||
Assert.That(gridBody.Fixtures.Count, Is.EqualTo(1));
|
||||
bounds = gridBody.Fixtures[0].Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
// Because it's a diagonal tile it will actually be 2x2 area (until we get accurate hitboxes anyway).
|
||||
Assert.That(MathHelper.CloseTo(Box2.Area(bounds), 4.0f, 0.1f));
|
||||
|
||||
// If we add a new chunk should be 2 now
|
||||
grid.SetTile(new Vector2i(-1, -1), new Tile(1));
|
||||
Assert.That(gridBody.Fixtures.Count, Is.EqualTo(2));
|
||||
bounds = gridBody.Fixtures[0].Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
|
||||
|
||||
// Even if we add a new tile old fixture should stay the same if they don't connect.
|
||||
Assert.That(MathHelper.CloseTo(Box2.Area(bounds), 1.0f, 0.1f));
|
||||
|
||||
// If we add a new chunk should be 3 now
|
||||
grid.SetTile(new Vector2i(-1, -1), new Tile(1));
|
||||
Assert.That(gridBody.Fixtures.Count, Is.EqualTo(3));
|
||||
|
||||
gridBody.LinearVelocity = Vector2.One;
|
||||
Assert.That(gridBody.LinearVelocity.Length, Is.EqualTo(0f));
|
||||
|
||||
@@ -11,7 +11,7 @@ using Robust.Shared.Timing;
|
||||
namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
[TestFixture, Parallelizable, TestOf(typeof(MapChunk))]
|
||||
class MapChunk_Tests
|
||||
sealed class MapChunk_Tests : RobustUnitTest
|
||||
{
|
||||
[Test]
|
||||
public void GetChunkSize()
|
||||
|
||||
Reference in New Issue
Block a user