diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 103ea0fe4..284fff122 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -35,12 +35,14 @@ END TEMPLATE--> ### Breaking changes -*None yet* +* ChildCount and ComputeAABB for IPhysShape were moved to SharedPhysicsSystem. +* Radius is now validated for physics shapes and negative values are no longer allowed. ### New features * Add a `OnlyRotation` property to MoveEvent where the EntityCoordinates remain the same. * ValueList now implements IList and not just IEnumerable. +* SharedPhysicsSystem now exposes TestOverlap and the collision manifold methods. ### Bugfixes diff --git a/Robust.Client/Debugging/DebugPhysicsSystem.cs b/Robust.Client/Debugging/DebugPhysicsSystem.cs index d34944a41..7f6eb1c36 100644 --- a/Robust.Client/Debugging/DebugPhysicsSystem.cs +++ b/Robust.Client/Debugging/DebugPhysicsSystem.cs @@ -135,7 +135,7 @@ public override void HandlePreSolve(Contact contact, in Manifold oldManifold) var state1 = new PointState[2]; var state2 = new PointState[2]; - CollisionManager.GetPointStates(ref state1, ref state2, oldManifold, manifold); + SharedPhysicsSystem.GetPointStates(ref state1, ref state2, oldManifold, manifold); Span points = stackalloc Vector2[2]; var transformA = _physics.GetPhysicsTransform(contact.EntityA); @@ -317,9 +317,9 @@ private void DrawWorld(DrawingHandleWorld worldHandle, OverlayDrawArgs args) foreach (var fixture in _entityManager.GetComponent(physBody).Fixtures.Values) { - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < _physicsSystem.GetChildCount(fixture.Shape); i++) { - var shapeBB = fixture.Shape.ComputeAABB(xform, i); + var shapeBB = _physicsSystem.ComputeAABB(fixture.Shape, xform, i); aabb = aabb?.Union(shapeBB) ?? shapeBB; } } diff --git a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs index 1a60f7bd9..ffc66e2e8 100644 --- a/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/GridChunkBoundsDebugSystem.cs @@ -9,6 +9,7 @@ using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; namespace Robust.Client.GameObjects @@ -20,6 +21,7 @@ public sealed partial class GridChunkBoundsDebugSystem : EntitySystem [Dependency] private IOverlayManager _overlayManager = default!; [Dependency] private TransformSystem _transform = default!; [Dependency] private SharedMapSystem _map = default!; + [Dependency] private SharedPhysicsSystem _physicsSystem = default!; private GridChunkBoundsOverlay? _overlay; @@ -40,7 +42,8 @@ public bool Enabled _eyeManager, _mapManager, _transform, - _map); + _map, + _physicsSystem); _overlayManager.AddOverlay(_overlay); } @@ -62,18 +65,20 @@ internal sealed class GridChunkBoundsOverlay : Overlay private readonly IMapManager _mapManager; private readonly SharedTransformSystem _transformSystem; private readonly SharedMapSystem _mapSystem; + private readonly SharedPhysicsSystem _physicsSystem; public override OverlaySpace Space => OverlaySpace.WorldSpace; private List> _grids = new(); - public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager, SharedTransformSystem transformSystem, SharedMapSystem mapSystem) + public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager, SharedTransformSystem transformSystem, SharedMapSystem mapSystem, SharedPhysicsSystem physicsSystem) { _entityManager = entManager; _eyeManager = eyeManager; _mapManager = mapManager; _transformSystem = transformSystem; _mapSystem = mapSystem; + _physicsSystem = physicsSystem; } protected internal override void Draw(in OverlayDrawArgs args) @@ -110,9 +115,9 @@ protected internal override void Draw(in OverlayDrawArgs args) worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, Color.Green.WithAlpha(0.2f)); - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < _physicsSystem.GetChildCount(fixture.Shape); i++) { - var aabb = fixture.Shape.ComputeAABB(transform, i); + var aabb = _physicsSystem.ComputeAABB(fixture.Shape, transform, i); args.WorldHandle.DrawRect(aabb, Color.Red.WithAlpha(0.5f), false); } diff --git a/Robust.Client/Physics/PhysicsSystem.Predict.cs b/Robust.Client/Physics/PhysicsSystem.Predict.cs index 5d8c130c7..2f90fff45 100644 --- a/Robust.Client/Physics/PhysicsSystem.Predict.cs +++ b/Robust.Client/Physics/PhysicsSystem.Predict.cs @@ -164,7 +164,7 @@ internal void UpdateIsTouching(List toUpdate) continue; } - if (indexA >= fixtureA.Proxies.Length || indexB >= fixtureB.Proxies.Length) + if (indexA >= fixtureA.ProxyCount || indexB >= fixtureB.ProxyCount) continue; var broadphaseA = xformA.Broadphase?.Uid; diff --git a/Robust.Server.Testing/RobustServerSimulation.cs b/Robust.Server.Testing/RobustServerSimulation.cs index 66eb92d86..fde608249 100644 --- a/Robust.Server.Testing/RobustServerSimulation.cs +++ b/Robust.Server.Testing/RobustServerSimulation.cs @@ -256,7 +256,6 @@ public ISimulation InitializeInstance() container.Register(); container.Register(); container.Register(); - container.Register(); container.Register(); container.Register(); container.Register(); diff --git a/Robust.Shared.IntegrationTests/Map/GridFixtures_Tests.cs b/Robust.Shared.IntegrationTests/Map/GridFixtures_Tests.cs index f7374c83b..ef67fc885 100644 --- a/Robust.Shared.IntegrationTests/Map/GridFixtures_Tests.cs +++ b/Robust.Shared.IntegrationTests/Map/GridFixtures_Tests.cs @@ -77,7 +77,7 @@ await server.WaitAssertion(() => Assert.That(manager.FixtureCount, Is.EqualTo(1)); // Also should only be a single tile. - var bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0); + var bounds = physSystem.ComputeAABB(manager.Fixtures.First().Value.Shape, new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0); // Poly probably has Box2D's radius added to it so won't be a unit square Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f)); @@ -85,7 +85,7 @@ await server.WaitAssertion(() => mapSystem.SetTile(grid, new Vector2i(0, 1), new Tile(1)); Assert.That(manager.FixtureCount, Is.EqualTo(1)); - bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0); + bounds = physSystem.ComputeAABB(manager.Fixtures.First().Value.Shape, 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.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f)); diff --git a/Robust.Shared.IntegrationTests/Physics/Broadphase_Test.cs b/Robust.Shared.IntegrationTests/Physics/Broadphase_Test.cs index cb87f2dba..154809fa8 100644 --- a/Robust.Shared.IntegrationTests/Physics/Broadphase_Test.cs +++ b/Robust.Shared.IntegrationTests/Physics/Broadphase_Test.cs @@ -1,3 +1,4 @@ +using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; @@ -144,11 +145,14 @@ public void ReparentBroadphase() Assert.That(fixture.ProxyCount, Is.EqualTo(1)); Assert.That(broadphase.StaticSundriesTree, Does.Not.Contain(ent)); - Assert.That(broadphase.StaticTree.GetProxy(fixture.Proxies[0].ProxyId)!.Equals(fixture.Proxies[0])); + var proxy = fixture.Proxies[0]; + Assert.That(broadphase.StaticTree.GetProxy(proxy.ProxyId)!.Equals(proxy)); // Now check we go to the map's tree correctly. xformSys.SetCoordinates(ent, new EntityCoordinates(mapEnt, Vector2.One)); - Assert.That(entManager.GetComponent(mapEnt).StaticTree.GetProxy(fixture.Proxies[0].ProxyId)!.Equals(fixture.Proxies[0])); + + proxy = fixture.Proxies[0]; + Assert.That(entManager.GetComponent(mapEnt).StaticTree.GetProxy(proxy.ProxyId)!.Equals(proxy)); Assert.That(xform.Broadphase!.Value.Uid.Equals(mapEnt)); } diff --git a/Robust.Shared.IntegrationTests/Physics/FixtureShape_Test.cs b/Robust.Shared.IntegrationTests/Physics/FixtureShape_Test.cs index 2615babbb..0b01369d1 100644 --- a/Robust.Shared.IntegrationTests/Physics/FixtureShape_Test.cs +++ b/Robust.Shared.IntegrationTests/Physics/FixtureShape_Test.cs @@ -34,6 +34,12 @@ public void TestCirclePoint() Assert.That(_shapeManager.TestPoint(circle, transform, posC), Is.EqualTo(true)); } + [Test] + public void TestCircleNegativeRadiusThrows() + { + Assert.Throws(() => new PhysShapeCircle(-0.5f)); + } + [Test] public void TestEdgePoint() { diff --git a/Robust.Shared.IntegrationTests/Physics/ManifoldManager_Test.cs b/Robust.Shared.IntegrationTests/Physics/ManifoldManager_Test.cs index 025fef85c..c2be84976 100644 --- a/Robust.Shared.IntegrationTests/Physics/ManifoldManager_Test.cs +++ b/Robust.Shared.IntegrationTests/Physics/ManifoldManager_Test.cs @@ -1,19 +1,21 @@ using System.Numerics; using NUnit.Framework; -using Robust.Shared.IoC; +using Robust.Shared.GameObjects; using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; +using Robust.UnitTesting.Server; namespace Robust.UnitTesting.Shared.Physics { [TestFixture] - [TestOf(typeof(IManifoldManager))] - internal sealed class ManifoldManager_Test : OurRobustUnitTest + [TestOf(typeof(SharedPhysicsSystem))] + internal sealed class ManifoldManager_Test { - private IManifoldManager _manifoldManager = default!; + private SharedPhysicsSystem _physics = default!; private PhysShapeCircle _circleA = default!; private PhysShapeCircle _circleB = default!; @@ -24,7 +26,10 @@ internal sealed class ManifoldManager_Test : OurRobustUnitTest [OneTimeSetUp] public void Setup() { - _manifoldManager = new CollisionManager(); + var sim = RobustServerSimulation + .NewSimulation() + .InitializeInstance(); + _physics = sim.Resolve().System(); _circleA = new PhysShapeCircle(0.5f); _circleB = new PhysShapeCircle(0.5f); _polyA = new PolygonShape(); @@ -40,15 +45,15 @@ public void TestCircleCollision() var transformB = new Transform(Vector2.One, 0f); // No overlap - Assert.That(_manifoldManager.TestOverlap(_circleA, 0, _circleB, 0, transformA, transformB), Is.EqualTo(false)); + Assert.That(_physics.TestOverlap(_circleA, 0, _circleB, 0, transformA, transformB), Is.EqualTo(false)); // Overlap directly transformA = new Transform(transformB.Position, 0f); - Assert.That(_manifoldManager.TestOverlap(_circleA, 0, _circleB, 0, transformA, transformB), Is.EqualTo(true)); + Assert.That(_physics.TestOverlap(_circleA, 0, _circleB, 0, transformA, transformB), Is.EqualTo(true)); // Overlap on edge transformA.Position = transformB.Position + new Vector2(0.5f, 0.0f); - Assert.That(_manifoldManager.TestOverlap(_circleA, 0, _circleB, 0, transformA, transformB), Is.EqualTo(true)); + Assert.That(_physics.TestOverlap(_circleA, 0, _circleB, 0, transformA, transformB), Is.EqualTo(true)); } [Test] @@ -58,18 +63,18 @@ public void TestPolyCollisions() var transformB = new Transform(Vector2.One, 0f); // No overlap - Assert.That(_manifoldManager.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(false)); + Assert.That(_physics.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(false)); // Overlap directly transformA = new Transform(transformB.Position, 0f); - Assert.That(_manifoldManager.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(true)); + Assert.That(_physics.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(true)); // Overlap on edge transformA.Position = transformB.Position + new Vector2(0.5f, 0.0f); - Assert.That(_manifoldManager.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(true)); + Assert.That(_physics.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(true)); transformA.Quaternion2D = transformA.Quaternion2D.Set(45f); - Assert.That(_manifoldManager.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(true)); + Assert.That(_physics.TestOverlap(_polyA, 0, _polyB, 0, transformA, transformB), Is.EqualTo(true)); } [Test] @@ -98,7 +103,7 @@ public void TestPolyOnPolyManifolds() } ) }; - _manifoldManager.CollidePolygons(ref manifold, _polyA, transformA, _polyB, transformB); + _physics.CollidePolygons(ref manifold, _polyA, transformA, _polyB, transformB); for (var i = 0; i < manifold.PointCount; i++) { diff --git a/Robust.Shared.IntegrationTests/Physics/PhysicsComponent_Test.cs b/Robust.Shared.IntegrationTests/Physics/PhysicsComponent_Test.cs index 70d9b64cd..622bbefc2 100644 --- a/Robust.Shared.IntegrationTests/Physics/PhysicsComponent_Test.cs +++ b/Robust.Shared.IntegrationTests/Physics/PhysicsComponent_Test.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.IoC; +using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; @@ -11,6 +12,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Systems; +using Robust.Shared.Utility; namespace Robust.UnitTesting.Shared.Physics { @@ -56,5 +58,38 @@ await server.WaitAssertion(() => Assert.That(box.AngularVelocity, Is.Not.EqualTo(0f)); }); } + + [Test] + public async Task TestShapeRadiusValidation() + { + var server = StartServer(new ServerIntegrationOptions + { + FailureLogLevel = LogLevel.Fatal, + }); + await server.WaitIdleAsync(); + + var entManager = server.ResolveDependency(); + var fixtureSystem = server.ResolveDependency() + .GetEntitySystem(); + var physicsSystem = server.ResolveDependency() + .GetEntitySystem(); + + await server.WaitAssertion(() => + { + entManager.System().CreateMap(out var mapId); + var uid = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); + var body = entManager.AddComponent(uid); + var shape = new PhysShapeCircle(0.5f); + var fixture = new Fixture(shape, 0, 0, false); + fixtureSystem.CreateFixture(uid, "fix1", fixture, body: body); + var fixtures = entManager.GetComponent(uid); + var xform = entManager.GetComponent(uid); + + Assert.Throws(() => + physicsSystem.SetRadius((uid, fixtures, body, xform), "fix1", fixture, shape, -0.5f)); + + Assert.That(shape.Radius, Is.EqualTo(0.5f)); + }); + } } } diff --git a/Robust.Shared.IntegrationTests/Physics/ShapeAABB_Test.cs b/Robust.Shared.IntegrationTests/Physics/ShapeAABB_Test.cs index 5b319b711..417e9eff9 100644 --- a/Robust.Shared.IntegrationTests/Physics/ShapeAABB_Test.cs +++ b/Robust.Shared.IntegrationTests/Physics/ShapeAABB_Test.cs @@ -5,12 +5,16 @@ using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Systems; namespace Robust.UnitTesting.Shared.Physics { [TestFixture] internal sealed class ShapeAABB_Test : OurRobustUnitTest { + private sealed class TestPhysicsSystem : SharedPhysicsSystem; + + private readonly TestPhysicsSystem _physics = new(); private Transform _transform; private Transform _rotatedTransform; @@ -26,7 +30,7 @@ public void Setup() public void TestCircleAABB() { var circle = new PhysShapeCircle(0.5f); - var aabb = circle.ComputeAABB(_transform, 0); + var aabb = _physics.ComputeAABB(circle, _transform, 0); Assert.That(aabb.Width, Is.EqualTo(1f)); Assert.That(aabb, Is.EqualTo(new Box2(0.5f, 0.5f, 1.5f, 1.5f))); } @@ -35,7 +39,7 @@ public void TestCircleAABB() public void TestRotatedCircleAABB() { var circle = new PhysShapeCircle(0.5f); - var aabb = circle.ComputeAABB(_rotatedTransform, 0); + var aabb = _physics.ComputeAABB(circle, _rotatedTransform, 0); Assert.That(aabb.Width, Is.EqualTo(1f)); Assert.That(aabb, Is.EqualTo(new Box2(0.5f, 0.5f, 1.5f, 1.5f))); } @@ -44,7 +48,7 @@ public void TestRotatedCircleAABB() public void TestEdgeAABB() { var edge = new EdgeShape(Vector2.Zero, Vector2.One); - var aabb = edge.ComputeAABB(_transform, 0); + var aabb = _physics.ComputeAABB(edge, _transform, 0); Assert.That(aabb.Width, Is.EqualTo(1.02f)); Assert.That(aabb, Is.EqualTo(new Box2(0.99f, 0.99f, 2.01f, 2.01f))); } @@ -53,7 +57,7 @@ public void TestEdgeAABB() public void TestRotatedEdgeAABB() { var edge = new EdgeShape(Vector2.Zero, Vector2.One); - var aabb = edge.ComputeAABB(_rotatedTransform, 0); + var aabb = _physics.ComputeAABB(edge, _rotatedTransform, 0); Assert.That(MathHelper.CloseToPercent(aabb.Width, 0.02f)); Assert.That(aabb.EqualsApprox(new Box2(0.99f, 0.99f, 1.01f, 2.42f), 0.01f)); } @@ -64,7 +68,7 @@ public void TestPolyAABB() var polygon = new PolygonShape(); // Radius is added to the AABB hence we'll just deduct it here for simplicity polygon.SetAsBox(0.49f, 0.49f); - var aabb = polygon.ComputeAABB(_transform, 0); + var aabb = _physics.ComputeAABB(polygon, _transform, 0); Assert.That(aabb.Width, Is.EqualTo(1f)); Assert.That(aabb, Is.EqualTo(new Box2(0.5f, 0.5f, 1.5f, 1.5f))); } @@ -75,7 +79,7 @@ public void TestRotatedPolyAABB() var polygon = new PolygonShape(); // Radius is added to the AABB hence we'll just deduct it here for simplicity polygon.SetAsBox(0.49f, 0.49f); - var aabb = polygon.ComputeAABB(_rotatedTransform, 0); + var aabb = _physics.ComputeAABB(polygon, _rotatedTransform, 0); // I already had a rough idea of what the AABB should be, I just put these in so the test passes. Assert.That(aabb.Width, Is.EqualTo(1.40592933f)); Assert.That(aabb, Is.EqualTo(new Box2(0.29703534f, 0.29703534f, 1.7029647f, 1.7029647f))); diff --git a/Robust.Shared.IntegrationTests/Serialization/TypeSerializers/NonNegativeFloatSerializerTest.cs b/Robust.Shared.IntegrationTests/Serialization/TypeSerializers/NonNegativeFloatSerializerTest.cs new file mode 100644 index 000000000..9f6b4fd52 --- /dev/null +++ b/Robust.Shared.IntegrationTests/Serialization/TypeSerializers/NonNegativeFloatSerializerTest.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; +using Robust.Shared.Physics.Serialization; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Markdown.Value; + +namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers; + +[TestFixture] +[TestOf(typeof(NonNegativeFloatSerializer))] +internal sealed class NonNegativeFloatSerializerTest : OurSerializationTest +{ + [Test] + public void ValidationAllowsZero() + { + var validation = Serialization.ValidateNode(new ValueDataNode("0")); + + Assert.That(validation.GetErrors(), Is.Empty); + } + + [Test] + public void ValidationRejectsNegative() + { + var validation = Serialization.ValidateNode(new ValueDataNode("-0.1")); + + Assert.That(validation.GetErrors(), Is.Not.Empty); + } + + [Test] + public void ReadRejectsNegative() + { + Assert.That( + () => Serialization.Read(new ValueDataNode("-0.1")), + Throws.InstanceOf()); + } +} diff --git a/Robust.Shared.Tests/Physics/Shapes/SlimPolygonTest.cs b/Robust.Shared.Tests/Physics/Shapes/SlimPolygonTest.cs index 34f940515..9f0360ff9 100644 --- a/Robust.Shared.Tests/Physics/Shapes/SlimPolygonTest.cs +++ b/Robust.Shared.Tests/Physics/Shapes/SlimPolygonTest.cs @@ -4,6 +4,7 @@ using Robust.Shared.Maths.Tests; using Robust.Shared.Physics; using Robust.Shared.Physics.Shapes; +using Robust.Shared.Physics.Systems; using Robust.UnitTesting; namespace Robust.Shared.Tests.Physics.Shapes; @@ -12,6 +13,10 @@ namespace Robust.Shared.Tests.Physics.Shapes; [TestOf(typeof(SlimPolygon))] public sealed class SlimPolygonTest { + private sealed class TestPhysicsSystem : SharedPhysicsSystem; + + private readonly TestPhysicsSystem _physics = new(); + /// /// Check that Slim and normal Polygon are equals /// @@ -30,7 +35,7 @@ public void TestAABB() { var shape = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One)); - Assert.That(shape.ComputeAABB(Transform.Empty, 0), Is.EqualTo(Box2.UnitCentered.Translated(Vector2.One))); + Assert.That(_physics.ComputeAABB(shape, Transform.Empty, 0), Is.EqualTo(Box2.UnitCentered.Translated(Vector2.One))); } [Test] @@ -66,7 +71,7 @@ public void TestBox2RotatedBounds([ValueSource(nameof(CalcBoundingBoxData))](Box { var box = new Box2Rotated(dat.baseBox, dat.rotation, dat.origin); var shape = new SlimPolygon(box); - var aabb = shape.ComputeAABB(Transform.Empty, 0); + var aabb = _physics.ComputeAABB(shape, Transform.Empty, 0); Assert.That(aabb, Is.Approximately(dat.expected)); } @@ -110,14 +115,14 @@ public void TestComputeAABB() // AABB of a 45 degree rotated unit box will be enlarged by a factor of sqrt(2) var transform = Transform.Empty; var expected = Box2.UnitCentered.Translated(new Vector2(1, 1 - MathF.Sqrt(2))).Scale(Vector2.One * MathF.Sqrt(2)); - var aabb = shape.ComputeAABB(transform, 0); + var aabb = _physics.ComputeAABB(shape, transform, 0); Assert.That(aabb, Is.Approximately(expected, 0.0001f)); Assert.That(aabb.Size, Is.Approximately(Vector2.One * MathF.Sqrt(2), 0.0001f)); // But if we pass a 45 degree rotation into ComputeAABB, the box will not be enlarged. transform = new Transform(Vector2.Zero, Angle.FromDegrees(45)); expected = Box2.UnitCentered.Translated(new Vector2(1, MathF.Sqrt(2) - 1)); - aabb = shape.ComputeAABB(transform, 0); + aabb = _physics.ComputeAABB(shape, transform, 0); Assert.That(aabb, Is.Approximately(expected, 0.0001f)); Assert.That(aabb.Size, Is.Approximately(Vector2.One, 0.0001f)); } diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs index 9eee69557..b92eb1ab3 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs @@ -82,14 +82,13 @@ private void AddEntitiesIntersecting(MapId mapId, Transform shapeTransform, LookupFlags flags) where T : IPhysShape { - var worldAABB = shape.ComputeAABB(shapeTransform, 0); + var worldAABB = _physics.ComputeAABB(shape, shapeTransform, 0); var state = new EntityQueryState(intersecting, shape, shapeTransform, _fixtures, this, _physics, - _manifoldManager, _fixturesQuery, flags); @@ -98,14 +97,14 @@ private void AddEntitiesIntersecting(MapId mapId, static (EntityUid uid, MapGridComponent _, ref EntityQueryState state) => { var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags); return true; }, approx: true, includeMap: false); var mapUid = _map.GetMapOrInvalid(mapId); var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, localTransform, flags); AddContained(intersecting, flags); @@ -130,7 +129,6 @@ private void AddEntitiesIntersecting( _fixtures, this, _physics, - _manifoldManager, _fixturesQuery, flags); @@ -168,7 +166,7 @@ static bool PhysicsQuery(ref EntityQueryState state, in FixtureProxy value) if (!approx) { var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value.Entity); - if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) + if (!state.Physics.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) { return true; } @@ -201,9 +199,9 @@ static bool SundriesQuery(ref EntityQueryState state, in EntityUid value) continue; anyFixture = true; - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < state.Physics.GetChildCount(fixture.Shape); i++) { - if (state.Manifolds.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, + if (state.Physics.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, intersectingTransform)) { state.Intersecting.Add(value); @@ -232,7 +230,7 @@ private bool AnyEntitiesIntersecting(MapId mapId, LookupFlags flags, EntityUid? ignored = null) where T : IPhysShape { - var worldAABB = shape.ComputeAABB(shapeTransform, 0); + var worldAABB = _physics.ComputeAABB(shape, shapeTransform, 0); var state = new AnyEntityQueryState(false, ignored, shape, @@ -240,7 +238,6 @@ private bool AnyEntitiesIntersecting(MapId mapId, _fixtures, this, _physics, - _manifoldManager, _fixturesQuery, flags); @@ -249,7 +246,7 @@ private bool AnyEntitiesIntersecting(MapId mapId, static (EntityUid uid, MapGridComponent _, ref AnyEntityQueryState state) => { var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); if (state.Lookup.AnyEntitiesIntersecting(uid, state.Shape, localAabb, localTransform, state.Flags, ignored: state.Ignored)) { @@ -264,7 +261,7 @@ private bool AnyEntitiesIntersecting(MapId mapId, { var mapUid = _map.GetMapOrInvalid(mapId); var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); state.Found = AnyEntitiesIntersecting(mapUid, shape, localAabb, localTransform, flags, ignored); } @@ -289,7 +286,6 @@ private bool AnyEntitiesIntersecting(EntityUid lookupUid, _fixtures, this, _physics, - _manifoldManager, _fixturesQuery, flags); @@ -339,7 +335,7 @@ static bool PhysicsQuery(ref AnyEntityQueryState state, in FixtureProxy value if (!approx) { var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value.Entity); - if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) + if (!state.Physics.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) { return true; } @@ -375,9 +371,9 @@ static bool SundriesQuery(ref AnyEntityQueryState state, in EntityUid value) continue; anyFixture = true; - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < state.Physics.GetChildCount(fixture.Shape); i++) { - if (state.Manifolds.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, + if (state.Physics.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, intersectingTransform)) { state.Found = true; @@ -591,7 +587,7 @@ static void EntityIntersectingQuery(EntityUid lookupUid, (EntityUid entity, Tran if (!fixture.Hard && (state.flags & LookupFlags.Sensors) == 0x0) continue; - var localAabb = fixture.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.physics.ComputeAABB(fixture.Shape, localTransform, 0); state.lookup.AddEntitiesIntersecting(lookupUid, state.intersecting, fixture.Shape, localAabb, localTransform, state.flags); } } @@ -599,7 +595,7 @@ static void EntityIntersectingQuery(EntityUid lookupUid, (EntityUid entity, Tran else { var shape = new PhysShapeCircle(LookupEpsilon); - var localAabb = shape.ComputeAABB(localTransform, 0); + var localAabb = state.physics.ComputeAABB(shape, localTransform, 0); state.lookup.AddEntitiesIntersecting(lookupUid, state.intersecting, shape, localAabb, localTransform, state.flags); } @@ -847,7 +843,6 @@ private record struct AnyEntityQueryState( FixtureSystem Fixtures, EntityLookupSystem Lookup, SharedPhysicsSystem Physics, - IManifoldManager Manifolds, EntityQuery FixturesQuery, LookupFlags Flags ) where T : IPhysShape; @@ -859,7 +854,6 @@ private readonly record struct EntityQueryState( FixtureSystem Fixtures, EntityLookupSystem Lookup, SharedPhysicsSystem Physics, - IManifoldManager Manifolds, EntityQuery FixturesQuery, LookupFlags Flags ) where T : IPhysShape; diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs index 16d33310d..80879a0e9 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs @@ -91,9 +91,9 @@ private bool IsIntersecting(MapId mapId, EntityUid uid, TransformCompone continue; anyFixture = true; - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < _physics.GetChildCount(fixture.Shape); i++) { - if (_manifoldManager.TestOverlap(shape, 0, fixture.Shape, i, shapeTransform, transform)) + if (_physics.TestOverlap(shape, 0, fixture.Shape, i, shapeTransform, transform)) { return true; } @@ -146,7 +146,6 @@ private void AddEntitiesIntersecting( localTransform, _fixtures, _physics, - _manifoldManager, query, _fixturesQuery, (flags & LookupFlags.Sensors) != 0, @@ -186,7 +185,7 @@ static bool PhysicsQuery(ref QueryState state, in FixtureProxy value) if (!state.Approximate) { var intersectingTransform = state.Physics.GetLocalPhysicsTransform(value.Entity); - if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) + if (!state.Physics.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) { return true; } @@ -218,9 +217,9 @@ static bool SundriesQuery(ref QueryState state, in EntityUid value) continue; anyFixture = true; - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < state.Physics.GetChildCount(fixture.Shape); i++) { - if (state.Manifolds.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, + if (state.Physics.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, intersectingTransform)) { state.Intersecting.Add((value, comp)); @@ -285,7 +284,6 @@ private bool AnyComponentsIntersecting( shapeTransform, _fixtures, _physics, - _manifoldManager, query, _fixturesQuery, flags); @@ -335,7 +333,7 @@ static bool PhysicsQuery(ref AnyQueryState state, in FixtureProxy val { var intersectingTransform = state.Physics.GetPhysicsTransform(value.Entity); - if (!state.Manifolds.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, + if (!state.Physics.TestOverlap(state.Shape, 0, value.Fixture.Shape, value.ChildIndex, state.Transform, intersectingTransform)) { return true; @@ -374,9 +372,9 @@ static bool SundriesQuery(ref AnyQueryState state, in EntityUid value continue; anyFixture = true; - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < state.Physics.GetChildCount(fixture.Shape); i++) { - if (state.Manifolds.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, + if (state.Physics.TestOverlap(state.Shape, 0, fixture.Shape, i, state.Transform, intersectingTransform)) { state.Found = true; @@ -437,7 +435,7 @@ public bool AnyComponentsIntersecting(Type type, MapId mapId, T shape, Transf if (mapId == MapId.Nullspace) return false; - var worldAABB = shape.ComputeAABB(shapeTransform, 0); + var worldAABB = _physics.ComputeAABB(shape, shapeTransform, 0); if (!UseBoundsQuery(type, worldAABB.Height * worldAABB.Width)) { @@ -529,7 +527,7 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, T shape, Transfor if (mapId == MapId.Nullspace) return; - var worldAABB = shape.ComputeAABB(shapeTransform, 0); + var worldAABB = _physics.ComputeAABB(shape, shapeTransform, 0); if (!UseBoundsQuery(type, worldAABB.Height * worldAABB.Width)) { @@ -554,14 +552,14 @@ public void GetEntitiesIntersecting(Type type, MapId mapId, T shape, Transfor static (EntityUid uid, MapGridComponent grid, ref GridQueryState state) => { var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query); return true; }, approx: true, includeMap: false); var mapUid = _map.GetMapOrInvalid(mapId); var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); AddEntitiesIntersecting(mapUid, intersecting, shape, localAabb, localTransform, flags, query); @@ -575,7 +573,7 @@ public void GetEntitiesIntersecting(MapId mapId, TShape shape, Transf { if (mapId == MapId.Nullspace) return; - var worldAABB = shape.ComputeAABB(shapeTransform, 0); + var worldAABB = _physics.ComputeAABB(shape, shapeTransform, 0); if (!UseBoundsQuery(worldAABB.Height * worldAABB.Width)) { @@ -600,7 +598,7 @@ public void GetEntitiesIntersecting(MapId mapId, TShape shape, Transf static (EntityUid uid, MapGridComponent grid, ref GridQueryState state) => { var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); state.Lookup.AddEntitiesIntersecting(uid, state.Intersecting, state.Shape, localAabb, localTransform, state.Flags, state.Query); return true; }, approx: true, includeMap: false); @@ -608,7 +606,7 @@ public void GetEntitiesIntersecting(MapId mapId, TShape shape, Transf // Get map entities var mapUid = _map.GetMapOrInvalid(mapId); var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, mapUid); - var localAabb = state.Shape.ComputeAABB(localTransform, 0); + var localAabb = state.Physics.ComputeAABB(state.Shape, localTransform, 0); AddEntitiesIntersecting(mapUid, entities, shape, localAabb, localTransform, flags, query); AddContained(entities, flags, query); @@ -855,7 +853,6 @@ private record struct AnyQueryState( Transform Transform, FixtureSystem Fixtures, SharedPhysicsSystem Physics, - IManifoldManager Manifolds, EntityQuery Query, EntityQuery FixturesQuery, LookupFlags Flags @@ -868,7 +865,6 @@ private readonly record struct QueryState( Transform Transform, FixtureSystem Fixtures, SharedPhysicsSystem Physics, - IManifoldManager Manifolds, EntityQuery Query, EntityQuery FixturesQuery, bool Sensors, diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs index 4aa285ed9..43bb67e4e 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.LocalQueries.cs @@ -73,7 +73,7 @@ public HashSet GetLocalEntitiesIntersecting(EntityUid gridId, Vector2 /// public void GetLocalEntitiesIntersecting(EntityUid gridUid, IPhysShape shape, Transform localTransform, HashSet intersecting, LookupFlags flags = DefaultFlags, BroadphaseComponent? lookup = null) { - var localAABB = shape.ComputeAABB(localTransform, 0); + var localAABB = _physics.ComputeAABB(shape, localTransform, 0); AddEntitiesIntersecting(gridUid, intersecting, shape, localAABB, localTransform, flags: flags, lookup: lookup); AddContained(intersecting, flags); } diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs index 1d1e90c67..9bcb5414b 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs @@ -72,7 +72,6 @@ public record struct WorldAABBEvent public sealed partial class EntityLookupSystem : EntitySystem { - [Dependency] private IManifoldManager _manifoldManager = default!; [Dependency] private IMapManager _mapManager = default!; [Dependency] private IGameTiming _timing = default!; [Dependency] private INetManager _netMan = default!; @@ -461,7 +460,7 @@ private void AddOrMoveProxies( { for (var i = 0; i < fixture.ProxyCount; i++) { - var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i); + var bounds = _physics.ComputeAABB(fixture.Shape, broadphaseTransform, i); var proxy = fixture.Proxies[i]; tree.MoveProxy(proxy.ProxyId, bounds); proxy.AABB = bounds; @@ -471,12 +470,12 @@ private void AddOrMoveProxies( return; } - var count = fixture.Shape.ChildCount; + var count = _physics.GetChildCount(fixture.Shape); var proxies = new FixtureProxy[count]; for (var i = 0; i < count; i++) { - var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i); + var bounds = _physics.ComputeAABB(fixture.Shape, broadphaseTransform, i); var proxy = new FixtureProxy(ent.Owner, ent.Comp1, ent.Comp2, bounds, fixtureId, fixture, i); proxy.ProxyId = tree.AddProxy(ref proxy); proxy.AABB = bounds; @@ -848,10 +847,10 @@ public Box2 GetAABBNoContainer(EntityUid uid, Vector2 position, Angle angle) // TODO cache this to speed up entity lookups & tree updating foreach (var fixture in fixtures.Fixtures.Values) { - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < _physics.GetChildCount(fixture.Shape); i++) { // TODO don't transform each fixture, just transform the final AABB - var boundy = fixture.Shape.ComputeAABB(transform, i); + var boundy = _physics.ComputeAABB(fixture.Shape, transform, i); bounds = bounds.Union(boundy); } } diff --git a/Robust.Shared/Map/MapManager.Queries.cs b/Robust.Shared/Map/MapManager.Queries.cs index f37a54552..529059654 100644 --- a/Robust.Shared/Map/MapManager.Queries.cs +++ b/Robust.Shared/Map/MapManager.Queries.cs @@ -28,9 +28,9 @@ private bool IsIntersecting( { var fixture = grid.Comp.Fixtures[id]; - for (var j = 0; j < fixture.Shape.ChildCount; j++) + for (var j = 0; j < _physics.GetChildCount(fixture.Shape); j++) { - if (_manifolds.TestOverlap(shape, 0, fixture.Shape, j, shapeTransform, gridTransform)) + if (_physics.TestOverlap(shape, 0, fixture.Shape, j, shapeTransform, gridTransform)) { return true; } @@ -108,7 +108,7 @@ public void FindGridsIntersecting( bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape { - FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, callback, approx: approx, includeMap: includeMap); + FindGridsIntersecting(mapEnt, shape, _physics.ComputeAABB(shape, transform, 0), transform, callback, approx: approx, includeMap: includeMap); } private void FindGridsIntersecting(EntityUid mapEnt, T shape, Box2 worldAABB, Transform transform, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape @@ -130,7 +130,7 @@ public void FindGridsIntersecting( bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape { - FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref state, callback, approx: approx, includeMap: includeMap); + FindGridsIntersecting(mapEnt, shape, _physics.ComputeAABB(shape, transform, 0), transform, ref state, callback, approx: approx, includeMap: includeMap); } private void FindGridsIntersecting( @@ -200,14 +200,14 @@ public void FindGridsIntersecting(EntityUid mapEnt, List shapes, Tra { foreach (var shape in shapes) { - FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref entities, approx: approx, includeMap: includeMap); + FindGridsIntersecting(mapEnt, shape, _physics.ComputeAABB(shape, transform, 0), transform, ref entities, approx: approx, includeMap: includeMap); } } public void FindGridsIntersecting(EntityUid mapEnt, T shape, Transform transform, ref List> grids, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap) where T : IPhysShape { - FindGridsIntersecting(mapEnt, shape, shape.ComputeAABB(transform, 0), transform, ref grids, approx: approx, includeMap: includeMap); + FindGridsIntersecting(mapEnt, shape, _physics.ComputeAABB(shape, transform, 0), transform, ref grids, approx: approx, includeMap: includeMap); } public void FindGridsIntersecting(EntityUid mapEnt, T shape, Box2 worldAABB, Transform transform, diff --git a/Robust.Shared/Map/MapManager.cs b/Robust.Shared/Map/MapManager.cs index c5e853c8b..2d146817f 100644 --- a/Robust.Shared/Map/MapManager.cs +++ b/Robust.Shared/Map/MapManager.cs @@ -3,7 +3,6 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map.Components; -using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; @@ -15,7 +14,6 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber { [Dependency] public IGameTiming GameTiming = default!; [Dependency] public IEntityManager EntityManager = default!; - [Dependency] private IManifoldManager _manifolds = default!; [Dependency] private ILogManager _logManager = default!; [Dependency] private IConsoleHost _conhost = default!; diff --git a/Robust.Shared/Physics/Collision/CollisionManager.Overlap.cs b/Robust.Shared/Physics/Collision/CollisionManager.Overlap.cs deleted file mode 100644 index bdd41857e..000000000 --- a/Robust.Shared/Physics/Collision/CollisionManager.Overlap.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Robust.Shared.Physics.Collision.Shapes; - -namespace Robust.Shared.Physics.Collision; - -internal sealed partial class CollisionManager -{ - /// - /// Test overlap between the two shapes. - /// - /// The first shape. - /// The second shape. - /// The transform for the first shape. - /// The transform for the seconds shape. - /// - public bool TestOverlap(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB) - where T : IPhysShape - where U : IPhysShape - { - var input = new DistanceInput(); - - input.ProxyA.Set(shapeA, indexA); - input.ProxyB.Set(shapeB, indexB); - input.TransformA = xfA; - input.TransformB = xfB; - input.UseRadii = true; - - DistanceManager.ComputeDistance(out var output, out _, input); - - return output.Distance < 10.0f * float.Epsilon; - } -} diff --git a/Robust.Shared/Physics/Collision/DistanceProxy.cs b/Robust.Shared/Physics/Collision/DistanceProxy.cs index 6f8a70b01..ff6e58639 100644 --- a/Robust.Shared/Physics/Collision/DistanceProxy.cs +++ b/Robust.Shared/Physics/Collision/DistanceProxy.cs @@ -56,41 +56,34 @@ internal DistanceProxy(ReadOnlySpan vertices, float radius) /// The shape. internal void Set(T shape, int index) where T : IPhysShape { - switch (shape.ShapeType) + switch (shape) { - case ShapeType.Circle: - var circle = Unsafe.As(shape); + case PhysShapeCircle circle: Buffer._00 = circle.Position; Vertices = Buffer.AsSpan[..1]; Radius = circle.Radius; break; - case ShapeType.Polygon: - if (shape is Polygon poly) - { - Span verts = new Vector2[poly.VertexCount]; - poly._vertices.AsSpan[..poly.VertexCount].CopyTo(verts); - Vertices = verts; - Radius = poly.Radius; - } - else if (shape is SlimPolygon fast) - { - Span verts = new Vector2[fast.VertexCount]; - fast._vertices.AsSpan[..fast.VertexCount].CopyTo(verts); - Vertices = verts; - Radius = fast.Radius; - } - else - { - var polyShape = Unsafe.As(shape); - Vertices = polyShape.Vertices.AsSpan()[..polyShape.VertexCount]; - Radius = polyShape.Radius; - } + case Polygon poly: + Span verts = new Vector2[poly.VertexCount]; + poly._vertices.AsSpan[..poly.VertexCount].CopyTo(verts); + Vertices = verts; + Radius = poly.Radius; + break; + + case SlimPolygon fast: + Span fastVerts = new Vector2[fast.VertexCount]; + fast._vertices.AsSpan[..fast.VertexCount].CopyTo(fastVerts); + Vertices = fastVerts; + Radius = fast.Radius; + break; + case PolygonShape polyShape: + Vertices = polyShape.Vertices.AsSpan()[..polyShape.VertexCount]; + Radius = polyShape.Radius; break; - case ShapeType.Chain: - var chain = Unsafe.As(shape); + case ChainShape chain: Debug.Assert(0 <= index && index < chain.Vertices.Length); Buffer._00 = chain.Vertices[index]; @@ -99,8 +92,7 @@ internal void Set(T shape, int index) where T : IPhysShape Radius = chain.Radius; break; - case ShapeType.Edge: - var edge = Unsafe.As(shape); + case EdgeShape edge: Buffer._00 = edge.Vertex1; Buffer._01 = edge.Vertex2; @@ -109,7 +101,7 @@ internal void Set(T shape, int index) where T : IPhysShape Radius = edge.Radius; break; default: - throw new InvalidOperationException($"Invalid shapetype specified {shape.ShapeType}"); + throw new InvalidOperationException($"Invalid shape specified {shape.GetType()}"); } } diff --git a/Robust.Shared/Physics/Collision/IManifoldManager.cs b/Robust.Shared/Physics/Collision/IManifoldManager.cs deleted file mode 100644 index 1afba98f9..000000000 --- a/Robust.Shared/Physics/Collision/IManifoldManager.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Robust.Shared.Physics.Collision.Shapes; - -namespace Robust.Shared.Physics.Collision; - -internal interface IManifoldManager -{ - // TODO: this SUCKS but it's better than an edge for every contact. - /// - /// Gets an edge from objectpool. - /// - EdgeShape GetContactEdge(); - - void ReturnEdge(EdgeShape edge); - - bool TestOverlap(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB) - where T : IPhysShape - where U : IPhysShape; - - void CollideCircles(ref Manifold manifold, PhysShapeCircle circleA, in Transform xfA, - PhysShapeCircle circleB, in Transform xfB); - - void CollideEdgeAndCircle(ref Manifold manifold, EdgeShape edgeA, in Transform transformA, - PhysShapeCircle circleB, in Transform transformB); - - void CollideEdgeAndPolygon(ref Manifold manifold, EdgeShape edgeA, in Transform xfA, - PolygonShape polygonB, in Transform xfB); - - void CollidePolygonAndCircle(ref Manifold manifold, PolygonShape polygonA, in Transform xfA, - PhysShapeCircle circleB, in Transform xfB); - - void CollidePolygons(ref Manifold manifold, PolygonShape polyA, in Transform transformA, - PolygonShape polyB, in Transform transformB); -} diff --git a/Robust.Shared/Physics/Collision/Manifold.cs b/Robust.Shared/Physics/Collision/Manifold.cs index ea6848f7d..7da2416ad 100644 --- a/Robust.Shared/Physics/Collision/Manifold.cs +++ b/Robust.Shared/Physics/Collision/Manifold.cs @@ -121,11 +121,11 @@ public override int GetHashCode() /// - Clip point versus plane with radius /// - Point versus point with radius (circles) /// The local point usage depends on the manifold type: -/// - ShapeType.Circles: the local center of circleA +/// - Circles: the local center of circleA /// - SeparationFunction.FaceA: the center of faceA /// - SeparationFunction.FaceB: the center of faceB /// Similarly the local normal usage: -/// - ShapeType.Circles: not used +/// - Circles: not used /// - SeparationFunction.FaceA: the normal on polygonA /// - SeparationFunction.FaceB: the normal on polygonB /// We store contacts in this way so that position correction can diff --git a/Robust.Shared/Physics/Collision/Shapes/ChainShape.cs b/Robust.Shared/Physics/Collision/Shapes/ChainShape.cs index 3f2916007..58fc3f7cc 100644 --- a/Robust.Shared/Physics/Collision/Shapes/ChainShape.cs +++ b/Robust.Shared/Physics/Collision/Shapes/ChainShape.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Numerics; using Robust.Shared.Maths; +using Robust.Shared.Physics.Serialization; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; @@ -17,11 +18,20 @@ public sealed partial class ChainShape : IPhysShape public int Count => Vertices.Length - 1; - public int ChildCount => Count - 1; + private float _radius = PhysicsConstants.PolygonRadius; - [DataField] - public float Radius { get; set; } = PhysicsConstants.PolygonRadius; - public ShapeType ShapeType => ShapeType.Chain; + [DataField(customTypeSerializer: typeof(NonNegativeFloatSerializer))] + public float Radius + { + get => _radius; + set + { + if (value < 0f) + throw new ArgumentOutOfRangeException(nameof(value), value, "Chain radius cannot be negative."); + + _radius = value; + } + } [DataField] public Vector2 PrevVertex; @@ -43,6 +53,9 @@ public void Clear() /// How many multiply radius by count to get total edges. public void CreateLoop(Vector2 position, float radius, bool outer = true, float count = 16f) { + if (radius < 0f) + throw new ArgumentOutOfRangeException(nameof(radius), radius, "Loop radius cannot be negative."); + int divisions = Math.Max(16,(int)(radius * count)); float arcLength = MathF.PI * 2 / divisions; Span vertices = stackalloc Vector2[divisions]; @@ -154,24 +167,4 @@ public bool Equals(ChainShape otherChain) Vertices.SequenceEqual(otherChain.Vertices); } - public Box2 ComputeAABB(Transform transform, int childIndex) - { - DebugTools.Assert(childIndex < Count); - - var i1 = childIndex; - var i2 = childIndex + 1; - if (i2 == Count) - { - i2 = 0; - } - - var v1 = Transform.Mul(transform, Vertices[i1]); - var v2 = Transform.Mul(transform, Vertices[i2]); - - var lower = Vector2.Min(v1, v2); - var upper = Vector2.Max(v1, v2); - - var r = new Vector2(Radius, Radius); - return new Box2(lower - r, upper + r); - } } diff --git a/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs b/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs index 2d8799409..684a0ed40 100644 --- a/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs +++ b/Robust.Shared/Physics/Collision/Shapes/EdgeShape.cs @@ -22,7 +22,11 @@ using System; using System.Numerics; +using Robust.Shared.GameObjects; using Robust.Shared.Maths; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Serialization; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; @@ -56,17 +60,25 @@ public sealed partial class EdgeShape : IPhysShape, IEquatable [DataField("vertex3"), Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] internal Vector2 Vertex3; - [DataField("oneSided"), Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] + [DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] public bool OneSided; - public int ChildCount => 1; + private float _radius = PhysicsConstants.PolygonRadius; - public ShapeType ShapeType => ShapeType.Edge; - - [DataField("radius"), + [DataField(customTypeSerializer: typeof(NonNegativeFloatSerializer)), Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] - public float Radius { get; set; } = PhysicsConstants.PolygonRadius; + public float Radius + { + get => _radius; + set + { + if (value < 0f) + throw new ArgumentOutOfRangeException(nameof(value), value, "Edge radius cannot be negative."); + + _radius = value; + } + } public EdgeShape() { @@ -114,20 +126,6 @@ public bool Equals(IPhysShape? other) Vertex3.Equals(edge.Vertex3); } - public Box2 ComputeAABB(Transform transform, int childIndex) - { - DebugTools.Assert(childIndex == 0); - - var v1 = Transform.Mul(transform, Vertex1); - var v2 = Transform.Mul(transform, Vertex2); - - var lower = Vector2.Min(v1, v2); - var upper = Vector2.Max(v1, v2); - - var radius = new Vector2(Radius, Radius); - return new Box2(lower - radius, upper + radius); - } - public float CalculateArea() { // It's a line diff --git a/Robust.Shared/Physics/Collision/Shapes/IPhysShape.cs b/Robust.Shared/Physics/Collision/Shapes/IPhysShape.cs index 078ce4071..569d7931b 100644 --- a/Robust.Shared/Physics/Collision/Shapes/IPhysShape.cs +++ b/Robust.Shared/Physics/Collision/Shapes/IPhysShape.cs @@ -1,30 +1,14 @@ using System; -using Robust.Shared.Maths; using Robust.Shared.Physics.Components; namespace Robust.Shared.Physics.Collision.Shapes { - public enum ShapeType : sbyte - { - Unknown = -1, - Circle = 0, - Edge = 1, - Polygon = 2, - Chain = 3, - TypeCount = 4, // Obviously increment this if you add something - } - /// /// A primitive physical shape that is used by a . /// [NotContentImplementable] public interface IPhysShape : IEquatable { - /// - /// Get the number of child primitives. Only relevant for chain shape. - /// - int ChildCount { get; } - /// /// Radius of the Shape /// Changing the radius causes a recalculation of shape properties. @@ -33,12 +17,5 @@ public interface IPhysShape : IEquatable // Sloth: I removed density because mass is way easier to work with. // If you really want it back then code it yaself (and also probably put it on the fixture). - - ShapeType ShapeType { get; } - - /// - /// Calculate the AABB of the shape. - /// - Box2 ComputeAABB(Transform transform, int childIndex); } } diff --git a/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs b/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs index a2818f7b6..d9e30c7c1 100644 --- a/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs +++ b/Robust.Shared/Physics/Collision/Shapes/PhysShapeAabb.cs @@ -5,6 +5,7 @@ using Robust.Shared.Configuration; using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Physics.Serialization; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; @@ -20,17 +21,19 @@ namespace Robust.Shared.Physics.Collision.Shapes [DataDefinition] public sealed partial class PhysShapeAabb : IPhysShape, IEquatable { - public int ChildCount => 1; - /// /// The radius of this AABB /// [ViewVariables(VVAccess.ReadWrite)] + [DataField(customTypeSerializer: typeof(NonNegativeFloatSerializer))] public float Radius { get => _radius; set { + if (value < 0f) + throw new ArgumentOutOfRangeException(nameof(value), value, "AABB radius cannot be negative."); + if (MathHelper.CloseToPercent(_radius, value)) return; _radius = value; } @@ -40,9 +43,7 @@ public float Radius internal Vector2 Centroid => Vector2.Zero; - public ShapeType ShapeType => ShapeType.Unknown; - - [DataField("bounds")] + [DataField] [ViewVariables(VVAccess.ReadWrite)] private Box2 _localBounds = Box2.UnitCentered; @@ -51,7 +52,7 @@ public float Radius public PhysShapeAabb(float radius) { - _radius = radius; + Radius = radius; } public PhysShapeAabb() @@ -59,11 +60,6 @@ public PhysShapeAabb() _radius = PhysicsConstants.PolygonRadius; } - public Box2 ComputeAABB(Transform transform, int childIndex) - { - return new Box2Rotated(_localBounds.Translated(transform.Position), transform.Quaternion2D.Angle, transform.Position).CalcBoundingBox().Enlarged(_radius); - } - [Pure] internal List GetVertices() { diff --git a/Robust.Shared/Physics/Collision/Shapes/PhysShapeCircle.cs b/Robust.Shared/Physics/Collision/Shapes/PhysShapeCircle.cs index df0fbc22b..a628270d8 100644 --- a/Robust.Shared/Physics/Collision/Shapes/PhysShapeCircle.cs +++ b/Robust.Shared/Physics/Collision/Shapes/PhysShapeCircle.cs @@ -1,6 +1,10 @@ using System; using System.Numerics; +using Robust.Shared.GameObjects; using Robust.Shared.Maths; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Serialization; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; @@ -17,18 +21,29 @@ namespace Robust.Shared.Physics.Collision.Shapes [DataDefinition] public sealed partial class PhysShapeCircle : IPhysShape, IEquatable { - public int ChildCount => 1; - - public ShapeType ShapeType => ShapeType.Circle; - private const float DefaultRadius = 0.5f; - [DataField("radius"), Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] - public float Radius { get; set; } = DefaultRadius; + private float _radius = DefaultRadius; - [DataField("position"), Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] + [DataField(customTypeSerializer: typeof(NonNegativeFloatSerializer)), + Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] + public float Radius + { + get => _radius; + set + { + if (value < 0f) + throw new ArgumentOutOfRangeException(nameof(value), value, "Circle radius cannot be negative."); + + _radius = value; + } + } + + [DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] public Vector2 Position; + public float Area => MathF.PI * _radius * _radius; + public PhysShapeCircle() { } @@ -45,27 +60,14 @@ public PhysShapeCircle(float radius, Vector2 position) Position = position; } - public float CalculateArea() - { - return MathF.PI * Radius * Radius; - } - - public Box2 ComputeAABB(Transform transform, int childIndex) - { - DebugTools.Assert(childIndex == 0); - - var p = transform.Position + Transform.Mul(transform.Quaternion2D, Position); - return new Box2(p.X - Radius, p.Y - Radius, p.X + Radius, p.Y + Radius); - } - public Box2 CalcLocalBounds() { // circle inscribed in box return new Box2( - Position.X - Radius, - Position.Y - Radius, - Position.X + Radius, - Position.Y + Radius); + Position.X - _radius, + Position.Y - _radius, + Position.X + _radius, + Position.Y + _radius); } public bool Equals(IPhysShape? other) @@ -81,7 +83,7 @@ public bool Equals(PhysShapeCircle? other) if (ReferenceEquals(this, other)) return true; - return MathHelper.CloseTo(Radius, other.Radius) && Position.EqualsApprox(other.Position); + return MathHelper.CloseTo(_radius, other._radius) && Position.EqualsApprox(other.Position); } public override bool Equals(object? obj) @@ -91,7 +93,7 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return HashCode.Combine(Radius, Position); + return HashCode.Combine(_radius, Position); } } } diff --git a/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs b/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs index 3ac076c93..0335bc097 100644 --- a/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs +++ b/Robust.Shared/Physics/Collision/Shapes/PolygonShape.cs @@ -25,8 +25,12 @@ using System.Numerics; using System.Runtime.InteropServices; using Robust.Shared.Configuration; +using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Serialization; using Robust.Shared.Physics.Shapes; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization; @@ -45,7 +49,7 @@ public sealed partial class PolygonShape : IPhysShape, ISerializationHooks, IEqu [ViewVariables] public int VertexCount => Vertices.Length; - [DataField("vertices"), + [DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] public Vector2[] Vertices = Array.Empty(); @@ -58,13 +62,24 @@ public sealed partial class PolygonShape : IPhysShape, ISerializationHooks, IEqu [ViewVariables, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] public Vector2 Centroid { get; internal set; } = Vector2.Zero; - public int ChildCount => 1; - /// /// The radius of this polygon. /// - [DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] - public float Radius { get; set; } = PhysicsConstants.PolygonRadius; + private float _radius = PhysicsConstants.PolygonRadius; + + [DataField(customTypeSerializer: typeof(NonNegativeFloatSerializer)), + Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)] + public float Radius + { + get => _radius; + set + { + if (value < 0f) + throw new ArgumentOutOfRangeException(nameof(value), value, "Polygon radius cannot be negative."); + + _radius = value; + } + } public bool Set(List vertices) { @@ -167,8 +182,6 @@ private static Vector2 ComputeCentroid(Vector2[] vs, int count) return c; } - public ShapeType ShapeType => ShapeType.Polygon; - public PolygonShape() { } @@ -313,23 +326,6 @@ public bool EqualsApprox(PolygonShape other, double tolerance) return true; } - public Box2 ComputeAABB(Transform transform, int childIndex) - { - DebugTools.Assert(childIndex == 0); - var lower = Transform.Mul(transform, Vertices[0]); - var upper = lower; - - for (var i = 1; i < VertexCount; ++i) - { - var v = Transform.Mul(transform, Vertices[i]); - lower = Vector2.Min(lower, v); - upper = Vector2.Max(upper, v); - } - - var r = new Vector2(Radius, Radius); - return new Box2(lower - r, upper + r); - } - public static explicit operator PolygonShape(PhysShapeAabb aabb) { // TODO: Need a test for this probably, if there is no AABB manifold generator done at least. diff --git a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs index ba21487e8..cb629a684 100644 --- a/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs +++ b/Robust.Shared/Physics/Dynamics/Contacts/Contact.cs @@ -44,7 +44,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts { public sealed class Contact : IEquatable { - private readonly IManifoldManager _manifoldManager; + private readonly SharedPhysicsSystem _physics; #if DEBUG internal SharedDebugPhysicsSystem _debugPhysics = default!; @@ -89,9 +89,9 @@ public sealed class Contact : IEquatable internal ContactFlags Flags = ContactFlags.None; - internal Contact(IManifoldManager manifoldManager) + internal Contact(SharedPhysicsSystem physics) { - _manifoldManager = manifoldManager; + _physics = physics; MapNode = new LinkedListNode(this); BodyANode = new LinkedListNode(this); @@ -197,7 +197,7 @@ internal ContactStatus Update(Transform bodyATransform, Transform bodyBTransform { var shapeA = FixtureA!.Shape; var shapeB = FixtureB!.Shape; - touching = _manifoldManager.TestOverlap(shapeA, ChildIndexA, shapeB, ChildIndexB, bodyATransform, bodyBTransform); + touching = _physics.TestOverlap(shapeA, ChildIndexA, shapeB, ChildIndexB, bodyATransform, bodyBTransform); // Sensors don't generate manifolds. Manifold.PointCount = 0; @@ -290,7 +290,7 @@ internal void UpdateIsTouching(Transform bodyATransform, Transform bodyBTransfor { var shapeA = FixtureA!.Shape; var shapeB = FixtureB!.Shape; - IsTouching = _manifoldManager.TestOverlap(shapeA, ChildIndexA, shapeB, ChildIndexB, bodyATransform, bodyBTransform); + IsTouching = _physics.TestOverlap(shapeA, ChildIndexA, shapeB, ChildIndexB, bodyATransform, bodyBTransform); } else { @@ -313,37 +313,37 @@ private void Evaluate(ref Manifold manifold, in Transform transformA, in Transfo { // TODO: Need a unit test for these. case ContactType.Polygon: - _manifoldManager.CollidePolygons(ref manifold, (PolygonShape) FixtureA!.Shape, transformA, (PolygonShape) FixtureB!.Shape, transformB); + _physics.CollidePolygons(ref manifold, (PolygonShape) FixtureA!.Shape, transformA, (PolygonShape) FixtureB!.Shape, transformB); break; case ContactType.PolygonAndCircle: - _manifoldManager.CollidePolygonAndCircle(ref manifold, (PolygonShape) FixtureA!.Shape, transformA, (PhysShapeCircle) FixtureB!.Shape, transformB); + _physics.CollidePolygonAndCircle(ref manifold, (PolygonShape) FixtureA!.Shape, transformA, (PhysShapeCircle) FixtureB!.Shape, transformB); break; case ContactType.EdgeAndCircle: - _manifoldManager.CollideEdgeAndCircle(ref manifold, (EdgeShape) FixtureA!.Shape, transformA, (PhysShapeCircle) FixtureB!.Shape, transformB); + _physics.CollideEdgeAndCircle(ref manifold, (EdgeShape) FixtureA!.Shape, transformA, (PhysShapeCircle) FixtureB!.Shape, transformB); break; case ContactType.EdgeAndPolygon: - _manifoldManager.CollideEdgeAndPolygon(ref manifold, (EdgeShape) FixtureA!.Shape, transformA, (PolygonShape) FixtureB!.Shape, transformB); + _physics.CollideEdgeAndPolygon(ref manifold, (EdgeShape) FixtureA!.Shape, transformA, (PolygonShape) FixtureB!.Shape, transformB); break; case ContactType.ChainAndCircle: { var chain = (ChainShape) FixtureA!.Shape; - var edge = _manifoldManager.GetContactEdge(); + var edge = _physics.GetContactEdge(); chain.GetChildEdge(ref edge, ChildIndexA); - _manifoldManager.CollideEdgeAndCircle(ref manifold, edge, in transformA, (PhysShapeCircle) FixtureB!.Shape, in transformB); - _manifoldManager.ReturnEdge(edge); + _physics.CollideEdgeAndCircle(ref manifold, edge, in transformA, (PhysShapeCircle) FixtureB!.Shape, in transformB); + _physics.ReturnEdge(edge); break; } case ContactType.ChainAndPolygon: { var loop2 = (ChainShape) FixtureA!.Shape; - var edge = _manifoldManager.GetContactEdge(); + var edge = _physics.GetContactEdge(); loop2.GetChildEdge(ref edge, ChildIndexA); - _manifoldManager.CollideEdgeAndPolygon(ref manifold, edge, in transformA, (PolygonShape) FixtureB!.Shape, in transformB); - _manifoldManager.ReturnEdge(edge); + _physics.CollideEdgeAndPolygon(ref manifold, edge, in transformA, (PolygonShape) FixtureB!.Shape, in transformB); + _physics.ReturnEdge(edge); break; } case ContactType.Circle: - _manifoldManager.CollideCircles(ref manifold, (PhysShapeCircle) FixtureA!.Shape, in transformA, (PhysShapeCircle) FixtureB!.Shape, in transformB); + _physics.CollideCircles(ref manifold, (PhysShapeCircle) FixtureA!.Shape, in transformA, (PhysShapeCircle) FixtureB!.Shape, in transformB); break; default: throw new ArgumentOutOfRangeException($"Collision between {FixtureA!.Shape.GetType()} and {FixtureB!.Shape.GetType()} not supported"); diff --git a/Robust.Shared/Physics/Serialization/NonNegativeFloatSerializer.cs b/Robust.Shared/Physics/Serialization/NonNegativeFloatSerializer.cs new file mode 100644 index 000000000..46fbd9642 --- /dev/null +++ b/Robust.Shared/Physics/Serialization/NonNegativeFloatSerializer.cs @@ -0,0 +1,54 @@ +using System.Globalization; +using Robust.Shared.IoC; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.Manager; +using Robust.Shared.Serialization.Markdown; +using Robust.Shared.Serialization.Markdown.Validation; +using Robust.Shared.Serialization.Markdown.Value; +using Robust.Shared.Serialization.TypeSerializers.Interfaces; + +namespace Robust.Shared.Physics.Serialization; + +public sealed class NonNegativeFloatSerializer : ITypeSerializer +{ + public ValidationNode Validate( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + ISerializationContext? context = null) + { + if (!float.TryParse(node.Value, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value)) + return new ErrorNode(node, $"Failed parsing float value: {node.Value}"); + + if (value < 0f) + return new ErrorNode(node, "Value must be non-negative."); + + return new ValidatedValueNode(node); + } + + public float Read( + ISerializationManager serializationManager, + ValueDataNode node, + IDependencyCollection dependencies, + SerializationHookContext hookCtx, + ISerializationContext? context = null, + ISerializationManager.InstantiationDelegate? instanceProvider = null) + { + var value = float.Parse(node.Value, CultureInfo.InvariantCulture); + + if (value < 0f) + throw new InvalidMappingException("Value must be non-negative."); + + return value; + } + + public DataNode Write( + ISerializationManager serializationManager, + float value, + IDependencyCollection dependencies, + bool alwaysWrite = false, + ISerializationContext? context = null) + { + return new ValueDataNode(value.ToString(CultureInfo.InvariantCulture)); + } +} diff --git a/Robust.Shared/Physics/Shapes/Polygon.cs b/Robust.Shared/Physics/Shapes/Polygon.cs index 5961d482d..798d9abc2 100644 --- a/Robust.Shared/Physics/Shapes/Polygon.cs +++ b/Robust.Shared/Physics/Shapes/Polygon.cs @@ -31,10 +31,7 @@ internal record struct Polygon : IPhysShape public Vector2 Centroid; - public int ChildCount => 1; public float Radius { get; set; } = PhysicsConstants.PolygonRadius; - public ShapeType ShapeType => ShapeType.Polygon; - // Hopefully this one is short-lived for a few months public Polygon(IPhysShape shape) : this((PolygonShape) shape) { @@ -210,25 +207,6 @@ public static Vector2 ComputeCentroid(ReadOnlySpan vs) return c; } - public Box2 ComputeAABB(Transform transform, int childIndex) - { - DebugTools.Assert(VertexCount > 0); - DebugTools.Assert(childIndex == 0); - var verts = _vertices.AsSpan; - var lower = Transform.Mul(transform, verts[0]); - var upper = lower; - - for (var i = 1; i < VertexCount; ++i) - { - var v = Transform.Mul(transform, verts[i]); - lower = Vector2.Min(lower, v); - upper = Vector2.Max(upper, v); - } - - var r = new Vector2(Radius, Radius); - return new Box2(lower - r, upper + r); - } - public bool Equals(IPhysShape? other) { if (other is SlimPolygon slim) diff --git a/Robust.Shared/Physics/Shapes/SlimPolygon.cs b/Robust.Shared/Physics/Shapes/SlimPolygon.cs index 53e69a161..e6a9f5a5e 100644 --- a/Robust.Shared/Physics/Shapes/SlimPolygon.cs +++ b/Robust.Shared/Physics/Shapes/SlimPolygon.cs @@ -30,10 +30,7 @@ internal record struct SlimPolygon : IPhysShape public byte VertexCount => 4; - public int ChildCount => 1; public float Radius { get; set; } = PhysicsConstants.PolygonRadius; - public ShapeType ShapeType => ShapeType.Polygon; - public SlimPolygon(Box2 box) { Radius = 0f; @@ -152,14 +149,6 @@ public Box2 ComputeAABBSse(Transform transform) return Unsafe.As, Box2>(ref lbrt); } - public Box2 ComputeAABB(Transform transform, int childIndex) - { - DebugTools.Assert(childIndex == 0); - return Sse.IsSupported - ? ComputeAABBSse(transform) - : ComputeAABBSlow(transform); - } - public bool Equals(SlimPolygon other) { return Radius.Equals(other.Radius) && _vertices.AsSpan[..VertexCount].SequenceEqual(other._vertices.AsSpan[..VertexCount]); diff --git a/Robust.Shared/Physics/Systems/RayCastSystem.cs b/Robust.Shared/Physics/Systems/RayCastSystem.cs index fb3187cf4..fc0430f75 100644 --- a/Robust.Shared/Physics/Systems/RayCastSystem.cs +++ b/Robust.Shared/Physics/Systems/RayCastSystem.cs @@ -229,8 +229,8 @@ public RayResult CastShape( DebugTools.Assert(translation.IsValid()); // Need to get the entire shape AABB to know what broadphases to even query. - var startAabb = shape.ComputeAABB(originTransform, 0); - var endAabb = shape.ComputeAABB(new Transform(originTransform.Position + translation, originTransform.Quaternion2D.Angle), 0); + var startAabb = _physics.ComputeAABB(shape, originTransform, 0); + var endAabb = _physics.ComputeAABB(shape, new Transform(originTransform.Position + translation, originTransform.Quaternion2D.Angle), 0); var aabb = startAabb.Union(endAabb); var result = new RayResult(); diff --git a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs index e6fbf61f9..76a2e8297 100644 --- a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs @@ -328,13 +328,13 @@ private void HandleGridCollisions(HashSet movedGrids) continue; bool addedPair = false; - for (var i = 0; i < fixtureA.Shape.ChildCount && !addedPair; i++) + for (var i = 0; i < tuple._physicsSystem.GetChildCount(fixtureA.Shape) && !addedPair; i++) { - var shapeAAabbInWorld = fixtureA.Shape.ComputeAABB(tuple.gridAToWorldRigid, i); + var shapeAAabbInWorld = tuple._physicsSystem.ComputeAABB(fixtureA.Shape, tuple.gridAToWorldRigid, i); - for (var j = 0; j < fixtureB.Shape.ChildCount && !addedPair; j++) + for (var j = 0; j < tuple._physicsSystem.GetChildCount(fixtureB.Shape) && !addedPair; j++) { - var shapeBAabbInWorld = fixtureB.Shape.ComputeAABB(gridBToWorldRigid, j); + var shapeBAabbInWorld = tuple._physicsSystem.ComputeAABB(fixtureB.Shape, gridBToWorldRigid, j); if (!shapeAAabbInWorld.Intersects(shapeBAabbInWorld)) continue; @@ -500,8 +500,9 @@ public void RegenerateContacts(Entity /// Compute contact points for edge versus circle. @@ -22,7 +24,7 @@ public void CollideEdgeAndCircle(ref Manifold manifold, EdgeShape edgeA, in Tran manifold.PointCount = 0; // Compute circle in frame of edge - var Q = Transform.MulT(transformA, Transform.Mul(transformB, circleB.Position)); + var Q = Physics.Transform.MulT(transformA, Physics.Transform.Mul(transformB, circleB.Position)); var A = edgeA.Vertex1; var B = edgeA.Vertex2; diff --git a/Robust.Shared/Physics/Collision/CollisionManager.EdgePolygon.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.EdgePolygon.cs similarity index 94% rename from Robust.Shared/Physics/Collision/CollisionManager.EdgePolygon.cs rename to Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.EdgePolygon.cs index 7a3473cb1..042db8730 100644 --- a/Robust.Shared/Physics/Collision/CollisionManager.EdgePolygon.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.EdgePolygon.cs @@ -1,12 +1,14 @@ using System; +using Transform = Robust.Shared.Physics.Transform; using System.Numerics; using Robust.Shared.Maths; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Collision; using Robust.Shared.Utility; -namespace Robust.Shared.Physics.Collision; +namespace Robust.Shared.Physics.Systems; -internal sealed partial class CollisionManager +public abstract partial class SharedPhysicsSystem { /// /// Collides and edge and a polygon, taking into account edge adjacency. @@ -21,9 +23,9 @@ public void CollideEdgeAndPolygon(ref Manifold manifold, EdgeShape edgeA, in Tra { manifold.PointCount = 0; - var xf = Transform.MulT(xfA, xfB); + var xf = Physics.Transform.MulT(xfA, xfB); - var centroidB = Transform.Mul(xf, polygonB.Centroid); + var centroidB = Physics.Transform.Mul(xf, polygonB.Centroid); var v1 = edgeA.Vertex1; var v2 = edgeA.Vertex2; @@ -47,8 +49,8 @@ public void CollideEdgeAndPolygon(ref Manifold manifold, EdgeShape edgeA, in Tra for (var i = 0; i < tempPolyCount; ++i) { - tempPolyVerts[i] = Transform.Mul(xf, polygonB.Vertices[i]); - tempPolyNorms[i] = Transform.Mul(xf.Quaternion2D, polygonB.Normals[i]); + tempPolyVerts[i] = Physics.Transform.Mul(xf, polygonB.Vertices[i]); + tempPolyNorms[i] = Physics.Transform.Mul(xf.Quaternion2D, polygonB.Normals[i]); } DebugTools.Assert(tempPolyVerts.Length == tempPolyCount); @@ -250,7 +252,7 @@ public void CollideEdgeAndPolygon(ref Manifold manifold, EdgeShape edgeA, in Tra if (primaryAxis.Type == EPAxisType.EdgeA) { - cp.LocalPoint = Transform.MulT(xf, clipPoints2[i].V); + cp.LocalPoint = Physics.Transform.MulT(xf, clipPoints2[i].V); cp.Id = clipPoints2[i].ID; } else diff --git a/Robust.Shared/Physics/Collision/CollisionManager.PolygonCircle.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.PolygonCircle.cs similarity index 92% rename from Robust.Shared/Physics/Collision/CollisionManager.PolygonCircle.cs rename to Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.PolygonCircle.cs index a032ef5be..7f81e38b0 100644 --- a/Robust.Shared/Physics/Collision/CollisionManager.PolygonCircle.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.PolygonCircle.cs @@ -1,12 +1,14 @@ using System; +using Transform = Robust.Shared.Physics.Transform; using System.Numerics; using Robust.Shared.Maths; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Collision; using Robust.Shared.Utility; -namespace Robust.Shared.Physics.Collision; +namespace Robust.Shared.Physics.Systems; -internal sealed partial class CollisionManager +public abstract partial class SharedPhysicsSystem { /// /// Compute the collision manifold between a polygon and a circle. @@ -22,8 +24,8 @@ public void CollidePolygonAndCircle(ref Manifold manifold, PolygonShape polygonA manifold.PointCount = 0; // Compute circle position in the frame of the polygon. - var c = Transform.Mul(xfB, circleB.Position); - var cLocal = Transform.MulT(xfA, c); + var c = Physics.Transform.Mul(xfB, circleB.Position); + var cLocal = Physics.Transform.MulT(xfA, c); // Find the min separating edge. int normalIndex = 0; diff --git a/Robust.Shared/Physics/Collision/CollisionManager.Polygons.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.Polygons.cs similarity index 90% rename from Robust.Shared/Physics/Collision/CollisionManager.Polygons.cs rename to Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.Polygons.cs index 69d9bc1ee..5598f3309 100644 --- a/Robust.Shared/Physics/Collision/CollisionManager.Polygons.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.Polygons.cs @@ -1,12 +1,14 @@ using System; +using Transform = Robust.Shared.Physics.Transform; using System.Numerics; using Robust.Shared.Maths; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Collision; using Robust.Shared.Utility; -namespace Robust.Shared.Physics.Collision; +namespace Robust.Shared.Physics.Systems; -internal sealed partial class CollisionManager +public abstract partial class SharedPhysicsSystem { /// /// Find the max separation between poly1 and poly2 using edge normals from poly1. @@ -47,7 +49,7 @@ private static float FindMaxSeparation(out int edgeIndex, PolygonShape poly1, in var v2s = poly2.Vertices; var count1 = poly1.VertexCount; var count2 = poly2.VertexCount; - var xf = Transform.MulT(xf2, xf1); + var xf = Physics.Transform.MulT(xf2, xf1); var bestIndex = 0; var maxSeparation = float.MinValue; @@ -55,8 +57,8 @@ private static float FindMaxSeparation(out int edgeIndex, PolygonShape poly1, in for (var i = 0; i < count1; i++) { // Get poly1 normal in frame2. - var n = Transform.Mul(xf.Quaternion2D, n1s[i]); - var v1 = Transform.Mul(xf, v1s[i]); + var n = Physics.Transform.Mul(xf.Quaternion2D, n1s[i]); + var v1 = Physics.Transform.Mul(xf, v1s[i]); // Find deepest point for normal i. var si = float.MaxValue; @@ -91,7 +93,7 @@ private static void FindIncidentEdge(Span c, PolygonShape poly1, in DebugTools.Assert(0 <= edge1 && edge1 < poly1.VertexCount); // Get the normal of the reference edge in poly2's frame. - var normal1 = Transform.MulT(xf2.Quaternion2D, Transform.Mul(xf1.Quaternion2D, normals1[edge1])); + var normal1 = Physics.Transform.MulT(xf2.Quaternion2D, Physics.Transform.Mul(xf1.Quaternion2D, normals1[edge1])); // Find the incident edge on poly2. var index = 0; @@ -114,14 +116,14 @@ private static void FindIncidentEdge(Span c, PolygonShape poly1, in ref var cv0 = ref c[0]; - cv0.V = Transform.Mul(xf2, vertices2[i1]); + cv0.V = Physics.Transform.Mul(xf2, vertices2[i1]); cv0.ID.Features.IndexA = (byte) edge1; cv0.ID.Features.IndexB = (byte) i1; cv0.ID.Features.TypeA = (byte) ContactFeatureType.Face; cv0.ID.Features.TypeB = (byte) ContactFeatureType.Vertex; ref var cv1 = ref c[1]; - cv1.V = Transform.Mul(xf2, vertices2[i2]); + cv1.V = Physics.Transform.Mul(xf2, vertices2[i2]); cv1.ID.Features.IndexA = (byte) edge1; cv1.ID.Features.IndexB = (byte) i2; cv1.ID.Features.TypeA = (byte) ContactFeatureType.Face; @@ -200,13 +202,13 @@ public void CollidePolygons(ref Manifold manifold, PolygonShape polyA, in Transf Vector2 localNormal = new Vector2(localTangent.Y, -localTangent.X); Vector2 planePoint = (v11 + v12) * 0.5f; - Vector2 tangent = Transform.Mul(xf1.Quaternion2D, localTangent); + Vector2 tangent = Physics.Transform.Mul(xf1.Quaternion2D, localTangent); float normalX = tangent.Y; float normalY = -tangent.X; - v11 = Transform.Mul(xf1, v11); - v12 = Transform.Mul(xf1, v12); + v11 = Physics.Transform.Mul(xf1, v11); + v12 = Physics.Transform.Mul(xf1, v12); // Face offset. float frontOffset = normalX * v11.X + normalY * v11.Y; @@ -248,7 +250,7 @@ public void CollidePolygons(ref Manifold manifold, PolygonShape polyA, in Transf if (separation <= totalRadius) { ref var cp = ref points[pointCount]; - cp.LocalPoint = Transform.MulT(xf2, clipPoints2[i].V); + cp.LocalPoint = Physics.Transform.MulT(xf2, clipPoints2[i].V); cp.Id = clipPoints2[i].ID; if (flip) diff --git a/Robust.Shared/Physics/Collision/CollisionManager.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.cs similarity index 83% rename from Robust.Shared/Physics/Collision/CollisionManager.cs rename to Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.cs index 7161bc582..75599a9a9 100644 --- a/Robust.Shared/Physics/Collision/CollisionManager.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Collision.cs @@ -1,4 +1,5 @@ /* +using Transform = Robust.Shared.Physics.Transform; * Farseer Physics Engine: * Copyright (c) 2012 Ian Qvist * @@ -25,13 +26,14 @@ using Microsoft.Extensions.ObjectPool; using Robust.Shared.Maths; using Robust.Shared.Physics.Collision.Shapes; +using Robust.Shared.Physics.Collision; -namespace Robust.Shared.Physics.Collision; +namespace Robust.Shared.Physics.Systems; /// /// Handles several collision features: Generating contact manifolds, testing shape overlap, /// -internal sealed partial class CollisionManager : IManifoldManager +public abstract partial class SharedPhysicsSystem { /* * Farseer had this as a static class with a ThreadStatic DistanceInput @@ -40,6 +42,31 @@ internal sealed partial class CollisionManager : IManifoldManager private ObjectPool _edgePool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + /// + /// Test overlap between the two shapes. + /// + /// The first shape. + /// The second shape. + /// The transform for the first shape. + /// The transform for the seconds shape. + /// + public bool TestOverlap(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB) + where T : IPhysShape + where U : IPhysShape + { + var input = new DistanceInput(); + + input.ProxyA.Set(shapeA, indexA); + input.ProxyB.Set(shapeB, indexB); + input.TransformA = xfA; + input.TransformB = xfB; + input.UseRadii = true; + + DistanceManager.ComputeDistance(out var output, out _, input); + + return output.Distance < 10.0f * float.Epsilon; + } + /// /// Used for debugging contact points. /// diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 88670635e..101f6da19 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -776,9 +776,9 @@ public Box2 GetWorldAABB(EntityUid uid, FixturesComponent? manager = null, Physi foreach (var fixture in manager.Fixtures.Values) { - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < GetChildCount(fixture.Shape); i++) { - var boundy = fixture.Shape.ComputeAABB(transform, i); + var boundy = ComputeAABB(fixture.Shape, transform, i); bounds = bounds.Union(boundy); } } @@ -805,9 +805,9 @@ public Box2 GetHardAABB(EntityUid uid, FixturesComponent? manager = null, Physic { if (!fixture.Hard) continue; - for (var i = 0; i < fixture.Shape.ChildCount; i++) + for (var i = 0; i < GetChildCount(fixture.Shape); i++) { - var boundy = fixture.Shape.ComputeAABB(transform, i); + var boundy = ComputeAABB(fixture.Shape, transform, i); bounds = bounds.Union(boundy); } } diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs index ffa33ad2a..f19228bf7 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Contacts.cs @@ -50,43 +50,6 @@ namespace Robust.Shared.Physics.Systems; public abstract partial class SharedPhysicsSystem { - // TODO: Jesus we should really have a test for this - /// - /// Ordering is under - /// uses enum to work out which collision evaluation to use. - /// - private static Contact.ContactType[,] _registers = - { - { - // Circle register - Contact.ContactType.Circle, - Contact.ContactType.EdgeAndCircle, - Contact.ContactType.PolygonAndCircle, - Contact.ContactType.ChainAndCircle, - }, - { - // Edge register - Contact.ContactType.EdgeAndCircle, - Contact.ContactType.NotSupported, // Edge - Contact.ContactType.EdgeAndPolygon, - Contact.ContactType.NotSupported, // Chain - }, - { - // Polygon register - Contact.ContactType.PolygonAndCircle, - Contact.ContactType.EdgeAndPolygon, - Contact.ContactType.Polygon, - Contact.ContactType.ChainAndPolygon, - }, - { - // Chain register - Contact.ContactType.ChainAndCircle, - Contact.ContactType.NotSupported, // Edge - Contact.ContactType.ChainAndPolygon, - Contact.ContactType.NotSupported, // Chain - } - }; - private int ContactCount => _activeContacts.Count; private const int ContactPoolInitialSize = 128; @@ -99,17 +62,17 @@ public abstract partial class SharedPhysicsSystem private sealed class ContactPoolPolicy : IPooledObjectPolicy { private readonly SharedDebugPhysicsSystem _debugPhysicsSystem; - private readonly IManifoldManager _manifoldManager; + private readonly SharedPhysicsSystem _physics; - public ContactPoolPolicy(SharedDebugPhysicsSystem debugPhysicsSystem, IManifoldManager manifoldManager) + public ContactPoolPolicy(SharedDebugPhysicsSystem debugPhysicsSystem, SharedPhysicsSystem physics) { _debugPhysicsSystem = debugPhysicsSystem; - _manifoldManager = manifoldManager; + _physics = physics; } public Contact Create() { - var contact = new Contact(_manifoldManager); + var contact = new Contact(_physics); #if DEBUG contact._debugPhysics = _debugPhysicsSystem; #endif @@ -181,7 +144,7 @@ private static void SetContact(Contact contact, private void InitializeContacts() { _contactPool = new DefaultObjectPool( - new ContactPoolPolicy(_debugPhysics, _manifoldManager), + new ContactPoolPolicy(_debugPhysics, this), 4096); InitializePool(); @@ -223,11 +186,8 @@ private Contact CreateContact( Fixture fixtureA, int indexA, Fixture fixtureB, int indexB) { - var type1 = fixtureA.Shape.ShapeType; - var type2 = fixtureB.Shape.ShapeType; - - DebugTools.Assert(ShapeType.Unknown < type1 && type1 < ShapeType.TypeCount); - DebugTools.Assert(ShapeType.Unknown < type2 && type2 < ShapeType.TypeCount); + var type1 = GetContactShapeOrder(fixtureA.Shape); + var type2 = GetContactShapeOrder(fixtureB.Shape); // Pull out a spare contact object var contact = _contactPool.Get(); @@ -235,7 +195,8 @@ private Contact CreateContact( contact.Flags = ContactFlags.PreInit; // Edge+Polygon is non-symmetrical due to the way Erin handles collision type registration. - if ((type1 >= type2 || (type1 == ShapeType.Edge && type2 == ShapeType.Polygon)) && !(type2 == ShapeType.Edge && type1 == ShapeType.Polygon)) + if ((type1 >= type2 || (fixtureA.Shape is EdgeShape && fixtureB.Shape is PolygonShape)) && + !(fixtureB.Shape is EdgeShape && fixtureA.Shape is PolygonShape)) { SetContact(contact, true, entA, entB, fixtureAId, fixtureBId, fixtureA, indexA, fixtureB, indexB); } @@ -244,11 +205,40 @@ private Contact CreateContact( SetContact(contact, true, entB, entA, fixtureBId, fixtureAId, fixtureB, indexB, fixtureA, indexA); } - contact.Type = _registers[(int)type1, (int)type2]; + contact.Type = GetContactType(fixtureA.Shape, fixtureB.Shape); return contact; } + private static int GetContactShapeOrder(IPhysShape shape) + { + return shape switch + { + PhysShapeCircle => 0, + EdgeShape => 1, + PolygonShape => 2, + ChainShape => 3, + _ => throw new InvalidOperationException($"Invalid shape specified {shape.GetType()}"), + }; + } + + private static Contact.ContactType GetContactType(TShape shapeA, TShape2 shapeB) + where TShape : IPhysShape + where TShape2 : IPhysShape + { + return (shapeA, shapeB) switch + { + (PhysShapeCircle, PhysShapeCircle) => Contact.ContactType.Circle, + (PhysShapeCircle, EdgeShape) or (EdgeShape, PhysShapeCircle) => Contact.ContactType.EdgeAndCircle, + (PhysShapeCircle, PolygonShape) or (PolygonShape, PhysShapeCircle) => Contact.ContactType.PolygonAndCircle, + (PhysShapeCircle, ChainShape) or (ChainShape, PhysShapeCircle) => Contact.ContactType.ChainAndCircle, + (EdgeShape, PolygonShape) or (PolygonShape, EdgeShape) => Contact.ContactType.EdgeAndPolygon, + (PolygonShape, PolygonShape) => Contact.ContactType.Polygon, + (PolygonShape, ChainShape) or (ChainShape, PolygonShape) => Contact.ContactType.ChainAndPolygon, + _ => Contact.ContactType.NotSupported, + }; + } + /// /// Try to create a contact between these 2 fixtures. /// @@ -478,8 +468,8 @@ internal void CollideContacts() // Special-case grid contacts. if ((contact.Flags & ContactFlags.Grid) != 0x0) { - var gridABounds = fixtureA.Shape.ComputeAABB(GetPhysicsTransform(uidA, xformA), 0); - var gridBBounds = fixtureB.Shape.ComputeAABB(GetPhysicsTransform(uidB, xformB), 0); + var gridABounds = ComputeAABB(fixtureA.Shape, GetPhysicsTransform(uidA, xformA), 0); + var gridBBounds = ComputeAABB(fixtureB.Shape, GetPhysicsTransform(uidB, xformB), 0); if (!gridABounds.Intersects(gridBBounds)) { diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs index d7243920e..3bbb97637 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Fixtures.cs @@ -1,7 +1,6 @@ using System; using Robust.Shared.GameObjects; using Robust.Shared.IoC; -using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Utility; @@ -73,45 +72,6 @@ public void SetRestitution(EntityUid uid, Fixture fixture, float value, bool upd _fixtures.FixtureUpdate(uid, manager: manager); } - /// - /// Increases or decreases all fixtures of an entity in size by a certain factor. - /// - public void ScaleFixtures(Entity ent, float factor) - { - if (!Resolve(ent, ref ent.Comp)) - return; - - foreach (var (id, fixture) in ent.Comp.Fixtures) - { - switch (fixture.Shape) - { - case EdgeShape edge: - SetVertices(ent, id, fixture, - edge, - edge.Vertex0 * factor, - edge.Vertex1 * factor, - edge.Vertex2 * factor, - edge.Vertex3 * factor, ent.Comp); - break; - case PhysShapeCircle circle: - SetPositionRadius(ent, id, fixture, circle, circle.Position * factor, circle.Radius * factor, ent.Comp); - break; - case PolygonShape poly: - var verts = poly.Vertices; - - for (var i = 0; i < poly.VertexCount; i++) - { - verts[i] *= factor; - } - - SetVertices(ent, id, fixture, poly, verts, ent.Comp); - break; - default: - throw new NotImplementedException(); - } - } - } - #region Collision Masks & Layers /// diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs index b8ac8c333..30bb62f8f 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs @@ -107,8 +107,9 @@ public HashSet GetEntitiesIntersectingBody( foreach (var fixture in fixtureComp.Fixtures.Values) { - foreach (var proxy in fixture.Proxies) + for (var i = 0; i < fixture.ProxyCount; i++) { + var proxy = fixture.Proxies[i]; broadphase.StaticTree.QueryAabb(ref state, (ref (PhysicsComponent body, HashSet entities) state, in FixtureProxy other) => @@ -553,7 +554,7 @@ public bool TryGetNearest(EntityUid uidA, EntityUid uidB, if (bodyA.Hard && !fixtureA.Hard) continue; - for (var i = 0; i < fixtureA.Shape.ChildCount; i++) + for (var i = 0; i < GetChildCount(fixtureA.Shape); i++) { input.ProxyA.Set(fixtureA.Shape, i); @@ -562,7 +563,7 @@ public bool TryGetNearest(EntityUid uidA, EntityUid uidB, if (bodyB.Hard && !fixtureB.Hard) continue; - for (var j = 0; j < fixtureB.Shape.ChildCount; j++) + for (var j = 0; j < GetChildCount(fixtureB.Shape); j++) { input.ProxyB.Set(fixtureB.Shape, j); DistanceManager.ComputeDistance(out var output, out _, input); diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs index ce20c17f1..49fb2d20d 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Shapes.cs @@ -5,90 +5,69 @@ using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Shapes; using Robust.Shared.Utility; namespace Robust.Shared.Physics.Systems; public abstract partial class SharedPhysicsSystem { - public void SetRadius( - EntityUid uid, + public void SetRadius( + Entity ent, string fixtureId, Fixture fixture, - IPhysShape shape, - float radius, - FixturesComponent? manager = null, - PhysicsComponent? body = null, - TransformComponent? xform = null) + TShape shape, + float radius) + where TShape : IPhysShape { - if (MathHelper.CloseTo(shape.Radius, radius) || !Resolve(uid, ref manager, ref body, ref xform)) + if (!ValidateRadius(ent, fixtureId, radius) || + MathHelper.CloseTo(shape.Radius, radius) || + !ResolveShapeEntity(ref ent)) + { return; + } shape.Radius = radius; - if (body.CanCollide && - TryComp(xform.Broadphase?.Uid, out var broadphase)) - { - _lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase); - _lookup.CreateProxies(uid, fixtureId, fixture, xform, body); - } - - _fixtures.FixtureUpdate(uid, manager: manager, body: body); + UpdateFixtureShape(ent, fixtureId, fixture); } #region Circle public void SetPositionRadius( - EntityUid uid, + Entity ent, string fixtureId, Fixture fixture, PhysShapeCircle shape, Vector2 position, - float radius, - FixturesComponent? manager = null, - PhysicsComponent? body = null, - TransformComponent? xform = null) + float radius) { - if ((MathHelper.CloseTo(shape.Radius, radius) && shape.Position.EqualsApprox(position)) || - !Resolve(uid, ref manager, ref body, ref xform)) + if (!ValidateRadius(ent, fixtureId, radius) || + MathHelper.CloseTo(shape.Radius, radius) && shape.Position.EqualsApprox(position) || + !ResolveShapeEntity(ref ent)) + { return; + } shape.Position = position; shape.Radius = radius; - if (body.CanCollide && - TryComp(xform.Broadphase?.Uid, out var broadphase)) - { - _lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase); - _lookup.CreateProxies(uid, fixtureId, fixture, xform, body); - } - - Dirty(uid, manager); + UpdateFixtureShape(ent, fixtureId, fixture); } public void SetPosition( - EntityUid uid, + Entity ent, string fixtureId, Fixture fixture, PhysShapeCircle circle, - Vector2 position, - FixturesComponent? manager = null, - PhysicsComponent? body = null, - TransformComponent? xform = null) + Vector2 position) { - if (circle.Position.EqualsApprox(position) || !Resolve(uid, ref manager, ref body, ref xform)) + if (circle.Position.EqualsApprox(position) || !ResolveShapeEntity(ref ent)) return; circle.Position = position; - if (body.CanCollide && - TryComp(xform.Broadphase?.Uid, out var broadphase)) - { - _lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase); - _lookup.CreateProxies(uid, fixtureId, fixture, xform, body); - } - - Dirty(uid, manager); + UpdateFixtureShape(ent, fixtureId, fixture); } #endregion @@ -96,19 +75,16 @@ public void SetPosition( #region Edge public void SetVertices( - EntityUid uid, + Entity ent, string fixtureId, Fixture fixture, EdgeShape edge, Vector2 vertex0, Vector2 vertex1, Vector2 vertex2, - Vector2 vertex3, - FixturesComponent? manager = null, - PhysicsComponent? body = null, - TransformComponent? xform = null) + Vector2 vertex3) { - if (!Resolve(uid, ref manager, ref body, ref xform)) + if (!ResolveShapeEntity(ref ent)) return; edge.Vertex0 = vertex0; @@ -116,14 +92,7 @@ public void SetVertices( edge.Vertex2 = vertex2; edge.Vertex3 = vertex3; - if (body.CanCollide && - TryComp(xform.Broadphase?.Uid, out var broadphase)) - { - _lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase); - _lookup.CreateProxies(uid, fixtureId, fixture, xform, body); - } - - _fixtures.FixtureUpdate(uid, manager: manager, body: body); + UpdateFixtureShape(ent, fixtureId, fixture); } #endregion @@ -131,29 +100,186 @@ public void SetVertices( #region Polygon public void SetVertices( - EntityUid uid, + Entity ent, string fixtureId, Fixture fixture, PolygonShape poly, - Vector2[] vertices, - FixturesComponent? manager = null, - PhysicsComponent? body = null, - TransformComponent? xform = null) + Vector2[] vertices) { - if (!Resolve(uid, ref manager, ref body, ref xform)) + if (!ResolveShapeEntity(ref ent)) return; poly.Set(vertices, vertices.Length); - if (body.CanCollide && - TryComp(xform.Broadphase?.Uid, out var broadphase)) + UpdateFixtureShape(ent, fixtureId, fixture); + } + + #endregion + + /// + /// Increases or decreases all fixtures of an entity in size by a certain factor. + /// + public void ScaleFixtures(Entity ent, float factor) + { + if (!_fixturesQuery.Resolve(ent, ref ent.Comp)) + return; + + foreach (var (id, fixture) in ent.Comp.Fixtures) { - _lookup.DestroyProxies(uid, fixtureId, fixture, xform, broadphase); - _lookup.CreateProxies(uid, fixtureId, fixture, xform, body); + ScaleFixture(ent, id, fixture, factor); } + } - _fixtures.FixtureUpdate(uid, manager: manager, body: body); + public int GetChildCount(TShape shape) where TShape : IPhysShape + { + return shape switch + { + ChainShape chain => chain.Count - 1, + _ => 1, + }; } - #endregion + public Box2 ComputeAABB(TShape shape, Transform transform, int childIndex) where TShape : IPhysShape + { + switch (shape) + { + case ChainShape chain: + { + DebugTools.Assert(childIndex < chain.Count); + + var i1 = childIndex; + var i2 = childIndex + 1; + if (i2 == chain.Count) + i2 = 0; + + var v1 = Physics.Transform.Mul(transform, chain.Vertices[i1]); + var v2 = Physics.Transform.Mul(transform, chain.Vertices[i2]); + + var lower = Vector2.Min(v1, v2); + var upper = Vector2.Max(v1, v2); + var r = new Vector2(chain.Radius, chain.Radius); + return new Box2(lower - r, upper + r); + } + case EdgeShape edge: + { + DebugTools.Assert(childIndex == 0); + + var v1 = Physics.Transform.Mul(transform, edge.Vertex1); + var v2 = Physics.Transform.Mul(transform, edge.Vertex2); + var lower = Vector2.Min(v1, v2); + var upper = Vector2.Max(v1, v2); + var radius = new Vector2(edge.Radius, edge.Radius); + return new Box2(lower - radius, upper + radius); + } + case PhysShapeAabb aabb: + return new Box2Rotated(aabb.LocalBounds.Translated(transform.Position), transform.Quaternion2D.Angle, transform.Position) + .CalcBoundingBox() + .Enlarged(aabb.Radius); + case PhysShapeCircle circle: + { + DebugTools.Assert(childIndex == 0); + + var p = transform.Position + Physics.Transform.Mul(transform.Quaternion2D, circle.Position); + return new Box2(p.X - circle.Radius, p.Y - circle.Radius, p.X + circle.Radius, p.Y + circle.Radius); + } + case PolygonShape poly: + { + DebugTools.Assert(childIndex == 0); + return ComputePolygonAabb(poly.Vertices, poly.VertexCount, poly.Radius, transform); + } + case Polygon poly: + { + DebugTools.Assert(childIndex == 0); + return ComputePolygonAabb(poly._vertices.AsSpan, poly.VertexCount, poly.Radius, transform); + } + case SlimPolygon slim: + { + DebugTools.Assert(childIndex == 0); + return ComputePolygonAabb(slim._vertices.AsSpan, slim.VertexCount, slim.Radius, transform); + } + default: + throw new NotImplementedException($"Cannot compute AABB for {shape.GetType()}."); + } + } + + private static Box2 ComputePolygonAabb(ReadOnlySpan vertices, int vertexCount, float radius, Transform transform) + { + DebugTools.Assert(vertexCount > 0); + var lower = Physics.Transform.Mul(transform, vertices[0]); + var upper = lower; + + for (var i = 1; i < vertexCount; ++i) + { + var v = Physics.Transform.Mul(transform, vertices[i]); + lower = Vector2.Min(lower, v); + upper = Vector2.Max(upper, v); + } + + var r = new Vector2(radius, radius); + return new Box2(lower - r, upper + r); + } + + public void ScaleFixture(Entity ent, string fixtureId, Fixture fixture, float factor) + { + switch (fixture.Shape) + { + case EdgeShape edge: + SetVertices((ent.Owner, ent.Comp, null, null), + fixtureId, + fixture, + edge, + edge.Vertex0 * factor, + edge.Vertex1 * factor, + edge.Vertex2 * factor, + edge.Vertex3 * factor); + break; + case PhysShapeCircle circle: + SetPositionRadius((ent.Owner, ent.Comp, null, null), fixtureId, fixture, circle, circle.Position * factor, circle.Radius * factor); + break; + case PolygonShape poly: + var verts = poly.Vertices; + + for (var i = 0; i < poly.VertexCount; i++) + { + verts[i] *= factor; + } + + SetVertices((ent.Owner, ent.Comp, null, null), fixtureId, fixture, poly, verts); + break; + default: + throw new NotImplementedException(); + } + } + + private bool ResolveShapeEntity(ref Entity ent) + { + return _fixturesQuery.Resolve(ent.Owner, ref ent.Comp1) && + PhysicsQuery.Resolve(ent.Owner, ref ent.Comp2) && + XformQuery.Resolve(ent.Owner, ref ent.Comp3); + } + + private void UpdateFixtureShape( + Entity ent, + string fixtureId, + Fixture fixture) + { + if (ent.Comp2!.CanCollide && + BroadphaseQuery.TryGetComponent(ent.Comp3!.Broadphase?.Uid, out var broadphase)) + { + _lookup.DestroyProxies(ent.Owner, fixtureId, fixture, ent.Comp3, broadphase); + _lookup.CreateProxies(ent.Owner, fixtureId, fixture, ent.Comp3, ent.Comp2); + } + + _fixtures.FixtureUpdate(ent.Owner, manager: ent.Comp1!, body: ent.Comp2); + } + + private bool ValidateRadius(Entity ent, string fixtureId, float radius) + { + if (radius >= 0f) + return true; + + Log.Error($"Tried to set fixture {fixtureId} on {ToPrettyString(ent.Owner)} to negative radius {radius}."); + DebugTools.Assert(radius >= 0f); + return false; + } } diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 074cc7fd7..28d0957e9 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -40,7 +40,6 @@ public abstract partial class SharedPhysicsSystem : EntitySystem }); [Dependency] private IConfigurationManager _cfg = default!; - [Dependency] private IManifoldManager _manifoldManager = default!; [Dependency] private IParallelManager _parallel = default!; [Dependency] private EntityLookupSystem _lookup = default!; [Dependency] private SharedBroadphaseSystem _broadphase = default!; @@ -61,6 +60,7 @@ public abstract partial class SharedPhysicsSystem : EntitySystem public bool MetricsEnabled { get; protected set; } private EntityQuery _anchorQuery; + private EntityQuery BroadphaseQuery; private EntityQuery _fixturesQuery; private EntityQuery JointQuery; private EntityQuery RelayTargetQuery; @@ -103,6 +103,7 @@ public override void Initialize() _angularVelocityIndex = 10; _anchorQuery = GetEntityQuery(); + BroadphaseQuery = GetEntityQuery(); _fixturesQuery = GetEntityQuery(); JointQuery = GetEntityQuery(); RelayTargetQuery = GetEntityQuery(); diff --git a/Robust.Shared/SharedIoC.cs b/Robust.Shared/SharedIoC.cs index d743f5bda..7add25e0c 100644 --- a/Robust.Shared/SharedIoC.cs +++ b/Robust.Shared/SharedIoC.cs @@ -10,7 +10,6 @@ using Robust.Shared.Map; using Robust.Shared.Network; using Robust.Shared.Physics; -using Robust.Shared.Physics.Collision; using Robust.Shared.Profiling; using Robust.Shared.Random; using Robust.Shared.Sandboxing; @@ -42,7 +41,6 @@ public static void RegisterIoC(IDependencyCollection deps) deps.Register(); deps.Register(); deps.Register(); - deps.Register(); deps.Register(); deps.Register(); deps.Register();