using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Engine.Core; namespace Engine.Integration.MonoGame; public class MonoGameCamera3D : BehaviourBase, ICamera3D, IFirstFrameUpdate, IPreDraw { public Event OnViewChanged { get; } = new(); public Event OnProjectionChanged { get; } = new(); public Event OnViewportChanged { get; } = new(); public Event OnFieldOfViewChanged { get; } = new(); private Matrix _view = Matrix.Identity; private Matrix _projection = Matrix.Identity; private Viewport _viewport = default; private float _fieldOfView = 1f; public GraphicsDeviceManager Graphics { get; private set; } = null!; public ITransform3D Transform { get; private set; } = null!; public Matrix View { get => _view; set { if (_view == value) return; Matrix previousView = _view; _view = value; OnViewChanged.Invoke(this, new(previousView)); } } public Matrix Projection { get => _projection; set { if (_projection == value) return; Matrix previousProjection = _projection; _projection = value; OnProjectionChanged.Invoke(this, new(previousProjection)); } } public Vector3D Position { get => Transform.Position; set => Transform.Position = value; } public Viewport Viewport { get => _viewport; set { if (_viewport.Equals(value)) return; Viewport previousViewport = _viewport; _viewport = value; OnViewportChanged.Invoke(this, new(previousViewport)); } } public float FieldOfView { get => _fieldOfView; set { float newValue = Math.Max(0.1f, value); if (_fieldOfView == newValue) return; float previousFieldOfView = _fieldOfView; _fieldOfView = newValue; OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView)); } } public Engine.Core.Quaternion Rotation { get => Transform.Rotation; set => Transform.Rotation = value; } // TODO This causes delay since OnPreDraw calls assuming this is called in in Update public Ray3D ScreenToWorldRay(Vector2D screenPosition) { Vector3 nearPoint = new(screenPosition.X, screenPosition.Y, 0f); Vector3 farPoint = new(screenPosition.X, screenPosition.Y, 1f); Vector3 worldNear = Viewport.Unproject(nearPoint, _projection, _view, Matrix.Identity); Vector3 worldFar = Viewport.Unproject(farPoint, _projection, _view, Matrix.Identity); Vector3 direction = Vector3.Normalize(worldFar - worldNear); return new(worldNear.ToVector3D(), direction.ToVector3D()); } public Vector2D WorldToScreenPosition(Vector3D worldPosition) => Viewport.Project(worldPosition.ToVector3(), _projection, _view, Matrix.Identity).ToVector3D(); public void FirstActiveFrame() { Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour().Window.Graphics; Viewport = Graphics.GraphicsDevice.Viewport; } public void PreDraw() { Vector3 cameraPosition = Position.ToVector3(); View = Matrix.CreateLookAt( cameraPosition, Transform.Forward.ToVector3() + cameraPosition, Transform.Up.ToVector3() ); Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Viewport.AspectRatio, 0.1f, 100f); } protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour(); protected sealed override void FinalizeInternal() => Transform = null!; public readonly record struct ViewChangedArguments(Matrix PreviousView); public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection); public readonly record struct ViewportChangedArguments(Viewport PreviousViewport); public readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView); }