Files
Syntriax.Engine/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera2D.cs

123 lines
3.6 KiB
C#

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<MonoGameCamera2D> OnViewMatrixChanged { get; } = new();
public Event<MonoGameCamera2D> OnProjectionMatrixChanged { get; } = new();
public Event<MonoGameCamera2D> OnViewportChanged { get; } = new();
public Event<MonoGameCamera2D> 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 = 2f * screenPosition.Y / Viewport.Height - 1f;
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 = (clip.Y + 1f) * .5f * Viewport.Height;
return new(screenX, screenY);
}
public void LastActiveFrame() => Transform = null!;
public void FirstActiveFrame()
{
Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>().Window.Graphics;
Viewport = Graphics.GraphicsDevice.Viewport;
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
}
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);
}
}