Skip to main content

Core

The heart of the engine, handling entity functionality, events and whatnot

Entity component system

At the center of this entity component system implementation sits EnTT library, everything I wrote is a wrapper for that library like entt::dispatcher for events, entt::registry for registry related tasks like entity creation and a means to hold pointers to task manager, imgui manager and so on

Components

We have a lot of components and since the engine is ever expanding, we will have a lot more

Directional light

This component adds some key details to the directional light entity

struct DirectionalLightComponent
{
// index in the directional light array of the scene ssbo buffer
int directionalLightIndex{ 0 };
// near and far plane, outside of those we don't cast shadows
float nearPlane{ 0.1f };
float farPlane{ 300.0f };

// how much in front of it does it see
float orthoSize{ 70.0f };
// just a positional modifier
float positionFactor{ 20.0f };
};

Direction, diffuse color, specular color, color, those should be handled by the next struct

struct DirectionalLight
{
glm::vec4 direction{ -0.3f, -1.0f, -0.5f, 0.0f };
glm::vec4 diffuse{ 1.0f, 1.0f, 1.0f, 1.0f };
glm::vec4 specular{ 1.0f, 1.0f, 1.0f, 1.0f };
};

Point light

The point light component holds just the index in the pointlight array of the scene ssbo buffer

struct PointLightComponent
{
uint32_t pointLightIndex;
};

On the other hand, the PointLight holds much more information

struct PointLight
{
glm::vec4 translation{ 0.0f, 10.0f, 0.0f, 1.0f };
glm::vec4 ambient{ 0.1f, 0.1f, 0.1f, 1.0f };
glm::vec4 diffuse{ 1.0f, 1.0f, 1.0f, 1.0f };
glm::vec4 specular{ 1.0f, 1.0f, 1.0f, 1.0f };
glm::vec4 color{ 1.0f, 1.0f, 1.0f, 1.0f };
// x = constant, y = linear, z = quadratic, w = enabled
glm::vec4 params{ 1.0f, 0.022f, 0.0019f, 1.0f };
};

Since the shader buffers are constrained by some size rules I packed the point light calculation variables in the params vector so everything is aligned

Identifier

This is just an id like component where the name, type and group of the entity are stored, it is very easy to understand its use

We add an IdentifierComponent to each Entity created/ copied/ moved

struct IdentifierComponent
{
std::string name{};
EntityType type{};
std::string group{};
};

Index

IndexComponent is something I use to know where the instancing data of a particular Mesh is stored

It looks like this

struct IndexComponent
{
uint32_t index;
};

In the scene we have an InstancedData structure and that is what this is used for, see add scene chapter chapter for more info on this topic

Mesh

The mesh component holds just a pointer to the Mesh object, the object itself lives in the asset manager

That's why I said the manager is tied deeply with assets lifetime too
Static mesh flag tells us if we can move a certain submesh relative to the parent (the Mesh object) or just treat it as a "single" mesh but this is not entirely implemented yet

struct MeshComponent
{
kogayonon_resources::Mesh* pMesh;
bool staticMesh{ false };
bool loaded{ false };
};

Outline

Outline is used to filter normal entities from the selected ones, currently the engine supports the selection of a single entity and it is as designed

tip

This is not provided to the fragment shader yet

struct OutlineComponent
{
glm::vec4 color{ 1.0f, 1.0f, 1.0f, 1.0f };
};

Rigidbody

We also have physics with Nvidia PhysX library, it is although early stages so I did not have that much playtime with those things

struct DynamicRigidbodyComponent
{
physx::PxRigidDynamic* pBody{ nullptr };
};

struct StaticRigidbodyComponent
{
physx::PxRigidStatic* pBody{ nullptr };
};

Texture

Having so much work with geometry I neglected the textures part of the engine, this has much more improvement coming its way

struct TextureComponent
{
std::weak_ptr<kogayonon_resources::Texture> pTexture;
};

Transform

A very important component for entities that also have MeshComponent

struct TransformComponent
{
glm::vec3 translation{ 0.0f };
glm::vec3 rotation{ 0.0f };
glm::vec3 scale{ 1.0f };
};

Currently I go back and forth with euler angles and quaternions, will make it so the transform will use and hold a quaternion for rotations for avoiding gimbal lock

Entity

This is a wrapper around some entt::registry functionality, it also has just two member variables

Those are:

  • pointer to the registry entity belongs to
  • entityId
private:
Registry* m_regsitry;
entt::entity m_entity;

Other than that the Entity has many templated functions centered around components

Each function is self explanatory

  template <typename TComponent>
inline bool hasComponent();

template <typename TComponent>
inline auto tryGetComponent() -> TComponent*;

template <typename TComponent>
inline auto getComponent() -> TComponent&;

template <typename TComponent, typename... Args>
inline auto addComponent( Args&&... args ) -> TComponent&;

template <typename TComponent>
inline void removeComponent();

template <typename TComponent, typename... Args>
inline void replaceComponent( Args&&... args );

Registry

This is the wrapper around entt::registry and currently, for convenience reasons, holds the implementations of the functions that entity has as well

We can choose to construct an Entity from the scene registry and the id, since it already exists the registry will not make a new one, and then access the funcs

Or we can just call registry.getComponent<TComponent>(entityId)

Events

Every Event inherits from a base IEvent class

class IEvent
{
public:
IEvent() = default;
virtual ~IEvent() = default;

inline virtual EventType getEventType()
{
return m_type;
}

private:
EventType m_type{ EventType::None };
};

At the moment of writing the documentation I don't seem to have a usecase for the EventType, we'll see how things develop

App events

Those are simple and easy enough to understand just from the code provided

Window close

class WindowCloseEvent : public IEvent
{
public:
WindowCloseEvent() = default;

private:
};

Window resize

class WindowResizeEvent : public IEvent

{
public:
WindowResizeEvent( int width, int height )
: m_Width( width )
, m_Height( height )
{
}

int getWidth() const
{
return m_Width;
}

int getHeight() const
{
return m_Height;
}

private:
int m_Width, m_Height;
};

File events

When I wrote this documentation I realised that the implementation is redundant, there is a class for each file event type, delete, create, modify, rename, and all of them just store the path of the file and the filename

So by adding just another variable we also store the type

// this is the current implementation for created event and we have 3 more of those classes
// in the hpp file
class FileCreatedEvent : public IEvent
{
public:
explicit FileCreatedEvent( const std::string& path, const std::string& name );
~FileCreatedEvent() = default;

inline std::string getName() const;
inline std::string getPath() const;

protected:
std::string m_name;
std::string m_path;
};

This is all the file_events.hpp file should contain

enum class FileEventType
{
Created,
Modified,
Deleted,
Renamed
};

class FileEvent : public IEvent
{
public:
explicit FileEvent( const std::string& path,
const std::string& name,
const FileEventType& type );

~FileEvent() = default;

inline std::string getName() const;
inline std::string getPath() const;
inline FileEventType getType() const;

protected:
std::string m_name;
std::string m_path;
FileEventType m_type;
};