using Robust.Client.Graphics; using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; using Robust.Shared.Physics.Components; using System.Numerics; namespace Robust.Client.Debugging; /// /// A collection of visual debug overlays for the client game. /// public sealed class DebugDrawingSystem : EntitySystem { [Dependency] private readonly IOverlayManager _overlayManager = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; private bool _debugPositions; private bool _debugRotations; private bool _debugVelocities; private bool _debugAngularVelocities; /// /// Toggles the visual overlay of the local origin for each entity on screen. /// public bool DebugPositions { get => _debugPositions; set { if (value == DebugPositions) { return; } _debugPositions = value; if (value && !_overlayManager.HasOverlay()) { _overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _transform)); } else { _overlayManager.RemoveOverlay(); } } } /// /// Toggles the visual overlay of the rotation for each entity on screen. /// public bool DebugRotations { get => _debugRotations; set { if (value == DebugRotations) { return; } _debugRotations = value; if (value && !_overlayManager.HasOverlay()) { _overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, _transform)); } else { _overlayManager.RemoveOverlay(); } } } /// /// Toggles the visual overlay of the local velocity for each entity on screen. /// public bool DebugVelocities { get => _debugVelocities; set { if (value == DebugVelocities) { return; } _debugVelocities = value; if (value && !_overlayManager.HasOverlay()) { _overlayManager.AddOverlay(new EntityVelocityOverlay(EntityManager, _lookup, _transform)); } else { _overlayManager.RemoveOverlay(); } } } /// /// Toggles the visual overlay of the angular velocity for each entity on screen. /// public bool DebugAngularVelocities { get => _debugAngularVelocities; set { if (value == DebugAngularVelocities) { return; } _debugAngularVelocities = value; if (value && !_overlayManager.HasOverlay()) { _overlayManager.AddOverlay(new EntityAngularVelocityOverlay(EntityManager, _lookup, _transform)); } else { _overlayManager.RemoveOverlay(); } } } private sealed class EntityPositionOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay { public override OverlaySpace Space => OverlaySpace.WorldSpace; protected internal override void Draw(in OverlayDrawArgs args) { const float stubLength = 0.25f; var worldHandle = (DrawingHandleWorld) args.DrawingHandle; foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds)) { var (center, worldRotation) = _transform.GetWorldPositionRotation(uid); var xLine = worldRotation.RotateVec(Vector2.UnitX); var yLine = worldRotation.RotateVec(Vector2.UnitY); worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red); worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green); } } } private sealed class EntityRotationOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay { public override OverlaySpace Space => OverlaySpace.WorldSpace; protected internal override void Draw(in OverlayDrawArgs args) { const float stubLength = 0.25f; var worldHandle = (DrawingHandleWorld) args.DrawingHandle; foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds)) { var (center, worldRotation) = _transform.GetWorldPositionRotation(uid); var drawLine = worldRotation.RotateVec(-Vector2.UnitY); worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red); } } } private sealed class EntityVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay { public override OverlaySpace Space => OverlaySpace.WorldSpace; protected internal override void Draw(in OverlayDrawArgs args) { const float multiplier = 0.2f; var worldHandle = (DrawingHandleWorld) args.DrawingHandle; var physicsQuery = _entityManager.GetEntityQuery(); foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds)) { if(!physicsQuery.TryGetComponent(uid, out var physicsComp)) continue; var center = _transform.GetWorldPosition(uid); var localVelocity = physicsComp.LinearVelocity; if (localVelocity != Vector2.Zero) worldHandle.DrawLine(center, center + localVelocity * multiplier, Color.Yellow); } } } private sealed class EntityAngularVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay { public override OverlaySpace Space => OverlaySpace.WorldSpace; protected internal override void Draw(in OverlayDrawArgs args) { const float multiplier = (float)(0.2 / (2 * System.Math.PI)); var worldHandle = (DrawingHandleWorld) args.DrawingHandle; var physicsQuery = _entityManager.GetEntityQuery(); foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds)) { if(!physicsQuery.TryGetComponent(uid, out var physicsComp)) continue; var center = _transform.GetWorldPosition(uid); var angularVelocity = physicsComp.AngularVelocity; if (angularVelocity != 0.0f) worldHandle.DrawCircle(center, angularVelocity * multiplier, angularVelocity > 0 ? Color.Magenta : Color.Blue, false); } } } }