using System; using System.Collections.Generic; using System.Numerics; using Robust.Shared.Collections; using Robust.Shared.Debugging; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Network; using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Utility; namespace Robust.Shared.Physics.Systems { /* * Handles all of the public query methods for physics. */ public partial class SharedPhysicsSystem { #if DEBUG [Dependency] private SharedDebugRayDrawingSystem _sharedDebugRaySystem = default!; [Dependency] private INetManager _netMan = default!; #endif /// /// Checks to see if the specified collision rectangle collides with any of the physBodies under management. /// Also fires the OnCollide event of the first managed physBody to intersect with the collider. /// /// Collision rectangle to check /// Map to check on /// /// true if collides, false if not public bool TryCollideRect(Box2 collider, MapId mapId, bool approximate = true) { var state = (collider, mapId, found: false); _broadphase.GetBroadphases(mapId, collider, broadphase => { var gridCollider = _transform.GetInvWorldMatrix(broadphase).TransformBox(collider); broadphase.Comp.StaticTree.QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) => { if (proxy.Fixture.CollisionLayer == 0x0) return true; if (proxy.AABB.Intersects(gridCollider)) { state.found = true; return false; } return true; }, gridCollider, approximate); broadphase.Comp.DynamicTree.QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) => { if (proxy.Fixture.CollisionLayer == 0x0) return true; if (proxy.AABB.Intersects(gridCollider)) { state.found = true; return false; } return true; }, gridCollider, approximate); }); return state.found; } /// /// Get all the entities whose fixtures intersect the fixtures of the given entity. Basically a variant of /// that allows the user to specify /// their own collision mask. /// public HashSet GetEntitiesIntersectingBody( EntityUid uid, int collisionMask, bool approximate = true, PhysicsComponent? body = null, FixturesComponent? fixtureComp = null, TransformComponent? xform = null) { var entities = new HashSet(); if (!Resolve(uid, ref body, ref fixtureComp, ref xform, false)) return entities; if (!_lookup.TryGetCurrentBroadphase(xform, out var broadphase)) return entities; var state = (body, entities); foreach (var fixture in fixtureComp.Fixtures.Values) { foreach (var proxy in fixture.Proxies) { broadphase.StaticTree.QueryAabb(ref state, (ref (PhysicsComponent body, HashSet entities) state, in FixtureProxy other) => { if (other.Body.Deleted || other.Body == body) return true; if ((collisionMask & other.Fixture.CollisionLayer) == 0x0) return true; state.entities.Add(other.Entity); return true; }, proxy.AABB, approximate); broadphase.DynamicTree.QueryAabb(ref state, (ref (PhysicsComponent body, HashSet entities) state, in FixtureProxy other) => { if (other.Body.Deleted || other.Body == body) return true; if ((collisionMask & other.Fixture.CollisionLayer) == 0x0) return true; state.entities.Add(other.Entity); return true; }, proxy.AABB, approximate); } } return entities; } /// /// Get all entities colliding with a certain body. /// [Obsolete("Use EntityLookupSystem")] public IEnumerable GetCollidingEntities(MapId mapId, in Box2 worldAABB) { if (mapId == MapId.Nullspace) return Array.Empty(); var aabb = worldAABB; var bodies = new HashSet(); var state = (_transform, bodies, aabb); _broadphase.GetBroadphases(mapId, worldAABB, ref state, static ( Entity entity, ref (SharedTransformSystem _transform, HashSet bodies, Box2 aabb) tuple) => { var gridAABB = tuple._transform.GetInvWorldMatrix(entity.Owner).TransformBox(tuple.aabb); foreach (var proxy in entity.Comp.StaticTree.QueryAabb(gridAABB, false)) { tuple.bodies.Add(proxy.Body); } foreach (var proxy in entity.Comp.DynamicTree.QueryAabb(gridAABB, false)) { tuple.bodies.Add(proxy.Body); } }); return bodies; } /// /// Get all entities colliding with a certain body. /// [Obsolete("Use EntityLookupSystem")] public IEnumerable> GetCollidingEntities(MapId mapId, in Box2Rotated worldBounds) { if (mapId == MapId.Nullspace) return Array.Empty>(); var bodies = new HashSet>(); var state = (_transform, bodies, worldBounds); _broadphase.GetBroadphases(mapId, worldBounds.CalcBoundingBox(), ref state, static ( Entity entity, ref (SharedTransformSystem _transform, HashSet> bodies, Box2Rotated worldBounds ) tuple) => { var gridAABB = tuple._transform.GetInvWorldMatrix(entity.Owner).TransformBox(tuple.worldBounds); foreach (var proxy in entity.Comp.StaticTree.QueryAabb(gridAABB, false)) { tuple.bodies.Add((proxy.Entity, proxy.Body)); } foreach (var proxy in entity.Comp.DynamicTree.QueryAabb(gridAABB, false)) { tuple.bodies.Add((proxy.Entity, proxy.Body)); } }); return bodies; } public void GetContactingEntities(Entity ent, HashSet contacting, bool approximate = false) { if (!Resolve(ent.Owner, ref ent.Comp)) return; var node = ent.Comp.Contacts.First; while (node != null) { var contact = node.Value; node = node.Next; if (approximate || contact.IsTouching) contacting.Add(ent.Owner == contact.EntityA ? contact.EntityB : contact.EntityA); } } public HashSet GetContactingEntities(EntityUid uid, PhysicsComponent? body = null, bool approximate = false) { var result = new HashSet(); GetContactingEntities((uid, body), result, approximate); return result; } /// /// Checks whether a body is colliding /// public bool IsInContact(PhysicsComponent body, bool approximate = false) { var node = body.Contacts.First; while (node != null) { if (approximate || node.Value.IsTouching) return true; node = node.Next; } return false; } #region RayCast /// /// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified) /// /// /// Ray to cast in the world. /// Maximum length of the ray in meters. /// A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored. /// If true, will only include the first hit entity in results. Otherwise, returns all of them. /// A result object describing the hit, if any. // TODO: Make the parameter order here consistent with the other overload. public IEnumerable IntersectRayWithPredicate(MapId mapId, CollisionRay ray, float maxLength = 50F, Func? predicate = null, bool returnOnFirstHit = true) { // No, rider. This is better than a local function! // ReSharper disable once ConvertToLocalFunction var wrapper = (EntityUid uid, Func? wrapped) => wrapped != null && wrapped(uid); return IntersectRayWithPredicate(mapId, ray, predicate, wrapper, maxLength, returnOnFirstHit); } /// /// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified) /// /// /// Ray to cast in the world. /// Maximum length of the ray in meters. /// A custom state to pass to the predicate. /// A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored. /// If true, will only include the first hit entity in results. Otherwise, returns all of them. /// You can avoid variable capture in many cases by using this method and passing a custom state to the predicate. /// A result object describing the hit, if any. public IEnumerable IntersectRayWithPredicate(MapId mapId, CollisionRay ray, TState state, Func predicate, float maxLength = 50F, bool returnOnFirstHit = true) { List results = new(); var endPoint = ray.Position + ray.Direction.Normalized() * maxLength; var rayBox = new Box2(Vector2.Min(ray.Position, endPoint), Vector2.Max(ray.Position, endPoint)); _broadphase.GetBroadphases(mapId, rayBox, broadphase => { var (_, rot, matrix, invMatrix) = _transform.GetWorldPositionRotationMatrixWithInv(broadphase.Owner); var position = Vector2.Transform(ray.Position, invMatrix); var gridRot = new Angle(-rot.Theta); var direction = gridRot.RotateVec(ray.Direction); var gridRay = new CollisionRay(position, direction, ray.CollisionMask); broadphase.Comp.StaticTree.QueryRay( (in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => { if (returnOnFirstHit && results.Count > 0) return true; if (distFromOrigin > maxLength) return true; if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) return true; if (!proxy.Fixture.Hard) return true; if (predicate.Invoke(proxy.Entity, state) == true) return true; // TODO: Shape raycast here // Need to convert it back to world-space. var result = new RayCastResults(distFromOrigin, Vector2.Transform(point, matrix), proxy.Entity); results.Add(result); #if DEBUG _sharedDebugRaySystem.ReceiveLocalRayFromAnyThread(new(ray, maxLength, result, _netMan.IsServer, mapId)); #endif return true; }, gridRay); broadphase.Comp.DynamicTree.QueryRay( (in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => { if (returnOnFirstHit && results.Count > 0) return true; if (distFromOrigin > maxLength) return true; if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) return true; if (!proxy.Fixture.Hard) return true; if (predicate.Invoke(proxy.Entity, state) == true) return true; // TODO: Shape raycast here // Need to convert it back to world-space. var result = new RayCastResults(distFromOrigin, Vector2.Transform(point, matrix), proxy.Entity); results.Add(result); #if DEBUG _sharedDebugRaySystem.ReceiveLocalRayFromAnyThread(new(ray, maxLength, result, _netMan.IsServer, mapId)); #endif return true; }, gridRay); }); #if DEBUG if (results.Count == 0) { _sharedDebugRaySystem.ReceiveLocalRayFromAnyThread(new(ray, maxLength, null, _netMan.IsServer, mapId)); } #endif results.Sort((a, b) => a.Distance.CompareTo(b.Distance)); return results; } /// /// Casts a ray in the world and returns the first entity it hits, or a list of all entities it hits. /// /// /// Ray to cast in the world. /// Maximum length of the ray in meters. /// A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity. /// If false, will return a list of everything it hits, otherwise will just return a list of the first entity hit /// An enumerable of either the first entity hit or everything hit public IEnumerable IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, EntityUid? ignoredEnt = null, bool returnOnFirstHit = true) { // ReSharper disable once ConvertToLocalFunction var wrapper = static (EntityUid uid, EntityUid? ignored) => uid == ignored; return IntersectRayWithPredicate(mapId, ray, ignoredEnt, wrapper, maxLength, returnOnFirstHit); } /// /// Casts a ray in the world and returns the distance the ray traveled while colliding with entities /// /// /// Ray to cast in the world. /// Maximum length of the ray in meters. /// A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity. /// The distance the ray traveled while colliding with entities public float IntersectRayPenetration(MapId mapId, CollisionRay ray, float maxLength, EntityUid? ignoredEnt = null) { var penetration = 0f; var endPoint = ray.Position + ray.Direction.Normalized() * maxLength; var rayBox = new Box2(Vector2.Min(ray.Position, endPoint), Vector2.Max(ray.Position, endPoint)); _broadphase.GetBroadphases(mapId, rayBox, broadphase => { var (_, rot, invMatrix) = _transform.GetWorldPositionRotationInvMatrix(broadphase); var position = Vector2.Transform(ray.Position, invMatrix); var gridRot = new Angle(-rot.Theta); var direction = gridRot.RotateVec(ray.Direction); var gridRay = new CollisionRay(position, direction, ray.CollisionMask); broadphase.Comp.StaticTree.QueryRay( (in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => { if (distFromOrigin > maxLength || proxy.Entity == ignoredEnt) return true; if (!proxy.Fixture.Hard) return true; if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) return true; if (new Ray(point + gridRay.Direction * proxy.AABB.Size.Length() * 2, -gridRay.Direction) .Intersects( proxy.AABB, out _, out var exitPoint)) { penetration += (point - exitPoint).Length(); } return true; }, gridRay); broadphase.Comp.DynamicTree.QueryRay( (in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => { if (distFromOrigin > maxLength || proxy.Entity == ignoredEnt) return true; if (!proxy.Fixture.Hard) return true; if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) return true; if (new Ray(point + gridRay.Direction * proxy.AABB.Size.Length() * 2, -gridRay.Direction) .Intersects( proxy.AABB, out _, out var exitPoint)) { penetration += (point - exitPoint).Length(); } return true; }, gridRay); }); // This hid rays that didn't penetrate something. Don't hide those because that causes rays to disappear that shouldn't. #if DEBUG _sharedDebugRaySystem.ReceiveLocalRayFromAnyThread(new(ray, maxLength, null, _netMan.IsServer, mapId)); #endif return penetration; } #endregion #region Distance /// /// Gets the nearest distance of 2 entities, ignoring any sensor proxies. /// public bool TryGetDistance(EntityUid uidA, EntityUid uidB, out float distance, TransformComponent? xformA = null, TransformComponent? xformB = null, FixturesComponent? managerA = null, FixturesComponent? managerB = null, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null) { return TryGetNearest(uidA, uidB, out _, out _, out distance, xformA, xformB, managerA, managerB, bodyA, bodyB); } /// /// Get the nearest non-sensor points on entity A and entity B to each other. /// public bool TryGetNearestPoints(EntityUid uidA, EntityUid uidB, out Vector2 pointA, out Vector2 pointB, TransformComponent? xformA = null, TransformComponent? xformB = null, FixturesComponent? managerA = null, FixturesComponent? managerB = null, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null) { return TryGetNearest(uidA, uidB, out pointA, out pointB, out _, xformA, xformB, managerA, managerB, bodyA, bodyB); } public bool TryGetNearest(EntityUid uidA, EntityUid uidB, out Vector2 pointA, out Vector2 pointB, out float distance, Transform xfA, Transform xfB, FixturesComponent? managerA = null, FixturesComponent? managerB = null, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null) { pointA = Vector2.Zero; pointB = Vector2.Zero; if (!Resolve(uidA, ref managerA, ref bodyA) || !Resolve(uidB, ref managerB, ref bodyB) || managerA.FixtureCount == 0 || managerB.FixtureCount == 0) { distance = 0f; return false; } distance = float.MaxValue; var input = new DistanceInput { TransformA = xfA, TransformB = xfB, UseRadii = true }; // No requirement on collision being enabled so chainshapes will fail foreach (var fixtureA in managerA.Fixtures.Values) { if (bodyA.Hard && !fixtureA.Hard) continue; for (var i = 0; i < fixtureA.Shape.ChildCount; i++) { input.ProxyA.Set(fixtureA.Shape, i); foreach (var fixtureB in managerB.Fixtures.Values) { if (bodyB.Hard && !fixtureB.Hard) continue; for (var j = 0; j < fixtureB.Shape.ChildCount; j++) { input.ProxyB.Set(fixtureB.Shape, j); DistanceManager.ComputeDistance(out var output, out _, input); if (distance < output.Distance) continue; pointA = output.PointA; pointB = output.PointB; distance = output.Distance; } } } } return true; } /// /// Gets the nearest points in map terms and the distance between them. /// If a body is hard it only considers hard fixtures. /// public bool TryGetNearest(EntityUid uid, MapCoordinates coordinates, out Vector2 point, out float distance, TransformComponent? xformA = null, FixturesComponent? manager = null, PhysicsComponent? body = null) { if (!Resolve(uid, ref xformA) || xformA.MapID != coordinates.MapId) { point = Vector2.Zero; distance = 0f; return false; } point = Vector2.Zero; if (!Resolve(uid, ref manager, ref body) || manager.FixtureCount == 0) { distance = 0f; return false; } var xfA = GetPhysicsTransform(uid, xformA); var xfB = new Transform(coordinates.Position, Angle.Zero); distance = float.MaxValue; var input = new DistanceInput(); input.TransformA = xfA; input.TransformB = xfB; input.UseRadii = true; var pointShape = new PhysShapeCircle(10 * float.Epsilon, Vector2.Zero); // No requirement on collision being enabled so chainshapes will fail foreach (var fixtureA in manager.Fixtures.Values) { // We ignore non-hard fixtures if there is at least one hard fixture (i.e., if the body is hard) if (body.Hard && !fixtureA.Hard) continue; DebugTools.Assert(fixtureA.ProxyCount <= 1); input.ProxyA.Set(fixtureA.Shape, 0); input.ProxyB.Set(pointShape, 0); DistanceManager.ComputeDistance(out var output, out _, input); if (distance < output.Distance) continue; point = output.PointA; distance = output.Distance; } return true; } /// /// Gets the nearest points in map terms and the distance between them. /// If a body is hard it only considers hard fixtures. /// public bool TryGetNearest(EntityUid uidA, EntityUid uidB, out Vector2 point, out Vector2 pointB, out float distance, TransformComponent? xformA = null, TransformComponent? xformB = null, FixturesComponent? managerA = null, FixturesComponent? managerB = null, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null) { if (!Resolve(uidA, ref xformA) || !Resolve(uidB, ref xformB) || xformA.MapID != xformB.MapID) { point = Vector2.Zero; pointB = Vector2.Zero; distance = 0f; return false; } var xfA = GetPhysicsTransform(uidA, xformA); var xfB = GetPhysicsTransform(uidB, xformB); return TryGetNearest(uidA, uidB, out point, out pointB, out distance, xfA, xfB, managerA, managerB, bodyA, bodyB); } #endregion } }