using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Engine.Core; namespace Engine.Integration.MonoGame; public class MonoGameCamera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw { public Event OnViewMatrixChanged { get; } = new(); public Event OnProjectionMatrixChanged { get; } = new(); public Event OnViewportChanged { get; } = new(); public Event OnZoomChanged { get; } = new(); public GraphicsDeviceManager Graphics { get; private set; } = null!; public ITransform2D Transform { get; private set; } = null!; public Matrix4x4 ProjectionMatrix { get; private set { if (field == value) return; field = value; OnProjectionMatrixChanged.Invoke(this); } } = Matrix4x4.Identity; public Matrix4x4 ViewMatrix { get; private set { if (field == value) return; field = value; OnViewMatrixChanged.Invoke(this); } } = Matrix4x4.Identity; public Viewport Viewport { get; set { if (field.Equals(value)) return; field = value; OnViewportChanged.Invoke(this); } } = default; public float Zoom { get; set { float newValue = Math.Max(0.1f, value); if (field == newValue) return; field = newValue; OnZoomChanged.Invoke(this); } } = 1f; // TODO This causes delay since OnPreDraw calls assuming this is called in in Update public Vector2D ScreenToWorldPosition(Vector2D screenPosition) { float x = 2f * screenPosition.X / Viewport.Width - 1f; float y = 1f - 2f * screenPosition.Y / Viewport.Height; Vector4D normalizedCoordinates = new(x, y, 0f, 1f); Matrix4x4 invertedViewProjectionMatrix = (ProjectionMatrix * ViewMatrix).Inverse; Vector4D worldPosition = invertedViewProjectionMatrix * normalizedCoordinates; if (worldPosition.W != 0f) worldPosition /= worldPosition.W; return new(worldPosition.X, worldPosition.Y); } public Vector2D WorldToScreenPosition(Vector2D worldPosition) { Vector4D worldPosition4D = new(worldPosition.X, worldPosition.Y, 0f, 1f); Matrix4x4 viewProjection = ProjectionMatrix * ViewMatrix; Vector4D clip = viewProjection * worldPosition4D; if (clip.W != 0f) clip /= clip.W; float screenX = (clip.X + 1f) * .5f * Viewport.Width; float screenY = (1f - clip.Y) * .5f * Viewport.Height; return new(screenX, screenY); } public void LastActiveFrame() => Transform = null!; public void FirstActiveFrame() { Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour().Window.Graphics; Viewport = Graphics.GraphicsDevice.Viewport; Transform = BehaviourController.GetRequiredBehaviour(); } public void PreDraw() { ProjectionMatrix = Matrix4x4.CreateOrthographicViewCentered(Viewport.Width, Viewport.Height); ViewMatrix = Matrix4x4.CreateTranslation(new Vector3D(-Transform.Position.X, -Transform.Position.Y, 0f)) .ApplyRotationZ(Transform.Rotation * Math.DegreeToRadian) .ApplyScale(Transform.Scale.X.Max(Transform.Scale.Y)) .ApplyScale(Zoom); } }