Get event system working; add collision events

This commit is contained in:
Bailey Harrison 2023-02-09 15:21:37 +00:00
parent a0b000d3df
commit 062b2d6c8f
18 changed files with 392 additions and 55 deletions

View File

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

2
TODO
View File

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

View File

@ -3,4 +3,4 @@ set -e
cd Debug
cmake --build .
cd test
./enginetest
./enginetest $@

View File

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

119
include/event_system.hpp Normal file
View File

@ -0,0 +1,119 @@
#pragma once
#include <queue>
#include <unordered_map>
#include <memory>
#include <cstdlib>
#include <cassert>
#include <typeinfo>
namespace engine {
enum class EventSubscriberKind {
ENTITY,
};
// Event handler base-class
template <typename T>
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 <typename T>
class EventQueue : public IEventQueue {
// holds events of type T and subscribers to those events
public:
void subscribe(EventSubscriberKind kind, uint32_t id, EventHandler<T>* 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<T>* 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<uint32_t, EventHandler<T>*> m_subscribers;
struct QueuedEvent {
EventHandler<T>* handler;
T event;
};
std::queue<QueuedEvent> m_eventQueue{};
};
class EventSystem {
public:
EventSystem();
EventSystem(const EventSystem&) = delete;
EventSystem& operator=(const EventSystem&) = delete;
~EventSystem();
template <typename T>
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<EventQueue<T>>());
}
template <typename T>
void subscribeToEventType(EventSubscriberKind kind, uint32_t id, EventHandler<T>* handler)
{
size_t hash = typeid(T).hash_code();
assert(m_eventQueues.contains(hash) && "Subscribing to event type that isn't registered!");
EventQueue<T>* queue = dynamic_cast<EventQueue<T>*>(m_eventQueues.at(hash).get());
assert(queue != nullptr && "This cast should work?!! wot");
queue->subscribe(kind, id, handler);
}
template <typename T>
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<T>* queue = dynamic_cast<EventQueue<T>*>(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<size_t, std::unique_ptr<IEventQueue>> m_eventQueues{};
};
}

View File

@ -3,6 +3,7 @@
#include "log.hpp"
#include "ecs_system.hpp"
#include "event_system.hpp"
#include <map>
#include <cstdint>
@ -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<EventSystem> m_eventSystem{};
};
}

View File

@ -17,18 +17,30 @@ namespace engine {
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<std::tuple<uint32_t, AABB, bool>> m_staticAABBs{};
std::vector<std::tuple<uint32_t, AABB, bool>> m_dynamicAABBs{};
struct PossibleCollision {
uint32_t staticEntity;
AABB staticAABB;
bool staticTrigger;
uint32_t dynamicEntity;
AABB dynamicAABB;
bool dynamicTrigger;
};
std::vector<CollisionInfo> m_staticInfos{};
std::vector<CollisionInfo> m_dynamicInfos{};
std::vector<PossibleCollision> m_possibleCollisions{};
std::vector<std::pair<uint32_t, CollisionEvent>> m_collisionInfos{}; // target entity, event info
};

View File

@ -3,4 +3,4 @@ set -e
cd Release
cmake --build .
cd test
./enginetest
./enginetest $@

15
src/event_system.cpp Normal file
View File

@ -0,0 +1,15 @@
#include "event_system.hpp"
namespace engine {
EventSystem::EventSystem()
{
}
EventSystem::~EventSystem()
{
}
}

127
src/gfx_device_null.cpp Normal file
View File

@ -0,0 +1,127 @@
// The implementation of the graphics layer using Vulkan 1.3.
#ifdef ENGINE_BUILD_NULL
#include "gfx_device.hpp"
#include <assert.h>
#include <unordered_set>
#include <array>
#include <fstream>
#include <filesystem>
#include <optional>
#include <queue>
#include <map>
#include <iostream>
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<Impl>();
}
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

View File

@ -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<VkClearValue, 2> 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();

View File

@ -12,6 +12,11 @@ namespace engine {
Scene::Scene(Application* app)
: m_app(app)
{
// event system
m_eventSystem = std::make_unique<EventSystem>();
// ecs configuration:
registerComponent<TransformComponent>();
registerComponent<RenderableComponent>();
registerComponent<ColliderComponent>();
@ -64,6 +69,7 @@ namespace engine {
system->onUpdate(ts);
}
m_eventSystem->dispatchEvents(); // clears event queue
}
}

View File

@ -35,13 +35,17 @@ namespace engine {
PhysicsSystem::PhysicsSystem(Scene* scene)
: System(scene, { typeid(TransformComponent).hash_code(), typeid(ColliderComponent).hash_code() })
{
m_scene->events()->registerEventType<CollisionEvent>();
}
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<TransformComponent>(entity);
const auto c = m_scene->getComponent<ColliderComponent>(entity);
@ -63,43 +67,51 @@ 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);
}
}
}

View File

@ -173,3 +173,9 @@ void CameraControllerSystem::onUpdate(float ts)
}
}
void CameraControllerSystem::onEvent(engine::PhysicsSystem::CollisionEvent info)
{
(void)info;
INFO("COLLISION EVENT!");
}

View File

@ -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<engine::PhysicsSystem::CollisionEvent> {
public:
CameraControllerSystem(engine::Scene* scene);
// engine::System overrides
void onUpdate(float ts) override;
// engine::EventHandler overrides
void onEvent(engine::PhysicsSystem::CollisionEvent info) override;
};

View File

@ -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<engine::TransformComponent>(camera)->position.y = 8.0f;
auto cameraCollider = myScene->addComponent<engine::ColliderComponent>(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<CameraControllerComponent>(camera);
myScene->events()->subscribeToEventType<engine::PhysicsSystem::CollisionEvent>(
engine::EventSubscriberKind::ENTITY, camera, myScene->getSystem<CameraControllerSystem>()
);
myScene->getSystem<engine::RenderSystem>()->setCameraEntity(camera);
}
@ -83,7 +87,7 @@ void playGame()
);
/* skybox */
{
if (0) {
uint32_t skybox = myScene->createEntity("skybox");
auto skyboxRenderable = myScene->addComponent<engine::RenderableComponent>(skybox);
skyboxRenderable->material = std::make_unique<engine::resources::Material>(app.getResource<engine::resources::Shader>("engine.skybox"));

View File

@ -1,3 +1,3 @@
#pragma once
void playGame();
void playGame(bool enableFrameLimiter);

View File

@ -7,15 +7,22 @@
#include <exception>
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) {