using System.Numerics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.CustomControls; using Robust.Shared.GameObjects; using Robust.Shared.Graphics; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; #nullable enable namespace Robust.Client.Graphics { /// public sealed partial class EyeManager : IEyeManager { // If you modify this make sure to edit the value in the Robust.Shared.Audio.AudioParams struct default too! // No I can't be bothered to make this a shared constant. /// /// Default scaling for the projection matrix. /// public const int PixelsPerMeter = 32; [Dependency] private IClyde _displayManager = default!; [Dependency] private IEntityManager _entityManager = default!; [Dependency] private IUserInterfaceManager _uiManager = default!; private ISawmill _logMill = default!; // We default to this when we get set to a null eye. private readonly FixedEye _defaultEye = new(); private IEye? _currentEye; /// public IEye CurrentEye { get => _currentEye ?? _defaultEye; set { var old = _currentEye; _currentEye = value; _entityManager.EventBus.RaiseEvent(EventSource.Local, new CurrentEyeChangedEvent(old, _currentEye)); } } public IViewportControl MainViewport { get; set; } = default!; public void ClearCurrentEye() { CurrentEye = _defaultEye; } void IEyeManager.Initialize() { MainViewport = _uiManager.MainViewport; _logMill = IoCManager.Resolve().RootSawmill; } /// public MapId CurrentMap => CurrentEye.Position.MapId; /// public Box2 GetWorldViewport() { return GetWorldViewbounds().CalcBoundingBox(); } /// public Box2Rotated GetWorldViewbounds() { // This is an inefficient and roundabout way of geting the viewport. // But its a method that shouldn't get used much. var vp = MainViewport as Control; var vpSize = vp?.PixelSize ?? _displayManager.ScreenSize; var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position; var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position; // This assumes the main viewports eye and the main eye are the same. var rotation = new Angle(CurrentEye.Rotation); var center = (bottomLeft + topRight) / 2; var localTopRight = topRight - center; var localBotLeft = bottomLeft - center; localTopRight = rotation.RotateVec(localTopRight); localBotLeft = rotation.RotateVec(localBotLeft); var bounds = new Box2(localBotLeft, localTopRight).Translated(center); return new Box2Rotated(bounds, -CurrentEye.Rotation, bounds.Center); } /// public Vector2 WorldToScreen(Vector2 point) { return MainViewport.WorldToScreen(point); } /// public void GetScreenProjectionMatrix(out Matrix3x2 projMatrix) { Matrix3x2 result = default; result.M11 = PixelsPerMeter; result.M22 = -PixelsPerMeter; var screenSize = _displayManager.ScreenSize; result.M31 = screenSize.X / 2f; result.M32 = screenSize.Y / 2f; /* column major Sx 0 Tx 0 Sy Ty 0 0 1 */ projMatrix = result; } /// public ScreenCoordinates CoordinatesToScreen(EntityCoordinates point) { var transformSystem = _entityManager.System(); return MapToScreen(transformSystem.ToMapCoordinates(point)); } public ScreenCoordinates MapToScreen(MapCoordinates point) { if (CurrentEye.Position.MapId != point.MapId) { _logMill.Error($"Attempted to convert map coordinates ({point}) to screen coordinates with an eye on another map ({CurrentEye.Position.MapId})"); return new(default, WindowId.Invalid); } return new(WorldToScreen(point.Position), MainViewport.Window?.Id ?? default); } /// public MapCoordinates ScreenToMap(ScreenCoordinates point) { if (_uiManager.MouseGetControl(point) is not IViewportControl viewport) return default; return viewport.ScreenToMap(point.Position); } /// public MapCoordinates ScreenToMap(Vector2 point) { return MainViewport.ScreenToMap(point); } /// public MapCoordinates PixelToMap(ScreenCoordinates point) { if (_uiManager.MouseGetControl(point) is not IViewportControl viewport) return default; return viewport.PixelToMap(point.Position); } /// public MapCoordinates PixelToMap(Vector2 point) { return MainViewport.PixelToMap(point); } } public sealed class CurrentEyeChangedEvent : EntityEventArgs { public IEye? Old { get; } public IEye New { get; } public CurrentEyeChangedEvent(IEye? oldEye, IEye newEye) { Old = oldEye; New = newEye; } } }