using System; using System.Collections.Generic; using System.Linq; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; namespace Robust.Shared.GameObjects { public abstract class OccluderSystem : EntitySystem { [Dependency] private readonly IMapManagerInternal _mapManager = default!; private const float TreeGrowthRate = 256; private Queue _updates = new(64); public override void Initialize() { base.Initialize(); UpdatesOutsidePrediction = true; SubscribeLocalEvent(ev => { if (ev.Created) OnMapCreated(ev); }); SubscribeLocalEvent(HandleGridInit); SubscribeLocalEvent(HandleOccluderTreeInit); SubscribeLocalEvent(HandleOccluderInit); SubscribeLocalEvent(HandleOccluderShutdown); SubscribeLocalEvent(EntMoved); SubscribeLocalEvent(EntParentChanged); SubscribeLocalEvent(ev => _updates.Enqueue(ev)); } internal IEnumerable GetOccluderTrees(MapId mapId, Box2 worldAABB) { if (mapId == MapId.Nullspace) yield break; foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB)) { yield return EntityManager.GetComponent(grid.GridEntityId); } yield return EntityManager.GetComponent(_mapManager.GetMapEntityId(mapId)); } private void HandleOccluderInit(EntityUid uid, OccluderComponent component, ComponentInit args) { if (!component.Enabled) return; _updates.Enqueue(new OccluderAddEvent(component)); } private void HandleOccluderShutdown(EntityUid uid, OccluderComponent component, ComponentShutdown args) { if (!component.Enabled) return; _updates.Enqueue(new OccluderRemoveEvent(component)); } private void HandleOccluderTreeInit(EntityUid uid, OccluderTreeComponent component, ComponentInit args) { var capacity = (int) Math.Min(256, Math.Ceiling(EntityManager.GetComponent(component.Owner).ChildCount / TreeGrowthRate) * TreeGrowthRate); component.Tree = new DynamicTree(ExtractAabbFunc, capacity: capacity); } private void HandleGridInit(GridInitializeEvent ev) { EntityManager.EnsureComponent(ev.EntityUid); } private OccluderTreeComponent? GetOccluderTree(OccluderComponent component) { var entity = component.Owner; if (EntityManager.Deleted(entity) || EntityManager.GetComponent(entity).MapID == MapId.Nullspace) return null; var parent = EntityManager.GetComponent(entity).Parent; while (true) { if (parent == null) break; if (EntityManager.TryGetComponent(parent.Owner, out OccluderTreeComponent? comp)) return comp; parent = parent.Parent; } return null; } public override void Shutdown() { base.Shutdown(); _updates.Clear(); } public override void FrameUpdate(float frameTime) { UpdateTrees(); } public override void Update(float frameTime) { UpdateTrees(); } private void UpdateTrees() { while (_updates.TryDequeue(out var occluderUpdate)) { OccluderTreeComponent? tree; var component = occluderUpdate.Component; switch (occluderUpdate) { case OccluderAddEvent: if (component.Tree != null) break; tree = GetOccluderTree(component); if (tree == null) break; component.Tree = tree; tree.Tree.Add(component); break; case OccluderUpdateEvent: var oldTree = component.Tree; tree = GetOccluderTree(component); if (oldTree != tree) { oldTree?.Tree.Remove(component); tree?.Tree.Add(component); component.Tree = tree; break; } tree?.Tree.Update(component); break; case OccluderRemoveEvent: tree = component.Tree; tree?.Tree.Remove(component); break; default: throw new ArgumentOutOfRangeException($"No implemented occluder update for {occluderUpdate.GetType()}"); } } } private void EntMoved(EntityUid uid, OccluderComponent component, ref MoveEvent args) { _updates.Enqueue(new OccluderUpdateEvent(component)); } private void EntParentChanged(EntityUid uid, OccluderComponent component, ref EntParentChangedMessage args) { _updates.Enqueue(new OccluderUpdateEvent(component)); } private void OnMapCreated(MapChangedEvent e) { if (e.Map == MapId.Nullspace) return; _mapManager.GetMapEntityId(e.Map).EnsureComponent(); } private Box2 ExtractAabbFunc(in OccluderComponent o) { return o.BoundingBox.Translated(EntityManager.GetComponent(o.Owner).LocalPosition); } public IEnumerable IntersectRayWithPredicate(MapId mapId, in Ray ray, float maxLength, Func? predicate = null, bool returnOnFirstHit = true) { // ReSharper disable once ConvertToLocalFunction var wrapper = (EntityUid uid, Func? wrapped) => wrapped != null && wrapped(uid); return IntersectRayWithPredicate(mapId, in ray, maxLength, predicate, wrapper, returnOnFirstHit); } public IEnumerable IntersectRayWithPredicate(MapId mapId, in Ray ray, float maxLength, TState state, Func predicate, bool returnOnFirstHit = true) { if (mapId == MapId.Nullspace) return Enumerable.Empty(); var list = new List(); var endPoint = ray.Position + ray.Direction * maxLength; var worldBox = new Box2(Vector2.ComponentMin(ray.Position, endPoint), Vector2.ComponentMax(ray.Position, endPoint)); var xforms = EntityManager.GetEntityQuery(); foreach (var comp in GetOccluderTrees(mapId, worldBox)) { var transform = xforms.GetComponent(comp.Owner); var (_, treeRot, matrix) = transform.GetWorldPositionRotationInvMatrix(); var relativeAngle = new Angle(-treeRot.Theta).RotateVec(ray.Direction); var treeRay = new Ray(matrix.Transform(ray.Position), relativeAngle); comp.Tree.QueryRay(ref list, (ref List listState, in OccluderComponent value, in Vector2 point, float distFromOrigin) => { if (distFromOrigin > maxLength) return true; if (!value.Enabled) return true; if (predicate.Invoke(value.Owner, state)) return true; var result = new RayCastResults(distFromOrigin, point, value.Owner); listState.Add(result); return !returnOnFirstHit; }, treeRay); } return list; } } internal sealed class OccluderAddEvent : OccluderEvent { public OccluderAddEvent(OccluderComponent component) : base(component) {} } internal sealed class OccluderUpdateEvent : OccluderEvent { public OccluderUpdateEvent(OccluderComponent component) : base(component) {} } internal sealed class OccluderRemoveEvent : OccluderEvent { public OccluderRemoveEvent(OccluderComponent component) : base(component) {} } internal abstract class OccluderEvent : EntityEventArgs { public OccluderComponent Component { get; } public OccluderEvent(OccluderComponent component) { Component = component; } } }