using System.Collections.Generic; using Robust.Shared.Map; using Robust.Shared.Map.Components; using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Shapes; using Robust.Shared.Physics.Systems; namespace Robust.Shared.GameObjects; public interface IFixtureQueryCallback { /// /// Invoked for each matching fixture. Return false to stop the query. /// bool Invoke(ref TState state, in FixtureProxy fixture); } public readonly record struct FixtureQueryArgs( QueryFilter Filter, bool Approximate = false, bool IgnoreShapeSkin = false) { public readonly QueryFilter Filter = Filter; public readonly bool Approximate = Approximate; public readonly bool IgnoreShapeSkin = IgnoreShapeSkin; } public sealed partial class EntityLookupSystem { /// /// Gets fixtures intersecting the specified world AABB. /// public void GetFixturesIntersecting( MapId mapId, Box2 worldAABB, HashSet fixtures, FixtureQueryArgs query) { ForEachFixtureIntersecting(mapId, worldAABB, ref fixtures, new AddFixtureToHashSetCallback(), query); } /// /// Invokes for each fixture intersecting the specified world AABB. /// Returns false if the callback stopped the query. /// public bool ForEachFixtureIntersecting( MapId mapId, Box2 worldAABB, ref TState state, TCallback callback, FixtureQueryArgs query) where TCallback : struct, IFixtureQueryCallback { var polygon = new SlimPolygon(worldAABB); return ForEachFixtureIntersecting(mapId, polygon, Physics.Transform.Empty, ref state, callback, query); } /// /// Gets fixtures intersecting the specified rotated world bounds. /// public void GetFixturesIntersecting( MapId mapId, Box2Rotated worldBounds, HashSet fixtures, FixtureQueryArgs query) { ForEachFixtureIntersecting(mapId, worldBounds, ref fixtures, new AddFixtureToHashSetCallback(), query); } /// /// Invokes for each fixture intersecting the specified rotated world bounds. /// Returns false if the callback stopped the query. /// public bool ForEachFixtureIntersecting( MapId mapId, Box2Rotated worldBounds, ref TState state, TCallback callback, FixtureQueryArgs query) where TCallback : struct, IFixtureQueryCallback { var polygon = new SlimPolygon(worldBounds); return ForEachFixtureIntersecting(mapId, polygon, Physics.Transform.Empty, ref state, callback, query); } /// /// Gets fixtures intersecting a shape in world-space. /// public void GetFixturesIntersecting( MapId mapId, TShape shape, Transform shapeTransform, HashSet fixtures, FixtureQueryArgs query) where TShape : IPhysShape { GetFixturesIntersecting(mapId, shape, 0, shapeTransform, fixtures, query); } /// /// Gets fixtures intersecting a child shape in world-space. /// public void GetFixturesIntersecting( MapId mapId, TShape shape, int childIndex, Transform shapeTransform, HashSet fixtures, FixtureQueryArgs query) where TShape : IPhysShape { ForEachFixtureIntersecting( mapId, shape, childIndex, shapeTransform, ref fixtures, new AddFixtureToHashSetCallback(), query); } /// /// Invokes for each fixture intersecting a shape in world-space. /// Returns false if the callback stopped the query. /// public bool ForEachFixtureIntersecting( MapId mapId, TShape shape, Transform shapeTransform, ref TState state, TCallback callback, FixtureQueryArgs query) where TShape : IPhysShape where TCallback : struct, IFixtureQueryCallback { return ForEachFixtureIntersecting(mapId, shape, 0, shapeTransform, ref state, callback, query); } /// /// Invokes for each fixture intersecting a child shape in world-space. /// Returns false if the callback stopped the query. /// public bool ForEachFixtureIntersecting( MapId mapId, TShape shape, int childIndex, Transform shapeTransform, ref TState state, TCallback callback, FixtureQueryArgs query) where TShape : IPhysShape where TCallback : struct, IFixtureQueryCallback { if (mapId == MapId.Nullspace) return true; var worldAABB = shape.ComputeAABB(shapeTransform, childIndex); var queryState = new GridFixtureQueryState( state, callback, shape, childIndex, shapeTransform, query, this, _physics); _map.FindGridsIntersecting(mapId, worldAABB, ref queryState, static (EntityUid uid, MapGridComponent grid, ref GridFixtureQueryState state) => { var localTransform = state.Physics.GetRelativePhysicsTransform(state.Transform, uid); var result = state.Lookup.ForEachLocalFixtureIntersecting( uid, state.Shape, state.ChildIndex, localTransform, ref state.State, state.Callback, state.Query); if (!result) state.Continue = false; return result; }, approx: true, includeMap: false); state = queryState.State; if (!queryState.Continue) return false; var mapUid = _map.GetMapOrInvalid(mapId); var mapTransform = _physics.GetRelativePhysicsTransform(shapeTransform, mapUid); return ForEachLocalFixtureIntersecting( mapUid, shape, childIndex, mapTransform, ref state, callback, query); } /// /// Invokes for each fixture intersecting a shape in the local coordinates of a broadphase entity. /// Returns false if the callback stopped the query. /// public bool ForEachLocalFixtureIntersecting( EntityUid lookupUid, TShape shape, Transform localTransform, ref TState state, TCallback callback, FixtureQueryArgs query, BroadphaseComponent? lookup = null) where TShape : IPhysShape where TCallback : struct, IFixtureQueryCallback { return ForEachLocalFixtureIntersecting(lookupUid, shape, 0, localTransform, ref state, callback, query, lookup); } /// /// Invokes for each fixture intersecting a child shape in the local coordinates of a broadphase entity. /// Returns false if the callback stopped the query. /// public bool ForEachLocalFixtureIntersecting( EntityUid lookupUid, TShape shape, int childIndex, Transform localTransform, ref TState state, TCallback callback, FixtureQueryArgs query, BroadphaseComponent? lookup = null) where TShape : IPhysShape where TCallback : struct, IFixtureQueryCallback { if (!_broadQuery.Resolve(lookupUid, ref lookup)) return true; var localAABB = shape.ComputeAABB(localTransform, childIndex); var queryState = new FixtureQueryState( state, callback, shape, childIndex, localTransform, query, _physics, _manifoldManager); if ((query.Filter.Flags & QueryFlags.Dynamic) == QueryFlags.Dynamic) { lookup.DynamicTree.QueryAabb(ref queryState, FixtureQuery, localAABB, false); if (!queryState.Continue) { state = queryState.State; return false; } } if ((query.Filter.Flags & QueryFlags.Static) == QueryFlags.Static) lookup.StaticTree.QueryAabb(ref queryState, FixtureQuery, localAABB, false); state = queryState.State; return queryState.Continue; } private static bool FixtureQuery( ref FixtureQueryState state, in FixtureProxy proxy) where TShape : IPhysShape where TCallback : struct, IFixtureQueryCallback { if (!FixtureMatchesFilter(proxy, state.Query.Filter)) return true; if (!state.Query.Approximate) { var transform = state.Physics.GetLocalPhysicsTransform(proxy.Entity, proxy.Xform); if (!state.Manifolds.TestOverlap( state.Shape, state.ChildIndex, proxy.Fixture.Shape, proxy.ChildIndex, state.Transform, transform, ignoreShapeSkin: state.Query.IgnoreShapeSkin)) { return true; } } if (state.Callback.Invoke(ref state.State, proxy)) return true; state.Continue = false; return false; } private static bool FixtureMatchesFilter(in FixtureProxy proxy, QueryFilter filter) { if ((filter.Flags & QueryFlags.Sensors) == 0 && !proxy.Fixture.Hard) return false; if ((proxy.Fixture.CollisionLayer & filter.MaskBits) == 0 && (proxy.Fixture.CollisionMask & filter.LayerBits) == 0) { return false; } if (filter.IsIgnored?.Invoke(proxy.Entity) == true) return false; return true; } private readonly struct AddFixtureToHashSetCallback : IFixtureQueryCallback> { public bool Invoke(ref HashSet state, in FixtureProxy fixture) { state.Add(fixture); return true; } } private struct GridFixtureQueryState where TShape : IPhysShape where TCallback : struct, IFixtureQueryCallback { public TState State; public TCallback Callback; public TShape Shape; public int ChildIndex; public Transform Transform; public FixtureQueryArgs Query; public EntityLookupSystem Lookup; public SharedPhysicsSystem Physics; public bool Continue; public GridFixtureQueryState( TState state, TCallback callback, TShape shape, int childIndex, Transform transform, FixtureQueryArgs query, EntityLookupSystem lookup, SharedPhysicsSystem physics) { State = state; Callback = callback; Shape = shape; ChildIndex = childIndex; Transform = transform; Query = query; Lookup = lookup; Physics = physics; Continue = true; } } private struct FixtureQueryState where TShape : IPhysShape where TCallback : struct, IFixtureQueryCallback { public TState State; public TCallback Callback; public TShape Shape; public int ChildIndex; public Transform Transform; public FixtureQueryArgs Query; public SharedPhysicsSystem Physics; public IManifoldManager Manifolds; public bool Continue; public FixtureQueryState( TState state, TCallback callback, TShape shape, int childIndex, Transform transform, FixtureQueryArgs query, SharedPhysicsSystem physics, IManifoldManager manifolds) { State = state; Callback = callback; Shape = shape; ChildIndex = childIndex; Transform = transform; Query = query; Physics = physics; Manifolds = manifolds; Continue = true; } } }