diff --git a/Engine.Core/Abstract/ICamera3D.cs b/Engine.Core/Abstract/ICamera3D.cs index 01ce185..a0024a0 100644 --- a/Engine.Core/Abstract/ICamera3D.cs +++ b/Engine.Core/Abstract/ICamera3D.cs @@ -6,7 +6,32 @@ namespace Engine.Core; public interface ICamera3D : IBehaviour3D { /// - /// Field of View (FOV) value of the camera + /// Event triggered when the near plane of the changes. + /// + Event OnNearPlaneChanged { get; } + + /// + /// Event triggered when the far plane of the changes. + /// + Event OnFarPlaneChanged { get; } + + /// + /// Event triggered when the field of view of the changes. + /// + Event OnFieldOfViewChanged { get; } + + /// + /// Near plane distance of the camera. + /// + float NearPlane { get; set; } + + /// + /// Far plane distance of the camera. + /// + float FarPlane { get; set; } + + /// + /// Field of View (FOV) value of the camera in degrees. /// float FieldOfView { get; set; } @@ -23,4 +48,8 @@ public interface ICamera3D : IBehaviour3D /// The position in world coordinates. /// The position in screen coordinates. Vector2D WorldToScreenPosition(Vector3D worldPosition); + + readonly record struct NearPlaneChangedArguments(float PreviousNearPlane); + readonly record struct FarPlaneChangedArguments(float PreviousFarPlane); + readonly record struct FieldOfViewChangedArguments(float PreviousFieldOfView); } diff --git a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs index 591ed3b..b43d873 100644 --- a/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs +++ b/Engine.Integration/Engine.Integration.MonoGame/Behaviours/MonoGameCamera3D.cs @@ -5,17 +5,23 @@ using Engine.Core; namespace Engine.Integration.MonoGame; -public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDraw +public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw { public Event OnViewChanged { get; } = new(); public Event OnProjectionChanged { get; } = new(); public Event OnViewportChanged { get; } = new(); - public Event OnFieldOfViewChanged { get; } = new(); + + public Event OnNearPlaneChanged { get; } = new(); + public Event OnFarPlaneChanged { 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; + 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!; @@ -63,27 +69,53 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra 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; + get => _fieldOfView * Math.RadianToDegree; set { - float newValue = Math.Max(0.1f, value); + value = value.Max(0.1f) * Math.DegreeToRadian; - if (_fieldOfView == newValue) + if (_fieldOfView == value) return; float previousFieldOfView = _fieldOfView; - _fieldOfView = newValue; + _fieldOfView = value; + SetForRecalculation(); OnFieldOfViewChanged.Invoke(this, new(previousFieldOfView)); } } - public Engine.Core.Quaternion Rotation + public Core.Quaternion Rotation { get => Transform.Rotation; set => Transform.Rotation = value; @@ -104,28 +136,36 @@ public class MonoGameCamera3D : Behaviour, ICamera3D, IFirstFrameUpdate, IPreDra 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(); Graphics = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour().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(MathHelper.PiOver4, Viewport.AspectRatio, 0.1f, 100f); - } + Projection = Matrix.CreatePerspectiveFieldOfView(_fieldOfView, Viewport.AspectRatio, 0.1f, 100f); - protected sealed override void InitializeInternal() => Transform = BehaviourController.GetRequiredBehaviour(); - protected sealed override void FinalizeInternal() => Transform = null!; + isRecalculationNeeded = false; + } 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); }