diff --git a/.clang-format b/.clang-format index 0a4a8f9..ed47f3a 100644 --- a/.clang-format +++ b/.clang-format @@ -37,7 +37,7 @@ SortIncludes: Never SortUsingDeclarations: Never SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: false -SpaceBeforeRangeBasedForLoopColon: false +SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpacesInAngles: false SpacesInContainerLiterals: false diff --git a/examples/ExampleApps/LabelDrawableExampleApp.cpp b/examples/ExampleApps/LabelDrawableExampleApp.cpp index 1ca40b7..72271d8 100644 --- a/examples/ExampleApps/LabelDrawableExampleApp.cpp +++ b/examples/ExampleApps/LabelDrawableExampleApp.cpp @@ -88,6 +88,7 @@ namespace OpenVulkano label.AddText("Additional text" + std::to_string(j)); } } + m_drawablesPool[i].SetIsHittable(true); m_scene.GetRoot()->AddChild(&m_nodesPool[i]); m_nodesPool[i].SetMatrix(Math::Utils::translate(glm::mat4x4(1.f), Vector3f(-5 + std::rand() % 5, -5 + std::rand() % 5, -std::rand() % 10))); m_nodesPool[i].AddDrawable(&m_drawablesPool[i]); diff --git a/examples/ExampleApps/MovingCubeApp.cpp b/examples/ExampleApps/MovingCubeApp.cpp index 1f637b0..d33dcd8 100644 --- a/examples/ExampleApps/MovingCubeApp.cpp +++ b/examples/ExampleApps/MovingCubeApp.cpp @@ -19,11 +19,15 @@ #include "Scene/MorphableCameraController.hpp" #include "Scene/PlaneCameraController.hpp" #include "Scene/UI/PerformanceInfo.hpp" +#include "Scene/SceneIntersectionTestController.hpp" #include "Input/InputManager.hpp" #include "Host/GraphicsAppManager.hpp" #include "Base/EngineConfiguration.hpp" #include "Base/Logger.hpp" #include "Controller/FreeCamCameraController.hpp" +#include "Scene/Prefabs/LabelDrawable.hpp" +#include "Scene/SimpleDrawable.hpp" +#include "Scene/Ray.hpp" #define USE_PLANE_CAM_CONTROL 0 @@ -54,6 +58,7 @@ namespace OpenVulkano Scene::SimpleAnimationController m_simpleAnimationController; Scene::SequenceAnimationController m_sequenceAnimationController; + Scene::SceneIntersectionTestController intersectionTestController; Scene::UI::SimpleUi m_ui; std::shared_ptr m_perfInfo; @@ -71,6 +76,8 @@ namespace OpenVulkano void CompleteSceneElement(SceneElement *dest) { dest->m_drawable.Init(&m_shader, &dest->m_geometry, &m_material); + dest->m_drawable.SetIsHittable(true); + //dest->m_geometry.freeAfterUpload = false; dest->m_node.Init(); m_scene.GetRoot()->AddChild(&dest->m_node); dest->m_node.SetUpdateFrequency(Scene::UpdateFrequency::Always); @@ -81,24 +88,28 @@ namespace OpenVulkano void CreateSceneElement(SceneElement *dest, const Math::Vector4f &color, float scale) { dest->m_geometry = Scene::GeometryFactory::MakeCube(scale, scale, scale, color); + dest->m_geometry.name = fmt::format("Cube {} {} {}", color.r, color.g, color.b); CompleteSceneElement(dest); } void CreatePlane(SceneElement *dest, const Math::Vector4f &color) { dest->m_geometry = Scene::GeometryFactory::MakePlane(1, 1, color); + dest->m_geometry.name = "Plane"; CompleteSceneElement(dest); } void CreateSphere(SceneElement *dest, const Math::Vector4f &color) { dest->m_geometry = Scene::GeometryFactory::MakeSphere(1, 32, 16, color); + dest->m_geometry.name = "Sphere"; CompleteSceneElement(dest); } void CreateHemisphere(SceneElement *dest, const Math::Vector4f &color) { dest->m_geometry = Scene::GeometryFactory::MakeHemisphere(1, 16, 16, color); + dest->m_geometry.name = "Hemisphere"; CompleteSceneElement(dest); } @@ -106,18 +117,21 @@ namespace OpenVulkano { dest->m_geometry = Scene::GeometryFactory::MakeTriangle( Math::Vector3f(0.5, 0., 0.), Math::Vector3f(0., 0.5, 0.), Math::Vector3f(-0.5, 0., 0.), color); + dest->m_geometry.name = "Triangle"; CompleteSceneElement(dest); } void CreateCylinder(SceneElement *dest, const Math::Vector4f &color) { dest->m_geometry = Scene::GeometryFactory::MakeCylinder(1, 3, 64, color); + dest->m_geometry.name = "Cylinder"; CompleteSceneElement(dest); } void CreatePyramid(SceneElement *dest, const Math::Vector4f &color) { dest->m_geometry = Scene::GeometryFactory::MakePyramid(0.5, 2, color); + dest->m_geometry.name = "Pyramid"; CompleteSceneElement(dest); } @@ -145,6 +159,10 @@ namespace OpenVulkano m_shader.AddShaderProgram(ShaderProgramType::FRAGMENT, "Shader/basic"); m_shader.AddVertexInputDescription(Vertex::GetVertexInputDescription()); + intersectionTestController.OnHit += EventHandler(this, &MovingCubeAppImpl::OnRayHit); + intersectionTestController.SetCamera(&m_camera); + intersectionTestController.SetDefaultKeybindings(); + GetGraphicsAppManager()->GetRenderer()->SetScene(&m_scene); #if USE_PLANE_CAM_CONTROL m_cameraControl.Init(&m_camera, PLANE_NORMAL); @@ -210,12 +228,23 @@ namespace OpenVulkano anim->Reset(); } + void OnRayHit(const Scene::DrawableRayHit &hit) + { + using namespace Scene; + Drawable *d = hit.drawable; + if (SimpleDrawable *sd = dynamic_cast(d)) + { + Logger::APP->info("Ray intersects object {}", sd->GetMesh()->name); + } + } + void Tick() override { m_cameraControl.Tick(); m_morphableCameraControl.Tick(); m_simpleAnimationController.Tick(); m_sequenceAnimationController.Tick(); + intersectionTestController.Tick(); } void Close() override {} diff --git a/openVulkanoCpp/Input/InputDeviceMouse.cpp b/openVulkanoCpp/Input/InputDeviceMouse.cpp index 626e905..632ab1d 100644 --- a/openVulkanoCpp/Input/InputDeviceMouse.cpp +++ b/openVulkanoCpp/Input/InputDeviceMouse.cpp @@ -33,6 +33,8 @@ namespace OpenVulkano::Input { axes[InputKey::Mouse::AXIS_X] = static_cast(posX - mousePosX); axes[InputKey::Mouse::AXIS_Y] = static_cast(posY - mousePosY); + axes[InputKey::Mouse::AXIS_X_ABS] = posX; + axes[InputKey::Mouse::AXIS_Y_ABS] = posY; mousePosX = posX; mousePosY = posY; Logger::INPUT->trace("Mouse moved posX: {0} posY: {1}, relativeX: {2}, relativeY: {3}", posX, posY, axes[InputKey::Mouse::AXIS_X], axes[InputKey::Mouse::AXIS_Y]); @@ -53,9 +55,9 @@ namespace OpenVulkano::Input void InputDeviceMouse::ClearAxes() { - for (float& axis : axes) + for (int i = InputKey::Mouse::AXIS_X; i < AXES_SIZE; i++) { - axis = 0; + axes[i] = 0; } } } \ No newline at end of file diff --git a/openVulkanoCpp/Input/InputDeviceMouse.hpp b/openVulkanoCpp/Input/InputDeviceMouse.hpp index d3a320b..21a5944 100644 --- a/openVulkanoCpp/Input/InputDeviceMouse.hpp +++ b/openVulkanoCpp/Input/InputDeviceMouse.hpp @@ -17,7 +17,8 @@ namespace OpenVulkano { class InputDeviceMouse : public InputDevice { - float axes[InputKey::Mouse::Axis::AXIS_LAST + 2] = { 0 }; + static constexpr int AXES_SIZE = InputKey::Mouse::Axis::AXIS_LAST + 2; + float axes[AXES_SIZE] = { 0 }; uint8_t pressedButtons = 0, lastPressedButtons = 0; double mousePosX = 0, mousePosY = 0; IWindow* lastWindow = nullptr; diff --git a/openVulkanoCpp/Input/InputKey.hpp b/openVulkanoCpp/Input/InputKey.hpp index 7640e4a..8e37b7b 100644 --- a/openVulkanoCpp/Input/InputKey.hpp +++ b/openVulkanoCpp/Input/InputKey.hpp @@ -177,7 +177,9 @@ namespace OpenVulkano::Input enum Axis : int16_t { - AXIS_X = 0, + AXIS_X_ABS = 0, + AXIS_Y_ABS, + AXIS_X, AXIS_Y, AXIS_WHEEL_X, AXIS_WHEEL_Y, diff --git a/openVulkanoCpp/Input/InputManager.cpp b/openVulkanoCpp/Input/InputManager.cpp index 5e36010..b93efbd 100644 --- a/openVulkanoCpp/Input/InputManager.cpp +++ b/openVulkanoCpp/Input/InputManager.cpp @@ -75,6 +75,23 @@ namespace OpenVulkano::Input return false; } + bool InputManager::GetButtonDown(InputAction* action) const + { + const std::vector& testDevices = action->GetDevices().empty() ? devices : action->GetDevices(); + for (const InputDevice* device : testDevices) + { + for (const KeyBinding binding : action->GetKeys()) + { + if (binding.key.GetInputDeviceType() != device->GetType()) + { + continue; + } + return device->GetButtonDown(binding.key); + } + } + return false; + } + bool InputManager::GetButton(InputKey key) const { for(const InputDevice* device : devices) @@ -85,6 +102,40 @@ namespace OpenVulkano::Input return false; } + InputDevice* InputManager::GetDevice(InputDeviceType type) const + { + if (type == InputDeviceType::UNKNOWN) + { + return nullptr; + } + for (InputDevice* device : devices) + { + if (device->GetType() == type) + { + return device; + } + } + return nullptr; + } + + std::vector InputManager::GetDevices(InputDeviceType type) const + { + if (type == InputDeviceType::UNKNOWN) + { + return {}; + } + std::vector devices; + devices.reserve(this->devices.size()); + for (InputDevice* device : this->devices) + { + if (device->GetType() == type) + { + devices.push_back(device); + } + } + return devices; + } + void InputManager::Tick() { for(InputDevice* device : devices) diff --git a/openVulkanoCpp/Input/InputManager.hpp b/openVulkanoCpp/Input/InputManager.hpp index d2aa6dc..116effb 100644 --- a/openVulkanoCpp/Input/InputManager.hpp +++ b/openVulkanoCpp/Input/InputManager.hpp @@ -40,8 +40,14 @@ namespace OpenVulkano::Input [[nodiscard]] bool GetButton(InputAction* action) const; + [[nodiscard]] bool GetButtonDown(InputAction* action) const; + [[nodiscard]] bool GetButton(InputKey key) const; + [[nodiscard]] InputDevice* GetDevice(InputDeviceType type) const; + + [[nodiscard]] std::vector GetDevices(InputDeviceType type) const; + [[nodiscard]] InputDevice* GetLastActiveDevice() const { return lastActiveDevice; diff --git a/openVulkanoCpp/Scene/Camera.cpp b/openVulkanoCpp/Scene/Camera.cpp new file mode 100644 index 0000000..55844d1 --- /dev/null +++ b/openVulkanoCpp/Scene/Camera.cpp @@ -0,0 +1,31 @@ +/* + * 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/. + */ + +#include "Camera.hpp" + +namespace OpenVulkano::Scene +{ + + Ray Camera::CastRay(const Math::Vector2i& xy) const + { + using namespace Math::Utils; + + // nds + float ndsX = (2.f * xy.x) / m_width - 1.0f; + float ndsY = (2.f * xy.y) / m_height - 1.0f; + + Math::Vector4f rayClip = { ndsX, ndsY, 0, 1.f }; + + Math::Vector4f rayEye = inverse(m_projection) * rayClip; + rayEye.z = -1; + rayEye.a = 0; + + Math::Vector3f rayWorld = normalize(GetWorldMatrix() * rayEye); + + Ray r(GetPosition(), rayWorld); + return r; + } +} diff --git a/openVulkanoCpp/Scene/Camera.hpp b/openVulkanoCpp/Scene/Camera.hpp index f755ded..3735dfe 100644 --- a/openVulkanoCpp/Scene/Camera.hpp +++ b/openVulkanoCpp/Scene/Camera.hpp @@ -9,6 +9,7 @@ #include "Node.hpp" #include "Math/Math.hpp" #include "Math/Frustum.hpp" +#include "Scene/Ray.hpp" #include namespace OpenVulkano::Scene @@ -83,6 +84,8 @@ namespace OpenVulkano::Scene virtual void UpdateProjectionMatrix() = 0; + Ray CastRay(const Math::Vector2i& xy) const; + void UpdateViewProjectionMatrix() { m_viewProjection = m_projection * m_view; diff --git a/openVulkanoCpp/Scene/Drawable.hpp b/openVulkanoCpp/Scene/Drawable.hpp index 5f58614..2c24b0d 100644 --- a/openVulkanoCpp/Scene/Drawable.hpp +++ b/openVulkanoCpp/Scene/Drawable.hpp @@ -7,6 +7,7 @@ #pragma once #include "Base/ICloseable.hpp" +#include "Scene/IRayIntersectable.hpp" #include "DrawEncoder.hpp" #include #include @@ -26,13 +27,14 @@ namespace OpenVulkano::Scene BACKGROUND = 0, MAIN, TRANSPARENT, POST }; - class Drawable : public ICloseable + class Drawable : public ICloseable, public IRayIntersectable { std::vector m_nodes; Scene* m_scene = nullptr; Shader* m_shader = nullptr; const DrawEncoder m_encoder; const DrawPhase m_drawPhase; + bool m_isHittable = false; public: explicit Drawable(const DrawEncoder& encoder, @@ -45,6 +47,12 @@ namespace OpenVulkano::Scene void SetShader(Shader* shader) { m_shader = shader; } + std::optional Intersect(const Ray& ray) const override { return {}; } + + void SetIsHittable(bool hittable) { m_isHittable = hittable; } + + bool IsHittable() const { return m_isHittable; } + [[nodiscard]] Scene* GetScene() const { return m_scene; } [[nodiscard]] const auto& GetNodes() const { return m_nodes; } diff --git a/openVulkanoCpp/Scene/Geometry.hpp b/openVulkanoCpp/Scene/Geometry.hpp index df2d101..c5c1159 100644 --- a/openVulkanoCpp/Scene/Geometry.hpp +++ b/openVulkanoCpp/Scene/Geometry.hpp @@ -30,8 +30,10 @@ namespace OpenVulkano Vertex* vertices = nullptr; void* indices = nullptr; VertexIndexType indexType = VertexIndexType::UINT16; - bool ownsMemory = true, freeAfterUpload = true; + // handle freeAfterUpload better. we can't free this memory if object is hittable + bool ownsMemory = true, freeAfterUpload = false; Math::AABB aabb; + std::string name; public: Geometry() = default; Geometry(const Geometry& other); diff --git a/openVulkanoCpp/Scene/IRayIntersectable.hpp b/openVulkanoCpp/Scene/IRayIntersectable.hpp new file mode 100644 index 0000000..0deaf91 --- /dev/null +++ b/openVulkanoCpp/Scene/IRayIntersectable.hpp @@ -0,0 +1,18 @@ +/* + * 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 "Scene/Ray.hpp" + +namespace OpenVulkano::Scene +{ + class IRayIntersectable + { + public: + virtual std::optional Intersect(const Ray& ray) const = 0; + }; +} diff --git a/openVulkanoCpp/Scene/Node.cpp b/openVulkanoCpp/Scene/Node.cpp index c362f43..344d669 100644 --- a/openVulkanoCpp/Scene/Node.cpp +++ b/openVulkanoCpp/Scene/Node.cpp @@ -110,6 +110,15 @@ namespace OpenVulkano::Scene UpdateWorldMatrix(parent ? parent->GetWorldMatrix() : Math::Matrix4f(1)); } + Node* Node::GetRoot() + { + if (IsRoot() || !parent) + { + return this; + } + return parent->GetRoot(); + } + void Node::UpdateWorldMatrix(const Math::Matrix4f& parentWorldMat) { worldMat = parentWorldMat * localMat; diff --git a/openVulkanoCpp/Scene/Node.hpp b/openVulkanoCpp/Scene/Node.hpp index 70bb07f..abcdcff 100644 --- a/openVulkanoCpp/Scene/Node.hpp +++ b/openVulkanoCpp/Scene/Node.hpp @@ -67,6 +67,8 @@ namespace OpenVulkano::Scene void SetMatrix(const Math::Matrix4f& mat); + [[nodiscard]] Node* GetRoot(); + [[nodiscard]] Math::Matrix3f GetRotationMatrix() const { return static_cast(localMat); } [[nodiscard]] const Math::Matrix4f& GetMatrix() const { return localMat; } diff --git a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp index 4ca1aff..e1ddfef 100644 --- a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp +++ b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.cpp @@ -80,6 +80,11 @@ namespace OpenVulkano::Scene m_billboardSettings = settings; } + std::optional LabelDrawable::Intersect(const Ray& ray) const + { + return ray.IntersectAABB(m_bbox); + } + void LabelDrawable::RecalculateBbox(const Math::AABB& other) { if (m_bbox.IsEmpty()) diff --git a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp index c8269fb..ce29ae7 100644 --- a/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp +++ b/openVulkanoCpp/Scene/Prefabs/LabelDrawable.hpp @@ -61,6 +61,7 @@ namespace OpenVulkano::Scene Math::Vector3f& GetPosition() { return m_position; } bool IsBillboard() const { return m_isBillboard; } const Math::AABB& GetBoundingBox() const { return m_bbox; } + std::optional Intersect(const Ray& ray) const override; private: void RecalculateBbox(const Math::AABB& other); void SetupShaders(); diff --git a/openVulkanoCpp/Scene/Ray.cpp b/openVulkanoCpp/Scene/Ray.cpp index c082328..7e2a30c 100644 --- a/openVulkanoCpp/Scene/Ray.cpp +++ b/openVulkanoCpp/Scene/Ray.cpp @@ -41,7 +41,7 @@ namespace OpenVulkano::Scene RayHit hitRes; if (intersectRaySphere(m_origin, m_dir, center, radius, hitRes.point, hitRes.normal)) { - hitRes.distance = distance(m_origin, hitRes.point); + hitRes.distance2 = distance2(m_origin, hitRes.point); return hitRes; } return {}; @@ -51,12 +51,14 @@ namespace OpenVulkano::Scene const Math::Vector3f& v2) const { RayHit hitRes; - if (intersectRayTriangle(m_origin, m_dir, v0, v1, v2, m_baryPos, hitRes.distance) && hitRes.distance >= 0) + float d; + if (intersectRayTriangle(m_origin, m_dir, v0, v1, v2, m_baryPos, d) && d >= 0) { hitRes.point = (1.f - m_baryPos.x - m_baryPos.y) * v0 + m_baryPos.x * v1 + m_baryPos.y * v2; Math::Vector3f e = v1 - v0; Math::Vector3f e2 = v2 - v0; // triangle normal. won't work if triangle is smoothly shaded. use other overloaded method instead. + hitRes.SetDistance(d); hitRes.normal = normalize(cross(e, e2)); return hitRes; } @@ -99,7 +101,7 @@ namespace OpenVulkano::Scene case 1: return h1; case 2: - return (h1.distance < h2.distance) ? h1 : h2; + return (h1.distance2 < h2.distance2) ? h1 : h2; } return {}; } @@ -169,8 +171,8 @@ namespace OpenVulkano::Scene p1.point = m_origin + txmin * m_dir; p2.point = m_origin + txmax * m_dir; - p1.distance = distance(m_origin, p1.point); - p2.distance = distance(m_origin, p2.point); + p1.distance2 = distance2(m_origin, p1.point); + p2.distance2 = distance2(m_origin, p2.point); p1.normal = p2.normal = Math::Vector3f(0); return intersections; } @@ -179,9 +181,11 @@ namespace OpenVulkano::Scene { RayHit hit; Math::Vector3f norm = normalize(pNorm); - if (intersectRayPlane(m_origin, m_dir, pOrigin, pNorm, hit.distance)) + float d; + if (intersectRayPlane(m_origin, m_dir, pOrigin, pNorm, d)) { - hit.point = m_origin + m_dir * hit.distance; + hit.SetDistance(d); + hit.point = m_origin + m_dir * d; hit.normal = norm; return hit; } @@ -214,7 +218,7 @@ namespace OpenVulkano::Scene return 0; } p1.point = m_origin + x1 * m_dir; - p1.distance = distance(m_origin, p1.point); + p1.distance2 = distance2(m_origin, p1.point); p1.normal = normalize(p1.point - center); p2 = p1; } @@ -228,10 +232,10 @@ namespace OpenVulkano::Scene if (x1 >= 0 && x2 >= 0) { p1.point = m_origin + x1 * m_dir; - p1.distance = distance(m_origin, p1.point); + p1.distance2 = distance2(m_origin, p1.point); p1.normal = normalize(p1.point - center); p2.point = m_origin + x2 * m_dir; - p2.distance = distance(m_origin, p2.point); + p2.distance2 = distance2(m_origin, p2.point); p2.normal = normalize(p2.point - center); } else @@ -242,11 +246,18 @@ namespace OpenVulkano::Scene x1 = x2; } p1.point = m_origin + x1 * m_dir; - p1.distance = distance(m_origin, p1.point); + p1.distance2 = distance2(m_origin, p1.point); p1.normal = normalize(p1.point - center); p2 = p1; } } return roots; } + + bool RayHit::operator==(const RayHit& other) const + { + return distance2 == other.distance2 && point == other.point && normal == other.normal; + } + + bool RayHit::operator!=(const RayHit& other) const { return !(*this == other); } } diff --git a/openVulkanoCpp/Scene/Ray.hpp b/openVulkanoCpp/Scene/Ray.hpp index 080766c..999888b 100644 --- a/openVulkanoCpp/Scene/Ray.hpp +++ b/openVulkanoCpp/Scene/Ray.hpp @@ -12,14 +12,39 @@ namespace OpenVulkano::Scene { + class Drawable; + class Node; struct RayHit { Math::Vector3f point; Math::Vector3f normal; - float distance; - bool operator==(const RayHit& other) const = default; - bool operator!=(const RayHit& other) const = default; + float distance2; + [[nodiscard]] float GetDistance() const + { + if (distance == -1) + { + distance = std::sqrt(distance2); + } + return distance; + } + void SetDistance(float d) + { + this->distance = d; + this->distance2 = d * d; + } + bool operator==(const RayHit& other) const; + bool operator!=(const RayHit& other) const; + private: + mutable float distance = -1; + }; + + struct DrawableRayHit : RayHit + { + DrawableRayHit() = default; + DrawableRayHit(const RayHit& hit) : RayHit(hit) {}; + Drawable* drawable = nullptr; + Node* node = nullptr; }; class Ray diff --git a/openVulkanoCpp/Scene/Scene.hpp b/openVulkanoCpp/Scene/Scene.hpp index 84aadb1..a3dc720 100644 --- a/openVulkanoCpp/Scene/Scene.hpp +++ b/openVulkanoCpp/Scene/Scene.hpp @@ -20,6 +20,7 @@ namespace OpenVulkano public: Node* root; std::vector shapeList; + std::vector rayHittableDrawables; Camera* camera; public: @@ -55,11 +56,21 @@ namespace OpenVulkano return root; } + void RegisterHittableDrawable(Drawable* drawable) + { + drawable->SetIsHittable(true); + RegisterDrawable(drawable); + } + void RegisterDrawable(Drawable* drawable) { if (drawable->GetScene() != this) drawable->SetScene(this); if (Utils::Contains(shapeList, drawable)) return; // Prevent duplicate entries shapeList.push_back(drawable); + if (drawable->IsHittable()) + { + rayHittableDrawables.push_back(drawable); + } if (shapeList.size() > 1 && shapeList[shapeList.size() - 2]->GetDrawPhase() < drawable->GetDrawPhase()) { std::sort(shapeList.begin(), shapeList.end(), @@ -70,12 +81,17 @@ namespace OpenVulkano void RemoveDrawable(Drawable* drawable) { Utils::Remove(shapeList, drawable); + Utils::Remove(rayHittableDrawables, drawable); drawable->SetScene(nullptr); } virtual void SetCamera(Camera* camera) { this->camera = camera; + if (!camera->scene) + { + this->GetRoot()->AddChild(camera->GetRoot()); + } } Camera* GetCamera() const diff --git a/openVulkanoCpp/Scene/SceneIntersectionTestController.cpp b/openVulkanoCpp/Scene/SceneIntersectionTestController.cpp new file mode 100644 index 0000000..bb18d68 --- /dev/null +++ b/openVulkanoCpp/Scene/SceneIntersectionTestController.cpp @@ -0,0 +1,68 @@ +/* + * 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/. + */ + +#include "Input/InputManager.hpp" +#include "SceneIntersectionTestController.hpp" +#include "Scene.hpp" +#include "SimpleDrawable.hpp" +#include "Prefabs/LabelDrawable.hpp" + +namespace OpenVulkano::Scene +{ + void SceneIntersectionTestController::Tick() + { + Input::InputManager* input = Input::InputManager::GetInstance(); + if (input->GetButtonDown(m_actionClick)) + { + const float x = input->GetAxis(m_actionClickX); + const float y = input->GetAxis(m_actionClickY); + const Ray ray = m_camera->CastRay(Math::Vector2i(x, y)); + const auto& camPos = m_camera->GetPosition(); + const auto scene = m_camera->GetScene(); + std::optional res; + for (Drawable* d : scene->rayHittableDrawables) + { + for (Node* n : d->GetNodes()) + { + const auto& m = n->GetWorldMatrix(); + const Math::Vector3f rayLocalDir = normalize(inverse(m) * Math::Vector4f(ray.GetDir(), 0)); + const Math::Vector4f rayLocalPos = inverse(m) * camPos; + const Ray ray(rayLocalPos, rayLocalDir); + if (auto hit = d->Intersect(ray)) + { + // choose the closest one + if (!res || (hit->distance2 < res->distance2)) + { + res = hit; + res->drawable = d; + res->node = n; + } + } + } + } + + if (res) + { + OnHit.NotifyAll(*res); + } + } + } + + void SceneIntersectionTestController::SetDefaultKeybindings() + { + auto input = Input::InputManager::GetInstance(); + m_actionClick = input->GetAction("ClickIntersection"); + m_actionClick->BindKey(Input::InputKey::Mouse::BUTTON_1); + m_actionClick->BindKey(Input::InputKey::Touch::BUTTON_TAP); + + m_actionClickX = input->GetAction("ClickIntersectionPosX"); + m_actionClickX->BindKey(Input::InputKey::Touch::Axis::AXIS_TAP_X); + m_actionClickX->BindKey(Input::InputKey::Mouse::Axis::AXIS_X_ABS); + m_actionClickY = input->GetAction("ClickIntersectionPosY"); + m_actionClickY->BindKey(Input::InputKey::Touch::Axis::AXIS_TAP_Y); + m_actionClickY->BindKey(Input::InputKey::Mouse::Axis::AXIS_Y_ABS); + } +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/SceneIntersectionTestController.hpp b/openVulkanoCpp/Scene/SceneIntersectionTestController.hpp new file mode 100644 index 0000000..f731c8c --- /dev/null +++ b/openVulkanoCpp/Scene/SceneIntersectionTestController.hpp @@ -0,0 +1,33 @@ +/* + * 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 "Base/ITickable.hpp" +#include "Base/Event.hpp" +#include "Scene/Ray.hpp" +#include "Input/InputAction.hpp" +#include "Camera.hpp" + +namespace OpenVulkano::Scene +{ + class SceneIntersectionTestController : public ITickable + { + public: + SceneIntersectionTestController() = default; + SceneIntersectionTestController(Camera* camera) : m_camera(camera) {} + void SetCamera(Camera* camera) { m_camera = camera; } + [[nodiscard]] Camera* GetCamera() const { return m_camera; } + void Tick() override; + void SetDefaultKeybindings(); + Event OnHit; + + private: + Input::InputAction* m_actionClick = nullptr; + Input::InputAction* m_actionClickX = nullptr; + Input::InputAction* m_actionClickY = nullptr; + Camera* m_camera; + }; +} \ No newline at end of file diff --git a/openVulkanoCpp/Scene/SimpleDrawable.cpp b/openVulkanoCpp/Scene/SimpleDrawable.cpp index 0920971..2d8aa99 100644 --- a/openVulkanoCpp/Scene/SimpleDrawable.cpp +++ b/openVulkanoCpp/Scene/SimpleDrawable.cpp @@ -5,7 +5,12 @@ */ #include "SimpleDrawable.hpp" +#include "Scene/Geometry.hpp" +#include "Scene/Shader/Shader.hpp" +#include "Base/Logger.hpp" +#include #include +#include namespace OpenVulkano::Scene { @@ -26,4 +31,70 @@ namespace OpenVulkano::Scene m_uniBuffer = drawable->m_uniBuffer; SetShader(drawable->GetShader()); } + + std::optional SimpleDrawable::Intersect(const Ray& ray) const + { + if (!m_mesh || !GetShader()) + { + return {}; + } + if (m_mesh->aabb.IsEmpty()) + { + m_mesh->CalculateAABB(); + } + auto bboxHit = ray.IntersectAABB(m_mesh->aabb); + if (!bboxHit) + { + return {}; + } + if (GetShader()->topology == Topology::TRIANGLE_LIST) + { + if (m_mesh->indexCount != 0) + { + assert(m_mesh->indexCount % 3 == 0 && "Topology is TRIANGLE_LIST but index count is not divisible by 3"); + for (int i = 0; i < m_mesh->indexCount; i += 3) + { + if (m_mesh->indexType == VertexIndexType::UINT16) + { + uint16_t* indices = m_mesh->GetIndices16(); + if (auto hit = ray.IntersectTriangle(m_mesh->vertices[indices[i]].position, + m_mesh->vertices[indices[i + 1]].position, + m_mesh->vertices[indices[i + 2]].position)) + { + return hit; + } + } + else + { + uint32_t* indices = m_mesh->GetIndices32(); + if (auto hit = ray.IntersectTriangle(m_mesh->vertices[indices[i]].position, + m_mesh->vertices[indices[i + 1]].position, + m_mesh->vertices[indices[i + 2]].position)) + { + return hit; + } + } + } + } + else + { + assert(m_mesh->indexCount % 3 == 0 && "Topology is TRIANGLE_LIST but vertex count is not divisible by 3"); + for (int i = 0; i < m_mesh->vertexCount; i += 3) + { + if (auto hit = ray.IntersectTriangle(m_mesh->vertices[i].position, + m_mesh->vertices[i + 1].position, + m_mesh->vertices[i + 2].position)) + { + return hit; + } + } + } + } + else + { + Logger::APP->debug("Bbox is hit, but intersection check for topology {} is not implemented", magic_enum::enum_name(GetShader()->topology)); + return bboxHit; + } + return {}; + } } \ No newline at end of file diff --git a/openVulkanoCpp/Scene/SimpleDrawable.hpp b/openVulkanoCpp/Scene/SimpleDrawable.hpp index 231a87b..f78139b 100644 --- a/openVulkanoCpp/Scene/SimpleDrawable.hpp +++ b/openVulkanoCpp/Scene/SimpleDrawable.hpp @@ -44,6 +44,8 @@ namespace OpenVulkano::Scene void Init(SimpleDrawable* drawable); + std::optional Intersect(const Ray& ray) const override; + [[nodiscard]] Geometry* GetMesh() const { return m_mesh; } [[nodiscard]] Material* GetMaterial() const { return m_material; } diff --git a/openVulkanoCpp/Scene/TextDrawable.cpp b/openVulkanoCpp/Scene/TextDrawable.cpp index 5413368..be09db9 100644 --- a/openVulkanoCpp/Scene/TextDrawable.cpp +++ b/openVulkanoCpp/Scene/TextDrawable.cpp @@ -150,6 +150,7 @@ namespace OpenVulkano::Scene return; } + m_text = text; auto GetActualLength = [&]() { auto begin = text.begin(); diff --git a/openVulkanoCpp/Scene/TextDrawable.hpp b/openVulkanoCpp/Scene/TextDrawable.hpp index 2d50129..3896651 100644 --- a/openVulkanoCpp/Scene/TextDrawable.hpp +++ b/openVulkanoCpp/Scene/TextDrawable.hpp @@ -51,6 +51,7 @@ namespace OpenVulkano::Scene Math::AABB& GetBoundingBox() { return m_bbox; } TextConfig& GetConfig() { return m_cfg; } Shader* GetShader() { return m_shader; } + [[nodiscard]] const std::string& GetText() const { return m_text; } std::shared_ptr GetAtlasData() { return m_atlasData; } private: Geometry m_geometry; @@ -59,6 +60,7 @@ namespace OpenVulkano::Scene std::shared_ptr m_atlasData; Math::AABB m_bbox; Shader* m_shader = nullptr; + std::string m_text; TextConfig m_cfg; }; } diff --git a/tests/RayTests.cpp b/tests/RayTests.cpp index 0f25abd..73a6eca 100644 --- a/tests/RayTests.cpp +++ b/tests/RayTests.cpp @@ -28,48 +28,56 @@ namespace TEST_CASE("RaySphereIntersection") { auto sphere = GeometryFactory::MakeSphere(1, 32, 16); - RayHit h1, h2; // 2 intersections { + RayHit h1, h2; Ray ray(Vector3f(0, 0, -5), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 2); REQUIRE((h1.point == Vector3f(0, 0, -1) && h2.point == Vector3f(0, 0, 1))); REQUIRE((h1.normal == Vector3f(0, 0, -1) && h2.normal == Vector3f(0, 0, 1))); - REQUIRE(h1.distance < h2.distance); - REQUIRE((h1.distance == distance(ray.GetOrigin(), h1.point) && h2.distance == distance(ray.GetOrigin(), h2.point))); + REQUIRE(h1.distance2 < h2.distance2); + REQUIRE((h1.GetDistance() == distance(ray.GetOrigin(), h1.point) + && h2.GetDistance() == distance(ray.GetOrigin(), h2.point))); + REQUIRE_THAT(h1.GetDistance(), Catch::Matchers::WithinRel(std::sqrt(h1.distance2))); // this returns just closest point if (auto opt = ray.IntersectSphere(Vector3f(0), 1)) { + opt->GetDistance(); REQUIRE(opt.value() == h1); } } // 1 intersection { + RayHit h1, h2; Ray ray(Vector3f(1, 0, -1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 1); REQUIRE(h1 == h2); REQUIRE(h1.point == Vector3f(1, 0, 0)); - REQUIRE(ray.IntersectSphere(Vector3f(0), 1).value() == h1); + REQUIRE(ray.IntersectSphere(Vector3f(0), 1) == h1); } // 0 intersections { + RayHit h1, h2; Ray ray(Vector3f(2, 0, -1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 0); REQUIRE(!ray.IntersectSphere(Vector3f(0), 1).has_value()); } // ray is inside sphere { + RayHit h1, h2; Ray ray(Vector3f(0, 0, 0), Vector3f(0.5, 0.5, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 1); REQUIRE(h1 == h2); ::CompareVec3Approx(h1.normal, h1.point); auto value = ray.IntersectSphere(Vector3f(0), 1); - REQUIRE(value->distance == h1.distance); + REQUIRE(value->GetDistance() == h1.GetDistance()); + REQUIRE_THAT(value->distance2, Catch::Matchers::WithinRel(h1.distance2)); ::CompareVec3Approx(value->normal, h1.normal); ::CompareVec3Approx(value->point, h1.point); } // ray intersects sphere behind the origin { + RayHit h1, h2; Ray ray(Vector3f(0, 0, 3), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectSphere(Vector3f(0), 1, h1, h2) == 0); REQUIRE(!ray.IntersectSphere(Vector3f(0), 1).has_value()); @@ -79,31 +87,30 @@ TEST_CASE("RaySphereIntersection") TEST_CASE("RayTriangleIntersection") { auto tri = GeometryFactory::MakeTriangle(Vector3f(0), Vector3f(3, 0, 0), Vector3f(1.5, 2, 0)); - std::optional hit; // intersects { Ray ray(Vector3f(1.5, 2, -5), Vector3f(0, 0, 1)); - hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); + std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(hit.has_value()); - REQUIRE(hit->distance == distance(ray.GetOrigin(), hit->point)); + REQUIRE(hit->GetDistance() == distance(ray.GetOrigin(), hit->point)); REQUIRE(hit->point == Vector3f(1.5, 2, 0)); } { Ray ray(Vector3f(1.5, 1, -1), Vector3f(0, 0, 1)); - hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); + std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(hit.has_value()); - REQUIRE(hit->distance == distance(ray.GetOrigin(), hit->point)); + REQUIRE(hit->GetDistance() == distance(ray.GetOrigin(), hit->point)); REQUIRE(hit->point == Vector3f(1.5, 1, 0)); } // no intersections { Ray ray(Vector3f(5, 0, 0), Vector3f(0, 0, 1)); - hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); + std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(!hit.has_value()); } { Ray ray(Vector3f(1.5, 1, 0.5), Vector3f(0, 0, 1)); - hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); + std::optional hit = ray.IntersectTriangle(tri.vertices[0].position, tri.vertices[1].position, tri.vertices[2].position); REQUIRE(!hit.has_value()); } } @@ -141,52 +148,59 @@ TEST_CASE("RayAABBIntersection") auto sphere = GeometryFactory::MakeSphere(1, 32, 16); sphere.CalculateAABB(); std::optional hit; - RayHit h1, h2; // intersects { + RayHit h1, h2; Ray ray(Vector3f(0, 0, -2), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 2); - REQUIRE(h1.distance < h2.distance); + REQUIRE(h1.distance2 < h2.distance2); REQUIRE(h1.point == Vector3f(0, 0, -1)); REQUIRE(h2.point == Vector3f(0, 0, 1)); auto p = ray.IntersectAABB(sphere.aabb); REQUIRE(p->point == h1.point); - REQUIRE(p->distance == h1.distance); + REQUIRE(p->distance2 == h1.distance2); + REQUIRE(p->GetDistance() == h1.GetDistance()); } { + RayHit h1, h2; Ray ray(Vector3f(0, 0, 1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 1); - REQUIRE(h1.distance == h2.distance); + REQUIRE(h1.distance2 == h2.distance2); CompareVec3Approx(h1.point, h2.point); REQUIRE(h1.point == Vector3f(0, 0, 1)); - REQUIRE(ray.IntersectAABB(sphere.aabb)->distance == h1.distance); + REQUIRE(ray.IntersectAABB(sphere.aabb)->distance2 == h1.distance2); } { + RayHit h1, h2; // inside sphere Ray ray(Vector3f(0), Vector3f(0.3)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 1); REQUIRE(h1 == h2); auto val = ray.IntersectAABB(sphere.aabb); REQUIRE(val.has_value()); - REQUIRE_THAT(val->distance, Catch::Matchers::WithinRel(h1.distance)); + REQUIRE_THAT(val->distance2, Catch::Matchers::WithinRel(h1.distance2)); CompareVec3Approx(val->point, h1.point); } { + RayHit h1, h2; Ray ray(Vector3f(2, -0.5, 1.5), Vector3f(-2, 0.5, -1.5)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 2); - REQUIRE(h1.distance < h2.distance); + REQUIRE(h1.distance2 < h2.distance2); auto val = ray.IntersectAABB(sphere.aabb); REQUIRE(val.has_value()); - REQUIRE(val->distance == h1.distance); + REQUIRE(val->distance2 == h1.distance2); + REQUIRE_THAT(val->GetDistance(), Catch::Matchers::WithinRel(h1.GetDistance())); CompareVec3Approx(val->point, h1.point); } // no intersections { + RayHit h1, h2; Ray ray(Vector3f(3, 0, 1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 0); REQUIRE(!ray.IntersectAABB(sphere.aabb).has_value()); } { + RayHit h1, h2; Ray ray(Vector3f(0, 0, 1.1), Vector3f(0, 0, 1)); REQUIRE(ray.IntersectAABB(sphere.aabb, h1, h2) == 0); REQUIRE(!ray.IntersectAABB(sphere.aabb).has_value()); @@ -202,7 +216,7 @@ TEST_CASE("RayPlaneIntersection") auto hit = ray.IntersectPlane(pOrigin, pNorm); REQUIRE(hit.has_value()); REQUIRE(hit->normal == pNorm); - REQUIRE(hit->distance == 2.f); + REQUIRE(hit->GetDistance() == 2.f); REQUIRE(hit->point == Vector3f(2, 0, 2)); } {