172 lines
5.6 KiB
C#
172 lines
5.6 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 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;
|
|
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));
|
|
}
|
|
}
|
|
|
|
public 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 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;
|
|
|
|
Vector3 cameraPosition = Position.ToVector3();
|
|
View = Matrix.CreateLookAt(
|
|
cameraPosition,
|
|
Transform.Forward.ToVector3() + cameraPosition,
|
|
Transform.Up.ToVector3()
|
|
);
|
|
Projection = Matrix.CreatePerspectiveFieldOfView(_fieldOfView, Viewport.AspectRatio, 0.1f, 100f);
|
|
|
|
isRecalculationNeeded = false;
|
|
}
|
|
|
|
public readonly record struct ViewChangedArguments(Matrix PreviousView);
|
|
public readonly record struct ProjectionChangedArguments(Matrix PreviousProjection);
|
|
public readonly record struct ViewportChangedArguments(Viewport PreviousViewport);
|
|
}
|