/* Copyright (c) 2024-present miku-666 * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1.The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ // movement code taken from: // https://github.com/TheCherno/Hazel/blob/master/Hazel/src/Hazel/Renderer/EditorCamera.cpp using System; using System.Drawing; using OpenTK; namespace PckStudio.Rendering.Camera { public class PerspectiveCamera : Camera { public float NearClip { get => _nearClip; set { _nearClip = value; UpdateProjection(); } } public float FarClip { get => _farClip; set { _farClip = value; UpdateProjection(); } } public float Distance { get => _spherical.Radius; set { _spherical.Radius = Math.Max(value, 2f); UpdateViewMatrix(); } } public float RotationSpeed { get; set; } = 5f; public Size ViewportSize { get => _viewportSize; set { _viewportSize = value; UpdateProjection(); } } public Vector3 WorldPosition => _position; public Vector3 FocalPoint { get => _focalPoint; set { _focalPoint = value; UpdateViewMatrix(); } } public float Pitch { get => _spherical.Theta; set { _spherical.Theta = MathHelper.Clamp(value, -90f, 90f); UpdateViewMatrix(); } } public float Yaw { get => _spherical.Phi; set { _spherical.Phi = value % 360f; UpdateViewMatrix(); } } public Vector3 Orientation => -Vector3.UnitZ; public Vector3 Up => Vector3.UnitY; public float MinimumFov { get; } = 30f; public float MaximumFov { get; } = 180f; public float Fov { get => _fov; set { _fov = MathHelper.Clamp(value, MinimumFov, MaximumFov); UpdateProjection(); } } public PerspectiveCamera(float fov, Vector3 target) { _fov = fov; _focalPoint = target; _nearClip = 1f; _farClip = 1000f; UpdateProjection(); } private Matrix4 viewMatrix; private float _fov; private float _nearClip; private float _farClip; private Spherical _spherical; private Size _viewportSize; private Vector3 _position; private Vector3 _focalPoint; public override Matrix4 GetViewProjection() { return viewMatrix * projectionMatrix; } public Matrix4 GetProjection() { return projectionMatrix; } private void UpdateViewMatrix() { Matrix4 rotation = Matrix4.CreateRotationY(MathHelper.DegreesToRadians(Yaw)) * Matrix4.CreateRotationX(MathHelper.DegreesToRadians(Pitch)); viewMatrix = Matrix4.CreateTranslation(FocalPoint) * rotation * Matrix4.CreateTranslation(0, 0, -Distance); // Position in Right-handed coordinates _position = viewMatrix.Inverted().ExtractTranslation(); } private void UpdateProjection() { float aspect = (float)ViewportSize.Width / (float)ViewportSize.Height; projectionMatrix = Matrix4.CreatePerspectiveFieldOfView((float)MathHelper.DegreesToRadians(Fov), aspect, NearClip, FarClip); } private Vector2 GetPanSpeed() { float x = Math.Min(ViewportSize.Width / 100.0f, 1.4f); // max = 2.4f float xFactor = 0.0366f * (x * x) - 0.1778f * x + 0.3021f; float y = Math.Min(ViewportSize.Height / 100.0f, 1.4f); // max = 2.4f float yFactor = 0.0366f * (y * y) - 0.1778f * y + 0.3021f; return new Vector2(xFactor, yFactor); } public void Pan(Vector2 delta) { Pan(delta.X, delta.Y); } public void Pan(float deltaX, float deltaY) { Vector2 panSpeed = GetPanSpeed(); // Taken from: blockbench // https://github.com/JannisX11/blockbench/blob/a56fe01a517ace8d013f67bbd3d02442c44d3141/js/preview/OrbitControls.js#L271-L322 Vector3 left = viewMatrix.Column0.Xyz * -Distance; Vector3 up = viewMatrix.Column1.Xyz * Distance; _focalPoint -= left * deltaX * panSpeed.X; _focalPoint -= up * deltaY * panSpeed.Y; UpdateViewMatrix(); } public void Rotate(Vector2 delta) { Rotate(delta.X, delta.Y); } public void Rotate(float deltaX,float deltaY) { Yaw += deltaX * RotationSpeed; Pitch += deltaY * RotationSpeed; } public override string ToString() { return $"FOV: {Fov}\nPosition: {WorldPosition}\nRotation: {_spherical}"; } } }