Merge pull request 'Ray casting from camera' (#156) from camera_raycasting into master

Reviewed-on: https://git.madvoxel.net/OpenVulkano/OpenVulkano/pulls/156
Reviewed-by: Georg Hagen <georg.hagen@madvoxel.com>
This commit is contained in:
Oleksii_Hyzha
2024-11-07 17:06:30 +01:00
27 changed files with 456 additions and 42 deletions

View File

@@ -37,7 +37,7 @@ SortIncludes: Never
SortUsingDeclarations: Never
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeRangeBasedForLoopColon: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpacesInAngles: false
SpacesInContainerLiterals: false

View File

@@ -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]);

View File

@@ -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<Scene::UI::PerformanceInfo> 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<SimpleDrawable *>(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 {}

View File

@@ -33,6 +33,8 @@ namespace OpenVulkano::Input
{
axes[InputKey::Mouse::AXIS_X] = static_cast<float>(posX - mousePosX);
axes[InputKey::Mouse::AXIS_Y] = static_cast<float>(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;
}
}
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -75,6 +75,23 @@ namespace OpenVulkano::Input
return false;
}
bool InputManager::GetButtonDown(InputAction* action) const
{
const std::vector<InputDevice*>& 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<InputDevice*> InputManager::GetDevices(InputDeviceType type) const
{
if (type == InputDeviceType::UNKNOWN)
{
return {};
}
std::vector<InputDevice*> 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)

View File

@@ -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<InputDevice*> GetDevices(InputDeviceType type) const;
[[nodiscard]] InputDevice* GetLastActiveDevice() const
{
return lastActiveDevice;

View File

@@ -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;
}
}

View File

@@ -9,6 +9,7 @@
#include "Node.hpp"
#include "Math/Math.hpp"
#include "Math/Frustum.hpp"
#include "Scene/Ray.hpp"
#include <array>
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;

View File

@@ -7,6 +7,7 @@
#pragma once
#include "Base/ICloseable.hpp"
#include "Scene/IRayIntersectable.hpp"
#include "DrawEncoder.hpp"
#include <memory>
#include <vector>
@@ -26,13 +27,14 @@ namespace OpenVulkano::Scene
BACKGROUND = 0, MAIN, TRANSPARENT, POST
};
class Drawable : public ICloseable
class Drawable : public ICloseable, public IRayIntersectable
{
std::vector<Node*> 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<RayHit> 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; }

View File

@@ -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);

View File

@@ -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<RayHit> Intersect(const Ray& ray) const = 0;
};
}

View File

@@ -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;

View File

@@ -67,6 +67,8 @@ namespace OpenVulkano::Scene
void SetMatrix(const Math::Matrix4f& mat);
[[nodiscard]] Node* GetRoot();
[[nodiscard]] Math::Matrix3f GetRotationMatrix() const { return static_cast<const Math::Matrix3f>(localMat); }
[[nodiscard]] const Math::Matrix4f& GetMatrix() const { return localMat; }

View File

@@ -80,6 +80,11 @@ namespace OpenVulkano::Scene
m_billboardSettings = settings;
}
std::optional<RayHit> LabelDrawable::Intersect(const Ray& ray) const
{
return ray.IntersectAABB(m_bbox);
}
void LabelDrawable::RecalculateBbox(const Math::AABB& other)
{
if (m_bbox.IsEmpty())

View File

@@ -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<RayHit> Intersect(const Ray& ray) const override;
private:
void RecalculateBbox(const Math::AABB& other);
void SetupShaders();

View File

@@ -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); }
}

View File

@@ -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

View File

@@ -20,6 +20,7 @@ namespace OpenVulkano
public:
Node* root;
std::vector<Drawable*> shapeList;
std::vector<Drawable*> 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

View File

@@ -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<DrawableRayHit> 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);
}
}

View File

@@ -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<DrawableRayHit> OnHit;
private:
Input::InputAction* m_actionClick = nullptr;
Input::InputAction* m_actionClickX = nullptr;
Input::InputAction* m_actionClickY = nullptr;
Camera* m_camera;
};
}

View File

@@ -5,7 +5,12 @@
*/
#include "SimpleDrawable.hpp"
#include "Scene/Geometry.hpp"
#include "Scene/Shader/Shader.hpp"
#include "Base/Logger.hpp"
#include <magic_enum.hpp>
#include <stdexcept>
#include <cassert>
namespace OpenVulkano::Scene
{
@@ -26,4 +31,70 @@ namespace OpenVulkano::Scene
m_uniBuffer = drawable->m_uniBuffer;
SetShader(drawable->GetShader());
}
std::optional<RayHit> 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 {};
}
}

View File

@@ -44,6 +44,8 @@ namespace OpenVulkano::Scene
void Init(SimpleDrawable* drawable);
std::optional<RayHit> Intersect(const Ray& ray) const override;
[[nodiscard]] Geometry* GetMesh() const { return m_mesh; }
[[nodiscard]] Material* GetMaterial() const { return m_material; }

View File

@@ -150,6 +150,7 @@ namespace OpenVulkano::Scene
return;
}
m_text = text;
auto GetActualLength = [&]()
{
auto begin = text.begin();

View File

@@ -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<AtlasData> GetAtlasData() { return m_atlasData; }
private:
Geometry m_geometry;
@@ -59,6 +60,7 @@ namespace OpenVulkano::Scene
std::shared_ptr<AtlasData> m_atlasData;
Math::AABB m_bbox;
Shader* m_shader = nullptr;
std::string m_text;
TextConfig m_cfg;
};
}

View File

@@ -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<RayHit> 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<RayHit> 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<RayHit> 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<RayHit> 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<RayHit> 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<RayHit> 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));
}
{