185 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using Microsoft.Xna.Framework;
 | 
						|
using Microsoft.Xna.Framework.Graphics;
 | 
						|
 | 
						|
using Engine.Core;
 | 
						|
 | 
						|
namespace Engine.Integration.MonoGame;
 | 
						|
 | 
						|
public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw
 | 
						|
{
 | 
						|
    public Event<MonoGameCamera3D, ViewChangedArguments> OnViewChanged { get; } = new();
 | 
						|
    public Event<MonoGameCamera3D, ProjectionChangedArguments> OnProjectionChanged { get; } = new();
 | 
						|
    public Event<MonoGameCamera3D, ViewportChangedArguments> OnViewportChanged { get; } = new();
 | 
						|
 | 
						|
    public Event<ICamera3D, ICamera3D.NearPlaneChangedArguments> OnNearPlaneChanged { get; } = new();
 | 
						|
    public Event<ICamera3D, ICamera3D.FarPlaneChangedArguments> OnFarPlaneChanged { get; } = new();
 | 
						|
    public Event<ICamera3D, ICamera3D.FieldOfViewChangedArguments> OnFieldOfViewChanged { get; } = new();
 | 
						|
 | 
						|
    private Matrix _view = Matrix.Identity;
 | 
						|
    private Matrix _projection = Matrix.Identity;
 | 
						|
    private Viewport _viewport = default;
 | 
						|
    private float _nearPlane = 0.01f;
 | 
						|
    private float _farPlane = 100f;
 | 
						|
    private float _fieldOfView = Math.DegreeToRadian * 70f;
 | 
						|
    private bool isRecalculationNeeded = true;
 | 
						|
 | 
						|
    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 Viewport Viewport
 | 
						|
    {
 | 
						|
        get => _viewport;
 | 
						|
        set
 | 
						|
        {
 | 
						|
            if (_viewport.Equals(value))
 | 
						|
                return;
 | 
						|
 | 
						|
            Viewport previousViewport = _viewport;
 | 
						|
            _viewport = value;
 | 
						|
            SetForRecalculation();
 | 
						|
            OnViewportChanged.Invoke(this, new(previousViewport));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public float NearPlane
 | 
						|
    {
 | 
						|
        get => _nearPlane;
 | 
						|
        set
 | 
						|
        {
 | 
						|
            float previousNearPlane = _nearPlane;
 | 
						|
            _nearPlane = value.Max(0.0001f);
 | 
						|
            SetForRecalculation();
 | 
						|
            OnNearPlaneChanged.Invoke(this, new(previousNearPlane));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public float FarPlane
 | 
						|
    {
 | 
						|
        get => _farPlane;
 | 
						|
        set
 | 
						|
        {
 | 
						|
            float previousFarPlane = _farPlane;
 | 
						|
            _farPlane = value.Max(NearPlane);
 | 
						|
            SetForRecalculation();
 | 
						|
            OnFarPlaneChanged.Invoke(this, new(previousFarPlane));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public float FieldOfView
 | 
						|
    {
 | 
						|
        get => _fieldOfView * Math.RadianToDegree;
 | 
						|
        set
 | 
						|
        {
 | 
						|
            value = value.Max(0.1f) * Math.DegreeToRadian;
 | 
						|
 | 
						|
            if (_fieldOfView == value)
 | 
						|
                return;
 | 
						|
 | 
						|
            float previousFieldOfView = _fieldOfView;
 | 
						|
            _fieldOfView = value;
 | 
						|
            SetForRecalculation();
 | 
						|
            OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView));
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // 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 LastActiveFrame() => Transform.OnTransformUpdated.RemoveListener(SetDirtyOnTransformUpdate);
 | 
						|
    public void FirstActiveFrame()
 | 
						|
    {
 | 
						|
        Transform = BehaviourController.GetRequiredBehaviour<ITransform3D>();
 | 
						|
        Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
 | 
						|
        Viewport = Graphics.GraphicsDevice.Viewport;
 | 
						|
 | 
						|
        Transform.OnTransformUpdated.AddListener(SetDirtyOnTransformUpdate);
 | 
						|
    }
 | 
						|
 | 
						|
    private void SetDirtyOnTransformUpdate(ITransform3D sender) => SetForRecalculation();
 | 
						|
    private void SetForRecalculation() => isRecalculationNeeded = true;
 | 
						|
 | 
						|
    public void PreDraw()
 | 
						|
    {
 | 
						|
        if (!isRecalculationNeeded)
 | 
						|
            return;
 | 
						|
 | 
						|
        CalculateView();
 | 
						|
        CalculateProjection();
 | 
						|
        isRecalculationNeeded = false;
 | 
						|
    }
 | 
						|
 | 
						|
    private void CalculateView()
 | 
						|
    {
 | 
						|
        Vector3 forward = Vector3.Normalize(Transform.Forward.ToVector3());
 | 
						|
        Vector3 up = Vector3.Normalize(Transform.Up.ToVector3());
 | 
						|
        Vector3 right = Vector3.Normalize(Transform.Right.ToVector3());
 | 
						|
        Vector3 position = Transform.Position.ToVector3();
 | 
						|
 | 
						|
        View = new Matrix(
 | 
						|
            right.X, up.X, forward.X, 0,
 | 
						|
            right.Y, up.Y, forward.Y, 0,
 | 
						|
            right.Z, up.Z, forward.Z, 0,
 | 
						|
            -Vector3.Dot(right, position),
 | 
						|
            -Vector3.Dot(up, position),
 | 
						|
            -Vector3.Dot(forward, position),
 | 
						|
            1
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    private void CalculateProjection()
 | 
						|
    {
 | 
						|
        float yScale = 1f / (float)Math.Tan(_fieldOfView / 2f);
 | 
						|
        float xScale = yScale / Viewport.AspectRatio;
 | 
						|
 | 
						|
        Projection = new Matrix(
 | 
						|
            xScale, 0, 0, 0,
 | 
						|
            0, yScale, 0, 0,
 | 
						|
            0, 0, _farPlane / (_farPlane - _nearPlane), 1,
 | 
						|
            0, 0, -_nearPlane * _farPlane / (_farPlane - _nearPlane), 0
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
    public readonly record struct ViewChangedArguments(Matrix PreviousView);
 | 
						|
    public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection);
 | 
						|
    public readonly record struct ViewportChangedArguments(Viewport PreviousViewport);
 | 
						|
}
 |