From 062b2d6c8fdecc6dd22f7336707e7703e61b7b7b Mon Sep 17 00:00:00 2001 From: bailwillharr Date: Thu, 9 Feb 2023 15:21:37 +0000 Subject: [PATCH] Get event system working; add collision events --- CMakeLists.txt | 6 ++ TODO | 2 + debug.sh | 2 +- include/components/collider.hpp | 13 ++-- include/event_system.hpp | 119 ++++++++++++++++++++++++++++++ include/scene.hpp | 5 ++ include/systems/collisions.hpp | 30 +++++--- release.sh | 2 +- src/event_system.cpp | 15 ++++ src/gfx_device_null.cpp | 127 ++++++++++++++++++++++++++++++++ src/gfx_device_vulkan.cpp | 12 ++- src/scene.cpp | 6 ++ src/systems/collisions.cpp | 70 ++++++++++-------- test/src/camera_controller.cpp | 6 ++ test/src/camera_controller.hpp | 9 ++- test/src/game.cpp | 10 ++- test/src/game.hpp | 2 +- test/src/main.cpp | 11 ++- 18 files changed, 392 insertions(+), 55 deletions(-) create mode 100644 include/event_system.hpp create mode 100644 src/event_system.cpp create mode 100644 src/gfx_device_null.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 360b4d6..af389b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,8 +23,12 @@ set(SRC_FILES "src/resources/mesh.cpp" "src/resources/texture.cpp" + "src/event_system.cpp" + "src/scene.cpp" + "src/gfx_device_vulkan.cpp" + "src/gfx_device_null.cpp" "src/util/files.cpp" "src/util/model_loader.cpp" @@ -51,6 +55,8 @@ set(INCLUDE_FILES "include/resources/mesh.hpp" "include/resources/texture.hpp" + "include/event_system.hpp" + "include/engine_api.h" "include/util/files.hpp" diff --git a/TODO b/TODO index aa2c371..71953fa 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,7 @@ ----- TO DO LIST ----- +TODO now: the collision system doesn't use the "isTrigger" bool properly. + Add support for shadows and other complex lighting. Also add post-processing. The engine needs an event/message system, this will be helpful for collision diff --git a/debug.sh b/debug.sh index 23530fc..73ad8b3 100755 --- a/debug.sh +++ b/debug.sh @@ -3,4 +3,4 @@ set -e cd Debug cmake --build . cd test -./enginetest +./enginetest $@ diff --git a/include/components/collider.hpp b/include/components/collider.hpp index cb3fa3f..e52cb25 100644 --- a/include/components/collider.hpp +++ b/include/components/collider.hpp @@ -20,13 +20,14 @@ namespace engine { } - bool isStatic; - AABB aabb; + bool isStatic = true; + bool isTrigger = false; // entity receives an event on collision enter and exit + AABB aabb{}; - glm::vec3 getLastCollisionNormal() { return {0.0f, 1.0f, 0.0f}; } - glm::vec3 getLastCollisionPoint() { return {}; } - bool getIsColliding() { return true; } - bool getJustUncollided() { return false; } + bool getIsColliding() { return isColliding; } + + private: + bool isColliding = false; }; diff --git a/include/event_system.hpp b/include/event_system.hpp new file mode 100644 index 0000000..0540531 --- /dev/null +++ b/include/event_system.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace engine { + + enum class EventSubscriberKind { + ENTITY, + }; + + // Event handler base-class + template + class EventHandler { + public: + virtual void onEvent(T data) = 0; + }; + + // Event queue interface to allow for different type queues to be in the map + class IEventQueue { + public: + virtual void dispatchEvents() = 0; + }; + + template + class EventQueue : public IEventQueue { + // holds events of type T and subscribers to those events + + public: + void subscribe(EventSubscriberKind kind, uint32_t id, EventHandler* handler) + { + // For the time being, ignore kind (TODO) + (void)kind; + assert(m_subscribers.contains(id) == false && "subscribing to an event with ID that's already in use!"); + m_subscribers.emplace(id, handler); + } + + void queueEvent(EventSubscriberKind kind, uint32_t id, T event) + { + // For the time being, ignore kind (TODO) + (void)kind; + assert(m_subscribers.contains(id) && "Attempt to submit event to non-existing subscriber!"); + EventHandler* handler = m_subscribers.at(id); + m_eventQueue.emplace(handler, event); + } + + void dispatchEvents() override + { + while (m_eventQueue.empty() == false) { + auto [handler, event] = m_eventQueue.front(); + handler->onEvent(event); + m_eventQueue.pop(); + } + } + + private: + std::unordered_map*> m_subscribers; + + struct QueuedEvent { + EventHandler* handler; + T event; + }; + std::queue m_eventQueue{}; + + }; + + class EventSystem { + + public: + EventSystem(); + EventSystem(const EventSystem&) = delete; + EventSystem& operator=(const EventSystem&) = delete; + ~EventSystem(); + + template + void registerEventType() + { + size_t hash = typeid(T).hash_code(); + assert(m_eventQueues.contains(hash) == false && "Registering an event queue more than once!"); + m_eventQueues.emplace(hash, std::make_unique>()); + } + + template + void subscribeToEventType(EventSubscriberKind kind, uint32_t id, EventHandler* handler) + { + size_t hash = typeid(T).hash_code(); + assert(m_eventQueues.contains(hash) && "Subscribing to event type that isn't registered!"); + EventQueue* queue = dynamic_cast*>(m_eventQueues.at(hash).get()); + assert(queue != nullptr && "This cast should work?!! wot"); + queue->subscribe(kind, id, handler); + } + + template + void queueEvent(EventSubscriberKind kind, uint32_t subscriberID, T event) + { + size_t hash = typeid(T).hash_code(); + assert(m_eventQueues.contains(hash) && "Subscribing to event type that isn't registered!"); + EventQueue* queue = dynamic_cast*>(m_eventQueues.at(hash).get()); + assert(queue != nullptr && "This cast should work?!! wot"); + queue->queueEvent(kind, subscriberID, event); + } + + void dispatchEvents() + { + for (auto& [hash, queue] : m_eventQueues) { + queue->dispatchEvents(); + } + } + + private: + std::unordered_map> m_eventQueues{}; + + }; + +} diff --git a/include/scene.hpp b/include/scene.hpp index 73232fd..50a339d 100644 --- a/include/scene.hpp +++ b/include/scene.hpp @@ -3,6 +3,7 @@ #include "log.hpp" #include "ecs_system.hpp" +#include "event_system.hpp" #include #include @@ -25,6 +26,8 @@ namespace engine { Application* app() { return m_app; } + EventSystem* events() { return m_eventSystem.get(); } + /* ecs stuff */ uint32_t createEntity(const std::string& tag, uint32_t parent = 0); @@ -130,6 +133,8 @@ namespace engine { return castedPtr; } + std::unique_ptr m_eventSystem{}; + }; } diff --git a/include/systems/collisions.hpp b/include/systems/collisions.hpp index 5a2aed3..c7f516d 100644 --- a/include/systems/collisions.hpp +++ b/include/systems/collisions.hpp @@ -16,19 +16,31 @@ namespace engine { void onUpdate(float ts) override; void onComponentInsert(uint32_t entity) override; + + struct CollisionEvent { + bool isCollisionEnter; // false == collision exit + uint32_t collidedEntity; // the entity that this entity collided with + glm::vec3 normal; // the normal of the surface this entity collided with; ignored on collision exit + }; private: - // dyanmic arrays to avoid realloc on every frame - struct CollisionInfo { - uint32_t entity; - AABB aabb; - // output - bool isMaybeColliding; // broad phase - bool isColliding; // narrow phase + // dynamic arrays to avoid realloc on every frame + + // entity, aabb, isTrigger + std::vector> m_staticAABBs{}; + std::vector> m_dynamicAABBs{}; + + struct PossibleCollision { + uint32_t staticEntity; + AABB staticAABB; + bool staticTrigger; + uint32_t dynamicEntity; + AABB dynamicAABB; + bool dynamicTrigger; }; - std::vector m_staticInfos{}; - std::vector m_dynamicInfos{}; + std::vector m_possibleCollisions{}; + std::vector> m_collisionInfos{}; // target entity, event info }; diff --git a/release.sh b/release.sh index 7cf175e..11c89a5 100755 --- a/release.sh +++ b/release.sh @@ -3,4 +3,4 @@ set -e cd Release cmake --build . cd test -./enginetest +./enginetest $@ diff --git a/src/event_system.cpp b/src/event_system.cpp new file mode 100644 index 0000000..6dbcf88 --- /dev/null +++ b/src/event_system.cpp @@ -0,0 +1,15 @@ +#include "event_system.hpp" + +namespace engine { + + EventSystem::EventSystem() + { + + } + + EventSystem::~EventSystem() + { + + } + +} diff --git a/src/gfx_device_null.cpp b/src/gfx_device_null.cpp new file mode 100644 index 0000000..87ae741 --- /dev/null +++ b/src/gfx_device_null.cpp @@ -0,0 +1,127 @@ +// The implementation of the graphics layer using Vulkan 1.3. + +#ifdef ENGINE_BUILD_NULL + +#include "gfx_device.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine { + + struct GFXDevice::Impl { + int FRAMECOUNT = 0; + }; + + GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window, bool vsync) + { + (void)appName; + (void)appVersion; + (void)window; + (void)vsync; + + pimpl = std::make_unique(); + + } + + GFXDevice::~GFXDevice() + { + } + + void GFXDevice::getViewportSize(uint32_t *w, uint32_t *h) + { + *w = 1024; + *h = 768; + } + + void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize, const gfx::Texture* texture) + { + (void)pipeline; + (void)vertexBuffer; + (void)indexBuffer; + (void)count; + (void)pushConstantData; + (void)pushConstantSize; + (void)texture; + } + + void GFXDevice::renderFrame() + { + pimpl->FRAMECOUNT++; + } + + gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize, bool alphaBlending, bool backfaceCulling) + { + (void)vertShaderPath; + (void)fragShaderPath; + (void)vertexFormat; + (void)uniformBufferSize; + (void)alphaBlending; + (void)backfaceCulling; + return nullptr; + } + + void GFXDevice::destroyPipeline(const gfx::Pipeline* pipeline) + { + (void)pipeline; + } + + void GFXDevice::updateUniformBuffer(const gfx::Pipeline* pipeline, const void* data, size_t size, uint32_t offset) + { + (void)pipeline; + (void)data; + (void)size; + (void)offset; + } + + gfx::Buffer* GFXDevice::createBuffer(gfx::BufferType type, uint64_t size, const void* data) + { + (void)type; + (void)size; + (void)data; + return nullptr; + } + + void GFXDevice::destroyBuffer(const gfx::Buffer* buffer) + { + (void)buffer; + } + + gfx::Texture* GFXDevice::createTexture( + const void* imageData, + uint32_t width, + uint32_t height, + gfx::TextureFilter minFilter, + gfx::TextureFilter magFilter, + gfx::MipmapSetting mipmapSetting, + bool useAnisotropy) + { + (void)imageData; + (void)width; + (void)height; + (void)minFilter; + (void)magFilter; + (void)mipmapSetting; + (void)useAnisotropy; + return nullptr; + } + + void GFXDevice::destroyTexture(const gfx::Texture* texture) + { + (void)texture; + } + + void GFXDevice::waitIdle() + { + } + +} + +#endif diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index dba2b19..2fa5ff1 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -492,7 +492,7 @@ namespace engine { vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties); VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; - counts %= VK_SAMPLE_COUNT_8_BIT; // restricts it to 8 + counts %= VK_SAMPLE_COUNT_4_BIT; // restricts it to 2 or 1 (0b11) if (counts & VK_SAMPLE_COUNT_64_BIT) { return VK_SAMPLE_COUNT_64_BIT; } if (counts & VK_SAMPLE_COUNT_32_BIT) { return VK_SAMPLE_COUNT_32_BIT; } if (counts & VK_SAMPLE_COUNT_16_BIT) { return VK_SAMPLE_COUNT_16_BIT; } @@ -657,6 +657,14 @@ namespace engine { // create the render pass if (swapchain->renderpass == VK_NULL_HANDLE) { + + /* + * render pass layout: + * 0: color attachment with msaa samples, + * 1: depth attachment with msaa samples, used for fragment shading + * 2: present src (resolve) attachment with 1 sample, used for swapchain present + */ + VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapchain->surfaceFormat.format; colorAttachment.samples = swapchain->msaaSamples; @@ -1579,7 +1587,7 @@ namespace engine { renderPassInfo.renderArea.extent = pimpl->swapchain.extent; std::array clearValues{}; - clearValues[0].color = { {0.1f, 0.1f, 0.1f, 1.0f} }; + clearValues[0].color = { {1.0f, 1.0f, 1.0f, 1.0f} }; clearValues[1].depthStencil = { 1.0f, 0 }; renderPassInfo.clearValueCount = (uint32_t)clearValues.size(); renderPassInfo.pClearValues = clearValues.data(); diff --git a/src/scene.cpp b/src/scene.cpp index 439794f..112e887 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -12,6 +12,11 @@ namespace engine { Scene::Scene(Application* app) : m_app(app) { + // event system + m_eventSystem = std::make_unique(); + + // ecs configuration: + registerComponent(); registerComponent(); registerComponent(); @@ -64,6 +69,7 @@ namespace engine { system->onUpdate(ts); } + m_eventSystem->dispatchEvents(); // clears event queue } } diff --git a/src/systems/collisions.cpp b/src/systems/collisions.cpp index 8e2e975..f7ba7b6 100644 --- a/src/systems/collisions.cpp +++ b/src/systems/collisions.cpp @@ -35,13 +35,17 @@ namespace engine { PhysicsSystem::PhysicsSystem(Scene* scene) : System(scene, { typeid(TransformComponent).hash_code(), typeid(ColliderComponent).hash_code() }) { + m_scene->events()->registerEventType(); } void PhysicsSystem::onComponentInsert(uint32_t entity) { (void)entity; - m_staticInfos.reserve(m_entities.size()); - m_dynamicInfos.reserve(m_entities.size()); + const size_t size = m_entities.size(); + m_staticAABBs.reserve(size); + m_dynamicAABBs.reserve(size); + m_possibleCollisions.reserve(size); + m_collisionInfos.reserve(size); TRACE("added entity {} to collider system", entity); } @@ -49,12 +53,12 @@ namespace engine { { (void)ts; - m_staticInfos.clear(); - m_dynamicInfos.clear(); + m_staticAABBs.clear(); + m_dynamicAABBs.clear(); + m_possibleCollisions.clear(); + m_collisionInfos.clear(); - TRACE("Getting collider entities:"); for (uint32_t entity : m_entities) { - TRACE(" has entity: {}", entity); const auto t = m_scene->getComponent(entity); const auto c = m_scene->getComponent(entity); @@ -63,42 +67,50 @@ namespace engine { AABB globalBoundingBox; globalBoundingBox.pos1 = globalPosition + localBoundingBox.pos1; globalBoundingBox.pos2 = globalPosition + localBoundingBox.pos2; - TRACE(" global bounding box:"); - TRACE(" pos1: {} {} {}", globalBoundingBox.pos1.x, globalBoundingBox.pos1.y, globalBoundingBox.pos1.z); - TRACE(" pos2: {} {} {}", globalBoundingBox.pos2.x, globalBoundingBox.pos2.y, globalBoundingBox.pos2.z); - CollisionInfo info{ - .entity = entity, - .aabb = globalBoundingBox, - .isMaybeColliding = false, - .isColliding = false - }; if (c->isStatic) { - m_staticInfos.push_back(info); + m_staticAABBs.emplace_back(std::make_tuple(entity, globalBoundingBox, c->isTrigger)); } else { - m_dynamicInfos.push_back(info); + m_dynamicAABBs.emplace_back(std::make_tuple(entity, globalBoundingBox, c->isTrigger)); } } /* BROAD PHASE */ - TRACE("Starting broad phase collision test"); - - struct PossibleCollision { - - }; - // Check every static collider against every dynamic collider, and every dynamic collider against every other one // This technique is inefficient for many entities. - for (size_t i = 0; i < m_staticInfos.size(); i++) { - for (size_t j = 0; j < m_dynamicInfos.size(); j++) { - if (checkCollisionFast(m_staticInfos[i].aabb, m_dynamicInfos[j].aabb)) { - m_staticInfos[i].isMaybeColliding = true; - m_dynamicInfos[i].isMaybeColliding = true; - TRACE("Collision detected between {} and {}", m_staticInfos[i].entity, m_dynamicInfos[j].entity); + for (auto [staticEntity, staticAABB, staticTrigger] : m_staticAABBs) { + for (auto [dynamicEntity, dynamicAABB, dynamicTrigger] : m_dynamicAABBs) { + if (checkCollisionFast(staticAABB, dynamicAABB)) { + if (staticTrigger || dynamicTrigger) { // only check collisions involved with triggers + m_possibleCollisions.emplace_back( + staticEntity, staticAABB, staticTrigger, + dynamicEntity, dynamicAABB, dynamicTrigger + ); + } } } } + + // get collision details and submit events + for (auto possibleCollision : m_possibleCollisions) { + if (possibleCollision.staticTrigger) { + CollisionEvent info{}; + info.isCollisionEnter = true; + info.collidedEntity = possibleCollision.dynamicEntity; + m_collisionInfos.emplace_back(possibleCollision.staticEntity, info); + } + if (possibleCollision.dynamicTrigger) { + CollisionEvent info{}; + info.isCollisionEnter = true; + info.collidedEntity = possibleCollision.staticEntity; + m_collisionInfos.emplace_back(possibleCollision.dynamicEntity, info); + } + } + + for (auto [entity, info] : m_collisionInfos) { + m_scene->events()->queueEvent(EventSubscriberKind::ENTITY, entity, info); + } } } diff --git a/test/src/camera_controller.cpp b/test/src/camera_controller.cpp index 001d50b..cbfa490 100644 --- a/test/src/camera_controller.cpp +++ b/test/src/camera_controller.cpp @@ -173,3 +173,9 @@ void CameraControllerSystem::onUpdate(float ts) } } + +void CameraControllerSystem::onEvent(engine::PhysicsSystem::CollisionEvent info) +{ + (void)info; + INFO("COLLISION EVENT!"); +} diff --git a/test/src/camera_controller.hpp b/test/src/camera_controller.hpp index 6706180..f1cbfd7 100644 --- a/test/src/camera_controller.hpp +++ b/test/src/camera_controller.hpp @@ -1,6 +1,9 @@ #pragma once #include "ecs_system.hpp" +#include "event_system.hpp" + +#include "systems/collisions.hpp" struct CameraControllerComponent { float m_cameraSensitivity = 0.007f; @@ -17,10 +20,14 @@ struct CameraControllerComponent { }; -class CameraControllerSystem : public engine::System { +class CameraControllerSystem : public engine::System, public engine::EventHandler { public: CameraControllerSystem(engine::Scene* scene); + // engine::System overrides void onUpdate(float ts) override; + + // engine::EventHandler overrides + void onEvent(engine::PhysicsSystem::CollisionEvent info) override; }; diff --git a/test/src/game.cpp b/test/src/game.cpp index 2f4a2a7..c177be2 100644 --- a/test/src/game.cpp +++ b/test/src/game.cpp @@ -38,11 +38,11 @@ static void configureInputs(engine::InputManager* inputManager) inputManager->addInputAxis("looky", engine::inputs::MouseAxis::Y); } -void playGame() +void playGame(bool enableFrameLimiter) { engine::Application app(PROJECT_NAME, PROJECT_VERSION); - app.setFrameLimiter(false); + app.setFrameLimiter(enableFrameLimiter); // configure window app.window()->setRelativeMouseMode(true); @@ -60,8 +60,12 @@ void playGame() myScene->getComponent(camera)->position.y = 8.0f; auto cameraCollider = myScene->addComponent(camera); cameraCollider->isStatic = false; + cameraCollider->isTrigger = true; cameraCollider->aabb = { { -0.2f, -1.5f, -0.2f }, { 0.2f, 0.2f, 0.2f} }; // Origin is at eye level myScene->addComponent(camera); + myScene->events()->subscribeToEventType( + engine::EventSubscriberKind::ENTITY, camera, myScene->getSystem() + ); myScene->getSystem()->setCameraEntity(camera); } @@ -83,7 +87,7 @@ void playGame() ); /* skybox */ - { + if (0) { uint32_t skybox = myScene->createEntity("skybox"); auto skyboxRenderable = myScene->addComponent(skybox); skyboxRenderable->material = std::make_unique(app.getResource("engine.skybox")); diff --git a/test/src/game.hpp b/test/src/game.hpp index faaa60d..f3d8e59 100644 --- a/test/src/game.hpp +++ b/test/src/game.hpp @@ -1,3 +1,3 @@ #pragma once -void playGame(); \ No newline at end of file +void playGame(bool enableFrameLimiter); diff --git a/test/src/main.cpp b/test/src/main.cpp index 151e851..2656bd9 100644 --- a/test/src/main.cpp +++ b/test/src/main.cpp @@ -7,15 +7,22 @@ #include -int main(int, char *[]) +int main(int argc, char* argv[]) { + bool enableFrameLimiter = true; + + if (argc == 2) { + const std::string arg { argv[1] }; + if (arg == "nofpslimit") enableFrameLimiter = false; + } + engine::setupLog(PROJECT_NAME); INFO("{} v{}", PROJECT_NAME, PROJECT_VERSION); try { - playGame(); + playGame(enableFrameLimiter); } catch (const std::exception& e) {